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 */
export const getPeriodOptions = () => {
return [
/* {
label: 'minute',
{
label: 'minutes',
value: 'minute',
prep: ''
},*/
prep: '',
},
{
label: 'hour',
value: 'hour',
@ -57,7 +57,7 @@ export const toDisplay = {
};
export const combinations = {
minute: /^(\*\s){4}\*$/, // "* * * * *"
minute: /^(\*\/\d{1,2})\s(\*\s){3}\*$/, // "*/? * * * *"
hour: /^\d{1,2}\s(\*\s){3}\*$/, // "? * * * *"
day: /^(\d{1,2}\s){2}(\*\s){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 = () => {
return getRangeOptions(60);
};
@ -161,8 +165,8 @@ export const getMonthOptions = () => {
});
};
export const getMinuteCron = () => {
return '* * * * *';
export const getMinuteCron = (value: any) => {
return `*/${value.min} * * * *`;
};
export const getHourCron = (value: any) => {

View File

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