Skip to content

Commit

Permalink
Add a numerical input to Create Periodic Scheduled Runs
Browse files Browse the repository at this point in the history
  • Loading branch information
dpanshug committed Nov 20, 2023
1 parent e8b1c06 commit a890b6f
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 35 deletions.
2 changes: 1 addition & 1 deletion frontend/src/concepts/pipelines/content/createRun/const.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PeriodicOptions } from '~/concepts/pipelines/content/createRun/types';

export const DEFAULT_CRON_STRING = '0 0 0 * * *';
export const DEFAULT_PERIODIC_OPTION = PeriodicOptions.HOUR;
export const DEFAULT_PERIODIC_OPTION = PeriodicOptions.WEEK;
export const DATE_FORMAT = 'YYYY-MM-DD';
export const DEFAULT_TIME = '12:00 AM';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import * as React from 'react';
import { ClipboardCopy, Radio, Stack, StackItem, Text } from '@patternfly/react-core';
import {
ClipboardCopy,
Radio,
Stack,
StackItem,
Split,
SplitItem,
Text,
} from '@patternfly/react-core';
import {
PeriodicOptions,
RunTypeScheduledData,
Expand All @@ -12,6 +20,8 @@ import {
DEFAULT_PERIODIC_OPTION,
} from '~/concepts/pipelines/content/createRun/const';
import EndDateBeforeStartDateError from '~/concepts/pipelines/content/createRun/contentSections/EndDateBeforeStartDateError';
import { replaceNonNumericPartWithString, replaceNumericPartWithString } from '~/utilities/string';
import NumberInputWrapper from '~/components/NumberInputWrapper';

type RunTypeSectionScheduledProps = {
data: RunTypeScheduledData;
Expand All @@ -32,22 +42,47 @@ const RunTypeSectionScheduled: React.FC<RunTypeSectionScheduledProps> = ({ data,
isChecked={data.triggerType === ScheduledType.PERIODIC}
id={ScheduledType.PERIODIC}
onChange={() =>
onChange({ ...data, triggerType: ScheduledType.PERIODIC, value: DEFAULT_PERIODIC_OPTION })
onChange({
...data,
triggerType: ScheduledType.PERIODIC,
value: DEFAULT_PERIODIC_OPTION,
})
}
body={
data.triggerType === ScheduledType.PERIODIC && (
<>
<Text>
<b>Run every</b>
</Text>
<SimpleDropdownSelect
options={Object.values(PeriodicOptions).map((v) => ({
key: v,
label: v,
}))}
value={data.value}
onChange={(value) => onChange({ ...data, value })}
/>
<Split hasGutter>
<SplitItem>
<NumberInputWrapper
min={1}
value={parseInt(data.value) || 1}
onChange={(value) =>
onChange({
...data,
value: replaceNumericPartWithString(data.value, value),
})
}
/>
</SplitItem>
<SplitItem>
<SimpleDropdownSelect
options={Object.values(PeriodicOptions).map((v) => ({
key: v,
label: v,
}))}
value={data.value.replace(/\d+/, '')}
onChange={(value) =>
onChange({
...data,
value: replaceNonNumericPartWithString(data.value, value),
})
}
/>
</SplitItem>
</Split>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
periodicOptionAsSeconds,
RunDateTime,
RunFormData,
RunTypeOption,
Expand All @@ -16,6 +15,7 @@ import {
} from '~/concepts/pipelines/kfTypes';
import { PipelineAPIs } from '~/concepts/pipelines/types';
import { isFilledRunFormData } from '~/concepts/pipelines/content/createRun/utils';
import { convertPeriodicTimeToSeconds } from '~/utilities/time';

const getResourceReferences = (formData: SafeRunFormData): ResourceReferenceKF[] => {
const refs: ResourceReferenceKF[] = [];
Expand Down Expand Up @@ -79,6 +79,7 @@ const createJob = async (

const startDate = convertDateDataToKFDateTime(formData.runType.data.start) ?? undefined;
const endDate = convertDateDataToKFDateTime(formData.runType.data.end) ?? undefined;
const periodicScheduleIntervalTime = convertPeriodicTimeToSeconds(formData.runType.data.value);
/* eslint-disable camelcase */
const data: CreatePipelineRunJobKFData = {
name: formData.nameDesc.name,
Expand All @@ -93,10 +94,7 @@ const createJob = async (
periodic_schedule:
formData.runType.data.triggerType === ScheduledType.PERIODIC
? {
interval_second:
periodicOptionAsSeconds[
formData.runType.data.value as keyof typeof periodicOptionAsSeconds
].toString(),
interval_second: periodicScheduleIntervalTime.toString(),
start_time: startDate,
end_time: endDate,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import * as React from 'react';
import useGenericObjectState from '~/utilities/useGenericObjectState';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import {
periodicOptionAsSeconds,
PeriodicOptions,
RunDateTime,
RunFormData,
RunType,
Expand All @@ -28,7 +26,7 @@ import {
DEFAULT_PERIODIC_OPTION,
DEFAULT_TIME,
} from '~/concepts/pipelines/content/createRun/const';
import { convertDateToTimeString } from '~/utilities/time';
import { convertDateToTimeString, convertSecondsToPeriodicTime } from '~/utilities/time';

const isPipelineRunJob = (
runOrJob?: PipelineRunJobKF | PipelineRunKF,
Expand Down Expand Up @@ -103,21 +101,6 @@ const parseKFTime = (kfTime?: DateTimeKF): RunDateTime | undefined => {
return { date, time: time ?? DEFAULT_TIME };
};

const intervalSecondsAsOption = (numberString?: string): PeriodicOptions => {
if (!numberString) {
return DEFAULT_PERIODIC_OPTION;
}

const isPeriodicOption = (option: string): option is PeriodicOptions => option in PeriodicOptions;
const seconds = parseInt(numberString);
const option = Object.values(PeriodicOptions).find((o) => periodicOptionAsSeconds[o] === seconds);
if (!option || !isPeriodicOption(option)) {
return DEFAULT_PERIODIC_OPTION;
}

return option;
};

export const useUpdateRunType = (
setFunction: UpdateObjectAtPropAndValue<RunFormData>,
initialData?: PipelineRunKF | PipelineRunJobKF,
Expand All @@ -139,7 +122,7 @@ export const useUpdateRunType = (
end = parseKFTime(trigger.cron_schedule.end_time);
} else if (trigger.periodic_schedule) {
triggerType = ScheduledType.PERIODIC;
value = intervalSecondsAsOption(trigger.periodic_schedule.interval_second);
value = convertSecondsToPeriodicTime(parseInt(trigger.periodic_schedule.interval_second));
start = parseKFTime(trigger.periodic_schedule.start_time);
end = parseKFTime(trigger.periodic_schedule.end_time);
} else {
Expand Down
73 changes: 73 additions & 0 deletions frontend/src/utilities/__tests__/string.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { replaceNonNumericPartWithString, replaceNumericPartWithString } from '~/utilities/string';

describe('replaceNumericPartWithString', () => {
it('should replace the numeric part of a string with a number', () => {
expect(replaceNumericPartWithString('abc123xyz', 456)).toBe('abc456xyz');
});

it('should handle empty input string', () => {
expect(replaceNumericPartWithString('', 789)).toBe('789');
});

it('should handle input string without numeric part', () => {
expect(replaceNumericPartWithString('abcdef', 123)).toBe('123abcdef');
});

it('should handle numeric part at the beginning of the string', () => {
expect(replaceNumericPartWithString('123xyz', 789)).toBe('789xyz');
});

it('should handle numeric part at the end of the string', () => {
expect(replaceNumericPartWithString('abc456', 123)).toBe('abc123');
});

it('should handle Pipeline scheduled time', () => {
expect(replaceNumericPartWithString('123Hour', 43424)).toBe('43424Hour');
});

it('should handle default Pipeline scheduled time', () => {
expect(replaceNumericPartWithString('1Week', 26)).toBe('26Week');
});
});

describe('replaceNonNumericPartWithString', () => {
it('should replace the non-numeric part of a string with another string', () => {
expect(replaceNonNumericPartWithString('abc123xyz', 'XYZ')).toBe('XYZ123xyz');
});

it('should handle empty input string', () => {
expect(replaceNonNumericPartWithString('', 'XYZ')).toBe('XYZ');
});

it('should handle input string with no non-numeric part', () => {
expect(replaceNonNumericPartWithString('123', 'XYZ')).toBe('123XYZ');
});

it('should handle input string with only non-numeric part', () => {
expect(replaceNonNumericPartWithString('abc', 'XYZ')).toBe('XYZ');
});

it('should handle input string with multiple non-numeric parts', () => {
expect(replaceNonNumericPartWithString('abc123def456', 'XYZ')).toBe('XYZ123def456');
});

it('should handle replacement string containing numbers', () => {
expect(replaceNonNumericPartWithString('abc123xyz', '123')).toBe('123123xyz');
});

it('should handle replacement string containing special characters', () => {
expect(replaceNonNumericPartWithString('abc123xyz', '@#$')).toBe('@#$123xyz');
});

it('should handle replacement string containing spaces', () => {
expect(replaceNonNumericPartWithString('abc123xyz', ' ')).toBe(' 123xyz');
});

it('should handle Pipeline scheduled time', () => {
expect(replaceNonNumericPartWithString('123Week', 'Minute')).toBe('123Minute');
});

it('should handle default Pipeline scheduled time', () => {
expect(replaceNonNumericPartWithString('1Week', 'Minute')).toBe('1Minute');
});
});
41 changes: 41 additions & 0 deletions frontend/src/utilities/__tests__/time.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { convertPeriodicTimeToSeconds, convertSecondsToPeriodicTime } from '~/utilities/time';

describe('Convert periodic time to seconds', () => {
it('should convert hours to seconds', () => {
expect(convertPeriodicTimeToSeconds('5Hour')).toBe(5 * 60 * 60);
});

it('should convert minutes to seconds', () => {
expect(convertPeriodicTimeToSeconds('6Minute')).toBe(6 * 60);
});

it('should convert days to seconds', () => {
expect(convertPeriodicTimeToSeconds('221Day')).toBe(221 * 24 * 60 * 60);
});

it('should convert weeks to seconds', () => {
expect(convertPeriodicTimeToSeconds('12Week')).toBe(12 * 7 * 24 * 60 * 60);
});

it('should default to 0 seconds for unrecognized units', () => {
expect(convertPeriodicTimeToSeconds('3Weeks')).toBe(0);
});
});

describe('Convert seconds to periodic time', () => {
it('should convert seconds to minutes', () => {
expect(convertSecondsToPeriodicTime(120)).toBe('2Minute');
});

it('should convert seconds to hours', () => {
expect(convertSecondsToPeriodicTime(7200)).toBe('2Hour');
});

it('should convert seconds to days', () => {
expect(convertSecondsToPeriodicTime(172800)).toBe('2Day');
});

it('should convert seconds to weeks', () => {
expect(convertSecondsToPeriodicTime(604800)).toBe('1Week');
});
});
55 changes: 55 additions & 0 deletions frontend/src/utilities/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,58 @@ export const genRandomChars = (len = 6): string =>
.toString(36)
.replace(/[^a-z0-9]+/g, '')
.substr(1, len);

/**
* This function replaces the first occurrence of a numeric part in the input string
* with the specified replacement numeric value.
* @param inputString
* @param replacementString
*/
export const replaceNumericPartWithString = (
inputString: string,
replacementString: number,
): string => {
// If the input string is empty or contains only whitespace, return the replacement as a string.
if (inputString.trim() === '') {
return replacementString.toString();
}

const match = inputString.match(/\d+/); //Find numeric part in string (only first occurance)
let updatedString = inputString;

if (match) {
const matchedNumber = match[0];
updatedString = inputString.replace(matchedNumber, String(replacementString));
} else {
// If no numeric part is found, prepend the replacement numeric value to the input string.
updatedString = replacementString + inputString;
}
return updatedString;
};

/**
* This function replaces the first occurrence of a non-numeric part in the input string
* with the specified replacement string.
* @param inputString
* @param replacementString
*/
export const replaceNonNumericPartWithString = (
inputString: string,
replacementString: string,
): string => {
if (inputString.trim() === '') {
return replacementString;
}

const match = inputString.match(/\D+/); //Find non-numeric part in string (only first occurance)
let updatedString = inputString;

if (match) {
const matchedString = match[0];
updatedString = inputString.replace(matchedString, replacementString);
} else {
// If no non-numeric part is found, append the replacement non-numeric value to the input string.
updatedString = inputString + replacementString;
}
return updatedString;
};
Loading

0 comments on commit a890b6f

Please sign in to comment.