Skip to content

Commit 752bbd4

Browse files
authored
Merge pull request #102 from smartcontractkit/ggama/feat-allow-approval-previous-job-specs
feat: allow approval of previous versions of job specs
2 parents 9db0cda + c63dca3 commit 752bbd4

File tree

3 files changed

+191
-5
lines changed

3 files changed

+191
-5
lines changed

.changeset/flat-toys-roll.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@smartcontractkit/operator-ui': patch
3+
---
4+
5+
feat: allow approval of previous versions of job specs

src/screens/JobProposal/SpecsView.test.tsx

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react'
2-
import { render, screen, waitFor } from 'support/test-utils'
2+
import { render, screen, waitFor, within } from 'support/test-utils'
33
import userEvent from '@testing-library/user-event'
44

55
import {
@@ -260,4 +260,151 @@ describe('SpecsView', () => {
260260
expect(queryByText('Cancel')).not.toBeInTheDocument()
261261
})
262262
})
263+
264+
describe('Approve button', () => {
265+
let specs: ReadonlyArray<JobProposal_SpecsFields>
266+
let proposal: JobProposalPayloadFields
267+
268+
beforeEach(() => {
269+
specs = [
270+
buildJobProposalSpec({ id: '106', version: 6, status: 'CANCELLED' }),
271+
buildJobProposalSpec({ id: '105', version: 5, status: 'PENDING' }),
272+
buildJobProposalSpec({ id: '104', version: 4, status: 'CANCELLED' }),
273+
buildJobProposalSpec({ id: '103', version: 3, status: 'CANCELLED' }),
274+
buildJobProposalSpec({ id: '102', version: 2, status: 'APPROVED' }),
275+
buildJobProposalSpec({ id: '101', version: 1, status: 'REVOKED' }),
276+
]
277+
})
278+
279+
it('is visible in latest two cancelled specs if proposal is not deleted or revoked', async () => {
280+
proposal = buildJobProposal({ status: 'PENDING' })
281+
renderComponent(specs, proposal)
282+
283+
const panels = screen.getAllByTestId('expansion-panel')
284+
expect(panels).toHaveLength(6)
285+
expect(
286+
within(panels[0]).queryByRole('button', {
287+
name: 'Approve',
288+
hidden: false,
289+
}),
290+
).toBeInTheDocument()
291+
expect(
292+
within(panels[1]).queryByRole('button', {
293+
name: 'Approve',
294+
hidden: true,
295+
}),
296+
).not.toBeInTheDocument()
297+
expect(
298+
within(panels[2]).queryByRole('button', {
299+
name: 'Approve',
300+
hidden: true,
301+
}),
302+
).toBeInTheDocument()
303+
expect(
304+
within(panels[3]).queryByRole('button', {
305+
name: 'Approve',
306+
hidden: true,
307+
}),
308+
).not.toBeInTheDocument()
309+
expect(
310+
within(panels[4]).queryByRole('button', {
311+
name: 'Approve',
312+
hidden: true,
313+
}),
314+
).not.toBeInTheDocument()
315+
expect(
316+
within(panels[5]).queryByRole('button', {
317+
name: 'Approve',
318+
hidden: true,
319+
}),
320+
).not.toBeInTheDocument()
321+
})
322+
323+
it('is visible in latest pending spec if proposal is not deleted or revoked', async () => {
324+
specs = [
325+
buildJobProposalSpec({ id: '103', version: 3, status: 'PENDING' }),
326+
buildJobProposalSpec({ id: '102', version: 2, status: 'PENDING' }),
327+
buildJobProposalSpec({ id: '101', version: 1, status: 'CANCELLED' }),
328+
]
329+
proposal = buildJobProposal({ status: 'PENDING' })
330+
renderComponent(specs, proposal)
331+
332+
const panels = screen.getAllByTestId('expansion-panel')
333+
expect(panels).toHaveLength(3)
334+
expect(
335+
within(panels[0]).queryByRole('button', {
336+
name: 'Approve',
337+
hidden: false,
338+
}),
339+
).toBeInTheDocument()
340+
expect(
341+
within(panels[1]).queryByRole('button', {
342+
name: 'Approve',
343+
hidden: true,
344+
}),
345+
).not.toBeInTheDocument()
346+
expect(
347+
within(panels[2]).queryByRole('button', {
348+
name: 'Approve',
349+
hidden: true,
350+
}),
351+
).toBeInTheDocument()
352+
})
353+
354+
it('is not visible in any specs if proposal is deleted', async () => {
355+
proposal = buildJobProposal({ status: 'DELETED' })
356+
renderComponent(specs, proposal)
357+
358+
const panels = screen.getAllByTestId('expansion-panel')
359+
expect(panels).toHaveLength(6)
360+
expect(
361+
screen.queryByRole('button', { name: 'Approve', hidden: false }),
362+
).not.toBeInTheDocument()
363+
})
364+
365+
it('is not visible in any specs if proposal is revoked', async () => {
366+
proposal = buildJobProposal({ status: 'REVOKED' })
367+
renderComponent(specs, proposal)
368+
369+
const panels = screen.getAllByTestId('expansion-panel')
370+
expect(panels).toHaveLength(6)
371+
expect(
372+
screen.queryByRole('button', { name: 'Approve', hidden: false }),
373+
).not.toBeInTheDocument()
374+
})
375+
376+
it('is visible with single pending job', async () => {
377+
proposal = buildJobProposal({ status: 'PENDING' })
378+
specs = [
379+
buildJobProposalSpec({ id: '101', version: 1, status: 'PENDING' }),
380+
]
381+
renderComponent(specs, proposal)
382+
383+
const panels = screen.getAllByTestId('expansion-panel')
384+
expect(panels).toHaveLength(1)
385+
expect(
386+
within(panels[0]).queryByRole('button', {
387+
name: 'Approve',
388+
hidden: false,
389+
}),
390+
).toBeInTheDocument()
391+
})
392+
393+
it('is visible with single cancelled job', async () => {
394+
proposal = buildJobProposal({ status: 'PENDING' })
395+
specs = [
396+
buildJobProposalSpec({ id: '101', version: 1, status: 'CANCELLED' }),
397+
]
398+
renderComponent(specs, proposal)
399+
400+
const panels = screen.getAllByTestId('expansion-panel')
401+
expect(panels).toHaveLength(1)
402+
expect(
403+
within(panels[0]).queryByRole('button', {
404+
name: 'Approve',
405+
hidden: false,
406+
}),
407+
).toBeInTheDocument()
408+
})
409+
})
263410
})

src/screens/JobProposal/SpecsView.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ interface Props extends WithStyles<typeof styles> {
7272
}
7373

7474
interface ConfirmationDialogArgs {
75-
action: 'reject' | 'approve' | 'cancel'
75+
action: 'reject' | 'approve' | 'approvePrevious' | 'cancel'
7676
id: string
7777
}
7878

@@ -81,6 +81,25 @@ const confirmationDialogText = {
8181
title: 'Approve Job Proposal',
8282
body: 'Approving this job proposal will start running a new job. WARNING: If a job using the same contract address already exists, it will be deleted before running the new one.',
8383
},
84+
approvePrevious: {
85+
title: 'Approve Previous Version of Job Proposal',
86+
body: (
87+
<div>
88+
<p>
89+
⚠️ You have selected a job spec version that is not the most recent
90+
one.
91+
</p>
92+
<p>
93+
Approving this job proposal will start running a new job with the old
94+
spec version.
95+
</p>
96+
<p>
97+
WARNING: If a job using the same contract address already exists, it
98+
will be deleted before running the new one.
99+
</p>
100+
</div>
101+
),
102+
},
84103
cancel: {
85104
title: 'Cancel Job Proposal',
86105
body: 'Cancelling this job proposal will delete the running job. Are you sure you want to cancel this job proposal?',
@@ -124,6 +143,11 @@ export const SpecsView = withStyles(styles)(
124143
return sorted.sort((a, b) => b.version - a.version)
125144
}, [specs])
126145

146+
const approvableCancelledJobSpecs = sortedSpecs
147+
.filter((spec) => spec.status === 'CANCELLED')
148+
.slice(0, 2)
149+
.map((spec) => spec.id)
150+
127151
const renderActions = (
128152
status: SpecStatus,
129153
specID: string,
@@ -193,15 +217,20 @@ export const SpecsView = withStyles(styles)(
193217
)
194218
case 'CANCELLED':
195219
if (
196-
latestSpec.id === specID &&
220+
approvableCancelledJobSpecs.includes(specID) &&
197221
proposal.status !== 'DELETED' &&
198222
proposal.status !== 'REVOKED'
199223
) {
200224
return (
201225
<Button
202226
variant="contained"
203227
color="primary"
204-
onClick={() => openConfirmationDialog('approve', specID)}
228+
onClick={() =>
229+
openConfirmationDialog(
230+
specID === latestSpec.id ? 'approve' : 'approvePrevious',
231+
specID,
232+
)
233+
}
205234
>
206235
Approve
207236
</Button>
@@ -217,7 +246,11 @@ export const SpecsView = withStyles(styles)(
217246
return (
218247
<div>
219248
{sortedSpecs.map((spec, idx) => (
220-
<ExpansionPanel defaultExpanded={idx === 0} key={idx}>
249+
<ExpansionPanel
250+
defaultExpanded={idx === 0}
251+
key={idx}
252+
data-testid="expansion-panel"
253+
>
221254
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
222255
<Typography className={classes.versionText}>
223256
Version {spec.version}
@@ -285,6 +318,7 @@ export const SpecsView = withStyles(styles)(
285318
if (confirmationDialog) {
286319
switch (confirmationDialog.action) {
287320
case 'approve':
321+
case 'approvePrevious':
288322
onApprove(confirmationDialog.id)
289323

290324
break

0 commit comments

Comments
 (0)