mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-30 20:06:19 +00:00
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:
parent
bcdae71eb0
commit
ee3f267e96
@ -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) => {
|
||||
|
@ -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)}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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 : ''}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user