@@ -43,6 +43,11 @@ import {
4343 Alert ,
4444} from '@mui/material'
4545import { 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'
4651import { useTheme } from '@emotion/react'
4752import { enqueueSnackbar } from 'notistack'
4853import CheckIcon from '@mui/icons-material/Check'
@@ -55,6 +60,8 @@ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
5560import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'
5661import TrendingFlatIcon from '@mui/icons-material/TrendingFlat'
5762import UndoIcon from '@mui/icons-material/Undo'
63+ import dayjs from 'dayjs'
64+ import 'dayjs/locale/en-gb'
5865
5966import PropTypes from 'prop-types'
6067
@@ -65,6 +72,7 @@ import {
6572import { rescore } from './api'
6673import {
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) => {
145154const 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 = {
915926const 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'
10181046ApplicableRescoringsRow . 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