mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-27 02:16:18 +00:00
UI :- Change normal select to ANTD select in Service Page (#9710)
* Change normal select to ANTD select in Service Page * fix cypress and unit test issue * remove empty space * fix cypress issue * code optimization * fix tests * optimize the code and changes made as per comments * fix unit test issue
This commit is contained in:
parent
6a4df5f460
commit
b59de5c7a5
@ -123,7 +123,8 @@ export const handleIngestionRetry = (
|
||||
export const scheduleIngestion = () => {
|
||||
// Schedule & Deploy
|
||||
cy.contains('Schedule for Ingestion').should('be.visible');
|
||||
cy.get('[data-testid="cron-type"]').should('be.visible').select('hour');
|
||||
cy.get('[data-testid="cron-type"]').should('be.visible').click();
|
||||
cy.get('.ant-select-item-option-content').contains('Hour').click();
|
||||
cy.get('[data-testid="deploy-button"]').should('be.visible').click();
|
||||
|
||||
// check success
|
||||
|
@ -35,7 +35,10 @@ describe('Kafka Ingestion', () => {
|
||||
goToAddNewServicePage(SERVICE_TYPE.Messaging);
|
||||
|
||||
// Select Dashboard services
|
||||
cy.get('[data-testid="service-category"]').select('messagingServices');
|
||||
cy.get('[data-testid="service-category"]').should('be.visible').click();
|
||||
cy.get('.ant-select-item-option-content')
|
||||
.contains('Messaging Services')
|
||||
.click();
|
||||
|
||||
const connectionInput = () => {
|
||||
cy.get('#root_bootstrapServers').type(
|
||||
|
@ -35,7 +35,10 @@ describe('Metabase Ingestion', () => {
|
||||
goToAddNewServicePage(SERVICE_TYPE.Dashboard);
|
||||
|
||||
// Select Dashboard services
|
||||
cy.get('[data-testid="service-category"]').select('dashboardServices');
|
||||
cy.get('[data-testid="service-category"]').should('be.visible').click();
|
||||
cy.get('.ant-select-item-option-content')
|
||||
.contains('Dashboard Services')
|
||||
.click();
|
||||
|
||||
const connectionInput = () => {
|
||||
cy.get('#root_username').type(Cypress.env('metabaseUsername'));
|
||||
|
@ -35,7 +35,10 @@ describe('Superset Ingestion', () => {
|
||||
goToAddNewServicePage(SERVICE_TYPE.Dashboard);
|
||||
|
||||
// Select Dashboard services
|
||||
cy.get('[data-testid="service-category"]').select('dashboardServices');
|
||||
cy.get('[data-testid="service-category"]').should('be.visible').click();
|
||||
cy.get('.ant-select-item-option-content')
|
||||
.contains('Dashboard Services')
|
||||
.click();
|
||||
|
||||
const connectionInput = () => {
|
||||
cy.get('#root_username').type(Cypress.env('supersetUsername'));
|
||||
|
@ -11,8 +11,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LOADING_STATE } from 'enums/common.enum';
|
||||
import { isEmpty, isUndefined, omit, trim } from 'lodash';
|
||||
import { LoadingState } from 'Models';
|
||||
import React, {
|
||||
Reducer,
|
||||
useCallback,
|
||||
@ -92,7 +92,6 @@ const AddIngestion = ({
|
||||
status,
|
||||
}: AddIngestionProps) => {
|
||||
const { t } = useTranslation();
|
||||
console.log('data:', data);
|
||||
const { sourceConfig, sourceConfigType } = useMemo(
|
||||
() => ({
|
||||
sourceConfig: data?.sourceConfig.config as ConfigClass,
|
||||
@ -204,7 +203,9 @@ const AddIngestion = ({
|
||||
Reducer<AddIngestionState, Partial<AddIngestionState>>
|
||||
>(reducerWithoutAction, initialState);
|
||||
|
||||
const [saveState, setSaveState] = useState<LoadingState>('initial');
|
||||
const [saveState, setSaveState] = useState<LOADING_STATE>(
|
||||
LOADING_STATE.INITIAL
|
||||
);
|
||||
const [showDeployModal, setShowDeployModal] = useState(false);
|
||||
|
||||
const handleStateChange = useCallback(
|
||||
@ -494,6 +495,7 @@ const AddIngestion = ({
|
||||
};
|
||||
|
||||
const createNewIngestion = () => {
|
||||
setSaveState(LOADING_STATE.WAITING);
|
||||
const { repeatFrequency, enableDebugLog, ingestionName } = state;
|
||||
const ingestionDetails: CreateIngestionPipeline = {
|
||||
airflowConfig: {
|
||||
@ -534,6 +536,7 @@ const AddIngestion = ({
|
||||
// ignore since error is displayed in toast in the parent promise
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => setSaveState(LOADING_STATE.INITIAL), 500);
|
||||
setTimeout(() => setShowDeployModal(false), 500);
|
||||
});
|
||||
}
|
||||
@ -560,11 +563,11 @@ const AddIngestion = ({
|
||||
};
|
||||
|
||||
if (onUpdateIngestion) {
|
||||
setSaveState('waiting');
|
||||
setSaveState(LOADING_STATE.WAITING);
|
||||
setShowDeployModal(true);
|
||||
onUpdateIngestion(updatedData, data, data.id as string, data.name)
|
||||
.then(() => {
|
||||
setSaveState('success');
|
||||
setSaveState(LOADING_STATE.SUCCESS);
|
||||
if (showSuccessScreen) {
|
||||
handleNext();
|
||||
} else {
|
||||
@ -572,7 +575,7 @@ const AddIngestion = ({
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => setSaveState('initial'), 500);
|
||||
setTimeout(() => setSaveState(LOADING_STATE.INITIAL), 500);
|
||||
setTimeout(() => setShowDeployModal(false), 500);
|
||||
});
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Form, InputNumber, Select, Typography } from 'antd';
|
||||
import { Button, Form, InputNumber, Select, Typography } from 'antd';
|
||||
import { isNil } from 'lodash';
|
||||
import React, { Fragment, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -22,7 +22,6 @@ import { ServiceCategory } from '../../../enums/service.enum';
|
||||
import { ProfileSampleType } from '../../../generated/entity/data/table';
|
||||
import { PipelineType } from '../../../generated/entity/services/ingestionPipelines/ingestionPipeline';
|
||||
import { getSeparator } from '../../../utils/CommonUtils';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import FilterPattern from '../../common/FilterPattern/FilterPattern';
|
||||
import RichTextEditor from '../../common/rich-text-editor/RichTextEditor';
|
||||
import { EditorContentRef } from '../../common/rich-text-editor/RichTextEditor.interface';
|
||||
@ -891,22 +890,19 @@ const ConfigureIngestion = ({
|
||||
layout="vertical">
|
||||
{getIngestionPipelineFields()}
|
||||
|
||||
<Field className="tw-flex tw-justify-end">
|
||||
<Field className="d-flex justify-end">
|
||||
<Button
|
||||
className="tw-mr-2"
|
||||
className="m-r-xs"
|
||||
data-testid="back-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="text"
|
||||
type="link"
|
||||
onClick={onCancel}>
|
||||
<span>{t('label.cancel')}</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="font-medium p-x-md p-y-xxs h-auto rounded-6"
|
||||
data-testid="next-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
type="primary"
|
||||
onClick={handleNext}>
|
||||
<span>{t('label.next')}</span>
|
||||
</Button>
|
||||
|
@ -12,12 +12,11 @@
|
||||
*/
|
||||
|
||||
import { CheckOutlined } from '@ant-design/icons';
|
||||
import { Button, Col, Row } from 'antd';
|
||||
import { LOADING_STATE } from 'enums/common.enum';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import CronEditor from '../../common/CronEditor/CronEditor';
|
||||
import { Field } from '../../Field/Field';
|
||||
import Loader from '../../Loader/Loader';
|
||||
import { ScheduleIntervalProps } from '../addIngestion.interface';
|
||||
|
||||
const ScheduleInterval = ({
|
||||
@ -36,8 +35,8 @@ const ScheduleInterval = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div data-testid="schedule-intervel-container">
|
||||
<Field>
|
||||
<Row data-testid="schedule-intervel-container">
|
||||
<Col span={24}>
|
||||
<div>
|
||||
<CronEditor
|
||||
includePeriodOptions={includePeriodOptions}
|
||||
@ -45,48 +44,35 @@ const ScheduleInterval = ({
|
||||
onChange={handleRepeatFrequencyChange}
|
||||
/>
|
||||
</div>
|
||||
</Field>
|
||||
<Field className="tw-flex tw-justify-end tw-mt-5">
|
||||
</Col>
|
||||
<Col className="d-flex justify-end mt-4" span={24}>
|
||||
<Button
|
||||
className="tw-mr-2"
|
||||
className="m-r-xs"
|
||||
data-testid="back-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="text"
|
||||
type="link"
|
||||
onClick={onBack}>
|
||||
<span>{t('label.back')}</span>
|
||||
</Button>
|
||||
|
||||
{status === 'waiting' ? (
|
||||
{status === 'success' ? (
|
||||
<Button
|
||||
disabled
|
||||
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained">
|
||||
<Loader size="small" type="white" />
|
||||
</Button>
|
||||
) : status === 'success' ? (
|
||||
<Button
|
||||
disabled
|
||||
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained">
|
||||
className="w-16 opacity-100 p-x-md p-y-xxs"
|
||||
type="primary">
|
||||
<CheckOutlined />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
className="font-medium p-x-md p-y-xxs h-auto rounded-6"
|
||||
data-testid="deploy-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
loading={status === LOADING_STATE.WAITING}
|
||||
type="primary"
|
||||
onClick={onDeploy}>
|
||||
<span>{submitButtonLabel}</span>
|
||||
{submitButtonLabel}
|
||||
</Button>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -11,22 +11,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Col, Row, Select } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { t } from 'i18next';
|
||||
import { startCase } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
excludedService,
|
||||
serviceTypes,
|
||||
SERVICE_CATEGORY_OPTIONS,
|
||||
} from '../../../constants/Services.constant';
|
||||
import { ServiceCategory } from '../../../enums/service.enum';
|
||||
import { MetadataServiceType } from '../../../generated/entity/services/metadataService';
|
||||
import { MlModelServiceType } from '../../../generated/entity/services/mlmodelService';
|
||||
import { errorMsg, getServiceLogo } from '../../../utils/CommonUtils';
|
||||
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import Searchbar from '../../common/searchbar/Searchbar';
|
||||
import { Field } from '../../Field/Field';
|
||||
import { SelectServiceTypeProps } from './Steps.interface';
|
||||
|
||||
const SelectServiceType = ({
|
||||
@ -38,6 +38,7 @@ const SelectServiceType = ({
|
||||
onCancel,
|
||||
onNext,
|
||||
}: SelectServiceTypeProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [category, setCategory] = useState('');
|
||||
const [connectorSearchTerm, setConnectorSearchTerm] = useState('');
|
||||
const [selectedConnectors, setSelectedConnectors] = useState<string[]>([]);
|
||||
@ -77,96 +78,91 @@ const SelectServiceType = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Field>
|
||||
<select
|
||||
className="tw-form-inputs tw-form-inputs-padding"
|
||||
data-testid="service-category"
|
||||
id="serviceCategory"
|
||||
name="serviceCategory"
|
||||
value={category}
|
||||
onChange={(e) => {
|
||||
setConnectorSearchTerm('');
|
||||
serviceCategoryHandler(e.target.value as ServiceCategory);
|
||||
}}>
|
||||
{Object.values(ServiceCategory).map((option, i) => (
|
||||
<option key={i} value={option}>
|
||||
{startCase(option)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field className="tw-mt-7">
|
||||
<Field>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Select
|
||||
className="w-full"
|
||||
data-testid="service-category"
|
||||
id="serviceCategory"
|
||||
options={SERVICE_CATEGORY_OPTIONS}
|
||||
value={category}
|
||||
onChange={(value) => {
|
||||
setConnectorSearchTerm('');
|
||||
serviceCategoryHandler(value as ServiceCategory);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="m-t-lg" span={24}>
|
||||
<Searchbar
|
||||
removeMargin
|
||||
placeholder={`${t('label.search-for-type', {
|
||||
placeholder={t('label.search-for-type', {
|
||||
type: t('label.connector'),
|
||||
})}...`}
|
||||
})}
|
||||
searchValue={connectorSearchTerm}
|
||||
typingInterval={500}
|
||||
onSearch={handleConnectorSearchTerm}
|
||||
/>
|
||||
</Field>
|
||||
<div className="tw-flex">
|
||||
<div
|
||||
className="tw-grid tw-grid-cols-6 tw-grid-flow-row tw-gap-4 tw-mt-4"
|
||||
data-testid="select-service">
|
||||
{filteredConnectors.map((type) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'tw-flex tw-flex-col tw-items-center tw-relative tw-p-2 tw-w-24 tw-cursor-pointer tw-border tw-rounded-md',
|
||||
{
|
||||
'tw-border-primary': type === selectServiceType,
|
||||
}
|
||||
)}
|
||||
data-testid={type}
|
||||
key={type}
|
||||
onClick={() => handleServiceTypeClick(type)}>
|
||||
<div className="tw-mb-2.5">
|
||||
<div data-testid="service-icon">
|
||||
{getServiceLogo(type || '', 'tw-h-9')}
|
||||
</div>
|
||||
<div className="tw-absolute tw-top-0 tw-right-1.5">
|
||||
{type === selectServiceType && (
|
||||
<SVGIcons alt="checkbox" icon={Icons.CHECKBOX_PRIMARY} />
|
||||
)}
|
||||
<div className="tw-flex">
|
||||
<div
|
||||
className="tw-grid tw-grid-cols-6 tw-grid-flow-row tw-gap-4 tw-mt-4"
|
||||
data-testid="select-service">
|
||||
{filteredConnectors.map((type) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'tw-flex tw-flex-col tw-items-center tw-relative tw-p-2 tw-w-24 tw-cursor-pointer tw-border tw-rounded-md',
|
||||
{
|
||||
'tw-border-primary': type === selectServiceType,
|
||||
}
|
||||
)}
|
||||
data-testid={type}
|
||||
key={type}
|
||||
onClick={() => handleServiceTypeClick(type)}>
|
||||
<div className="tw-mb-2.5">
|
||||
<div data-testid="service-icon">
|
||||
{getServiceLogo(type || '', 'tw-h-9')}
|
||||
</div>
|
||||
<div className="tw-absolute tw-top-0 tw-right-1.5">
|
||||
{type === selectServiceType && (
|
||||
<SVGIcons
|
||||
alt="checkbox"
|
||||
icon={Icons.CHECKBOX_PRIMARY}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="break-word text-center">
|
||||
{type.includes('Custom') ? startCase(type) : type}
|
||||
</p>
|
||||
</div>
|
||||
<p className="break-word text-center">
|
||||
{type.includes('Custom') ? startCase(type) : type}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showError &&
|
||||
errorMsg(
|
||||
t('message.field-text-is-required', {
|
||||
fieldText: t('label.service'),
|
||||
})
|
||||
)}
|
||||
</Field>
|
||||
<Field className="tw-flex tw-justify-end tw-mt-10">
|
||||
<Button
|
||||
className={classNames('tw-mr-2')}
|
||||
data-testid="previous-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="text"
|
||||
onClick={onCancel}>
|
||||
<span>{t('label.cancel')}</span>
|
||||
</Button>
|
||||
{showError &&
|
||||
errorMsg(
|
||||
t('message.field-text-is-required', {
|
||||
fieldText: t('label.service'),
|
||||
})
|
||||
)}
|
||||
</Col>
|
||||
|
||||
<Button
|
||||
data-testid="next-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={onNext}>
|
||||
<span>{t('label.next')}</span>
|
||||
</Button>
|
||||
</Field>
|
||||
<Col className="d-flex justify-end mt-12" span={24}>
|
||||
<Button
|
||||
className="m-r-xs"
|
||||
data-testid="previous-button"
|
||||
type="link"
|
||||
onClick={onCancel}>
|
||||
{t('label.cancel')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="font-medium p-x-md p-y-xxs h-auto rounded-6"
|
||||
data-testid="next-button"
|
||||
type="primary"
|
||||
onClick={onNext}>
|
||||
{t('label.next')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -196,3 +196,10 @@ export const getMonthCron = (value: any) => {
|
||||
export const getYearCron = (value: any) => {
|
||||
return `${value.min} ${value.hour} ${value.dom} ${value.mon} *`;
|
||||
};
|
||||
|
||||
export const SELECTED_PERIOD_OPTIONS = {
|
||||
hour: 'selectedHourOption',
|
||||
day: 'selectedDayOption',
|
||||
week: 'selectedWeekOption',
|
||||
minute: 'selectedMinuteOption',
|
||||
};
|
||||
|
@ -90,3 +90,5 @@ export interface CronEditorProp {
|
||||
disabled?: boolean;
|
||||
includePeriodOptions?: string[];
|
||||
}
|
||||
|
||||
export type CronType = 'minute' | 'hour' | 'day' | 'week';
|
||||
|
@ -11,7 +11,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import {
|
||||
act,
|
||||
findByRole,
|
||||
fireEvent,
|
||||
getByText,
|
||||
getByTitle,
|
||||
render,
|
||||
screen,
|
||||
waitForElement,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import CronEditor from './CronEditor';
|
||||
@ -21,6 +30,35 @@ const mockProps: CronEditorProp = {
|
||||
onChange: jest.fn,
|
||||
};
|
||||
|
||||
const getHourDescription = (value: string) =>
|
||||
`label.schedule-to-run-every hour ${value} past the hour`;
|
||||
|
||||
const getMinuteDescription = (value: string) =>
|
||||
`label.schedule-to-run-every ${value}`;
|
||||
|
||||
const getDayDescription = () => 'label.schedule-to-run-every day at 00:00';
|
||||
|
||||
const handleScheduleEverySelector = async (text: string) => {
|
||||
const everyDropdown = await screen.findByTestId('time-dropdown-container');
|
||||
|
||||
expect(everyDropdown).toBeInTheDocument();
|
||||
|
||||
const cronSelect = await findByRole(everyDropdown, 'combobox');
|
||||
act(() => {
|
||||
userEvent.click(cronSelect);
|
||||
});
|
||||
await waitForElement(
|
||||
async () => await expect(screen.getByText(text)).toBeInTheDocument()
|
||||
);
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText(text));
|
||||
});
|
||||
|
||||
await waitForElement(
|
||||
async () => await expect(getByText(everyDropdown, text)).toBeInTheDocument()
|
||||
);
|
||||
};
|
||||
|
||||
describe('Test CronEditor component', () => {
|
||||
it('CronEditor component should render', async () => {
|
||||
render(<CronEditor {...mockProps} />);
|
||||
@ -35,46 +73,79 @@ describe('Test CronEditor component', () => {
|
||||
it('Hour option should render corresponding component', async () => {
|
||||
render(<CronEditor disabled={false} onChange={jest.fn} />);
|
||||
|
||||
const cronType = await screen.findByTestId('cron-type');
|
||||
userEvent.selectOptions(cronType, 'hour');
|
||||
await handleScheduleEverySelector('label.hour');
|
||||
|
||||
expect(screen.getByTestId('schedule-description')).toHaveTextContent(
|
||||
getHourDescription('0 minute')
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId('hour-segment-container')
|
||||
).toBeInTheDocument();
|
||||
|
||||
const minutOptions = await screen.findByTestId('minute-options');
|
||||
const minutesOptions = await screen.findByTestId('minute-options');
|
||||
|
||||
expect(minutOptions).toBeInTheDocument();
|
||||
expect(minutesOptions).toBeInTheDocument();
|
||||
|
||||
userEvent.selectOptions(minutOptions, '10');
|
||||
const minuteSelect = await findByRole(minutesOptions, 'combobox');
|
||||
|
||||
expect(await screen.findByText('10')).toBeInTheDocument();
|
||||
act(() => {
|
||||
userEvent.click(minuteSelect);
|
||||
});
|
||||
await waitForElement(() => screen.getByText('03'));
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('03'));
|
||||
});
|
||||
|
||||
expect(await getByTitle(minutesOptions, '03')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('schedule-description')).toHaveTextContent(
|
||||
getHourDescription('3 minutes')
|
||||
);
|
||||
});
|
||||
|
||||
it('Minute option should render corrosponding component', async () => {
|
||||
it('Minute option should render corresponding component', async () => {
|
||||
render(<CronEditor disabled={false} onChange={jest.fn} />);
|
||||
|
||||
const cronType = await screen.findByTestId('cron-type');
|
||||
userEvent.selectOptions(cronType, 'minute');
|
||||
await handleScheduleEverySelector('label.minute-plural');
|
||||
|
||||
expect(screen.getByTestId('schedule-description')).toHaveTextContent(
|
||||
getMinuteDescription('5')
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId('minute-segment-container')
|
||||
).toBeInTheDocument();
|
||||
|
||||
const minutOptions = await screen.findByTestId('minute-segment-options');
|
||||
const minutesOptions = await screen.findByTestId('minute-segment-options');
|
||||
|
||||
expect(minutOptions).toBeInTheDocument();
|
||||
expect(minutesOptions).toBeInTheDocument();
|
||||
|
||||
userEvent.selectOptions(minutOptions, '10');
|
||||
const minuteSelect = await findByRole(minutesOptions, 'combobox');
|
||||
|
||||
expect(await screen.findByText('10')).toBeInTheDocument();
|
||||
act(() => {
|
||||
userEvent.click(minuteSelect);
|
||||
});
|
||||
await waitForElement(() => screen.getByText('15'));
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('15'));
|
||||
});
|
||||
|
||||
expect(await screen.getAllByText('15')).toHaveLength(2);
|
||||
|
||||
expect(screen.getByTestId('schedule-description')).toHaveTextContent(
|
||||
getMinuteDescription('15')
|
||||
);
|
||||
});
|
||||
|
||||
it('Day option should render corresponding component', async () => {
|
||||
render(<CronEditor disabled={false} onChange={jest.fn} />);
|
||||
|
||||
const cronType = await screen.findByTestId('cron-type');
|
||||
userEvent.selectOptions(cronType, 'day');
|
||||
await handleScheduleEverySelector('label.day');
|
||||
|
||||
expect(screen.getByTestId('schedule-description')).toHaveTextContent(
|
||||
getDayDescription()
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId('day-segment-container')
|
||||
@ -83,24 +154,37 @@ describe('Test CronEditor component', () => {
|
||||
await screen.findByTestId('time-option-container')
|
||||
).toBeInTheDocument();
|
||||
|
||||
const minutOptions = await screen.findByTestId('minute-options');
|
||||
// For Hours Selector
|
||||
const hourOptions = await screen.findByTestId('hour-options');
|
||||
|
||||
expect(minutOptions).toBeInTheDocument();
|
||||
expect(hourOptions).toBeInTheDocument();
|
||||
|
||||
userEvent.selectOptions(minutOptions, '10');
|
||||
userEvent.selectOptions(hourOptions, '2');
|
||||
const hourSelect = await findByRole(hourOptions, 'combobox');
|
||||
act(() => {
|
||||
userEvent.click(hourSelect);
|
||||
});
|
||||
|
||||
expect(await screen.findAllByText('10')).toHaveLength(2);
|
||||
expect(await screen.findAllByText('02')).toHaveLength(2);
|
||||
await waitForElement(() => screen.getByText('01'));
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('01'));
|
||||
});
|
||||
|
||||
expect(await getByTitle(hourOptions, '01')).toBeInTheDocument();
|
||||
|
||||
// For Minute Selector
|
||||
const minutesOptions = await screen.findByTestId('minute-options');
|
||||
|
||||
expect(minutesOptions).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('week option should render corresponding component', async () => {
|
||||
render(<CronEditor disabled={false} onChange={jest.fn} />);
|
||||
|
||||
const cronType = await screen.findByTestId('cron-type');
|
||||
userEvent.selectOptions(cronType, 'week');
|
||||
await handleScheduleEverySelector('label.week');
|
||||
|
||||
expect(screen.getByTestId('schedule-description')).toHaveTextContent(
|
||||
'label.schedule-to-run-every week on label.monday at 00:00'
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId('week-segment-time-container')
|
||||
@ -112,17 +196,35 @@ describe('Test CronEditor component', () => {
|
||||
await screen.findByTestId('week-segment-day-option-container')
|
||||
).toBeInTheDocument();
|
||||
|
||||
const minutOptions = await screen.findByTestId('minute-options');
|
||||
// For Hours Selector
|
||||
const hourOptions = await screen.findByTestId('hour-options');
|
||||
|
||||
expect(minutOptions).toBeInTheDocument();
|
||||
expect(hourOptions).toBeInTheDocument();
|
||||
|
||||
userEvent.selectOptions(minutOptions, '10');
|
||||
userEvent.selectOptions(hourOptions, '2');
|
||||
const hourSelect = await findByRole(hourOptions, 'combobox');
|
||||
act(() => {
|
||||
userEvent.click(hourSelect);
|
||||
});
|
||||
|
||||
expect(await screen.findAllByText('10')).toHaveLength(2);
|
||||
expect(await screen.findAllByText('02')).toHaveLength(2);
|
||||
await waitForElement(() => screen.getByText('10'));
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('10'));
|
||||
});
|
||||
|
||||
expect(await getByTitle(hourOptions, '10')).toBeInTheDocument();
|
||||
|
||||
// For Minute Selector
|
||||
const minutesOptions = await screen.findByTestId('minute-options');
|
||||
|
||||
expect(minutesOptions).toBeInTheDocument();
|
||||
|
||||
// For Days Selector
|
||||
|
||||
const daysContainer = await screen.findByTestId(
|
||||
'week-segment-day-option-container'
|
||||
);
|
||||
|
||||
expect(daysContainer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('None option should render corresponding component', async () => {
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Select } from 'antd';
|
||||
import { isEmpty, toNumber } from 'lodash';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -25,12 +26,14 @@ import {
|
||||
getMonthDaysOptions,
|
||||
getMonthOptions,
|
||||
getPeriodOptions,
|
||||
SELECTED_PERIOD_OPTIONS,
|
||||
toDisplay,
|
||||
} from './CronEditor.constant';
|
||||
import {
|
||||
Combination,
|
||||
CronEditorProp,
|
||||
CronOption,
|
||||
CronType,
|
||||
CronValue,
|
||||
SelectedDayOption,
|
||||
SelectedHourOption,
|
||||
@ -41,7 +44,6 @@ import {
|
||||
|
||||
const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getCronType = (cronStr: string) => {
|
||||
for (const c in combinations) {
|
||||
if (combinations[c as keyof Combination].test(cronStr)) {
|
||||
@ -94,10 +96,9 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
|
||||
stateVal.selectedPeriod = cronType || stateVal.selectedPeriod;
|
||||
|
||||
if (!isEmpty(t)) {
|
||||
const stateIndex = `${t('label.selected-lowercase')}${cronType
|
||||
?.charAt(0)
|
||||
.toUpperCase()}${cronType?.substring(1)}${t('label.option')}`;
|
||||
if (!isEmpty(cronType)) {
|
||||
const stateIndex =
|
||||
SELECTED_PERIOD_OPTIONS[(cronType as CronType) || 'hour'];
|
||||
const selectedPeriodObj = stateVal[
|
||||
stateIndex as keyof StateValue
|
||||
] as SelectedYearOption;
|
||||
@ -154,16 +155,12 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
onChange(getCron(state) ?? '');
|
||||
};
|
||||
|
||||
const onPeriodSelect = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
changeValue({ ...state, selectedPeriod: event.target.value });
|
||||
setState((prev) => ({ ...prev, selectedPeriod: event.target.value }));
|
||||
const onPeriodSelect = (value: string) => {
|
||||
changeValue({ ...state, selectedPeriod: value });
|
||||
setState((prev) => ({ ...prev, selectedPeriod: value }));
|
||||
};
|
||||
|
||||
const onHourOptionSelect = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>,
|
||||
key: string
|
||||
) => {
|
||||
const value = event.target.value;
|
||||
const onHourOptionSelect = (value: number, key: string) => {
|
||||
const obj = { [key]: value };
|
||||
|
||||
const { selectedHourOption } = state;
|
||||
@ -172,12 +169,8 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
setState((prev) => ({ ...prev, selectedHourOption: hourOption }));
|
||||
};
|
||||
|
||||
const onMinOptionSelect = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>,
|
||||
key: string
|
||||
) => {
|
||||
const selectedValue = event.target.value;
|
||||
const obj = { [key]: selectedValue };
|
||||
const onMinOptionSelect = (value: number, key: string) => {
|
||||
const obj = { [key]: value };
|
||||
|
||||
const { selectedMinOption } = state;
|
||||
const minOption = Object.assign({}, selectedMinOption, obj);
|
||||
@ -185,11 +178,7 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
setState((prev) => ({ ...prev, selectedMinOption: minOption }));
|
||||
};
|
||||
|
||||
const onDayOptionSelect = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>,
|
||||
key: string
|
||||
) => {
|
||||
const value = parseInt(event.target.value);
|
||||
const onDayOptionSelect = (value: number, key: string) => {
|
||||
const obj = { [key]: value };
|
||||
|
||||
const { selectedDayOption } = state;
|
||||
@ -198,13 +187,10 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
setState((prev) => ({ ...prev, selectedDayOption: dayOption }));
|
||||
};
|
||||
|
||||
const onWeekOptionSelect = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>,
|
||||
key: string
|
||||
) => {
|
||||
const value = event.target.value || event.target.dataset.value;
|
||||
const numberValue = value ? parseInt(value) : '';
|
||||
const obj = { [key]: numberValue };
|
||||
const onWeekOptionSelect = (value: number, key: string) => {
|
||||
const obj = {
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const { selectedWeekOption } = state;
|
||||
const weekOption = Object.assign({}, selectedWeekOption, obj);
|
||||
@ -212,11 +198,7 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
setState((prev) => ({ ...prev, selectedWeekOption: weekOption }));
|
||||
};
|
||||
|
||||
const onMonthOptionSelect = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>,
|
||||
key: string
|
||||
) => {
|
||||
const value = event.target.value || event.target.dataset.value;
|
||||
const onMonthOptionSelect = (value: number, key: string) => {
|
||||
const obj = { [key]: value };
|
||||
|
||||
const { selectedMonthOption } = state;
|
||||
@ -225,12 +207,10 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
setState((prev) => ({ ...prev, selectedMonthOption: monthOption }));
|
||||
};
|
||||
|
||||
const onYearOptionSelect = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>,
|
||||
key: string
|
||||
) => {
|
||||
const value = event.target.value || event.target.dataset.value;
|
||||
const obj = { [key]: value };
|
||||
const onYearOptionSelect = (value: number, key: string) => {
|
||||
const obj = {
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const { selectedYearOption } = state;
|
||||
const yearOption = Object.assign({}, selectedYearOption, obj);
|
||||
@ -238,20 +218,16 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
setState((prev) => ({ ...prev, selectedYearOption: yearOption }));
|
||||
};
|
||||
|
||||
const getOptionComponent = (key: string) => {
|
||||
const optionRenderer = (o: CronOption, i: number) => {
|
||||
return (
|
||||
<option key={`${key}_${i}`} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
);
|
||||
const getOptionComponent = () => {
|
||||
const optionRenderer = (o: CronOption) => {
|
||||
return { label: o.label, value: o.value };
|
||||
};
|
||||
|
||||
return optionRenderer;
|
||||
};
|
||||
|
||||
const getTextComp = (str: string) => {
|
||||
return <div>{str}</div>;
|
||||
return <div data-testid="schedule-description">{str}</div>;
|
||||
};
|
||||
|
||||
const findHourOption = (hour: number) => {
|
||||
@ -268,61 +244,55 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
|
||||
const getHourSelect = (
|
||||
selectedOption: SelectedDayOption,
|
||||
onChangeCB: (e: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
onChangeCB: (value: number) => void
|
||||
) => {
|
||||
const { disabled } = props;
|
||||
|
||||
return (
|
||||
<select
|
||||
className="tw-form-inputs tw-py-1 tw-px-1"
|
||||
<Select
|
||||
className="w-full"
|
||||
data-testid="hour-options"
|
||||
disabled={disabled}
|
||||
id="hour-select"
|
||||
options={hourOptions.map(getOptionComponent())}
|
||||
value={selectedOption.hour}
|
||||
onChange={(e) => {
|
||||
e.persist();
|
||||
onChangeCB(e);
|
||||
}}>
|
||||
{hourOptions.map(getOptionComponent('hour_option'))}
|
||||
</select>
|
||||
onChange={onChangeCB}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const getMinuteSelect = (
|
||||
selectedOption: SelectedHourOption,
|
||||
onChangeCB: (e: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
onChangeCB: (value: number) => void
|
||||
) => {
|
||||
const { disabled } = props;
|
||||
|
||||
return (
|
||||
<select
|
||||
className="tw-form-inputs tw-py-1 tw-px-1"
|
||||
<Select
|
||||
className="w-full"
|
||||
data-testid="minute-options"
|
||||
disabled={disabled}
|
||||
id="minute-select"
|
||||
options={minuteOptions.map(getOptionComponent())}
|
||||
value={selectedOption.min}
|
||||
onChange={(e) => {
|
||||
e.persist();
|
||||
onChangeCB(e);
|
||||
}}>
|
||||
{minuteOptions.map(getOptionComponent('minute_option'))}
|
||||
</select>
|
||||
onChange={onChangeCB}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getMinuteSegmentSelect = (
|
||||
selectedOption: SelectedHourOption,
|
||||
onChangeCB: (e: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
onChangeCB: (value: number) => void
|
||||
) => {
|
||||
return (
|
||||
<select
|
||||
className="tw-form-inputs tw-py-1 tw-px-1"
|
||||
<Select
|
||||
className="w-full"
|
||||
data-testid="minute-segment-options"
|
||||
disabled={props.disabled}
|
||||
id="minute-segment-select"
|
||||
options={minuteSegmentOptions.map(getOptionComponent())}
|
||||
value={selectedOption.min}
|
||||
onChange={(e) => {
|
||||
e.persist();
|
||||
onChangeCB(e);
|
||||
}}>
|
||||
{minuteSegmentOptions.map(getOptionComponent('minute_option'))}
|
||||
</select>
|
||||
onChange={onChangeCB}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -330,36 +300,30 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
options: CronOption[],
|
||||
value: number,
|
||||
substrVal: number,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onClick: (e: any) => void
|
||||
) => {
|
||||
const { disabled } = props;
|
||||
const optionComps: JSX.Element[] = [];
|
||||
|
||||
options.forEach((o, i) => {
|
||||
let strVal = o.label;
|
||||
onClick: (value: number) => void
|
||||
) =>
|
||||
options.map(({ label, value: optionValue }, index) => {
|
||||
let strVal = label;
|
||||
|
||||
if (substrVal) {
|
||||
strVal = strVal.substr(0, substrVal);
|
||||
}
|
||||
const comp = (
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`cron-badge-option ${o.value === value ? 'active' : ''} ${
|
||||
disabled || !onClick ? 'disabled' : ''
|
||||
}`}
|
||||
data-value={o.value}
|
||||
key={i}
|
||||
onClick={(e) => onClick?.(e)}>
|
||||
className={`cron-badge-option ${
|
||||
optionValue === value ? 'active' : ''
|
||||
} ${props.disabled || !onClick ? 'disabled' : ''}`}
|
||||
data-value={optionValue}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
onClick?.(Number(optionValue));
|
||||
}}>
|
||||
{strVal}
|
||||
</span>
|
||||
);
|
||||
|
||||
optionComps.push(comp);
|
||||
});
|
||||
|
||||
return optionComps;
|
||||
};
|
||||
|
||||
const getMinuteComponent = (cronPeriodString: string) => {
|
||||
const { selectedMinOption } = state;
|
||||
|
||||
@ -367,11 +331,9 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
state.selectedPeriod === 'minute' && (
|
||||
<>
|
||||
<div className="tw-mb-1.5" data-testid="minute-segment-container">
|
||||
<label>{`${t('label.minute-lowercase')}:`}</label>
|
||||
{getMinuteSegmentSelect(
|
||||
selectedMinOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onMinOptionSelect(e, 'min')
|
||||
<label>{`${t('label.minute')}:`}</label>
|
||||
{getMinuteSegmentSelect(selectedMinOption, (value: number) =>
|
||||
onMinOptionSelect(value, 'min')
|
||||
)}
|
||||
</div>
|
||||
<div className="tw-col-span-2">
|
||||
@ -391,11 +353,9 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
state.selectedPeriod === 'hour' && (
|
||||
<>
|
||||
<div className="tw-mb-1.5" data-testid="hour-segment-container">
|
||||
<label>{`${t('label.minute-lowercase')}:`}</label>
|
||||
{getMinuteSelect(
|
||||
selectedHourOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onHourOptionSelect(e, 'min')
|
||||
<label>{`${t('label.minute')}:`}</label>
|
||||
{getMinuteSelect(selectedHourOption, (value: number) =>
|
||||
onHourOptionSelect(value, 'min')
|
||||
)}
|
||||
</div>
|
||||
<div className="tw-col-span-2">
|
||||
@ -423,16 +383,12 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
<div className="tw-mb-1.5" data-testid="day-segment-container">
|
||||
<label>{`${t('label.time')}:`}</label>
|
||||
<div className="tw-flex" data-testid="time-option-container">
|
||||
{getHourSelect(
|
||||
selectedDayOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onDayOptionSelect(e, 'hour')
|
||||
{getHourSelect(selectedDayOption, (value: number) =>
|
||||
onDayOptionSelect(value, 'hour')
|
||||
)}
|
||||
<span className="tw-mx-2 tw-self-center">:</span>
|
||||
{getMinuteSelect(
|
||||
selectedDayOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onDayOptionSelect(e, 'min')
|
||||
{getMinuteSelect(selectedDayOption, (value: number) =>
|
||||
onDayOptionSelect(value, 'min')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -462,16 +418,12 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
<div
|
||||
className="tw-flex"
|
||||
data-testid="week-segment-time-options-container">
|
||||
{getHourSelect(
|
||||
selectedWeekOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onWeekOptionSelect(e, 'hour')
|
||||
{getHourSelect(selectedWeekOption, (value: number) =>
|
||||
onWeekOptionSelect(value, 'hour')
|
||||
)}
|
||||
<span className="tw-mx-2 tw-self-center">:</span>
|
||||
{getMinuteSelect(
|
||||
selectedWeekOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onWeekOptionSelect(e, 'min')
|
||||
{getMinuteSelect(selectedWeekOption, (value: number) =>
|
||||
onWeekOptionSelect(value, 'min')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -484,8 +436,7 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
dayOptions,
|
||||
selectedWeekOption.dow,
|
||||
1,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onWeekOptionSelect(e, 'dow')
|
||||
(value: number) => onWeekOptionSelect(value, 'dow')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -519,21 +470,18 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
monthDaysOptions,
|
||||
selectedMonthOption.dom,
|
||||
0,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onMonthOptionSelect(e, 'dom')
|
||||
(value: number) => onMonthOptionSelect(value, 'dom')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="cron-field-row">
|
||||
<span className="m-l-xs">{`${t('label.time')}:`}</span>
|
||||
{`${getHourSelect(
|
||||
selectedMonthOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onMonthOptionSelect(e, 'hour')
|
||||
)} : ${getMinuteSelect(
|
||||
selectedMonthOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onMonthOptionSelect(e, 'min')
|
||||
{`${getHourSelect(selectedMonthOption, (e: number) =>
|
||||
onMonthOptionSelect(e, 'hour')
|
||||
)}
|
||||
:
|
||||
${getMinuteSelect(selectedMonthOption, (e: number) =>
|
||||
onMonthOptionSelect(e, 'min')
|
||||
)}`}
|
||||
</div>
|
||||
{getTextComp(
|
||||
@ -567,8 +515,7 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
monthOptions,
|
||||
selectedYearOption.mon,
|
||||
3,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onYearOptionSelect(e, 'mon')
|
||||
(value: number) => onYearOptionSelect(value, 'mon')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@ -579,23 +526,18 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
monthDaysOptions,
|
||||
selectedYearOption.dom,
|
||||
0,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onYearOptionSelect(e, 'dom')
|
||||
(value: number) => onYearOptionSelect(value, 'dom')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="cron-field-row">
|
||||
<span className="m-l-xs">{`${t('label.time')}:`}</span>
|
||||
{`${getHourSelect(
|
||||
selectedYearOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onYearOptionSelect(e, 'hour')
|
||||
{`${getHourSelect(selectedYearOption, (value: number) =>
|
||||
onYearOptionSelect(value, 'hour')
|
||||
)}
|
||||
:
|
||||
${getMinuteSelect(
|
||||
selectedYearOption,
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
onYearOptionSelect(e, 'min')
|
||||
${getMinuteSelect(selectedYearOption, (value: number) =>
|
||||
onYearOptionSelect(value, 'min')
|
||||
)}`}
|
||||
</div>
|
||||
{getTextComp(
|
||||
@ -612,25 +554,18 @@ const CronEditor: FC<CronEditorProp> = (props) => {
|
||||
<div className="tw-grid tw-grid-cols-2 tw-gap-4">
|
||||
<div className="tw-mb-1.5" data-testid="time-dropdown-container">
|
||||
<label htmlFor="cronType">{`${t('label.every')}:`}</label>
|
||||
<select
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
<Select
|
||||
className="w-full"
|
||||
data-testid="cron-type"
|
||||
disabled={disabled}
|
||||
id="cronType"
|
||||
name="cronType"
|
||||
options={filteredPeriodOptions.map(({ label, value }) => ({
|
||||
label,
|
||||
value,
|
||||
}))}
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => {
|
||||
e.persist();
|
||||
onPeriodSelect(e);
|
||||
}}>
|
||||
{filteredPeriodOptions.map((t, index) => {
|
||||
return (
|
||||
<option key={`period_option_${index}`} value={t.value}>
|
||||
{t.label}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
onChange={onPeriodSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{getMinuteComponent(startText)}
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { map, startCase } from 'lodash';
|
||||
import { ServiceTypes } from 'Models';
|
||||
import i18n from 'utils/i18next/LocalUtil';
|
||||
import addPlaceHolder from '../assets/img/add-placeholder.svg';
|
||||
@ -240,3 +241,8 @@ export const COMMON_UI_SCHEMA = {
|
||||
|
||||
export const OPENMETADATA = 'OpenMetadata';
|
||||
export const JWT_CONFIG = 'openMetadataJWTClientConfig';
|
||||
|
||||
export const SERVICE_CATEGORY_OPTIONS = map(ServiceCategory, (value) => ({
|
||||
label: startCase(value),
|
||||
value,
|
||||
}));
|
||||
|
@ -455,6 +455,7 @@
|
||||
"metrics-summary": "Metrics Summary",
|
||||
"min": "Min",
|
||||
"minor": "Minor",
|
||||
"minute": "Minute",
|
||||
"minute-lowercase": "minute",
|
||||
"minute-plural": "Minutes",
|
||||
"ml-model": "ML Model",
|
||||
@ -1078,6 +1079,7 @@
|
||||
"pipeline-description-message": "Description of the pipeline.",
|
||||
"pipeline-trigger-success-message": "Pipeline triggered successfully!",
|
||||
"pipeline-will-trigger-manually": "Pipeline will only be triggered manually.",
|
||||
"pipeline-will-triggered-manually": "Pipeline will only be triggered manually",
|
||||
"process-pii-sensitive-column-message": "Check column names to auto tag PII Senstive/nonSensitive columns.",
|
||||
"profile-sample-percentage-message": "Set the Profiler value as percentage",
|
||||
"profile-sample-row-count-message": " Set the Profiler value as row count",
|
||||
@ -1099,6 +1101,7 @@
|
||||
"result-limit-message": "Configuration to set the limit for query logs.",
|
||||
"run-sample-data-to-ingest-sample-data": "'Run sample data to ingest sample data assets into your OpenMetadata.'",
|
||||
"schedule-for-ingestion-description": "Scheduling can be set up at an hourly, daily, or weekly cadence. The timezone is in UTC.",
|
||||
"scheduled-run-every": "Scheduled to run every",
|
||||
"scopes-comma-separated": "Add the Scopes value, separated by commas",
|
||||
"search-for-entity-types": "Search for Tables, Topics, Dashboards, Pipelines and ML Models.",
|
||||
"search-for-ingestion": "Search for ingestion",
|
||||
|
Loading…
x
Reference in New Issue
Block a user