Skip to content

Commit

Permalink
feat: render CronSchedule (#107)
Browse files Browse the repository at this point in the history
* feat: render CronSchedule

Render CronSchedule

* feat: support schedule aliases

* feat: remove offset rendering for now

* feat: render offset
  • Loading branch information
honnix authored Oct 21, 2020
1 parent f0209f9 commit c3c5d5d
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 2 deletions.
90 changes: 90 additions & 0 deletions src/common/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,54 @@ export function millisecondsToHMS(valueMS: number): string {
return parts.length ? parts.join(' ') : unknownValueString;
}

/** Outputs a value in moment.Duration in (Y M D H M S) format (ex. 1y 1M 1d 2h 3m 30s) */
export function durationToYMWDHMS(duration: moment.Duration): string {
if (duration.asSeconds() === 0) {
return '';
}

const parts = [];

if (duration.years() !== 0) {
parts.push(`${Math.abs(duration.years())}y`);
}

if (duration.months() !== 0) {
parts.push(`${Math.abs(duration.months())}M`);
}

// ISO-8601 does not permit mixing between the PnYnMnD and PnW formats.
// Any week-based input is multiplied by 7 and treated as a number of days.
// However moment can parse the mixture resulting both a number of weeks and a number of days.
// For example both P8D and P1W1D result duration.weeks() == 1 and duration.days() == 8.
// Here we skip showing weeks and only take the total number of days.
if (duration.days() !== 0) {
parts.push(`${Math.abs(duration.days())}d`);
}

if (duration.hours() !== 0) {
parts.push(`${Math.abs(duration.hours())}h`);
}

if (duration.minutes() !== 0) {
parts.push(`${Math.abs(duration.minutes())}m`);
}

if (duration.seconds() !== 0) {
parts.push(`${Math.abs(duration.seconds())}s`);
}

const now = moment();
const sign = now
.clone()
.add(duration)
.isBefore(now)
? '-'
: '+';

return `(${sign}) ${parts.join(' ')}`;
}

/** Converts a protobuf Duration value to (H M S) format (ex. 2h 3m 30s)*/
export function protobufDurationToHMS(duration: Protobuf.IDuration) {
return millisecondsToHMS(durationToMilliseconds(duration));
Expand Down Expand Up @@ -122,6 +170,31 @@ export function fixedRateToString({ value, unit }: Admin.IFixedRate): string {
return `Every ${value} ${fixedRateUnitStrings[unit]}`;
}

const hourlyAliases = ['@hourly', 'hourly', 'hours'];
const dailyAliases = ['@daily', 'daily', 'days'];
const weeklyAliases = ['@weekly', 'weekly', 'weeks'];
const monthlyAliases = ['@monthly', 'monthly', 'months'];
const yearlyAliases = ['@yearly', 'yearly', 'years', '@annually', 'annually'];

export function getScheduleFrequencyStringFromAlias(schedule: string) {
if (hourlyAliases.includes(schedule)) {
return 'Every hour';
}
if (dailyAliases.includes(schedule)) {
return 'Every day';
}
if (weeklyAliases.includes(schedule)) {
return 'Every week';
}
if (monthlyAliases.includes(schedule)) {
return 'Every month';
}
if (yearlyAliases.includes(schedule)) {
return 'Every year';
}
return '';
}

export function getScheduleFrequencyString(schedule?: Admin.ISchedule) {
if (schedule == null) {
return '';
Expand All @@ -134,6 +207,23 @@ export function getScheduleFrequencyString(schedule?: Admin.ISchedule) {
if (schedule.rate) {
return fixedRateToString(schedule.rate);
}
if (schedule.cronSchedule && schedule.cronSchedule.schedule) {
return (
getScheduleFrequencyStringFromAlias(
schedule.cronSchedule.schedule
) || cronstrue.toString(schedule.cronSchedule.schedule)
);
}
return '';
}

export function getScheduleOffsetString(schedule?: Admin.ISchedule) {
if (schedule == null) {
return '';
}
if (schedule.cronSchedule && schedule.cronSchedule.offset) {
return durationToYMWDHMS(moment.duration(schedule.cronSchedule.offset));
}
return '';
}

Expand Down
88 changes: 88 additions & 0 deletions src/common/test/formatters.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { millisecondsToDuration } from 'common/utils';
import { Admin } from 'flyteidl';
import * as moment from 'moment-timezone';
import {
subSecondString,
unknownValueString,
Expand All @@ -8,10 +10,13 @@ import {
dateDiffString,
dateFromNow,
dateWithFromNow,
durationToYMWDHMS,
ensureUrlWithProtocol,
formatDate,
formatDateLocalTimezone,
formatDateUTC,
getScheduleFrequencyString,
getScheduleOffsetString,
leftPaddedNumber,
millisecondsToHMS,
protobufDurationToHMS
Expand Down Expand Up @@ -160,6 +165,89 @@ describe('millisecondsToHMS', () => {
);
});

describe('durationToYMWDHMS', () => {
// input and expected result
const cases: [string, string][] = [
['P1Y1M1W1D', '(+) 1y 1M 8d'],
['P1Y1M1W1DT1H1M1S', '(+) 1y 1M 8d 1h 1m 1s'],
['P1Y1M1DT1H1M1S', '(+) 1y 1M 1d 1h 1m 1s'],
['P1M1DT1H1M1S', '(+) 1M 1d 1h 1m 1s'],
['P1DT1H1M1S', '(+) 1d 1h 1m 1s'],
['PT1H1M1S', '(+) 1h 1m 1s'],
['PT1M1S', '(+) 1m 1s'],
['PT1S', '(+) 1s'],
['PT1M-1S', '(+) 59s'],
['-P1Y1M1W1D', '(-) 1y 1M 8d'],
['-P1Y1M1W1DT1H1M1S', '(-) 1y 1M 8d 1h 1m 1s'],
['-P1Y1M1DT1H1M1S', '(-) 1y 1M 1d 1h 1m 1s'],
['-P1M1DT1H1M1S', '(-) 1M 1d 1h 1m 1s'],
['-P1DT1H1M1S', '(-) 1d 1h 1m 1s'],
['-PT1H1M1S', '(-) 1h 1m 1s'],
['-PT1M1S', '(-) 1m 1s'],
['-PT1S', '(-) 1s'],
['PT-1M1S', '(-) 59s'],
['', '']
];
cases.forEach(([input, expected]) =>
it(`should produce ${expected} with input ${input}`, () => {
expect(durationToYMWDHMS(moment.duration(input))).toEqual(expected);
})
);
});

describe('getScheduleFrequencyString', () => {
// input and expected result
const cases: [Admin.ISchedule, string][] = [
[{ cronExpression: '* * * * *' }, 'Every minute'],
[
{ rate: { value: 1, unit: Admin.FixedRateUnit.MINUTE } },
'Every 1 minutes'
],
[{ cronSchedule: { schedule: '* * * * *' } }, 'Every minute'],
[{ cronSchedule: { schedule: '@hourly' } }, 'Every hour'],
[{ cronSchedule: { schedule: 'hourly' } }, 'Every hour'],
[{ cronSchedule: { schedule: 'hours' } }, 'Every hour'],
[{ cronSchedule: { schedule: '@daily' } }, 'Every day'],
[{ cronSchedule: { schedule: 'daily' } }, 'Every day'],
[{ cronSchedule: { schedule: 'days' } }, 'Every day'],
[{ cronSchedule: { schedule: '@weekly' } }, 'Every week'],
[{ cronSchedule: { schedule: 'weekly' } }, 'Every week'],
[{ cronSchedule: { schedule: 'weeks' } }, 'Every week'],
[{ cronSchedule: { schedule: '@monthly' } }, 'Every month'],
[{ cronSchedule: { schedule: 'monthly' } }, 'Every month'],
[{ cronSchedule: { schedule: 'months' } }, 'Every month'],
[{ cronSchedule: { schedule: '@yearly' } }, 'Every year'],
[{ cronSchedule: { schedule: 'yearly' } }, 'Every year'],
[{ cronSchedule: { schedule: 'years' } }, 'Every year'],
[{ cronSchedule: { schedule: '@annually' } }, 'Every year'],
[{ cronSchedule: { schedule: 'annually' } }, 'Every year'],
[null!, ''],
[{ cronSchedule: { schedule: '' } }, '']
];

cases.forEach(([input, expected]) =>
it(`should produce ${expected} with input ${input}`, () => {
expect(getScheduleFrequencyString(input)).toEqual(expected);
})
);
});

describe('getScheduleOffsetString', () => {
// input and expected result
const cases: [Admin.ISchedule, string][] = [
[{ cronSchedule: { offset: 'P1D' } }, '(+) 1d'],
[{ cronSchedule: { offset: 'P-1D' } }, '(-) 1d'],
[null!, ''],
[{ cronSchedule: { offset: '' } }, '']
];

cases.forEach(([input, expected]) =>
it(`should produce ${expected} with input ${input}`, () => {
expect(getScheduleOffsetString(input)).toEqual(expected);
})
);
});

describe('ensureUrlWithProtocol', () => {
// input and expected result
const cases: [string, string][] = [
Expand Down
11 changes: 9 additions & 2 deletions src/components/Entities/EntitySchedules.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Typography } from '@material-ui/core';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { getScheduleFrequencyString } from 'common/formatters';
import {
getScheduleFrequencyString,
getScheduleOffsetString
} from 'common/formatters';
import { WaitForData } from 'components/common';
import { useCommonStyles } from 'components/common/styles';
import { useWorkflowSchedules } from 'components/hooks';
Expand All @@ -23,7 +26,11 @@ const RenderSchedules: React.FC<{
{launchPlans.map((launchPlan, idx) => {
const { schedule } = launchPlan.spec.entityMetadata;
const frequencyString = getScheduleFrequencyString(schedule);
return <li key={idx}>{frequencyString}</li>;
const offsetString = getScheduleOffsetString(schedule);
const scheduleString = offsetString
? `${frequencyString} (offset by ${offsetString})`
: frequencyString;
return <li key={idx}>{scheduleString}</li>;
})}
</ul>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/Launch/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const launchPlansTableColumnWidths = {
export const schedulesTableColumnsWidths = {
active: 80,
frequency: 300,
offset: 300,
name: 250
};

0 comments on commit c3c5d5d

Please sign in to comment.