Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

94 client UI for assertion generation extra features #198

Merged
merged 4 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 65 additions & 46 deletions client/src/component/DOS/DefineAudit/GenerateAssertionsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Breadcrumb, Button, Card, Intent, Spinner} from '@blueprintjs/core';
import dashboardRefresh from 'corla/action/dos/dashboardRefresh';
import * as _ from 'lodash';
import * as React from 'react';

import {Breadcrumb, Button, Card, Intent, Spinner} from '@blueprintjs/core';

import exportAssertionsAsCsv from 'corla/action/dos/exportAssertionsAsCsv';
import exportAssertionsAsJson from 'corla/action/dos/exportAssertionsAsJson';
import generateAssertions from 'corla/action/dos/generateAssertions';
Expand All @@ -25,19 +25,19 @@ interface GenerateAssertionsPageProps {
}

interface GenerateAssertionsPageState {
canGenerateAssertions: boolean;
generatingAssertions: boolean;
generationTimeOutSeconds: number;
generationWasRun: boolean;
}

class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps, GenerateAssertionsPageState> {
public constructor(props: GenerateAssertionsPageProps) {
super(props);

this.state = {
canGenerateAssertions: !props.dosState.assertionsGenerated && props.readyToGenerate,
generatingAssertions: false,
generationTimeOutSeconds: 5,
generationWasRun: false,
};
}

Expand All @@ -50,18 +50,19 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps

const generate = async () => {
this.setState({generatingAssertions: true});
this.setState({canGenerateAssertions: false});
this.render();

const timeoutQueryParams = new URLSearchParams();
timeoutQueryParams.set(generationTimeoutParam, this.state.generationTimeOutSeconds.toString());
generateAssertions(timeoutQueryParams).then()
.catch(reason => {
alert('generateAssertions error in fetchAction ' + reason);
});

this.setState({generatingAssertions: false});
this.setState({canGenerateAssertions: true});
.catch(reason => {
alert('generateAssertions error in fetchAction ' + reason);
}).finally(() => {
this.setState({generatingAssertions: false});
this.setState({generationWasRun: true});
// Refresh dashboard to collect full assertion info
dashboardRefresh();
});
};

const main =
Expand Down Expand Up @@ -96,6 +97,7 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
}
<div className='control-buttons mt-default'>
<Button onClick={forward}
disabled={this.disableNextButton()}
className='pt-button pt-intent-primary'>
Next
</Button>
Expand All @@ -118,16 +120,29 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
this.setState({generationTimeOutSeconds: Number(timeout)});
}

private disableNextButton() {
const assertionsAlreadyGenerated = this.props.dosState.generateAssertionsSummaries.length > 0;
const atLeastOneIrvContest = Object.values(this.props.dosState.contests)
.some(contest => contest.description === 'IRV');

// Activate button if any attempt to generate is made
if (this.state.generationWasRun) {
return false;
}

return (!assertionsAlreadyGenerated && atLeastOneIrvContest);
}

private getAssertionGenerationStatusTable() {

interface CombinedData {
contestName: string,
succeeded: boolean | undefined,
retry: boolean | undefined,
winner: string,
error: string,
warning: string,
message: string
contestName: string;
succeeded: boolean | undefined;
retry: boolean | undefined;
winner: string;
error: string;
warning: string;
message: string;
}

interface CombinationSummary {
Expand All @@ -139,15 +154,15 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps

// Succeeded and retry can be undefined - if so this leaves the space blank.
// {combinedData.succeeded != undefined ? {combinedData.succeeded ? 'Success' : 'Failure'} : ''}
let successString = combinedData.succeeded ? 'Success' : 'Failure';
let retryString = combinedData.retry ? 'Yes' : 'No';
const successString = combinedData.succeeded ? 'Success' : 'Failure';
const retryString = combinedData.retry ? 'Yes' : 'No';
return (
<tr>
<td>{combinedData.contestName}</td>
<td style={{color: combinedData.succeeded ? 'green' : 'red'}}>
{combinedData.succeeded == undefined ? '' : successString}
{combinedData.succeeded === undefined ? '' : successString}
</td>
<td>{combinedData.retry == undefined ? '' : retryString}</td>
<td>{combinedData.retry === undefined ? '' : retryString}</td>
<td>{combinedData.winner}</td>
<td>{combinedData.error}</td>
<td>{combinedData.warning}</td>
Expand All @@ -160,55 +175,57 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
const fillBlankStatus = (s: DOS.GenerateAssertionsSummary): CombinedData => {
return {
contestName: s.contestName,
succeeded: undefined,
retry: undefined,
winner: s.winner,
error: s.error,
message: s.message,
retry: undefined,
succeeded: undefined,
warning: s.warning,
message: s.message
winner: s.winner,
};
}
};

// Make a CombinedData structure out of an AssertionsStatus by filling in blank summary data.
const fillBlankSummary = (s: DOS.AssertionStatus): CombinedData => {
return {
contestName: s.contestName,
succeeded: s.succeeded,
retry: s.retry,
winner: '',
error: '',
message: '',
retry: s.retry,
succeeded: s.succeeded,
warning: '',
message: ''
winner: '',
};
}
};

// Make a CombinedData structure out of an AssertionsStatus and a GenerateAssertionsSummary.
const combineSummaryAndStatus = (s: DOS.AssertionStatus, t: DOS.GenerateAssertionsSummary): CombinedData => {
return {
contestName: s.contestName,
succeeded: s.succeeded,
retry: s.retry,
winner: t.winner,
error: t.error,
message: t.message,
retry: s.retry,
succeeded: s.succeeded,
warning: t.warning,
message: t.message
winner: t.winner,
};
}
};

// Join up the rows by contest name if matching. If there is no matching contest name in the other
// list, add a row with blanks for the missing data.
// Various kinds of absences are possible, because there may be empty summaries at the start;
// conversely, in later phases we may rerun generation (and hence get status) for only a few contests.
const joinRows = (statuses: DOS.AssertionGenerationStatuses | undefined, summaries: DOS.GenerateAssertionsSummary[]) => {
const joinRows = (statuses: DOS.AssertionGenerationStatuses
| undefined, summaries: DOS.GenerateAssertionsSummary[]) => {
summaries.sort((a, b) => a.contestName < b.contestName ? -1 : 1);
let rows: CombinedData[] = [];
let i = 0, j = 0;
const rows: CombinedData[] = [];
let i = 0;
let j = 0;

if (statuses === undefined) {
// No status yet. Just print summary data.
return summaries.map(s => {
return fillBlankStatus(s);
})
});

} else {
// Iterate along the two sorted lists at once, combining them if the contest name matches, and
Expand Down Expand Up @@ -238,11 +255,12 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
}
}
return rows;
}
};

const combinedRows = _.map(joinRows(this.props.dosState.assertionGenerationStatuses, this.props.dosState.generateAssertionsSummaries), d => (
<CombinedTableRow combinedData={d}/>
))
const combinedRows = _.map(joinRows(this.props.dosState.assertionGenerationStatuses,
this.props.dosState.generateAssertionsSummaries), d => (
<CombinedTableRow key={d.contestName} combinedData={d}/>
));

if (this.state.generatingAssertions) {
return (
Expand All @@ -251,7 +269,8 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
<div>Generating Assertions...</div>
</Card>
);
} else if (this.props.dosState.assertionGenerationStatuses || this.props.dosState.generateAssertionsSummaries.length > 0) {
} else if (this.props.dosState.assertionGenerationStatuses
|| this.props.dosState.generateAssertionsSummaries.length > 0) {
return (
<div>
<span className='generate-assertions-exports'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ import withPoll from 'corla/component/withPoll';

import counties from 'corla/data/counties';

// The next URL path to transition to.
const NEXT_PATH = '/sos/audit/generate-assertions';

// The previous URL path to transition to.
// const PREV_PATH = '/sos/audit';

/**
* Denormalize the DOS.Contests data structure from the application state into
* something that can be easily displayed in a tabular format.
Expand Down Expand Up @@ -67,7 +61,6 @@ interface Props {
history: History;
}


const PageContainer = (props: Props) => {
const {
areChoicesLoaded,
Expand All @@ -77,31 +70,39 @@ const PageContainer = (props: Props) => {
history,
} = props;

let isRequestInProgress = false;
let isRequestInProgress = false;
const nextPage = (data: DOS.Form.StandardizeChoices.FormData) => {
if (isRequestInProgress) {
return;
}
isRequestInProgress = true;
console.log('!!!!!!!!!!!!! calling submit set-contest-name');

standardizeChoices(contests, data).then(r => {
isRequestInProgress = false;
// use the result here
if (r.ok) {
history.push(NEXT_PATH);
}
history.push(getNextPath());
}
})
.catch(reason => {
alert('standardizeChoices error in submitAction ' + reason);
})

});
};

const previousPage = async () => {
await resetAudit();
history.push('/sos/audit');
};

function getNextPath() {
// Only route to Assertion Generation page if IRV contests exist
if (Object.values(contests).some(contest => contest.description === 'IRV')) {
return '/sos/audit/generate-assertions';
} else {
return '/sos/audit/estimate-sample-sizes';
}
}

if (asm === 'DOS_AUDIT_ONGOING') {
return <Redirect to='/sos' />;
}
Expand All @@ -112,7 +113,7 @@ console.log('!!!!!!!!!!!!! calling submit set-contest-name');
rows = filterRows(flattenContests(contests, canonicalChoices));

if (_.isEmpty(rows)) {
return <Redirect to={ NEXT_PATH } />;
return <Redirect to={ getNextPath() } />;
}
}

Expand Down
Loading