Skip to content

Commit 53e7aab

Browse files
authored
EDM-2742: Allow users to configure startGraceDuration for updates (#416) (#434)
(cherry picked from commit 6de3a6a)
1 parent d1394dc commit 53e7aab

8 files changed

Lines changed: 64 additions & 51 deletions

File tree

libs/i18n/locales/en/translation.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,8 @@
642642
"Thu": "Thu",
643643
"Fri": "Fri",
644644
"Sat": "Sat",
645+
"Start grace duration": "Start grace duration",
646+
"Devices can start the update within this time window after the scheduled trigger.": "Devices can start the update within this time window after the scheduled trigger.",
645647
"Use different update schedules for downloading and installing updates": "Use different update schedules for downloading and installing updates",
646648
"Deletion of fleet {{fleetId}} failed.": "Deletion of fleet {{fleetId}} failed.",
647649
"Delete fleet?": "Delete fleet?",
@@ -759,6 +761,8 @@
759761
"Time must be in hh:mm with 24-hour format": "Time must be in hh:mm with 24-hour format",
760762
"Installing start time is required": "Installing start time is required",
761763
"Installing end time is required": "Installing end time is required",
764+
"Start grace duration is required": "Start grace duration is required",
765+
"Duration must be a number followed by a unit (\"s\", \"m\", or \"h\")": "Duration must be a number followed by a unit (\"s\", \"m\", or \"h\")",
762766
"Select at least one day of the week for \"weekly\" schedules": "Select at least one day of the week for \"weekly\" schedules",
763767
"Select the timezone for downloading updates": "Select the timezone for downloading updates",
764768
"Select the timezone for installing updates": "Select the timezone for installing updates",

libs/types/models/UpdateSchedule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ import type { TimeZone } from './TimeZone';
1111
export type UpdateSchedule = {
1212
timeZone?: TimeZone;
1313
at: CronExpression;
14-
startGraceDuration?: Duration;
14+
startGraceDuration: Duration;
1515
};
1616

libs/ui-components/src/components/Fleet/CreateFleet/fleetSpecUtils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,25 @@ export const getUpdatePolicyValues = (updateSpec?: DeviceUpdatePolicySpec): Upda
6161
const downloadWeekDays = timeUtils.getWeekDays(updateSpec?.downloadSchedule?.at);
6262
const installWeekDays = timeUtils.getWeekDays(updateSpec?.updateSchedule?.at);
6363

64+
const downloadStartGraceDuration = updateSpec?.downloadSchedule?.startGraceDuration;
65+
const installStartGraceDuration = isEqual
66+
? downloadStartGraceDuration
67+
: updateSpec?.updateSchedule?.startGraceDuration;
68+
6469
return {
6570
isAdvanced: Boolean(updateSpec?.downloadSchedule?.at || updateSpec?.updateSchedule?.at),
6671
downloadAndInstallDiffer: !isEqual,
6772
downloadStartsAt,
68-
downloadEndsAt: timeUtils.getEndTime(downloadStartsAt, updateSpec?.downloadSchedule?.startGraceDuration),
73+
downloadEndsAt: timeUtils.getEndTime(downloadStartsAt, downloadStartGraceDuration),
74+
downloadStartGraceDuration,
6975
downloadWeekDays: downloadWeekDays.selectedDays,
7076
downloadScheduleMode: downloadWeekDays.allSelected
7177
? timeUtils.UpdateScheduleMode.Daily
7278
: timeUtils.UpdateScheduleMode.Weekly,
7379
downloadTimeZone: updateSpec?.downloadSchedule?.timeZone || timeUtils.localDeviceTimezone,
7480
installStartsAt,
75-
installEndsAt: timeUtils.getEndTime(installStartsAt, updateSpec?.updateSchedule?.startGraceDuration),
81+
installEndsAt: timeUtils.getEndTime(installStartsAt, installStartGraceDuration),
82+
installStartGraceDuration,
7683
installWeekDays: installWeekDays.selectedDays,
7784
installScheduleMode: installWeekDays.allSelected
7885
? timeUtils.UpdateScheduleMode.Daily

libs/ui-components/src/components/Fleet/CreateFleet/steps/UpdateStepUpdatePolicy.tsx

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import CheckboxField, { CheckboxFieldGroupValidation } from '../../../form/Check
1111
import FormSelectTypeahead from '../../../form/FormSelectTypeahead';
1212
import RadioField from '../../../form/RadioField';
1313
import ErrorHelperText from '../../../form/FieldHelperText';
14+
import TextField from '../../../form/TextField';
1415

1516
import './UpdateStepUpdatePolicy.css';
1617

@@ -113,9 +114,9 @@ const ScheduleBlock = ({
113114
const isWeekly = scheduleMode === timeUtils.UpdateScheduleMode.Weekly;
114115

115116
return (
116-
<FormGroupWithHelperText isRequired label={ariaLabel} content={helperContent}>
117-
<Stack hasGutter>
118-
<StackItem>
117+
<Stack hasGutter>
118+
<StackItem>
119+
<FormGroupWithHelperText isRequired label={ariaLabel} content={helperContent}>
119120
<Flex>
120121
<FlexItem>
121122
<FormSelectTypeahead
@@ -159,12 +160,11 @@ const ScheduleBlock = ({
159160
/>
160161
</FlexItem>
161162
</Flex>
162-
</StackItem>
163-
{isWeekly && (
164-
<FieldArray name={`updatePolicy.${blockType}WeekDays`}>
165-
{() => (
166-
<>
167-
<StackItem>
163+
164+
{isWeekly && (
165+
<FieldArray name={`updatePolicy.${blockType}WeekDays`}>
166+
{() => (
167+
<>
168168
<Flex>
169169
<FlexItem>
170170
<CheckboxFieldGroupValidation
@@ -216,19 +216,29 @@ const ScheduleBlock = ({
216216
/>
217217
</FlexItem>
218218
</Flex>
219-
</StackItem>
220-
<StackItem>
221219
<ErrorHelperText error={weekDayError} />
222-
</StackItem>
223-
</>
224-
)}
225-
</FieldArray>
226-
)}
227-
<StackItem style={{ maxWidth: 500 }}>
228-
<ScheduleTimeZone blockType={blockType} isReadOnly={isReadOnly} />
229-
</StackItem>
230-
</Stack>
231-
</FormGroupWithHelperText>
220+
</>
221+
)}
222+
</FieldArray>
223+
)}
224+
</FormGroupWithHelperText>
225+
</StackItem>
226+
<StackItem>
227+
<ScheduleTimeZone blockType={blockType} isReadOnly={isReadOnly} />
228+
</StackItem>
229+
<StackItem>
230+
<FormGroup isRequired label={t('Start grace duration')}>
231+
<TextField
232+
name={`updatePolicy.${blockType}StartGraceDuration`}
233+
label={t('Start grace duration')}
234+
helperText={t('Devices can start the update within this time window after the scheduled trigger.')}
235+
isRequired
236+
isDisabled={isReadOnly}
237+
placeholder="30m"
238+
/>
239+
</FormGroup>
240+
</StackItem>
241+
</Stack>
232242
);
233243
};
234244

libs/ui-components/src/components/form/validations.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ const TEMPLATE_VARIABLES_REGEXP = /{{.+?}}/g;
6060
// Special characters allowed: "dot", "pipe", "spaces" "quote", "backward slash", "underscore", "forward slash", "dash"
6161
const TEMPLATE_VARIABLES_CONTENT_REGEXP = /^([.a-zA-Z0-9|\s"\\_\/-])+$/;
6262
const TIME_VALUE_REGEXP = /^([01][0-9]|2[0-3]):([0-5][0-9])$/;
63+
// Go duration format: positive number followed by unit (s, m, or h). Examples: "30m", "1h", "45m", "30s"
64+
const DURATION_REGEXP = /^\d+[smh]$/;
6365

6466
const absolutePathRegex = /^\/.*$/;
6567

@@ -871,6 +873,16 @@ const requiredInstallTimes = (t: TFunction, isStartTime: boolean) =>
871873
return Yup.string();
872874
});
873875

876+
const requiredStartGraceDuration = (t: TFunction, isInstallField: boolean = false) =>
877+
Yup.string().when(['isAdvanced', 'downloadAndInstallDiffer'], ([isAdvanced, downloadAndInstallDiffer]) => {
878+
if (!isAdvanced || (isInstallField && !downloadAndInstallDiffer)) {
879+
return Yup.string();
880+
}
881+
return Yup.string()
882+
.required(t('Start grace duration is required'))
883+
.matches(DURATION_REGEXP, t('Duration must be a number followed by a unit ("s", "m", or "h")'));
884+
});
885+
874886
const updateWeekDaysSchema = (t: TFunction) =>
875887
Yup.array()
876888
.required()
@@ -899,11 +911,13 @@ export const validUpdatePolicySchema = (t: TFunction) => {
899911
// Fields are flattened so "isAdvanced" can be used for validating them
900912
downloadStartsAt: requiredDownloadTimes(t, true),
901913
downloadEndsAt: requiredDownloadTimes(t, false),
914+
downloadStartGraceDuration: requiredStartGraceDuration(t, false),
902915
downloadScheduleMode: Yup.string().oneOf([UpdateScheduleMode.Weekly, UpdateScheduleMode.Daily]).required(),
903916
downloadWeekDays: updateWeekDaysSchema(t),
904917
downloadTimeZone: Yup.string().required(t('Select the timezone for downloading updates')),
905918
installStartsAt: requiredInstallTimes(t, true),
906919
installEndsAt: requiredInstallTimes(t, false),
920+
installStartGraceDuration: requiredStartGraceDuration(t, true),
907921
installScheduleMode: Yup.string().oneOf([UpdateScheduleMode.Weekly, UpdateScheduleMode.Daily]).required(),
908922
installWeekDays: updateWeekDaysSchema(t),
909923
installTimeZone: Yup.string().required(t('Select the timezone for installing updates')),

libs/ui-components/src/types/deviceSpec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,13 @@ export type UpdatePolicyForm = {
277277
downloadAndInstallDiffer: boolean;
278278
downloadStartsAt?: string;
279279
downloadEndsAt?: string;
280+
downloadStartGraceDuration?: string;
280281
downloadScheduleMode: UpdateScheduleMode;
281282
downloadWeekDays: boolean[];
282283
downloadTimeZone: string;
283284
installStartsAt?: string;
284285
installEndsAt?: string;
286+
installStartGraceDuration?: string;
285287
installScheduleMode: UpdateScheduleMode;
286288
installWeekDays: boolean[];
287289
installTimeZone: string;

libs/ui-components/src/utils/patch.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
RolloutPolicyForm,
1919
UpdatePolicyForm,
2020
} from '../types/deviceSpec';
21-
import { getStartGraceDuration, getUpdateCronExpression, localDeviceTimezone } from './time';
21+
import { getUpdateCronExpression, localDeviceTimezone } from './time';
2222

2323
export const appendJSONPatch = <V = unknown>({
2424
patches,
@@ -156,14 +156,14 @@ export const schedulesAreEqual = (a: UpdateSchedule | undefined, b: UpdateSchedu
156156
export const updatePolicyFormToApi = (form: Required<UpdatePolicyForm>) => {
157157
const downloadSchedule = {
158158
at: getUpdateCronExpression(form.downloadStartsAt, form.downloadScheduleMode, form.downloadWeekDays),
159-
startGraceDuration: getStartGraceDuration(form.downloadStartsAt, form.downloadEndsAt),
159+
startGraceDuration: form.downloadStartGraceDuration || '0s',
160160
timeZone: form.downloadTimeZone === localDeviceTimezone ? undefined : form.downloadTimeZone,
161161
};
162162
let updateSchedule: UpdateSchedule;
163163
if (form.downloadAndInstallDiffer) {
164164
updateSchedule = {
165165
at: getUpdateCronExpression(form.installStartsAt, form.installScheduleMode, form.installWeekDays),
166-
startGraceDuration: getStartGraceDuration(form.installStartsAt, form.installEndsAt),
166+
startGraceDuration: form.installStartGraceDuration || '0s',
167167
timeZone: form.installTimeZone === localDeviceTimezone ? undefined : form.installTimeZone,
168168
};
169169
} else {

libs/ui-components/src/utils/time.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -91,23 +91,6 @@ export const getTime = (cronExp?: string) => {
9191
return `${formatTimePart(hours)}:${formatTimePart(minutes)}`;
9292
};
9393

94-
const getMinuteDifference = (timeA: string, timeB: string) => {
95-
const [aHours, aMinutes] = timeA.split(':').map(Number);
96-
const [bHours, bMinutes] = timeB.split(':').map(Number);
97-
98-
const totalMinutesA = aHours * 60 + aMinutes;
99-
const totalMinutesB = bHours * 60 + bMinutes;
100-
101-
let difference = totalMinutesB - totalMinutesA;
102-
103-
// Adjust for crossing midnight by adding 24 hours if A is after B
104-
if (difference < 0) {
105-
difference += 24 * 60;
106-
}
107-
108-
return difference;
109-
};
110-
11194
export const getWeekDays = (cronExp?: string): { allSelected: boolean; selectedDays: boolean[] } => {
11295
const defaultDays = [true, true, true, true, true, true, true];
11396

@@ -138,10 +121,3 @@ export const getUpdateCronExpression = (startTime: string, scheduleMode: UpdateS
138121
.filter((num) => num !== null);
139122
return `${minutes} ${hours} * * ${weekDayVals.join(',')}`;
140123
};
141-
142-
export const getStartGraceDuration = (startTime: string, endTime: string) => {
143-
if (startTime && startTime === endTime) {
144-
return '24h';
145-
}
146-
return `${getMinuteDifference(startTime, endTime)}m`;
147-
};

0 commit comments

Comments
 (0)