Skip to content

Commit 6bfba69

Browse files
authored
Show last login time on audit progress (#2061)
Show last successful login in audit progress `Status` column
1 parent 7cde291 commit 6bfba69

16 files changed

+661
-165
lines changed

client/jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module.exports = {
1212
// this number should only be getting closer to 0).
1313
global: {
1414
lines: -161,
15-
branches: -194,
15+
branches: -195,
1616
},
1717
},
1818
moduleFileExtensions: [

client/src/components/AuditAdmin/ActivityLog.tsx

+3-6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { IOrganization, useAuthDataContext, IAuditAdmin } from '../UserContext'
55
import { Wrapper, Inner } from '../Atoms/Wrapper'
66
import { StyledTable, downloadTableAsCSV } from '../Atoms/Table'
77
import { fetchApi } from '../../utils/api'
8+
import useAuditAdminsOrganizations from '../useAuditAdminsOrganizations'
89

9-
interface IActivity {
10+
export interface IActivity {
1011
id: string
1112
activityName: string
1213
timestamp: string
@@ -73,11 +74,7 @@ const prettyAction = (activity: IActivity) => {
7374
const ActivityLog: React.FC = () => {
7475
const auth = useAuthDataContext()
7576
const user = auth && (auth.user as IAuditAdmin)
76-
const organizations = useQuery<IOrganization[]>(
77-
'orgs',
78-
() => fetchApi(`/api/audit_admins/${user!.id}/organizations`),
79-
{ enabled: !!user }
80-
)
77+
const organizations = useAuditAdminsOrganizations(user)
8178
if (!organizations.isSuccess) return null
8279
return <ActivityLogOrgsLoaded organizations={organizations.data} />
8380
}

client/src/components/AuditAdmin/AuditAdminView.test.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ describe('AA setup flow', () => {
260260
}),
261261
aaApiCalls.getRounds(roundMocks.singleIncomplete),
262262
aaApiCalls.getJurisdictions,
263+
aaApiCalls.getLastLoginByJurisdiction(),
263264
aaApiCalls.getMapData,
264265
]
265266
await withMockFetch(expectedCalls, async () => {
@@ -299,6 +300,7 @@ describe('AA setup flow', () => {
299300
},
300301
aaApiCalls.getJurisdictions,
301302
aaApiCalls.getRounds(roundMocks.empty),
303+
aaApiCalls.getLastLoginByJurisdiction(),
302304
aaApiCalls.getMapData,
303305
]
304306
await withMockFetch(expectedCalls, async () => {
@@ -324,6 +326,7 @@ describe('AA setup flow', () => {
324326
aaApiCalls.getJurisdictions,
325327
aaApiCalls.getContests(contestMocks.filledTargeted),
326328
aaApiCalls.getSettings(auditSettingsMocks.all),
329+
aaApiCalls.getLastLoginByJurisdiction(),
327330
aaApiCalls.getMapData,
328331
jaApiCalls.getBallotManifestFile(manifestMocks.empty),
329332
...jaApiCalls.uploadManifestCalls,
@@ -332,6 +335,7 @@ describe('AA setup flow', () => {
332335
...aaApiCalls.getJurisdictions,
333336
response: { jurisdictions: jurisdictionMocks.allManifests },
334337
},
338+
aaApiCalls.getLastLoginByJurisdiction(),
335339
]
336340
await withMockFetch(expectedCalls, async () => {
337341
const { container } = render('progress')
@@ -344,7 +348,7 @@ describe('AA setup flow', () => {
344348
let rows = screen.getAllByRole('row')
345349
let row1 = within(rows[1]).getAllByRole('cell')
346350
expect(row1[0]).toHaveTextContent('Jurisdiction One')
347-
within(row1[1]).getByText('No manifest uploaded')
351+
within(row1[1]).getByText('Logged in')
348352

349353
// Click on a jurisdiction name to open the detail modal
350354
userEvent.click(screen.getByRole('button', { name: 'Jurisdiction One' }))
@@ -383,6 +387,7 @@ it('finishes a round', async () => {
383387
},
384388
aaApiCalls.getContests(contestMocks.filledTargeted),
385389
aaApiCalls.getSettings(auditSettingsMocks.all),
390+
aaApiCalls.getLastLoginByJurisdiction(),
386391
aaApiCalls.getMapData,
387392
aaApiCalls.postFinishRound,
388393
aaApiCalls.getRounds(roundMocks.singleComplete),

client/src/components/AuditAdmin/AuditAdminView.tsx

+15-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import {
1414
IRound,
1515
useFinishRound,
1616
} from './useRoundsAuditAdmin'
17-
import { useJurisdictions, jurisdictionsQueryKey } from '../useJurisdictions'
17+
import {
18+
jurisdictionsQueryKey,
19+
jurisdictionsWithLastLoginQueryKey,
20+
useJurisdictions,
21+
} from '../useJurisdictions'
1822
import { useContests } from '../useContests'
1923
import { useAuditSettings } from '../useAuditSettings'
2024
import { Wrapper, Inner } from '../Atoms/Wrapper'
@@ -48,16 +52,19 @@ const AuditAdminView: React.FC = () => {
4852
isDrawSampleComplete(rounds)
4953
) {
5054
queryClient.invalidateQueries(jurisdictionsQueryKey(electionId))
55+
queryClient.invalidateQueries(
56+
jurisdictionsWithLastLoginQueryKey(electionId)
57+
)
5158
history.push(`/election/${electionId}/progress`)
5259
}
5360
lastFetchedRounds.current = rounds
5461
},
5562
})
63+
const jurisdictionsQuery = useJurisdictions(electionId)
5664
const startNextRoundMutation = useStartNextRound(electionId)
5765
const finishRoundMutation = useFinishRound(electionId)
5866
const undoRoundStartMutation = useUndoRoundStart(electionId)
5967

60-
const jurisdictionsQuery = useJurisdictions(electionId)
6168
const contestsQuery = useContests(electionId)
6269
const auditSettingsQuery = useAuditSettings(electionId)
6370

@@ -66,13 +73,14 @@ const AuditAdminView: React.FC = () => {
6673
!contestsQuery.isSuccess ||
6774
!roundsQuery.isSuccess ||
6875
!auditSettingsQuery.isSuccess
69-
)
76+
) {
7077
return null // Still loading
78+
}
7179

72-
const jurisdictions = jurisdictionsQuery.data
7380
const contests = contestsQuery.data
7481
const rounds = roundsQuery.data
7582
const auditSettings = auditSettingsQuery.data
83+
const jurisdictions = jurisdictionsQuery.data
7684

7785
if (isDrawingSample(rounds)) {
7886
return (
@@ -153,6 +161,9 @@ const AuditAdminView: React.FC = () => {
153161
refresh={() => {
154162
queryClient.invalidateQueries(roundsQueryKey(electionId))
155163
queryClient.invalidateQueries(jurisdictionsQueryKey(electionId))
164+
queryClient.invalidateQueries(
165+
jurisdictionsWithLastLoginQueryKey(electionId)
166+
)
156167
}}
157168
/>
158169
</AuditAdminStatusBox>

client/src/components/AuditAdmin/Progress/JurisdictionDetail.test.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,32 @@ const render = (props: Partial<IJurisdictionDetailProps>) =>
6666
)
6767

6868
describe('JurisdictionDetail', () => {
69+
it('shows last login if it exists', async () => {
70+
const expectedCalls = [
71+
jaApiCalls.getBallotManifestFile(manifestMocks.empty),
72+
]
73+
await withMockFetch(expectedCalls, async () => {
74+
const loginTime = new Date().toLocaleString()
75+
render({
76+
jurisdiction: jurisdictionMocks.oneManifest[0],
77+
lastLoginActivity: {
78+
id: '0',
79+
activityName: 'JurisdictionAdminLogin',
80+
timestamp: loginTime,
81+
user: {
82+
type: 'jurisdiction-admin',
83+
key: 'ja-1@example.com',
84+
supportUser: false,
85+
},
86+
election: null,
87+
info: {},
88+
},
89+
})
90+
91+
await screen.findByText(`ja-1@example.com at ${loginTime}`)
92+
})
93+
})
94+
6995
it('before launch, shows manifest for ballot polling audit', async () => {
7096
const expectedCalls = [
7197
jaApiCalls.getBallotManifestFile(manifestMocks.empty),

client/src/components/AuditAdmin/Progress/JurisdictionDetail.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import DownloadBatchRetrievalListButton from '../../JurisdictionAdmin/BatchRound
3232
import DownloadBatchTallySheetsButton from '../../JurisdictionAdmin/BatchRoundSteps/DownloadBatchTallySheetsButton'
3333
import { candidateTotalsByBatchTemplateCsvPath } from '../../JurisdictionAdmin/candidateTotalsByBatchTemplateCsv'
3434
import DownloadStackLabelsButton from '../../JurisdictionAdmin/BatchRoundSteps/DownloadStackLabelsButton'
35+
import { IActivity } from '../ActivityLog'
3536

3637
const StatusCard = styled(Card)`
3738
&:not(:last-child) {
@@ -51,6 +52,7 @@ export interface IJurisdictionDetailProps {
5152
electionId: string
5253
round: IRound | null
5354
auditSettings: IAuditSettings
55+
lastLoginActivity?: IActivity
5456
}
5557

5658
const JurisdictionDetail: React.FC<IJurisdictionDetailProps> = ({
@@ -59,6 +61,7 @@ const JurisdictionDetail: React.FC<IJurisdictionDetailProps> = ({
5961
electionId,
6062
round,
6163
auditSettings,
64+
lastLoginActivity,
6265
}) => {
6366
const cvrsEnabled =
6467
auditSettings.auditType === 'BALLOT_COMPARISON' ||
@@ -125,6 +128,16 @@ const JurisdictionDetail: React.FC<IJurisdictionDetailProps> = ({
125128
auditSettings={auditSettings}
126129
/>
127130
)}
131+
{lastLoginActivity && (
132+
<>
133+
<H5>Last Login</H5>
134+
<span>
135+
{`${lastLoginActivity.user!.key} at ${new Date(
136+
lastLoginActivity.timestamp
137+
).toLocaleString()}`}
138+
</span>
139+
</>
140+
)}
128141
</div>
129142
</Dialog>
130143
)

0 commit comments

Comments
 (0)