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