Skip to content

Commit 6922108

Browse files
committed
Allow manual input of due_date in rescoring view
1 parent 05b0e30 commit 6922108

2 files changed

Lines changed: 133 additions & 30 deletions

File tree

src/consts.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ export const META_SPRINT_NAMES = {
218218
Object.freeze(META_SPRINT_NAMES)
219219

220220

221+
export const META_ALLOWED_PROCESSING_TIME = {
222+
INPUT: 'input',
223+
}
224+
Object.freeze(META_ALLOWED_PROCESSING_TIME)
225+
226+
221227
export const USER_IDENTITIES = {
222228
EMAIL_ADDRESS: 'emailAddress',
223229
GITHUB_USER: 'githubUser',

src/rescoring.js

Lines changed: 127 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ import {
4343
Alert,
4444
} from '@mui/material'
4545
import { alpha } from '@mui/material/styles'
46+
import {
47+
DatePicker,
48+
LocalizationProvider,
49+
} from '@mui/x-date-pickers'
50+
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
4651
import { useTheme } from '@emotion/react'
4752
import { enqueueSnackbar } from 'notistack'
4853
import CheckIcon from '@mui/icons-material/Check'
@@ -55,6 +60,8 @@ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
5560
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'
5661
import TrendingFlatIcon from '@mui/icons-material/TrendingFlat'
5762
import UndoIcon from '@mui/icons-material/Undo'
63+
import dayjs from 'dayjs'
64+
import 'dayjs/locale/en-gb'
5865

5966
import PropTypes from 'prop-types'
6067

@@ -65,6 +72,7 @@ import {
6572
import { rescore } from './api'
6673
import {
6774
errorSnackbarProps,
75+
META_ALLOWED_PROCESSING_TIME,
6876
META_RESCORING_RULES,
6977
META_SPRINT_NAMES,
7078
RESCORING_MODES,
@@ -128,6 +136,7 @@ const patchRescoringProposals = (rescoringProposals, ocmNode) => {
128136
ocmNode: ocmNode,
129137
originalSeverityProposal: rescoringProposal.severity,
130138
originalMatchingRules: rescoringProposal.matching_rules,
139+
originalDueDate: rescoringProposal.due_date,
131140
}
132141
}))
133142
}
@@ -145,8 +154,10 @@ const rescoringIdentity = (rescoring) => {
145154
const rescoringNeedsComment = (rescoring) => {
146155
return (
147156
rescoring.matching_rules.includes(META_RESCORING_RULES.CUSTOM_RESCORING)
148-
&& rescoring.severity !== rescoring.originalSeverityProposal
149-
&& !rescoring.comment
157+
&& (
158+
rescoring.severity !== rescoring.originalSeverityProposal
159+
|| rescoring.due_date !== rescoring.originalDueDate
160+
) && !rescoring.comment
150161
)
151162
}
152163

@@ -915,6 +926,7 @@ AppliedRulesExtraInfo.propTypes = {
915926
const ApplicableRescoringsRow = ({
916927
findingCfg,
917928
applicableRescoring,
929+
discoveryDate,
918930
priority,
919931
fetchDeleteApplicableRescoring,
920932
}) => {
@@ -938,6 +950,19 @@ const ApplicableRescoringsRow = ({
938950
findingCfg: findingCfg,
939951
})
940952

953+
const dueDate = () => {
954+
const _dueDate = applicableRescoring.data.due_date
955+
const _allowedProcessingTime = applicableRescoring.data.allowed_processing_time
956+
957+
if (_dueDate) return (new Date(_dueDate)).toLocaleDateString()
958+
if (!_allowedProcessingTime) return null
959+
960+
const _discoveryDate = new Date(discoveryDate)
961+
// `allowed_processing_time` of a rescoring is always stored as seconds
962+
_discoveryDate.setSeconds(_discoveryDate.getSeconds() + _allowedProcessingTime.replace('s', ''))
963+
return _discoveryDate.toLocaleDateString()
964+
}
965+
941966
return <TableRow
942967
onMouseEnter={() => setRowHovered(true)}
943968
onMouseLeave={() => {
@@ -985,6 +1010,9 @@ const ApplicableRescoringsRow = ({
9851010
<TableCell>
9861011
<Typography variant='inherit' sx={{ wordWrap: 'break-word' }}>{applicableRescoring.data.comment}</Typography>
9871012
</TableCell>
1013+
<TableCell>
1014+
<Typography align='center' variant='inherit'>{dueDate()}</Typography>
1015+
</TableCell>
9881016
<TableCell>
9891017
{
9901018
applicableRescoring.data.matching_rules.map((rule_name) => <Typography key={rule_name} variant='inherit'>
@@ -1018,6 +1046,7 @@ ApplicableRescoringsRow.displayName = 'ApplicableRescoringsRow'
10181046
ApplicableRescoringsRow.propTypes = {
10191047
findingCfg: PropTypes.object.isRequired,
10201048
applicableRescoring: PropTypes.object.isRequired,
1049+
discoveryDate: PropTypes.string.isRequired,
10211050
priority: PropTypes.number.isRequired,
10221051
fetchDeleteApplicableRescoring: PropTypes.func.isRequired,
10231052
}
@@ -1125,10 +1154,9 @@ const ApplicableRescorings = ({
11251154
</TableCell>
11261155
<TableCell width='11%' align='center'>Categorisation</TableCell>
11271156
<TableCell width='11%' align='center'>User</TableCell>
1128-
<TableCell width='25%'>Comment</TableCell>
1129-
<TableCell width='19%'>
1130-
<Typography variant='inherit'>Applied Rules</Typography>
1131-
</TableCell>
1157+
<TableCell width='15%'>Comment</TableCell>
1158+
<TableCell width='10%' align='center'>New Due Date</TableCell>
1159+
<TableCell width='19%'>Applied Rules</TableCell>
11321160
<TableCell width='5%' sx={{ border: 0 }}/>
11331161
</TableRow>
11341162
</TableHead>
@@ -1138,6 +1166,7 @@ const ApplicableRescorings = ({
11381166
key={idx}
11391167
findingCfg={findingCfg}
11401168
applicableRescoring={ap}
1169+
discoveryDate={rescoring.discovery_date}
11411170
priority={idx + 1}
11421171
fetchDeleteApplicableRescoring={fetchDeleteApplicableRescoring}
11431172
/>)
@@ -1543,9 +1572,12 @@ const RescoringContentTableRow = ({
15431572
severity,
15441573
matching_rules,
15451574
applicable_rescorings,
1575+
discovery_date,
1576+
due_date,
15461577
ocmNode,
15471578
originalSeverityProposal,
15481579
originalMatchingRules,
1580+
originalDueDate,
15491581
} = rescoring
15501582

15511583
const [expanded, setExpanded] = React.useState(false)
@@ -1565,15 +1597,21 @@ const RescoringContentTableRow = ({
15651597

15661598
const sprintInfo = sprints.find((s) => s.name === sprintNameForRescoring({rescoring, findingCfg}))
15671599

1568-
const currentDays = currentCategorisation.allowed_processing_time / 24 / 60 / 60
1569-
const rescoredDays = rescoredCategorisation.allowed_processing_time / 24 / 60 / 60
1600+
const allowedProcessingDays = (dueDate) => {
1601+
return Math.max(0, Math.round((new Date(dueDate) - new Date(discovery_date)) / 1000 / 60 / 60 / 24)) // ms -> days
1602+
}
1603+
1604+
const currentDays = allowedProcessingDays(originalDueDate)
1605+
const rescoredDays = rescoredCategorisation.allowed_processing_time === META_ALLOWED_PROCESSING_TIME.INPUT
1606+
? allowedProcessingDays(due_date)
1607+
: rescoredCategorisation.allowed_processing_time / 60 / 60 / 24 // sec -> days
15701608

15711609
// don't show day-diff if one of both categorisations has no processing time set or there is no difference
15721610
const diffDays = (
15731611
currentCategorisation.allowed_processing_time !== null
15741612
&& rescoredCategorisation.allowed_processing_time !== null
15751613
&& currentDays !== rescoredDays
1576-
) ? `${rescoredDays - currentDays >= 0 ? '+' : ''}${rescoredDays - currentDays} days`
1614+
) ? `${rescoredDays - currentDays >= 0 ? '+' : ''}${rescoredDays - currentDays} day${Math.abs(rescoredDays - currentDays) === 1 ? '' : 's'}`
15771615
: null
15781616

15791617
const newProccesingDays = diffDays ? <Tooltip
@@ -1586,6 +1624,7 @@ const RescoringContentTableRow = ({
15861624

15871625
const delayRescoringUpdate = ({
15881626
comment,
1627+
due_date,
15891628
}) => {
15901629
if (updateDelayTimer) {
15911630
clearTimeout(updateDelayTimer)
@@ -1596,6 +1635,7 @@ const RescoringContentTableRow = ({
15961635
editRescoring({
15971636
rescoring,
15981637
comment,
1638+
due_date,
15991639
})
16001640
if (!selectedRescorings.find((r) => rescoringIdentity(r) === rescoringIdentity(rescoring))) {
16011641
selectRescoring(rescoring)
@@ -1639,13 +1679,13 @@ const RescoringContentTableRow = ({
16391679
</TableCell>
16401680
<TableCell align='center'>
16411681
{
1642-
sprintInfo ? <Tooltip
1682+
sprintInfo && <Tooltip
16431683
title={<Typography
16441684
variant='inherit'
16451685
whiteSpace='pre-line'
16461686
>
16471687
{
1648-
`${sprintInfo.tooltip}\nFirst discovered on ${new Date(rescoring.discovery_date).toLocaleDateString()}`
1688+
`${sprintInfo.tooltip}\nFirst discovered on ${new Date(discovery_date).toLocaleDateString()}`
16491689
}
16501690
</Typography>}
16511691
>
@@ -1655,7 +1695,7 @@ const RescoringContentTableRow = ({
16551695
color={sprintInfo.color}
16561696
size='small'
16571697
/>
1658-
</Tooltip> : <></>
1698+
</Tooltip>
16591699
}
16601700
</TableCell>
16611701
<TableCell align='right' sx={{ paddingX: 0 }}>
@@ -1675,10 +1715,17 @@ const RescoringContentTableRow = ({
16751715
<Select
16761716
value={severity}
16771717
onChange={(e) => {
1718+
const id = e.target.value
1719+
const categorisation = findCategorisationById({id, findingCfg})
1720+
const dueDate = categorisation.allowed_processing_time === META_ALLOWED_PROCESSING_TIME.INPUT
1721+
? originalDueDate
1722+
: new Date(discovery_date) + categorisation.allowed_processing_time * 1000 // sec -> ms
1723+
16781724
editRescoring({
16791725
rescoring: rescoring,
1680-
severity: e.target.value,
1726+
severity: id,
16811727
matchingRules: [META_RESCORING_RULES.CUSTOM_RESCORING],
1728+
due_date: dueDate,
16821729
})
16831730
if (!selectedRescorings.find((r) => rescoringIdentity(r) === rescoringIdentity(rescoring))) {
16841731
selectRescoring(rescoring)
@@ -1737,22 +1784,50 @@ const RescoringContentTableRow = ({
17371784
</div>
17381785
</TableCell>
17391786
<TableCell>
1740-
<TextField
1741-
label='Comment'
1742-
defaultValue={rescoring.comment}
1743-
onChange={(e) => delayRescoringUpdate({comment: e.target.value})}
1744-
onClick={(e) => e.stopPropagation()}
1745-
error={rescoringNeedsComment(rescoring)}
1746-
size='small'
1747-
maxRows={4}
1748-
InputProps={{
1749-
sx: {
1750-
fontSize: 'inherit',
1751-
},
1752-
}}
1753-
fullWidth
1754-
multiline
1755-
/>
1787+
<Stack spacing={2}>
1788+
<TextField
1789+
label='Comment'
1790+
defaultValue={rescoring.comment}
1791+
onChange={(e) => delayRescoringUpdate({comment: e.target.value})}
1792+
onClick={(e) => e.stopPropagation()}
1793+
error={rescoringNeedsComment(rescoring)}
1794+
size='small'
1795+
maxRows={4}
1796+
InputProps={{
1797+
sx: {
1798+
fontSize: 'inherit',
1799+
},
1800+
}}
1801+
fullWidth
1802+
multiline
1803+
/>
1804+
{
1805+
rescoredCategorisation.allowed_processing_time === META_ALLOWED_PROCESSING_TIME.INPUT && <Box
1806+
onClick={(e) => e.stopPropagation()}
1807+
style={{
1808+
display: 'flex',
1809+
flexDirection: 'column',
1810+
}}
1811+
>
1812+
<LocalizationProvider
1813+
dateAdapter={AdapterDayjs}
1814+
adapterLocale='en-gb'
1815+
>
1816+
<DatePicker
1817+
label='Due Date'
1818+
defaultValue={dayjs(due_date ? new Date(due_date) : new Date())}
1819+
minDate={dayjs(new Date())}
1820+
onChange={(value) => delayRescoringUpdate({due_date: value.format('YYYY-MM-DD')})}
1821+
slotProps={{
1822+
textField: {
1823+
size: 'small',
1824+
},
1825+
}}
1826+
/>
1827+
</LocalizationProvider>
1828+
</Box>
1829+
}
1830+
</Stack>
17561831
</TableCell>
17571832
<TableCell align='center'>
17581833
{
@@ -2352,6 +2427,7 @@ const Rescore = ({
23522427
handleClose,
23532428
setShowProgress,
23542429
scope,
2430+
findingCfg,
23552431
fetchComplianceData,
23562432
fetchComplianceSummary,
23572433
}) => {
@@ -2412,12 +2488,24 @@ const Rescore = ({
24122488
}
24132489
}
24142490

2491+
const categorisation = findCategorisationById({
2492+
id: rescoring.severity,
2493+
findingCfg: findingCfg,
2494+
})
2495+
2496+
const _allowedProcessingTime = categorisation.allowed_processing_time
2497+
const allowedProcessingTime = !_allowedProcessingTime || _allowedProcessingTime === META_ALLOWED_PROCESSING_TIME.INPUT
2498+
? _allowedProcessingTime
2499+
: `${_allowedProcessingTime}s` // `allowed_processing_time` of a rescoring is always stored as seconds
2500+
24152501
const data = {
24162502
finding: findingForType(rescoring.finding_type),
24172503
referenced_type: rescoring.finding_type,
24182504
severity: rescoring.severity,
24192505
matching_rules: rescoring.matching_rules,
24202506
comment: rescoring.comment,
2507+
allowed_processing_time: allowedProcessingTime,
2508+
due_date: allowedProcessingTime === META_ALLOWED_PROCESSING_TIME.INPUT ? rescoring.due_date : null,
24212509
}
24222510

24232511
return {artefact, meta, data}
@@ -2543,6 +2631,7 @@ Rescore.propTypes = {
25432631
handleClose: PropTypes.func.isRequired,
25442632
setShowProgress: PropTypes.func.isRequired,
25452633
scope: PropTypes.string.isRequired,
2634+
findingCfg: PropTypes.object.isRequired,
25462635
fetchComplianceData: PropTypes.func,
25472636
fetchComplianceSummary: PropTypes.func,
25482637
}
@@ -2596,6 +2685,7 @@ const RescoringModal = ({
25962685
severity,
25972686
matchingRules,
25982687
comment,
2688+
due_date,
25992689
}) => {
26002690
setRescorings((prev) => {
26012691
// don't mess up table sorting, therefore insert at index
@@ -2608,6 +2698,7 @@ const RescoringModal = ({
26082698
matching_rules: severity === rescoring.originalSeverityProposal ? rescoring.originalMatchingRules : matchingRules,
26092699
},
26102700
...comment !== undefined && {comment: comment},
2701+
...due_date !== undefined && {due_date: due_date}
26112702
}
26122703

26132704
// reconstruct array to trigger state-update (thus re-render)
@@ -2650,9 +2741,14 @@ const RescoringModal = ({
26502741
}, [setSprints, rescoringsForType])
26512742

26522743
const allowedRescorings = filteredRescorings?.filter((rescoring) => {
2744+
// only rescorings are allowed iff their severity has changed OR they allow a custom due date
2745+
// input and the due date has changed
26532746
return (
26542747
selectedRescorings.find((r) => rescoringIdentity(r) === rescoringIdentity(rescoring))
2655-
&& rescoring.severity !== categoriseRescoringProposal({rescoring, findingCfg}).id
2748+
&& (
2749+
rescoring.severity !== categoriseRescoringProposal({rescoring, findingCfg}).id
2750+
|| rescoring.due_date !== rescoring.originalDueDate
2751+
)
26562752
)
26572753
})
26582754
const filteredOutRescoringsLength = filteredRescorings ? selectedRescorings.length - allowedRescorings.length : 0
@@ -2869,6 +2965,7 @@ const RescoringModal = ({
28692965
handleClose={handleClose}
28702966
setShowProgress={setShowProgress}
28712967
scope={scope}
2968+
findingCfg={findingCfg}
28722969
fetchComplianceData={fetchComplianceData}
28732970
fetchComplianceSummary={fetchComplianceSummary}
28742971
/>

0 commit comments

Comments
 (0)