From 80c31e645c25898a6189efc5c5018a33269aeae1 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 11 Dec 2023 17:15:06 +0300 Subject: [PATCH 01/38] Proof of concept implementation of rewind-the-rules-table UI --- ui/src/components/RuleCard/index.jsx | 20 +++-- ui/src/services/rules.js | 3 +- ui/src/views/Rules/ListRules/index.jsx | 115 ++++++++++++++++++------- 3 files changed, 99 insertions(+), 39 deletions(-) diff --git a/ui/src/components/RuleCard/index.jsx b/ui/src/components/RuleCard/index.jsx index 9d8579c168..e9a22d30f0 100644 --- a/ui/src/components/RuleCard/index.jsx +++ b/ui/src/components/RuleCard/index.jsx @@ -161,6 +161,7 @@ function RuleCard({ user, readOnly, actionLoading, + disableActions, // We don't actually use these, but we need to avoid passing them onto // `Card` like the rest of the props. onAuthorize: _, @@ -871,7 +872,9 @@ function RuleCard({ rulesFilter, }, }}> - + ) : ( + ) : ( @@ -906,14 +911,16 @@ function RuleCard({ (user && user.email in rule.scheduledChange.signoffs ? ( ) : ( @@ -935,12 +942,15 @@ RuleCard.propTypes = { // If true, card buttons that trigger a request // navigating to a different view will be disabled actionLoading: bool, + // If true, the card will disable all buttons + disableActions: bool, }; RuleCard.defaultProps = { onRuleDelete: Function.prototype, readOnly: false, actionLoading: false, + disableActions: false, }; export default withUser(RuleCard); diff --git a/ui/src/services/rules.js b/ui/src/services/rules.js index 118dd5a782..5d0af08d25 100644 --- a/ui/src/services/rules.js +++ b/ui/src/services/rules.js @@ -1,7 +1,8 @@ import { stringify } from 'qs'; import axios from 'axios'; -const getRules = () => axios.get('/rules'); +const getRules = (timestamp = null) => + timestamp ? axios.get(`/rules?timestamp=${timestamp}`) : axios.get('/rules'); const getRule = id => axios.get(`/rules/${id}`); const getChannels = () => axios.get('/rules/columns/channel'); const getProducts = () => axios.get('/rules/columns/product'); diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index d40da4baab..0f28654a20 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -14,6 +14,7 @@ import RadioGroup from '@material-ui/core/RadioGroup'; import FormControl from '@material-ui/core/FormControl'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormLabel from '@material-ui/core/FormLabel'; +import Checkbox from '@material-ui/core/Checkbox'; import Switch from '@material-ui/core/Switch'; import SpeedDialAction from '@material-ui/lab/SpeedDialAction'; import Drawer from '@material-ui/core/Drawer'; @@ -155,6 +156,8 @@ function ListRules(props) { addSeconds(new Date(), -30) ); const [dateTimePickerError, setDateTimePickerError] = useState(null); + const [rewindDate, setRewindDate] = useState(null); + const [rewindDateError, setRewindDateError] = useState(null); const [scrollToRow, setScrollToRow] = useState(null); const [roles, setRoles] = useState([]); const [requiredRoles, setRequiredRoles] = useState([]); @@ -340,24 +343,17 @@ function ListRules(props) { }, [products.data, channels.data, rules.data]); useEffect(() => { - Promise.all([ - fetchScheduledChanges(), - fetchRules(), - fetchRequiredSignoffs(OBJECT_NAMES.PRODUCT_REQUIRED_SIGNOFF), - fetchEmergencyShutoffs(), - fetchScheduledEmergencyShutoffs(), - fetchProducts(), - fetchChannels(), - ]).then(([sc, r, rs, es, scheduledEs]) => { - if (!sc.data || !r.data || !rs.data) { - return; - } + if (!rules.data || !scheduledChanges.data || !requiredSignoffs.data) { + return; + } - const scheduledChanges = sc.data.data.scheduled_changes; - const requiredSignoffs = rs.data.data.required_signoffs; - const { rules } = r.data.data; - const rulesWithScheduledChanges = rules.map(rule => { - const sc = scheduledChanges.find(sc => rule.rule_id === sc.rule_id); + if (rewindDate) { + setRulesWithScheduledChanges(rules.data.data.rules); + } else { + const rulesWithScheduledChanges = rules.data.data.rules.map(rule => { + const sc = scheduledChanges.data.data.scheduled_changes.find( + sc => rule.rule_id === sc.rule_id + ); const returnedRule = { ...rule }; if (sc) { @@ -368,7 +364,7 @@ function ListRules(props) { } returnedRule.required_signoffs = {}; - requiredSignoffs.forEach(rs => { + requiredSignoffs.data.data.required_signoffs.forEach(rs => { if (ruleMatchesRequiredSignoff(rule, rs)) { returnedRule.required_signoffs[rs.role] = rs.signoffs_required; } @@ -377,7 +373,7 @@ function ListRules(props) { return returnedRule; }); - scheduledChanges.forEach(sc => { + scheduledChanges.data.data.scheduled_changes.forEach(sc => { if (sc.change_type === 'insert') { const rule = { scheduledChange: sc }; @@ -414,24 +410,52 @@ function ListRules(props) { setRulesWithScheduledChanges(sortedRules); - if (es.data && scheduledEs.data) { - const shutoffs = es.data.data.shutoffs.map(shutoff => { - const returnedShutoff = clone(shutoff); - const sc = scheduledEs.data.data.scheduled_changes.find( - ses => - ses.product === shutoff.product && ses.channel === shutoff.channel - ); - - if (sc) { - returnedShutoff.scheduledChange = sc; + if ( + emergencyShutoffsAction.data && + scheduledEmergencyShutoffsAction.data + ) { + const shutoffs = emergencyShutoffsAction.data.data.shutoffs.map( + shutoff => { + const returnedShutoff = clone(shutoff); + const sc = scheduledEmergencyShutoffsAction.data.data.scheduled_changes.find( + ses => + ses.product === shutoff.product && + ses.channel === shutoff.channel + ); + + if (sc) { + returnedShutoff.scheduledChange = sc; + } + + return returnedShutoff; } - - return returnedShutoff; - }); + ); setEmergencyShutoffs(shutoffs); } - }); + } + }, [ + rules.data, + scheduledChanges.data, + requiredSignoffs.data, + emergencyShutoffsAction.data, + scheduledEmergencyShutoffsAction.data, + ]); + + useEffect(() => { + fetchRules(rewindDate ? rewindDate.getTime() : null); + }, [rewindDate]); + + useEffect(() => { + Promise.all([ + fetchScheduledChanges(), + fetchRules(), + fetchRequiredSignoffs(OBJECT_NAMES.PRODUCT_REQUIRED_SIGNOFF), + fetchEmergencyShutoffs(), + fetchScheduledEmergencyShutoffs(), + fetchProducts(), + fetchChannels(), + ]); }, []); useEffect(() => { @@ -493,6 +517,15 @@ function ListRules(props) { setDateTimePickerError(null); }; + const handleRewindDateTimePickerError = error => { + setRewindDateError(error); + }; + + const handleRewindDateTimeChange = date => { + setRewindDate(date); + setRewindDateError(null); + }; + const handleDialogError = error => { setDialogState({ ...dialogState, error }); }; @@ -1235,6 +1268,7 @@ function ListRules(props) { : Object.values(rule.scheduledChange).join('-') } style={style}> + {/* should we go read only mode if rewindDate is set instead? */} roles.includes(r)) .length } onSignoff={() => handleSignoff(rule)} onRevoke={() => handleRevoke(rule)} onViewReleaseClick={handleViewRelease} + disableActions={Boolean(rewindDate)} actionLoading={isActionLoading} /> @@ -1291,6 +1327,19 @@ function ListRules(props) { {!isLoading && productChannelOptions && (
+ + + Diff? + + Filter by rules with scheduled changes From b3ba8964cc6ff4627a1168dbdec8a859e543c94d Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 11 Dec 2023 17:44:16 +0300 Subject: [PATCH 02/38] Lint fix for recurring max-len error --- ui/src/views/Rules/ListRules/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 0f28654a20..3675e23547 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -417,6 +417,7 @@ function ListRules(props) { const shutoffs = emergencyShutoffsAction.data.data.shutoffs.map( shutoff => { const returnedShutoff = clone(shutoff); + /* eslint-disable-next-line max-len */ const sc = scheduledEmergencyShutoffsAction.data.data.scheduled_changes.find( ses => ses.product === shutoff.product && From 36135426d45c0ae796f61221bf906d2625c0363d Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Wed, 13 Dec 2023 12:29:44 +0300 Subject: [PATCH 03/38] WIP: Implement horrible hardcoded way to use diff mode --- ui/src/views/Rules/ListRules/index.jsx | 68 +++++++++++++++++--------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 3675e23547..d4b9995936 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -27,6 +27,7 @@ import RuleCard from '../../../components/RuleCard'; import DialogAction from '../../../components/DialogAction'; import DateTimePicker from '../../../components/DateTimePicker'; import VariableSizeList from '../../../components/VariableSizeList'; +import DiffRule from '../../../components/DiffRule'; import SpeedDial from '../../../components/SpeedDial'; import Link from '../../../utils/Link'; import getDiffedProperties from '../../../utils/getDiffedProperties'; @@ -141,6 +142,7 @@ function ListRules(props) { const [rulesWithScheduledChanges, setRulesWithScheduledChanges] = useState( [] ); + const [rewoundRules, setRewoundRules] = useState([]); const searchFieldRef = useRef(); const [productChannelOptions, setProductChannelOptions] = useState([]); const productChannelQueries = query.product @@ -348,7 +350,7 @@ function ListRules(props) { } if (rewindDate) { - setRulesWithScheduledChanges(rules.data.data.rules); + setRewoundRules(rules.data.data.rules); } else { const rulesWithScheduledChanges = rules.data.data.rules.map(rule => { const sc = scheduledChanges.data.data.scheduled_changes.find( @@ -409,6 +411,7 @@ function ListRules(props) { }); setRulesWithScheduledChanges(sortedRules); + setRewoundRules([]); if ( emergencyShutoffsAction.data && @@ -487,7 +490,17 @@ function ListRules(props) { } // Product channel dropdown filter - filteredRules = filteredRules.filter(rule => { + // should be rulesWithScheduledChanges if rewoundRules.length is 0, + // or we're diffing rewound rules against current rules + // TODO: make this work without hardcodes :) + // use this line for non-diff mode + // let rulesToShow = rewoundRules.length === 0 ? + // filteredRules : + // rewoundRules; + // use this line for diff mode + let rulesToShow = filteredRules; + + rulesToShow = rulesToShow.filter(rule => { const [productFilter, channelFilter] = productChannelQueries; const ruleProduct = rule.product || (rule.scheduledChange && rule.scheduledChange.product); @@ -503,11 +516,12 @@ function ListRules(props) { return true; }); - return filteredRules; + return rulesToShow; }, [ productChannelQueries, rulesWithScheduledChanges, query.onlyScheduledChanges, + rewoundRules, ]); const handleDateTimePickerError = error => { setDateTimePickerError(error); @@ -1258,7 +1272,12 @@ function ListRules(props) { }; const Row = ({ index, style }) => { + // if we're in rewind mode, rule is a historical rule, not the current one const rule = filteredRulesWithScheduledChanges[index]; + // this is always the current version + const currentRule = rulesWithScheduledChanges.filter( + r => r.rule_id === rule.rule_id + ); const isSelected = isRuleSelected(rule); return ( @@ -1270,25 +1289,30 @@ function ListRules(props) { } style={style}> {/* should we go read only mode if rewindDate is set instead? */} - roles.includes(r)) - .length - } - onSignoff={() => handleSignoff(rule)} - onRevoke={() => handleRevoke(rule)} - onViewReleaseClick={handleViewRelease} - disableActions={Boolean(rewindDate)} - actionLoading={isActionLoading} - /> + {rewoundRules.length === 0 ? ( + roles.includes(r)) + .length + } + onSignoff={() => handleSignoff(rule)} + onRevoke={() => handleRevoke(rule)} + onViewReleaseClick={handleViewRelease} + disableActions={Boolean(rewindDate)} + actionLoading={isActionLoading} + /> + ) : ( + // TODO: Why is the current version of the rule on the left side? + + )}
); }; From 1af3ea9654495df2a9d33ccab928c8ca9eb32ca2 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Wed, 13 Dec 2023 12:43:52 +0300 Subject: [PATCH 04/38] Don't use DiffRule directly in ListRules --- ui/src/views/Rules/ListRules/index.jsx | 48 ++++++++++---------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index d4b9995936..d2af59f2be 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -27,7 +27,6 @@ import RuleCard from '../../../components/RuleCard'; import DialogAction from '../../../components/DialogAction'; import DateTimePicker from '../../../components/DateTimePicker'; import VariableSizeList from '../../../components/VariableSizeList'; -import DiffRule from '../../../components/DiffRule'; import SpeedDial from '../../../components/SpeedDial'; import Link from '../../../utils/Link'; import getDiffedProperties from '../../../utils/getDiffedProperties'; @@ -1274,10 +1273,6 @@ function ListRules(props) { const Row = ({ index, style }) => { // if we're in rewind mode, rule is a historical rule, not the current one const rule = filteredRulesWithScheduledChanges[index]; - // this is always the current version - const currentRule = rulesWithScheduledChanges.filter( - r => r.rule_id === rule.rule_id - ); const isSelected = isRuleSelected(rule); return ( @@ -1289,30 +1284,25 @@ function ListRules(props) { } style={style}> {/* should we go read only mode if rewindDate is set instead? */} - {rewoundRules.length === 0 ? ( - roles.includes(r)) - .length - } - onSignoff={() => handleSignoff(rule)} - onRevoke={() => handleRevoke(rule)} - onViewReleaseClick={handleViewRelease} - disableActions={Boolean(rewindDate)} - actionLoading={isActionLoading} - /> - ) : ( - // TODO: Why is the current version of the rule on the left side? - - )} + roles.includes(r)) + .length + } + onSignoff={() => handleSignoff(rule)} + onRevoke={() => handleRevoke(rule)} + onViewReleaseClick={handleViewRelease} + disableActions={Boolean(rewindDate)} + actionLoading={isActionLoading} + /> ); }; From eb41ec2c2ec70e0723196b616bc14e5d06770a9a Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Wed, 13 Dec 2023 17:51:06 +0300 Subject: [PATCH 05/38] Add horrible hack to implement diff checkbox --- ui/src/views/Rules/ListRules/index.jsx | 42 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index d2af59f2be..3a914b31f6 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -159,6 +159,7 @@ function ListRules(props) { const [dateTimePickerError, setDateTimePickerError] = useState(null); const [rewindDate, setRewindDate] = useState(null); const [rewindDateError, setRewindDateError] = useState(null); + const [showRewindDiff, setShowRewindDiff] = useState(false); const [scrollToRow, setScrollToRow] = useState(null); const [roles, setRoles] = useState([]); const [requiredRoles, setRequiredRoles] = useState([]); @@ -305,6 +306,8 @@ function ListRules(props) { setSnackbarState({ message, variant, open: true }); }; + const handleRewindDiffChange = ({ target: { checked: value } }) => + setShowRewindDiff(value); const handleSnackbarClose = (event, reason) => { if (reason === 'clickaway') { return; @@ -489,15 +492,9 @@ function ListRules(props) { } // Product channel dropdown filter - // should be rulesWithScheduledChanges if rewoundRules.length is 0, - // or we're diffing rewound rules against current rules - // TODO: make this work without hardcodes :) - // use this line for non-diff mode - // let rulesToShow = rewoundRules.length === 0 ? - // filteredRules : - // rewoundRules; - // use this line for diff mode - let rulesToShow = filteredRules; + + // use this line for non-diff mode on rewound rules + let rulesToShow = rewoundRules.length === 0 ? filteredRules : rewoundRules; rulesToShow = rulesToShow.filter(rule => { const [productFilter, channelFilter] = productChannelQueries; @@ -1274,6 +1271,27 @@ function ListRules(props) { // if we're in rewind mode, rule is a historical rule, not the current one const rule = filteredRulesWithScheduledChanges[index]; const isSelected = isRuleSelected(rule); + const currentRule = rulesWithScheduledChanges.filter( + // eslint-disable-next-line eqeqeq + r => r.rule_id == rule.rule_id + ); + + if (rewoundRules.length !== 0) { + // TODO: horrible hack, need to fix this + // maybe add diffAgainst argument to RuleCard + // to control whether or not DiffRule is shown + if (showRewindDiff && currentRule.length !== 0) { + const [currRule] = currentRule; + + rule.scheduledChange = currRule; + rule.scheduledChange.change_type = 'update'; + rule.scheduledChange.required_signoffs = {}; + rule.scheduledChange.signoffs = {}; + rule.scheduledChange.when = new Date(); + } else if (rule.scheduledChange) { + delete rule.scheduledChange; + } + } return (
Diff? - + From 1f151c7df34acb9536e654f65e123d42d028e883 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 14 Dec 2023 14:05:55 +0300 Subject: [PATCH 06/38] Lint fixes --- ui/src/views/Rules/ListRules/index.jsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 3a914b31f6..1fe2894845 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1271,19 +1271,16 @@ function ListRules(props) { // if we're in rewind mode, rule is a historical rule, not the current one const rule = filteredRulesWithScheduledChanges[index]; const isSelected = isRuleSelected(rule); - const currentRule = rulesWithScheduledChanges.filter( - // eslint-disable-next-line eqeqeq - r => r.rule_id == rule.rule_id + const currentRule = rulesWithScheduledChanges.find( + r => r.rule_id === rule.rule_id ); if (rewoundRules.length !== 0) { // TODO: horrible hack, need to fix this // maybe add diffAgainst argument to RuleCard // to control whether or not DiffRule is shown - if (showRewindDiff && currentRule.length !== 0) { - const [currRule] = currentRule; - - rule.scheduledChange = currRule; + if (showRewindDiff && currentRule) { + rule.scheduledChange = currentRule; rule.scheduledChange.change_type = 'update'; rule.scheduledChange.required_signoffs = {}; rule.scheduledChange.signoffs = {}; From 6b7a1fc2dfa117bf6b0df7870a8fab8d7e9ed806 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 14 Dec 2023 14:19:36 +0300 Subject: [PATCH 07/38] Fix button disabling --- ui/src/components/RuleCard/index.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/components/RuleCard/index.jsx b/ui/src/components/RuleCard/index.jsx index e9a22d30f0..0adf353a28 100644 --- a/ui/src/components/RuleCard/index.jsx +++ b/ui/src/components/RuleCard/index.jsx @@ -861,7 +861,7 @@ function RuleCard({ {!readOnly && ( - {user ? ( + {user && !disableActions ? ( ) : ( - )} - {user ? ( + {user && !disableActions ? ( ) : ( - )} From 4a6a0c23ed4afc55cc466957689cbad91c673452 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 14 Dec 2023 16:20:07 +0300 Subject: [PATCH 08/38] Center and space rule list options --- ui/src/views/Rules/ListRules/index.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 1fe2894845..d2688893fa 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -84,10 +84,13 @@ const useStyles = makeStyles(theme => ({ left: 0, padding: `${theme.spacing(4)}px ${theme.spacing(4)}px 0`, display: 'flex', - justifyContent: 'flex-end', + justifyContent: 'center', position: 'fixed', zIndex: 2, }, + checkbox: { + padding: `${theme.spacing(2)}px ${theme.spacing(1)}px`, + }, dropdown: { minWidth: 200, marginBottom: theme.spacing(2), @@ -1366,7 +1369,7 @@ function ListRules(props) { onDateTimeChange={handleRewindDateTimeChange} value={rewindDate} /> - + Diff? Date: Thu, 14 Dec 2023 17:17:17 +0300 Subject: [PATCH 09/38] Fix search field cutting off first rule card in rewind mode --- ui/src/views/Rules/ListRules/index.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index d2688893fa..b6387f5622 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -145,7 +145,8 @@ function ListRules(props) { [] ); const [rewoundRules, setRewoundRules] = useState([]); - const searchFieldRef = useRef(); + const searchFieldRef = useRef(null); + const [searchFieldHeight, setSearchFieldHeight] = useState(0); const [productChannelOptions, setProductChannelOptions] = useState([]); const productChannelQueries = query.product ? [query.product, query.channel] @@ -467,6 +468,10 @@ function ListRules(props) { ]); }, []); + useEffect(() => { + setSearchFieldHeight(searchFieldRef.current.clientHeight); + }, []); + useEffect(() => { if (username) { fetchRoles(username).then(userInfo => { @@ -1400,12 +1405,7 @@ function ListRules(props) { ))}
-
+
{productChannelFilter !== ALL && productChannelQueries && productChannelQueries.length === 2 && From 138fb99c56942ab759b7e984e3e321414ba93449 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 14 Dec 2023 17:24:19 +0300 Subject: [PATCH 10/38] Rename disableActions to isRewound --- ui/src/components/RuleCard/index.jsx | 26 ++++++++++++-------------- ui/src/views/Rules/ListRules/index.jsx | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/ui/src/components/RuleCard/index.jsx b/ui/src/components/RuleCard/index.jsx index 0adf353a28..32085fed67 100644 --- a/ui/src/components/RuleCard/index.jsx +++ b/ui/src/components/RuleCard/index.jsx @@ -161,7 +161,7 @@ function RuleCard({ user, readOnly, actionLoading, - disableActions, + isRewound, // We don't actually use these, but we need to avoid passing them onto // `Card` like the rest of the props. onAuthorize: _, @@ -861,7 +861,7 @@ function RuleCard({ {!readOnly && ( - {user && !disableActions ? ( + {user && !isRewound ? ( - ) : ( - )} - {user && !disableActions ? ( + {user && !isRewound ? ( - ) : ( - )} @@ -911,16 +911,14 @@ function RuleCard({ (user && user.email in rule.scheduledChange.signoffs ? ( ) : ( @@ -943,14 +941,14 @@ RuleCard.propTypes = { // navigating to a different view will be disabled actionLoading: bool, // If true, the card will disable all buttons - disableActions: bool, + isRewound: bool, }; RuleCard.defaultProps = { onRuleDelete: Function.prototype, readOnly: false, actionLoading: false, - disableActions: false, + isRewound: false, }; export default withUser(RuleCard); diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index b6387f5622..9e0c985c0f 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1323,7 +1323,7 @@ function ListRules(props) { onSignoff={() => handleSignoff(rule)} onRevoke={() => handleRevoke(rule)} onViewReleaseClick={handleViewRelease} - disableActions={Boolean(rewindDate)} + isRewound={Boolean(rewindDate)} actionLoading={isActionLoading} />
From 0f4525b222ff9986cd197b0f52402b35c892ae91 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Fri, 15 Dec 2023 18:05:42 +0300 Subject: [PATCH 11/38] Fix bug where rules wouldn't rewind when productChannelFilter is all --- ui/src/views/Rules/ListRules/index.jsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 9e0c985c0f..3ef5c1740e 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -495,15 +495,16 @@ function ListRules(props) { filteredRules = filteredRules.filter(rule => rule.scheduledChange); } + const rewoundFilteredRules = clone(rewoundRules); + // use this line for non-diff mode on rewound rules + let rulesToShow = + rewoundRules.length === 0 ? filteredRules : rewoundFilteredRules; + if (!productChannelQueries) { - return filteredRules; + return rulesToShow; } // Product channel dropdown filter - - // use this line for non-diff mode on rewound rules - let rulesToShow = rewoundRules.length === 0 ? filteredRules : rewoundRules; - rulesToShow = rulesToShow.filter(rule => { const [productFilter, channelFilter] = productChannelQueries; const ruleProduct = From 053abc2bc20055d51bea0d6e7253bf6cbf7cc930 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Fri, 15 Dec 2023 18:18:34 +0300 Subject: [PATCH 12/38] Remove diff mode --- ui/src/views/Rules/ListRules/index.jsx | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 3ef5c1740e..63724d9b1f 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -14,7 +14,7 @@ import RadioGroup from '@material-ui/core/RadioGroup'; import FormControl from '@material-ui/core/FormControl'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormLabel from '@material-ui/core/FormLabel'; -import Checkbox from '@material-ui/core/Checkbox'; +// import Checkbox from '@material-ui/core/Checkbox'; import Switch from '@material-ui/core/Switch'; import SpeedDialAction from '@material-ui/lab/SpeedDialAction'; import Drawer from '@material-ui/core/Drawer'; @@ -163,7 +163,7 @@ function ListRules(props) { const [dateTimePickerError, setDateTimePickerError] = useState(null); const [rewindDate, setRewindDate] = useState(null); const [rewindDateError, setRewindDateError] = useState(null); - const [showRewindDiff, setShowRewindDiff] = useState(false); + // const [showRewindDiff, setShowRewindDiff] = useState(false); const [scrollToRow, setScrollToRow] = useState(null); const [roles, setRoles] = useState([]); const [requiredRoles, setRequiredRoles] = useState([]); @@ -310,8 +310,8 @@ function ListRules(props) { setSnackbarState({ message, variant, open: true }); }; - const handleRewindDiffChange = ({ target: { checked: value } }) => - setShowRewindDiff(value); + // const handleRewindDiffChange = ({ target: { checked: value } }) => + // setShowRewindDiff(value); const handleSnackbarClose = (event, reason) => { if (reason === 'clickaway') { return; @@ -1280,24 +1280,24 @@ function ListRules(props) { // if we're in rewind mode, rule is a historical rule, not the current one const rule = filteredRulesWithScheduledChanges[index]; const isSelected = isRuleSelected(rule); - const currentRule = rulesWithScheduledChanges.find( - r => r.rule_id === rule.rule_id - ); - - if (rewoundRules.length !== 0) { - // TODO: horrible hack, need to fix this - // maybe add diffAgainst argument to RuleCard - // to control whether or not DiffRule is shown - if (showRewindDiff && currentRule) { - rule.scheduledChange = currentRule; - rule.scheduledChange.change_type = 'update'; - rule.scheduledChange.required_signoffs = {}; - rule.scheduledChange.signoffs = {}; - rule.scheduledChange.when = new Date(); - } else if (rule.scheduledChange) { - delete rule.scheduledChange; - } - } + // const currentRule = rulesWithScheduledChanges.find( + // r => r.rule_id === rule.rule_id + // ); + + // if (rewoundRules.length !== 0) { + // // TODO: horrible hack, need to fix this + // // maybe add diffAgainst argument to RuleCard + // // to control whether or not DiffRule is shown + // if (showRewindDiff && currentRule) { + // rule.scheduledChange = currentRule; + // rule.scheduledChange.change_type = 'update'; + // rule.scheduledChange.required_signoffs = {}; + // rule.scheduledChange.signoffs = {}; + // rule.scheduledChange.when = new Date(); + // } else if (rule.scheduledChange) { + // delete rule.scheduledChange; + // } + // } return (
- + {/* Diff? - + */} Filter by rules with scheduled changes From 26b150e53c233ff462dd0731c5103d4b05c0694f Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Fri, 15 Dec 2023 18:46:03 +0300 Subject: [PATCH 13/38] Add timestamp query to url and datetime string to banner --- ui/src/views/Rules/ListRules/index.jsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 63724d9b1f..00300873dd 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -161,7 +161,9 @@ function ListRules(props) { addSeconds(new Date(), -30) ); const [dateTimePickerError, setDateTimePickerError] = useState(null); - const [rewindDate, setRewindDate] = useState(null); + const [rewindDate, setRewindDate] = useState( + query.timestamp ? new Date(parseInt(query.timestamp, 10)) : null + ); const [rewindDateError, setRewindDateError] = useState(null); // const [showRewindDiff, setShowRewindDiff] = useState(false); const [scrollToRow, setScrollToRow] = useState(null); @@ -544,6 +546,13 @@ function ListRules(props) { const handleRewindDateTimeChange = date => { setRewindDate(date); setRewindDateError(null); + + const qs = { + ...query, + timestamp: date ? date.getTime() : undefined, + }; + + props.history.push(`/rules${stringify(qs, { addQueryPrefix: true })}`); }; const handleDialogError = error => { @@ -1360,7 +1369,12 @@ function ListRules(props) { }, [hashQuery, filteredRulesCount]); return ( - + {isLoading && } {error && } {!isLoading && productChannelOptions && ( From 86c4d50bf68bdb4534ceff25c29a8bc1717c8f49 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Fri, 15 Dec 2023 19:03:25 +0300 Subject: [PATCH 14/38] Lint fixes --- ui/src/views/Rules/ListRules/index.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 00300873dd..a6a2ceb029 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1371,9 +1371,7 @@ function ListRules(props) { return ( {isLoading && } {error && } From af42a0cb77ca5a2b8bcf59129b01baf52918990a Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Fri, 15 Dec 2023 19:28:51 +0300 Subject: [PATCH 15/38] Enable return to current mode from rewind mode --- ui/src/views/Rules/ListRules/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index a6a2ceb029..d015adcb16 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1327,6 +1327,7 @@ function ListRules(props) { onRuleDelete={handleRuleDelete} canSignoff={ !rewindDate && + rule.required_signoffs && Object.keys(rule.required_signoffs).filter(r => roles.includes(r)) .length } @@ -1380,6 +1381,7 @@ function ListRules(props) {
Date: Mon, 18 Dec 2023 16:33:56 +0300 Subject: [PATCH 16/38] Stop fetching rules twice on page load --- ui/src/views/Rules/ListRules/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index d015adcb16..7e8b30737e 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -461,7 +461,6 @@ function ListRules(props) { useEffect(() => { Promise.all([ fetchScheduledChanges(), - fetchRules(), fetchRequiredSignoffs(OBJECT_NAMES.PRODUCT_REQUIRED_SIGNOFF), fetchEmergencyShutoffs(), fetchScheduledEmergencyShutoffs(), From 0c4980374b08a336ac3b26516b02c7d26b9fd5f4 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 18 Dec 2023 16:37:44 +0300 Subject: [PATCH 17/38] Remove read-only mode comment --- ui/src/views/Rules/ListRules/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 7e8b30737e..4ac10b63bf 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1315,7 +1315,6 @@ function ListRules(props) { : Object.values(rule.scheduledChange).join('-') } style={style}> - {/* should we go read only mode if rewindDate is set instead? */} Date: Mon, 18 Dec 2023 17:20:10 +0300 Subject: [PATCH 18/38] Align rewind date picker with other options --- ui/src/views/Rules/ListRules/index.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 4ac10b63bf..f58fe9ddd5 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -120,6 +120,9 @@ const useStyles = makeStyles(theme => ({ pendingSignoffFormLabel: { transform: 'scale(0.75)', }, + rewindPicker: { + marginTop: theme.spacing(0.5), + }, })); function ListRules(props) { @@ -1378,6 +1381,7 @@ function ListRules(props) {
Date: Mon, 18 Dec 2023 17:29:19 +0300 Subject: [PATCH 19/38] Disable scheduled changes filter in rewind mode --- ui/src/views/Rules/ListRules/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index f58fe9ddd5..1a724d42bb 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1404,6 +1404,7 @@ function ListRules(props) { Filter by rules with scheduled changes From 67e663ffbea4f461be36187bba207074e60c0061 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 18 Dec 2023 17:36:57 +0300 Subject: [PATCH 20/38] Completely remove diff mode --- ui/src/views/Rules/ListRules/index.jsx | 33 -------------------------- 1 file changed, 33 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 1a724d42bb..9bc6ff3cdf 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -14,7 +14,6 @@ import RadioGroup from '@material-ui/core/RadioGroup'; import FormControl from '@material-ui/core/FormControl'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormLabel from '@material-ui/core/FormLabel'; -// import Checkbox from '@material-ui/core/Checkbox'; import Switch from '@material-ui/core/Switch'; import SpeedDialAction from '@material-ui/lab/SpeedDialAction'; import Drawer from '@material-ui/core/Drawer'; @@ -88,9 +87,6 @@ const useStyles = makeStyles(theme => ({ position: 'fixed', zIndex: 2, }, - checkbox: { - padding: `${theme.spacing(2)}px ${theme.spacing(1)}px`, - }, dropdown: { minWidth: 200, marginBottom: theme.spacing(2), @@ -168,7 +164,6 @@ function ListRules(props) { query.timestamp ? new Date(parseInt(query.timestamp, 10)) : null ); const [rewindDateError, setRewindDateError] = useState(null); - // const [showRewindDiff, setShowRewindDiff] = useState(false); const [scrollToRow, setScrollToRow] = useState(null); const [roles, setRoles] = useState([]); const [requiredRoles, setRequiredRoles] = useState([]); @@ -315,8 +310,6 @@ function ListRules(props) { setSnackbarState({ message, variant, open: true }); }; - // const handleRewindDiffChange = ({ target: { checked: value } }) => - // setShowRewindDiff(value); const handleSnackbarClose = (event, reason) => { if (reason === 'clickaway') { return; @@ -1291,24 +1284,6 @@ function ListRules(props) { // if we're in rewind mode, rule is a historical rule, not the current one const rule = filteredRulesWithScheduledChanges[index]; const isSelected = isRuleSelected(rule); - // const currentRule = rulesWithScheduledChanges.find( - // r => r.rule_id === rule.rule_id - // ); - - // if (rewoundRules.length !== 0) { - // // TODO: horrible hack, need to fix this - // // maybe add diffAgainst argument to RuleCard - // // to control whether or not DiffRule is shown - // if (showRewindDiff && currentRule) { - // rule.scheduledChange = currentRule; - // rule.scheduledChange.change_type = 'update'; - // rule.scheduledChange.required_signoffs = {}; - // rule.scheduledChange.signoffs = {}; - // rule.scheduledChange.when = new Date(); - // } else if (rule.scheduledChange) { - // delete rule.scheduledChange; - // } - // } return (
- {/* - Diff? - - */} Filter by rules with scheduled changes From d02609c0aa699d8b2caa196f1551d6670908138e Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 18 Dec 2023 17:48:13 +0300 Subject: [PATCH 21/38] Add comment on search field ref --- ui/src/views/Rules/ListRules/index.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 9bc6ff3cdf..8216210f66 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -466,6 +466,9 @@ function ListRules(props) { }, []); useEffect(() => { + // Except for initialization, reading ref.current during rendering + // results in unpredictable behaviour. So, we read ref.current + // on initialization and save the value to state. setSearchFieldHeight(searchFieldRef.current.clientHeight); }, []); From 2ba28f6936a0e86235f7827f77365e30d4b20a51 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 18 Dec 2023 18:19:19 +0300 Subject: [PATCH 22/38] Disable rule creation in rewind mode --- ui/src/views/Rules/ListRules/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 8216210f66..960b4f922f 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1458,7 +1458,7 @@ function ListRules(props) { state: { rulesFilter: productChannelQueries }, }}> - + From 17927a394558c773f575e95e6a90d3c3d8776073 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 18 Dec 2023 19:19:21 +0300 Subject: [PATCH 23/38] Readability improvements --- ui/src/views/Rules/ListRules/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 960b4f922f..b44c400ac8 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -143,7 +143,6 @@ function ListRules(props) { const [rulesWithScheduledChanges, setRulesWithScheduledChanges] = useState( [] ); - const [rewoundRules, setRewoundRules] = useState([]); const searchFieldRef = useRef(null); const [searchFieldHeight, setSearchFieldHeight] = useState(0); const [productChannelOptions, setProductChannelOptions] = useState([]); @@ -160,6 +159,7 @@ function ListRules(props) { addSeconds(new Date(), -30) ); const [dateTimePickerError, setDateTimePickerError] = useState(null); + const [rewoundRules, setRewoundRules] = useState([]); const [rewindDate, setRewindDate] = useState( query.timestamp ? new Date(parseInt(query.timestamp, 10)) : null ); From 7e657926d2389990231c0489ebc447bc1c9260db Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Tue, 19 Dec 2023 16:07:59 +0300 Subject: [PATCH 24/38] Implement rewind date clearing in the primary UI --- ui/src/views/Rules/ListRules/index.jsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index b44c400ac8..e0975f28dc 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -17,8 +17,10 @@ import FormLabel from '@material-ui/core/FormLabel'; import Switch from '@material-ui/core/Switch'; import SpeedDialAction from '@material-ui/lab/SpeedDialAction'; import Drawer from '@material-ui/core/Drawer'; +import IconButton from '@material-ui/core/IconButton'; import PlusIcon from 'mdi-react/PlusIcon'; import PauseIcon from 'mdi-react/PauseIcon'; +import CloseIcon from 'mdi-react/CloseIcon'; import Dashboard from '../../../components/Dashboard'; import ErrorPanel from '../../../components/ErrorPanel'; import EmergencyShutoffCard from '../../../components/EmergencyShutoffCard'; @@ -1361,13 +1363,27 @@ function ListRules(props) { handleRewindDateTimeChange(null)} + disabled={!rewindDate} + style={{ order: 1 }} + color="disabled"> + + + ), + }} + InputAdornmentProps={{ + position: 'start', + style: { order: 2, marginLeft: 0 }, + }} /> From e01d38cdf52005304b5bb414fc0625cdea47196a Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Tue, 19 Dec 2023 17:16:53 +0300 Subject: [PATCH 25/38] Sort rules in rewind mode --- ui/src/views/Rules/ListRules/index.jsx | 45 +++++++++++++++----------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index e0975f28dc..d389820df5 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -327,6 +327,28 @@ function ListRules(props) { rules.data.data.rules.some( rule => rule.product === product && rule.channel === channel ); + const sortRules = rules => { + // Rules are sorted by priority. Rules that are + // pending (ie: still just a Scheduled Change) will be inserted based + // on the priority in the Scheduled Change. Rules that have Scheduled + // updates or deletes will remain sorted on their current priority + // because it's more important to make it easy to assess current state + // than future state. + const sortedRules = rules.sort((ruleA, ruleB) => { + const priorityA = + ruleA.scheduledChange && ruleA.scheduledChange.priority + ? ruleA.scheduledChange.priority + : ruleA.priority; + const priorityB = + ruleB.scheduledChange && ruleB.scheduledChange.priority + ? ruleB.scheduledChange.priority + : ruleB.priority; + + return priorityB - priorityA; + }); + + return sortedRules; + }; useEffect(() => { if (!products.data || !channels.data || !rules.data) { @@ -356,7 +378,9 @@ function ListRules(props) { } if (rewindDate) { - setRewoundRules(rules.data.data.rules); + const sortedRewoundRules = sortRules(rules.data.data.rules); + + setRewoundRules(sortedRewoundRules); } else { const rulesWithScheduledChanges = rules.data.data.rules.map(rule => { const sc = scheduledChanges.data.data.scheduled_changes.find( @@ -397,24 +421,7 @@ function ListRules(props) { } }); - // Rules are sorted by priority. Rules that are - // pending (ie: still just a Scheduled Change) will be inserted based - // on the priority in the Scheduled Change. Rules that have Scheduled - // updates or deletes will remain sorted on their current priority - // because it's more important to make it easy to assess current state - // than future state. - const sortedRules = rulesWithScheduledChanges.sort((ruleA, ruleB) => { - const priorityA = - ruleA.scheduledChange && ruleA.scheduledChange.priority - ? ruleA.scheduledChange.priority - : ruleA.priority; - const priorityB = - ruleB.scheduledChange && ruleB.scheduledChange.priority - ? ruleB.scheduledChange.priority - : ruleB.priority; - - return priorityB - priorityA; - }); + const sortedRules = sortRules(rulesWithScheduledChanges); setRulesWithScheduledChanges(sortedRules); setRewoundRules([]); From deb059ff3dbccd424ccad6a3905b1bc9620bee1f Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 21 Dec 2023 12:38:37 +0300 Subject: [PATCH 26/38] Show info when there are no rules matching rewind date or product/channel filter --- ui/src/views/Rules/ListRules/index.jsx | 38 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index d389820df5..0dd4963ead 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -17,6 +17,8 @@ import FormLabel from '@material-ui/core/FormLabel'; import Switch from '@material-ui/core/Switch'; import SpeedDialAction from '@material-ui/lab/SpeedDialAction'; import Drawer from '@material-ui/core/Drawer'; +import Alert from '@material-ui/lab/Alert'; +import AlertTitle from '@material-ui/lab/AlertTitle'; import IconButton from '@material-ui/core/IconButton'; import PlusIcon from 'mdi-react/PlusIcon'; import PauseIcon from 'mdi-react/PauseIcon'; @@ -506,8 +508,7 @@ function ListRules(props) { const rewoundFilteredRules = clone(rewoundRules); // use this line for non-diff mode on rewound rules - let rulesToShow = - rewoundRules.length === 0 ? filteredRules : rewoundFilteredRules; + let rulesToShow = rewindDate ? rewoundFilteredRules : filteredRules; if (!productChannelQueries) { return rulesToShow; @@ -537,6 +538,21 @@ function ListRules(props) { query.onlyScheduledChanges, rewoundRules, ]); + const getFilteredRulesInfo = () => { + const [product, channel] = productChannelQueries; + let info; + + if (!productChannelQueries) { + info = `No rules found on ${rewindDate}.`; + } else if (channel) { + info = `No rules found for the ${product} ${channel} channel on ${rewindDate}.`; + } else { + info = `No rules found for the ${product} channel on ${rewindDate}.`; + } + + return info; + }; + const handleDateTimePickerError = error => { setDateTimePickerError(error); }; @@ -1438,7 +1454,7 @@ function ListRules(props) { onRevoke={handleRevokeEnableUpdates} /> )} - {filteredRulesWithScheduledChanges && ( + {filteredRulesWithScheduledChanges.length !== 0 ? ( + ) : ( + handleRewindDateTimeChange(null)} + color="inherit" + size="small"> + + + } + {...props}> + Info + {getFilteredRulesInfo()} + )}
From 0343cf6b7a10510f47dea7c973950046efe055d9 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 21 Dec 2023 13:29:08 +0300 Subject: [PATCH 27/38] Combine use cases for disabling actions on rule cards --- ui/src/components/RuleCard/index.jsx | 40 ++++++++++++-------------- ui/src/views/Rules/ListRules/index.jsx | 2 +- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/ui/src/components/RuleCard/index.jsx b/ui/src/components/RuleCard/index.jsx index 32085fed67..ff08b8e0fe 100644 --- a/ui/src/components/RuleCard/index.jsx +++ b/ui/src/components/RuleCard/index.jsx @@ -161,7 +161,7 @@ function RuleCard({ user, readOnly, actionLoading, - isRewound, + disableActions, // We don't actually use these, but we need to avoid passing them onto // `Card` like the rest of the props. onAuthorize: _, @@ -861,7 +861,11 @@ function RuleCard({ {!readOnly && ( - {user && !isRewound ? ( + {disableActions ? ( + + ) : ( - + - ) : ( - )} - {user && !isRewound ? ( + {disableActions ? ( + + ) : ( - + - ) : ( - )} @@ -911,14 +907,14 @@ function RuleCard({ (user && user.email in rule.scheduledChange.signoffs ? ( ) : ( @@ -941,14 +937,14 @@ RuleCard.propTypes = { // navigating to a different view will be disabled actionLoading: bool, // If true, the card will disable all buttons - isRewound: bool, + disableActions: bool, }; RuleCard.defaultProps = { onRuleDelete: Function.prototype, readOnly: false, actionLoading: false, - isRewound: false, + disableActions: false, }; export default withUser(RuleCard); diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 0dd4963ead..4ccedbcb9d 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1338,7 +1338,7 @@ function ListRules(props) { onSignoff={() => handleSignoff(rule)} onRevoke={() => handleRevoke(rule)} onViewReleaseClick={handleViewRelease} - isRewound={Boolean(rewindDate)} + disableActions={!props.user || Boolean(rewindDate)} actionLoading={isActionLoading} />
From 76a93456dfb400314f7ec8330311ffc8fcc70a5a Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 21 Dec 2023 14:21:35 +0300 Subject: [PATCH 28/38] Fix error where info doesn't show when product channel filter is all --- ui/src/views/Rules/ListRules/index.jsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 4ccedbcb9d..0bc146e14d 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -539,15 +539,16 @@ function ListRules(props) { rewoundRules, ]); const getFilteredRulesInfo = () => { - const [product, channel] = productChannelQueries; let info; - if (!productChannelQueries) { - info = `No rules found on ${rewindDate}.`; - } else if (channel) { - info = `No rules found for the ${product} ${channel} channel on ${rewindDate}.`; - } else { - info = `No rules found for the ${product} channel on ${rewindDate}.`; + if (rewindDate) { + if (!productChannelQueries) { + info = `No rules found on ${rewindDate}.`; + } else if (productChannelQueries[1]) { + info = `No rules found for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel on ${rewindDate}.`; + } else { + info = `No rules found for the ${productChannelQueries[0]} channel on ${rewindDate}.`; + } } return info; From a8d36f18a3d491a316153c13ce64bffd6a64f181 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 21 Dec 2023 14:24:05 +0300 Subject: [PATCH 29/38] Show info when no rules with scheduled changes & reset scheduled changes filter in rewind mode --- ui/src/views/Rules/ListRules/index.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 0bc146e14d..82c5757166 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -549,6 +549,8 @@ function ListRules(props) { } else { info = `No rules found for the ${productChannelQueries[0]} channel on ${rewindDate}.`; } + } else if (query.onlyScheduledChanges) { + info = `No rules found with scheduled changes.`; } return info; @@ -574,6 +576,7 @@ function ListRules(props) { const qs = { ...query, timestamp: date ? date.getTime() : undefined, + onlyScheduledChanges: undefined, }; props.history.push(`/rules${stringify(qs, { addQueryPrefix: true })}`); From 165651e4e78aa2686866f185fa517fa84250bd52 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 21 Dec 2023 14:55:25 +0300 Subject: [PATCH 30/38] Move filtered rules info to a utility function --- ui/src/utils/getFilteredRulesInfo.js | 33 ++++++++++++++++++++++++++ ui/src/views/Rules/ListRules/index.jsx | 25 +++++-------------- 2 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 ui/src/utils/getFilteredRulesInfo.js diff --git a/ui/src/utils/getFilteredRulesInfo.js b/ui/src/utils/getFilteredRulesInfo.js new file mode 100644 index 0000000000..ee3d259e17 --- /dev/null +++ b/ui/src/utils/getFilteredRulesInfo.js @@ -0,0 +1,33 @@ +// This utility builds the string shown in the +// info alert when no rules match the selected filters +export default (productChannelQueries, rewindDate, onlyScheduledChanges) => { + let info = 'No rules found '; + const rewindDateStr = `on ${rewindDate}.`; + const scheduledChangesStr = 'with scheduled changes.'; + + if (!productChannelQueries) { + if (rewindDate) { + info += rewindDateStr; + } else if (onlyScheduledChanges) { + info += scheduledChangesStr; + } + } else if (productChannelQueries[1]) { + info += `for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel `; + + if (rewindDate) { + info += rewindDateStr; + } else if (onlyScheduledChanges) { + info += scheduledChangesStr; + } + } else { + info += `for the ${productChannelQueries[0]} channel `; + + if (rewindDate) { + info += rewindDateStr; + } else if (onlyScheduledChanges) { + info += scheduledChangesStr; + } + } + + return info; +}; diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 82c5757166..6aa8f8a060 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -73,6 +73,7 @@ import remToPx from '../../../utils/remToPx'; import elementsHeight from '../../../utils/elementsHeight'; import Snackbar from '../../../components/Snackbar'; import { ruleMatchesChannel } from '../../../utils/rules'; +import getFilteredRulesInfo from '../../../utils/getFilteredRulesInfo'; const ALL = 'all'; const useStyles = makeStyles(theme => ({ @@ -538,24 +539,6 @@ function ListRules(props) { query.onlyScheduledChanges, rewoundRules, ]); - const getFilteredRulesInfo = () => { - let info; - - if (rewindDate) { - if (!productChannelQueries) { - info = `No rules found on ${rewindDate}.`; - } else if (productChannelQueries[1]) { - info = `No rules found for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel on ${rewindDate}.`; - } else { - info = `No rules found for the ${productChannelQueries[0]} channel on ${rewindDate}.`; - } - } else if (query.onlyScheduledChanges) { - info = `No rules found with scheduled changes.`; - } - - return info; - }; - const handleDateTimePickerError = error => { setDateTimePickerError(error); }; @@ -1482,7 +1465,11 @@ function ListRules(props) { } {...props}> Info - {getFilteredRulesInfo()} + {getFilteredRulesInfo( + productChannelQueries, + rewindDate, + query.onlyScheduledChanges + )} )}
From e1bdb4160d1bc6441d11442ace842fe2543619a3 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 21 Dec 2023 15:31:17 +0300 Subject: [PATCH 31/38] Modify links in dashboard to add query string while on target url --- ui/src/components/Dashboard/SettingsMenu.jsx | 8 +++++++- ui/src/components/Dashboard/index.jsx | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/src/components/Dashboard/SettingsMenu.jsx b/ui/src/components/Dashboard/SettingsMenu.jsx index e29352a930..bb24bbaf9d 100644 --- a/ui/src/components/Dashboard/SettingsMenu.jsx +++ b/ui/src/components/Dashboard/SettingsMenu.jsx @@ -59,7 +59,13 @@ function SettingsMenu({ user, disabled }) { onClose={handleMenuClose}> {menuItems.settings.map(navItem => ( - + {navItem.value.toUpperCase()} diff --git a/ui/src/components/Dashboard/index.jsx b/ui/src/components/Dashboard/index.jsx index 3d26171435..239aa8d5c6 100644 --- a/ui/src/components/Dashboard/index.jsx +++ b/ui/src/components/Dashboard/index.jsx @@ -71,7 +71,11 @@ export default function Dashboard(props) { key={menuItem.value} className={disabled ? classes.disabledLink : classes.link} nav - to={menuItem.path}> + to={ + window.location.pathname === menuItem.path + ? `${window.location.pathname}${window.location.search}` + : menuItem.path + }> ))} From 919940f4dac7fca515a3d263f3a90a66d2be5e93 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Thu, 21 Dec 2023 15:40:35 +0300 Subject: [PATCH 32/38] Remove add rule button in rewind mode to fix tooltip error --- ui/src/views/Rules/ListRules/index.jsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 6aa8f8a060..0e0f81b6f3 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1498,17 +1498,19 @@ function ListRules(props) { - - - - - - - + {!rewindDate && ( + + + + + + + + )} Date: Fri, 22 Dec 2023 14:31:29 +0300 Subject: [PATCH 33/38] Fix cutting off of rule card when link with anchor is loaded --- ui/src/components/VariableSizeList/index.jsx | 10 ++++++++-- ui/src/views/Rules/ListRules/index.jsx | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/src/components/VariableSizeList/index.jsx b/ui/src/components/VariableSizeList/index.jsx index ba3a3f52e4..cae28d8298 100644 --- a/ui/src/components/VariableSizeList/index.jsx +++ b/ui/src/components/VariableSizeList/index.jsx @@ -9,13 +9,19 @@ import { AutoSizer, WindowScroller, List } from 'react-virtualized'; import { APP_BAR_HEIGHT } from '../../utils/constants'; const VariableSizeList = forwardRef((props, ref) => { - const { scrollToRow, ...rest } = props; + const { scrollToRow, pathname, ...rest } = props; const listRef = useRef(null); useEffect(() => { const rowOffset = listRef.current.getOffsetForRow({ index: scrollToRow }); - listRef.current.scrollToPosition(rowOffset - APP_BAR_HEIGHT); + if (pathname === '/rules') { + listRef.current.scrollToPosition( + rowOffset - APP_BAR_HEIGHT - rest.searchFieldHeight + ); + } else { + listRef.current.scrollToPosition(rowOffset - APP_BAR_HEIGHT); + } }, [scrollToRow]); useImperativeHandle(ref, () => ({ diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 0e0f81b6f3..ca69ec4e32 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1449,6 +1449,8 @@ function ListRules(props) { scrollToRow={scrollToRow} rowHeight={getRowHeight} rowCount={filteredRulesCount} + searchFieldHeight={searchFieldHeight} + pathname={props.location.pathname} /> ) : ( From 5400364b2b50b02f1a83edc5d61ebdc17a8abe5d Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Fri, 22 Dec 2023 15:03:44 +0300 Subject: [PATCH 34/38] Remove clear button in rewind date picker when rules are not rewound --- ui/src/views/Rules/ListRules/index.jsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index ca69ec4e32..66e17a1d79 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -1379,17 +1379,19 @@ function ListRules(props) { helperText={rewindDateError} onDateTimeChange={handleRewindDateTimeChange} value={rewindDate} - InputProps={{ - endAdornment: ( - handleRewindDateTimeChange(null)} - disabled={!rewindDate} - style={{ order: 1 }} - color="disabled"> - - - ), - }} + InputProps={ + rewindDate && { + endAdornment: ( + handleRewindDateTimeChange(null)} + disabled={!rewindDate} + style={{ order: 1 }} + color="disabled"> + + + ), + } + } InputAdornmentProps={{ position: 'start', style: { order: 2, marginLeft: 0 }, From b27623be39e357ef81c730ac548bdf4c82930086 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Fri, 22 Dec 2023 15:12:28 +0300 Subject: [PATCH 35/38] Fix disable updates banner not showing consistently in rewind mode --- ui/src/views/Rules/ListRules/index.jsx | 40 ++++++++++++-------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index 66e17a1d79..c89b8f0d41 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -428,31 +428,27 @@ function ListRules(props) { setRulesWithScheduledChanges(sortedRules); setRewoundRules([]); + } - if ( - emergencyShutoffsAction.data && - scheduledEmergencyShutoffsAction.data - ) { - const shutoffs = emergencyShutoffsAction.data.data.shutoffs.map( - shutoff => { - const returnedShutoff = clone(shutoff); - /* eslint-disable-next-line max-len */ - const sc = scheduledEmergencyShutoffsAction.data.data.scheduled_changes.find( - ses => - ses.product === shutoff.product && - ses.channel === shutoff.channel - ); - - if (sc) { - returnedShutoff.scheduledChange = sc; - } - - return returnedShutoff; + if (emergencyShutoffsAction.data && scheduledEmergencyShutoffsAction.data) { + const shutoffs = emergencyShutoffsAction.data.data.shutoffs.map( + shutoff => { + const returnedShutoff = clone(shutoff); + /* eslint-disable-next-line max-len */ + const sc = scheduledEmergencyShutoffsAction.data.data.scheduled_changes.find( + ses => + ses.product === shutoff.product && ses.channel === shutoff.channel + ); + + if (sc) { + returnedShutoff.scheduledChange = sc; } - ); - setEmergencyShutoffs(shutoffs); - } + return returnedShutoff; + } + ); + + setEmergencyShutoffs(shutoffs); } }, [ rules.data, From 4d37246401dde6e4dab3c56779b1d50737ae8cd5 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 8 Jan 2024 16:33:00 +0300 Subject: [PATCH 36/38] Fix updates are disabled dialog disappearing on rewind date change --- ui/src/views/Rules/ListRules/index.jsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ui/src/views/Rules/ListRules/index.jsx b/ui/src/views/Rules/ListRules/index.jsx index c89b8f0d41..41161d99c7 100644 --- a/ui/src/views/Rules/ListRules/index.jsx +++ b/ui/src/views/Rules/ListRules/index.jsx @@ -429,7 +429,9 @@ function ListRules(props) { setRulesWithScheduledChanges(sortedRules); setRewoundRules([]); } + }, [rules.data, scheduledChanges.data, requiredSignoffs.data]); + useEffect(() => { if (emergencyShutoffsAction.data && scheduledEmergencyShutoffsAction.data) { const shutoffs = emergencyShutoffsAction.data.data.shutoffs.map( shutoff => { @@ -450,13 +452,7 @@ function ListRules(props) { setEmergencyShutoffs(shutoffs); } - }, [ - rules.data, - scheduledChanges.data, - requiredSignoffs.data, - emergencyShutoffsAction.data, - scheduledEmergencyShutoffsAction.data, - ]); + }, [emergencyShutoffsAction.data, scheduledEmergencyShutoffsAction.data]); useEffect(() => { fetchRules(rewindDate ? rewindDate.getTime() : null); From a00bbf462b037e4c956d12974ba344005379fc47 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 8 Jan 2024 18:56:55 +0300 Subject: [PATCH 37/38] Test getFilteredRulesInfo --- ui/test/getFilteredRulesInfo_test.jsx | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 ui/test/getFilteredRulesInfo_test.jsx diff --git a/ui/test/getFilteredRulesInfo_test.jsx b/ui/test/getFilteredRulesInfo_test.jsx new file mode 100644 index 0000000000..310c6be0ce --- /dev/null +++ b/ui/test/getFilteredRulesInfo_test.jsx @@ -0,0 +1,50 @@ +import getFilteredRulesInfo from '../src/utils/getFilteredRulesInfo'; + +describe('info for filtered rules', () => { + console.log(getFilteredRulesInfo); + let infoStartStr; + beforeAll(() => { + infoStartStr = 'No rules found'; + }); + + test('should have rewind date when there is a rewind date and no product channel queries', () => { + const date = new Date("2022-12-17T11:43:00"); + const result = getFilteredRulesInfo(null, date); + + expect(result).toMatch(`${infoStartStr} on ${date}.`); + }); + test('should have rewind date and product when there is a rewind date and product query but no channel query', () => { + const productChannelQueries = ["Firefox"]; + const date = new Date("2022-12-17T11:43:00"); + const result = getFilteredRulesInfo(productChannelQueries, date); + + expect(result).toMatch(`${infoStartStr} for the ${productChannelQueries[0]} channel on ${date}.`); + }); + test('should have rewind date, product and channel when there is a rewind date, product query and channel query', () => { + const productChannelQueries = ["Firefox", "beta"]; + const date = new Date("2022-12-17T11:43:00"); + const result = getFilteredRulesInfo(productChannelQueries, date); + + expect(result).toMatch(`${infoStartStr} for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel on ${date}.`); + }); + test('should have scheduled changes string when scheduled changes filter is applied and there are no product channel queries', () => { + const scheduledChangesStr = 'with scheduled changes.'; + const result = getFilteredRulesInfo(null, null, 1); + + expect(result).toMatch(`${infoStartStr} ${scheduledChangesStr}`); + }); + test('should have scheduled changes string and product query when scheduled changes filter is applied and there product query but no channel query', () => { + const productChannelQueries = ["Thunderbird"]; + const scheduledChangesStr = 'with scheduled changes.'; + const result = getFilteredRulesInfo(productChannelQueries, null, 1); + + expect(result).toMatch(`${infoStartStr} for the ${productChannelQueries[0]} channel ${scheduledChangesStr}`); + }); + test('should have scheduled changes string, product query and channel query when scheduled changes filter is applied and there is a product query and channel query', () => { + const productChannelQueries = ["Thunderbird", "nightly"]; + const scheduledChangesStr = 'with scheduled changes.'; + const result = getFilteredRulesInfo(productChannelQueries, null, 1); + + expect(result).toMatch(`${infoStartStr} for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel ${scheduledChangesStr}`); + }); + }); From 368a3d7f149e05e99b5b3c948b3579e36343d508 Mon Sep 17 00:00:00 2001 From: Michelle Mounde Date: Mon, 8 Jan 2024 18:58:50 +0300 Subject: [PATCH 38/38] Lint fixes --- ui/test/getFilteredRulesInfo_test.jsx | 100 ++++++++++++++------------ 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/ui/test/getFilteredRulesInfo_test.jsx b/ui/test/getFilteredRulesInfo_test.jsx index 310c6be0ce..da88dc4702 100644 --- a/ui/test/getFilteredRulesInfo_test.jsx +++ b/ui/test/getFilteredRulesInfo_test.jsx @@ -1,50 +1,58 @@ import getFilteredRulesInfo from '../src/utils/getFilteredRulesInfo'; describe('info for filtered rules', () => { - console.log(getFilteredRulesInfo); - let infoStartStr; - beforeAll(() => { - infoStartStr = 'No rules found'; - }); - - test('should have rewind date when there is a rewind date and no product channel queries', () => { - const date = new Date("2022-12-17T11:43:00"); - const result = getFilteredRulesInfo(null, date); - - expect(result).toMatch(`${infoStartStr} on ${date}.`); - }); - test('should have rewind date and product when there is a rewind date and product query but no channel query', () => { - const productChannelQueries = ["Firefox"]; - const date = new Date("2022-12-17T11:43:00"); - const result = getFilteredRulesInfo(productChannelQueries, date); - - expect(result).toMatch(`${infoStartStr} for the ${productChannelQueries[0]} channel on ${date}.`); - }); - test('should have rewind date, product and channel when there is a rewind date, product query and channel query', () => { - const productChannelQueries = ["Firefox", "beta"]; - const date = new Date("2022-12-17T11:43:00"); - const result = getFilteredRulesInfo(productChannelQueries, date); - - expect(result).toMatch(`${infoStartStr} for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel on ${date}.`); - }); - test('should have scheduled changes string when scheduled changes filter is applied and there are no product channel queries', () => { - const scheduledChangesStr = 'with scheduled changes.'; - const result = getFilteredRulesInfo(null, null, 1); - - expect(result).toMatch(`${infoStartStr} ${scheduledChangesStr}`); - }); - test('should have scheduled changes string and product query when scheduled changes filter is applied and there product query but no channel query', () => { - const productChannelQueries = ["Thunderbird"]; - const scheduledChangesStr = 'with scheduled changes.'; - const result = getFilteredRulesInfo(productChannelQueries, null, 1); - - expect(result).toMatch(`${infoStartStr} for the ${productChannelQueries[0]} channel ${scheduledChangesStr}`); - }); - test('should have scheduled changes string, product query and channel query when scheduled changes filter is applied and there is a product query and channel query', () => { - const productChannelQueries = ["Thunderbird", "nightly"]; - const scheduledChangesStr = 'with scheduled changes.'; - const result = getFilteredRulesInfo(productChannelQueries, null, 1); - - expect(result).toMatch(`${infoStartStr} for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel ${scheduledChangesStr}`); - }); + let infoStartStr; + + beforeAll(() => { + infoStartStr = 'No rules found'; + }); + + test('should have rewind date when there is a rewind date and no product channel queries', () => { + const date = new Date('2022-12-17T11:43:00'); + const result = getFilteredRulesInfo(null, date); + + expect(result).toMatch(`${infoStartStr} on ${date}.`); + }); + test('should have rewind date and product when there is a rewind date and product query but no channel query', () => { + const productChannelQueries = ['Firefox']; + const date = new Date('2022-12-17T11:43:00'); + const result = getFilteredRulesInfo(productChannelQueries, date); + + expect(result).toMatch( + `${infoStartStr} for the ${productChannelQueries[0]} channel on ${date}.` + ); + }); + test('should have rewind date, product and channel when there is a rewind date, product query and channel query', () => { + const productChannelQueries = ['Firefox', 'beta']; + const date = new Date('2022-12-17T11:43:00'); + const result = getFilteredRulesInfo(productChannelQueries, date); + + expect(result).toMatch( + `${infoStartStr} for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel on ${date}.` + ); + }); + test('should have scheduled changes string when scheduled changes filter is applied and there are no product channel queries', () => { + const scheduledChangesStr = 'with scheduled changes.'; + const result = getFilteredRulesInfo(null, null, 1); + + expect(result).toMatch(`${infoStartStr} ${scheduledChangesStr}`); + }); + test('should have scheduled changes string and product query when scheduled changes filter is applied and there product query but no channel query', () => { + const productChannelQueries = ['Thunderbird']; + const scheduledChangesStr = 'with scheduled changes.'; + const result = getFilteredRulesInfo(productChannelQueries, null, 1); + + expect(result).toMatch( + `${infoStartStr} for the ${productChannelQueries[0]} channel ${scheduledChangesStr}` + ); + }); + test('should have scheduled changes string, product query and channel query when scheduled changes filter is applied and there is a product query and channel query', () => { + const productChannelQueries = ['Thunderbird', 'nightly']; + const scheduledChangesStr = 'with scheduled changes.'; + const result = getFilteredRulesInfo(productChannelQueries, null, 1); + + expect(result).toMatch( + `${infoStartStr} for the ${productChannelQueries[0]} ${productChannelQueries[1]} channel ${scheduledChangesStr}` + ); }); +});