Added support for minute level selection for ingestion scheduler (#4994)

* Added support for minute level selection for ingestion scheduler

* fix sonarCloude

* addressing comment

* fixing failing test
This commit is contained in:
Shailesh Parmar 2022-05-18 13:31:08 +05:30 committed by GitHub
parent bcdae71eb0
commit ee3f267e96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 210 additions and 30 deletions

View File

@ -14,11 +14,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
export const getPeriodOptions = () => { export const getPeriodOptions = () => {
return [ return [
/* { {
label: 'minute', label: 'minutes',
value: 'minute', value: 'minute',
prep: '' prep: '',
},*/ },
{ {
label: 'hour', label: 'hour',
value: 'hour', value: 'hour',
@ -57,7 +57,7 @@ export const toDisplay = {
}; };
export const combinations = { export const combinations = {
minute: /^(\*\s){4}\*$/, // "* * * * *" minute: /^(\*\/\d{1,2})\s(\*\s){3}\*$/, // "*/? * * * *"
hour: /^\d{1,2}\s(\*\s){3}\*$/, // "? * * * *" hour: /^\d{1,2}\s(\*\s){3}\*$/, // "? * * * *"
day: /^(\d{1,2}\s){2}(\*\s){2}\*$/, // "? ? * * *" day: /^(\d{1,2}\s){2}(\*\s){2}\*$/, // "? ? * * *"
week: /^(\d{1,2}\s){2}(\*\s){2}\d{1,2}$/, // "? ? * * ?" week: /^(\d{1,2}\s){2}(\*\s){2}\d{1,2}$/, // "? ? * * ?"
@ -78,6 +78,10 @@ export const getRangeOptions = (n: number) => {
}); });
}; };
export const getMinuteSegmentOptions = () => {
return getRangeOptions(60).filter((v) => v.value !== 0 && v.value % 5 === 0);
};
export const getMinuteOptions = () => { export const getMinuteOptions = () => {
return getRangeOptions(60); return getRangeOptions(60);
}; };
@ -161,8 +165,8 @@ export const getMonthOptions = () => {
}); });
}; };
export const getMinuteCron = () => { export const getMinuteCron = (value: any) => {
return '* * * * *'; return `*/${value.min} * * * *`;
}; };
export const getHourCron = (value: any) => { export const getHourCron = (value: any) => {

View File

@ -14,6 +14,7 @@
/* eslint-disable */ /* eslint-disable */
import React, { useState } from 'react'; import React, { useState } from 'react';
import { pluralize } from '../../../utils/CommonUtils';
import { import {
combinations, combinations,
getDayCron, getDayCron,
@ -22,6 +23,7 @@ import {
getHourOptions, getHourOptions,
getMinuteCron, getMinuteCron,
getMinuteOptions, getMinuteOptions,
getMinuteSegmentOptions,
getMonthCron, getMonthCron,
getMonthDaysOptions, getMonthDaysOptions,
getMonthOptions, getMonthOptions,
@ -34,6 +36,7 @@ import {
const getCron = (state) => { const getCron = (state) => {
const { const {
selectedPeriod, selectedPeriod,
selectedMinOption,
selectedHourOption, selectedHourOption,
selectedDayOption, selectedDayOption,
selectedWeekOption, selectedWeekOption,
@ -43,7 +46,7 @@ const getCron = (state) => {
switch (selectedPeriod) { switch (selectedPeriod) {
case 'minute': case 'minute':
return getMinuteCron({}); return getMinuteCron(selectedMinOption);
case 'hour': case 'hour':
return getHourCron(selectedHourOption); return getHourCron(selectedHourOption);
case 'day': case 'day':
@ -72,6 +75,9 @@ const CronEditor = (props) => {
const getStateValue = (valueStr) => { const getStateValue = (valueStr) => {
let stateVal = { let stateVal = {
selectedPeriod: 'week', selectedPeriod: 'week',
selectedMinOption: {
min: 5,
},
selectedHourOption: { selectedHourOption: {
min: 0, min: 0,
}, },
@ -131,14 +137,13 @@ const CronEditor = (props) => {
}; };
const [value, setCronValue] = useState(props.value || '0 0 * * 0'); const [value, setCronValue] = useState(props.value || '0 0 * * 0');
const [state, setState] = useState(getStateValue(value)); const [state, setState] = useState(getStateValue(value));
const [periodOptions, setPeriodOptions] = useState(getPeriodOptions()); const [periodOptions] = useState(getPeriodOptions());
const [minuteOptions, setMinuteOptions] = useState(getMinuteOptions()); const [minuteSegmentOptions] = useState(getMinuteSegmentOptions());
const [hourOptions, setHourOptions] = useState(getHourOptions()); const [minuteOptions] = useState(getMinuteOptions());
const [dayOptions, setDayOptions] = useState(getDayOptions()); const [hourOptions] = useState(getHourOptions());
const [monthDaysOptions, setMonthDaysOptions] = useState( const [dayOptions] = useState(getDayOptions());
getMonthDaysOptions() const [monthDaysOptions] = useState(getMonthDaysOptions());
); const [monthOptions] = useState(getMonthOptions());
const [monthOptions, setMonthOptions] = useState(getMonthOptions());
const { className, disabled } = props; const { className, disabled } = props;
const { selectedPeriod } = state; const { selectedPeriod } = state;
@ -171,6 +176,17 @@ const CronEditor = (props) => {
setState((prev) => ({ ...prev, selectedHourOption: hourOption })); setState((prev) => ({ ...prev, selectedHourOption: hourOption }));
}; };
const onMinOptionSelect = (event, key) => {
const selectedValue = event.target.value;
const obj = {};
obj[key] = selectedValue;
const { selectedMinOption } = state;
const minOption = Object.assign({}, selectedMinOption, obj);
changeValue({ ...state, selectedMinOption: minOption });
setState((prev) => ({ ...prev, selectedMinOption: minOption }));
};
const onDayOptionSelect = (event, key) => { const onDayOptionSelect = (event, key) => {
const value = event.target.value; const value = event.target.value;
const obj = {}; const obj = {};
@ -247,6 +263,7 @@ const CronEditor = (props) => {
return ( return (
<select <select
className="tw-form-inputs tw-py-1 tw-px-1" className="tw-form-inputs tw-py-1 tw-px-1"
data-testid="hour-options"
disabled={disabled} disabled={disabled}
value={selectedOption.hour} value={selectedOption.hour}
onChange={(e) => { onChange={(e) => {
@ -263,6 +280,7 @@ const CronEditor = (props) => {
return ( return (
<select <select
className="tw-form-inputs tw-py-1 tw-px-1" className="tw-form-inputs tw-py-1 tw-px-1"
data-testid="minute-options"
disabled={disabled} disabled={disabled}
value={selectedOption.min} value={selectedOption.min}
onChange={(e) => { onChange={(e) => {
@ -273,6 +291,23 @@ const CronEditor = (props) => {
</select> </select>
); );
}; };
const getMinuteSegmentSelect = (selectedOption, onChangeCB) => {
return (
<select
className="tw-form-inputs tw-py-1 tw-px-1"
data-testid="minute-segment-options"
disabled={props.disabled}
value={selectedOption.min}
onChange={(e) => {
e.persist();
onChangeCB(e);
}}>
{minuteSegmentOptions.map(getOptionComponent('minute_option'))}
</select>
);
};
const getBadgeOptions = (options, value, substrVal, onClick) => { const getBadgeOptions = (options, value, substrVal, onClick) => {
const { disabled } = props; const { disabled } = props;
const optionComps = []; const optionComps = [];
@ -302,11 +337,22 @@ const CronEditor = (props) => {
}; };
const getMinuteComponent = (cronPeriodString) => { const getMinuteComponent = (cronPeriodString) => {
const { selectedMinOption } = state;
return ( return (
state.selectedPeriod === 'minute' && ( state.selectedPeriod === 'minute' && (
<cron-minute-component> <>
{getTextComp(`${cronPeriodString}`)} <div className="tw-mb-1.5" data-testid="minute-segment-container">
</cron-minute-component> <label>Minute :</label>
{getMinuteSegmentSelect(selectedMinOption, (e) =>
onMinOptionSelect(e, 'min')
)}
</div>
<div className="tw-col-span-2">
{getTextComp(
`${cronPeriodString} ${selectedMinOption.min} minutes`
)}
</div>
</>
) )
); );
}; };
@ -317,7 +363,7 @@ const CronEditor = (props) => {
return ( return (
state.selectedPeriod === 'hour' && ( state.selectedPeriod === 'hour' && (
<> <>
<div className="tw-mb-1.5"> <div className="tw-mb-1.5" data-testid="hour-segment-container">
<label>Minute :</label> <label>Minute :</label>
{getMinuteSelect(selectedHourOption, (e) => {getMinuteSelect(selectedHourOption, (e) =>
onHourOptionSelect(e, 'min') onHourOptionSelect(e, 'min')
@ -325,7 +371,10 @@ const CronEditor = (props) => {
</div> </div>
<div className="tw-col-span-2"> <div className="tw-col-span-2">
{getTextComp( {getTextComp(
`${cronPeriodString} ${selectedHourOption.min} minute past the hour` `${cronPeriodString} ${pluralize(
+selectedHourOption.min,
'minute'
)} past the hour`
)} )}
</div> </div>
</> </>
@ -342,9 +391,9 @@ const CronEditor = (props) => {
return ( return (
state.selectedPeriod === 'day' && ( state.selectedPeriod === 'day' && (
<> <>
<div className="tw-mb-1.5"> <div className="tw-mb-1.5" data-testid="day-segment-container">
<label>Time :</label> <label>Time :</label>
<div className="tw-flex"> <div className="tw-flex" data-testid="time-option-container">
{getHourSelect(selectedDayOption, (e) => {getHourSelect(selectedDayOption, (e) =>
onDayOptionSelect(e, 'hour') onDayOptionSelect(e, 'hour')
)} )}
@ -375,9 +424,11 @@ const CronEditor = (props) => {
return ( return (
state.selectedPeriod === 'week' && ( state.selectedPeriod === 'week' && (
<> <>
<div className="tw-mb-1.5"> <div className="tw-mb-1.5" data-testid="week-segment-time-container">
<label>Time :</label> <label>Time :</label>
<div className="tw-flex"> <div
className="tw-flex"
data-testid="week-segment-time-options-container">
{getHourSelect(selectedWeekOption, (e) => {getHourSelect(selectedWeekOption, (e) =>
onWeekOptionSelect(e, 'hour') onWeekOptionSelect(e, 'hour')
)} )}
@ -387,7 +438,9 @@ const CronEditor = (props) => {
)} )}
</div> </div>
</div> </div>
<div className="tw-pt-2"> <div
className="tw-pt-2"
data-testid="week-segment-day-option-container">
<span>Day : </span> <span>Day : </span>
<div className="cron-badge-option-container week-opt-container"> <div className="cron-badge-option-container week-opt-container">
{getBadgeOptions(dayOptions, selectedWeekOption.dow, 1, (e) => {getBadgeOptions(dayOptions, selectedWeekOption.dow, 1, (e) =>
@ -501,16 +554,17 @@ const CronEditor = (props) => {
}; };
return ( return (
<div className={`${className} cron-row`}> <div className={`${className} cron-row`} data-testid="cron-container">
<div className=""> <div className="">
<div className="tw-grid tw-grid-cols-2 tw-gap-4"> <div className="tw-grid tw-grid-cols-2 tw-gap-4">
<div className="tw-mb-1.5"> <div className="tw-mb-1.5" data-testid="time-dropdown-container">
<label htmlFor="ingestionType">Every:</label> <label htmlFor="ingestionType">Every:</label>
<select <select
className="tw-form-inputs tw-px-3 tw-py-1" className="tw-form-inputs tw-px-3 tw-py-1"
disabled={disabled} disabled={disabled}
id="ingestionType" id="ingestionType"
name="ingestionType" name="ingestionType"
data-testid="ingestion-type"
value={selectedPeriod} value={selectedPeriod}
onChange={(e) => { onChange={(e) => {
e.persist(); e.persist();
@ -526,7 +580,7 @@ const CronEditor = (props) => {
</select> </select>
</div> </div>
{getMinuteComponent(cronPeriodString)} {getMinuteComponent(startText)}
{getHourComponent(cronPeriodString)} {getHourComponent(cronPeriodString)}
{getDayComponent(cronPeriodString)} {getDayComponent(cronPeriodString)}
{getWeekComponent(cronPeriodString)} {getWeekComponent(cronPeriodString)}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import CronEditor from './CronEditor';
describe('Test CronEditor component', () => {
it('CronEditor component should render', async () => {
render(<CronEditor />);
expect(await screen.findByTestId('cron-container')).toBeInTheDocument();
expect(
await screen.findByTestId('time-dropdown-container')
).toBeInTheDocument();
expect(await screen.findByTestId('ingestion-type')).toBeInTheDocument();
});
it('Hour option should render corrosponding component', async () => {
render(<CronEditor disabled={false} onChange={jest.fn} />);
const ingestionType = await screen.findByTestId('ingestion-type');
userEvent.selectOptions(ingestionType, 'hour');
expect(
await screen.findByTestId('hour-segment-container')
).toBeInTheDocument();
const minutOptions = await screen.findByTestId('minute-options');
expect(minutOptions).toBeInTheDocument();
userEvent.selectOptions(minutOptions, '10');
expect(await screen.findByText('10')).toBeInTheDocument();
});
it('Minute option should render corrosponding component', async () => {
render(<CronEditor disabled={false} onChange={jest.fn} />);
const ingestionType = await screen.findByTestId('ingestion-type');
userEvent.selectOptions(ingestionType, 'minute');
expect(
await screen.findByTestId('minute-segment-container')
).toBeInTheDocument();
const minutOptions = await screen.findByTestId('minute-segment-options');
expect(minutOptions).toBeInTheDocument();
userEvent.selectOptions(minutOptions, '10');
expect(await screen.findByText('10')).toBeInTheDocument();
});
it('Day option should render corrosponding component', async () => {
render(<CronEditor disabled={false} onChange={jest.fn} />);
const ingestionType = await screen.findByTestId('ingestion-type');
userEvent.selectOptions(ingestionType, 'day');
expect(
await screen.findByTestId('day-segment-container')
).toBeInTheDocument();
expect(
await screen.findByTestId('time-option-container')
).toBeInTheDocument();
const minutOptions = await screen.findByTestId('minute-options');
const hourOptions = await screen.findByTestId('hour-options');
expect(minutOptions).toBeInTheDocument();
expect(hourOptions).toBeInTheDocument();
userEvent.selectOptions(minutOptions, '10');
userEvent.selectOptions(hourOptions, '2');
expect((await screen.findAllByText('10')).length).toBe(2);
expect((await screen.findAllByText('02')).length).toBe(2);
});
it('week option should render corrosponding component', async () => {
render(<CronEditor disabled={false} onChange={jest.fn} />);
const ingestionType = await screen.findByTestId('ingestion-type');
userEvent.selectOptions(ingestionType, 'week');
expect(
await screen.findByTestId('week-segment-time-container')
).toBeInTheDocument();
expect(
await screen.findByTestId('week-segment-time-options-container')
).toBeInTheDocument();
expect(
await screen.findByTestId('week-segment-day-option-container')
).toBeInTheDocument();
const minutOptions = await screen.findByTestId('minute-options');
const hourOptions = await screen.findByTestId('hour-options');
expect(minutOptions).toBeInTheDocument();
expect(hourOptions).toBeInTheDocument();
userEvent.selectOptions(minutOptions, '10');
userEvent.selectOptions(hourOptions, '2');
expect((await screen.findAllByText('10')).length).toBe(2);
expect((await screen.findAllByText('02')).length).toBe(2);
});
});

View File

@ -159,7 +159,7 @@ export const pluralize = (count: number, noun: string, suffix = 's') => {
count > 1 ? noun : noun.slice(0, noun.length - 1) count > 1 ? noun : noun.slice(0, noun.length - 1)
}`; }`;
} else { } else {
return `${countString} ${noun}${count !== 1 ? suffix : ''}`; return `${countString} ${noun}${count > 1 ? suffix : ''}`;
} }
} }
}; };