mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-22 16:08:13 +00:00
parent
a9570a21c6
commit
a4ac73e867
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { isEmpty, isUndefined } from 'lodash';
|
||||
import { LoadingState } from 'Models';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
INGESTION_SCHEDULER_INITIAL_VALUE,
|
||||
@ -31,6 +32,7 @@ import {
|
||||
IngestionPipeline,
|
||||
} from '../../generated/entity/services/ingestionPipelines/ingestionPipeline';
|
||||
import { getCurrentDate, getCurrentUserId } from '../../utils/CommonUtils';
|
||||
import { getIngestionName } from '../../utils/ServiceUtils';
|
||||
import SuccessScreen from '../common/success-screen/SuccessScreen';
|
||||
import IngestionStepper from '../IngestionStepper/IngestionStepper.component';
|
||||
import { AddIngestionProps } from './addIngestion.interface';
|
||||
@ -53,9 +55,11 @@ const AddIngestion = ({
|
||||
handleCancelClick,
|
||||
handleViewServiceClick,
|
||||
}: AddIngestionProps) => {
|
||||
const [ingestionName] = useState(
|
||||
data?.name ?? `${serviceData.name}_${pipelineType}`
|
||||
const [saveState, setSaveState] = useState<LoadingState>('initial');
|
||||
const [ingestionName, setIngestionName] = useState(
|
||||
data?.name ?? getIngestionName(serviceData.name, pipelineType)
|
||||
);
|
||||
const [description, setDescription] = useState(data?.description ?? '');
|
||||
const [repeatFrequency, setRepeatFrequency] = useState(
|
||||
data?.airflowConfig.scheduleInterval ?? INGESTION_SCHEDULER_INITIAL_VALUE
|
||||
);
|
||||
@ -89,6 +93,11 @@ const AddIngestion = ({
|
||||
(data?.source.sourceConfig.config as ConfigClass)?.chartFilterPattern
|
||||
)
|
||||
);
|
||||
const [showFqnFilter, setShowFqnFilter] = useState(
|
||||
!isUndefined(
|
||||
(data?.source.sourceConfig.config as ConfigClass)?.fqnFilterPattern
|
||||
)
|
||||
);
|
||||
const [includeView, setIncludeView] = useState(
|
||||
Boolean((data?.source.sourceConfig.config as ConfigClass)?.includeViews)
|
||||
);
|
||||
@ -121,6 +130,10 @@ const AddIngestion = ({
|
||||
(data?.source.sourceConfig.config as ConfigClass)?.chartFilterPattern ??
|
||||
INITIAL_FILTER_PATTERN
|
||||
);
|
||||
const [fqnFilterPattern, setFqnFilterPattern] = useState<FilterPattern>(
|
||||
(data?.source.sourceConfig.config as ConfigClass)?.fqnFilterPattern ??
|
||||
INITIAL_FILTER_PATTERN
|
||||
);
|
||||
|
||||
const [queryLogDuration, setQueryLogDuration] = useState<number>(
|
||||
(data?.source.sourceConfig.config as ConfigClass)?.queryLogDuration ?? 1
|
||||
@ -138,6 +151,12 @@ const AddIngestion = ({
|
||||
ConfigType.DatabaseUsage
|
||||
);
|
||||
}, [data]);
|
||||
const profilerIngestionType = useMemo(() => {
|
||||
return (
|
||||
(data?.source.sourceConfig.config as ConfigClass)?.type ??
|
||||
ConfigType.Profiler
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
const getIncludeValue = (value: Array<string>, type: FilterPatternEnum) => {
|
||||
switch (type) {
|
||||
@ -163,6 +182,10 @@ const AddIngestion = ({
|
||||
case FilterPatternEnum.CHART:
|
||||
setChartFilterPattern({ ...topicFilterPattern, includes: value });
|
||||
|
||||
break;
|
||||
case FilterPatternEnum.FQN:
|
||||
setFqnFilterPattern({ ...fqnFilterPattern, includes: value });
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
@ -190,6 +213,10 @@ const AddIngestion = ({
|
||||
case FilterPatternEnum.CHART:
|
||||
setChartFilterPattern({ ...topicFilterPattern, excludes: value });
|
||||
|
||||
break;
|
||||
case FilterPatternEnum.FQN:
|
||||
setFqnFilterPattern({ ...fqnFilterPattern, excludes: value });
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
@ -215,6 +242,10 @@ const AddIngestion = ({
|
||||
case FilterPatternEnum.CHART:
|
||||
setShowChartFilter(value);
|
||||
|
||||
break;
|
||||
case FilterPatternEnum.FQN:
|
||||
setShowFqnFilter(value);
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
@ -246,6 +277,38 @@ const AddIngestion = ({
|
||||
return filterPattern;
|
||||
};
|
||||
|
||||
const getConfigData = (type: PipelineType): ConfigClass => {
|
||||
switch (type) {
|
||||
case PipelineType.Usage: {
|
||||
return {
|
||||
queryLogDuration,
|
||||
resultLimit,
|
||||
stageFileLocation,
|
||||
type: usageIngestionType,
|
||||
};
|
||||
}
|
||||
case PipelineType.Profiler: {
|
||||
return {
|
||||
fqnFilterPattern: getFilterPatternData(fqnFilterPattern),
|
||||
type: profilerIngestionType,
|
||||
};
|
||||
}
|
||||
case PipelineType.Metadata:
|
||||
default: {
|
||||
return {
|
||||
enableDataProfiler: enableDataProfiler,
|
||||
generateSampleData: ingestSampleData,
|
||||
includeViews: includeView,
|
||||
schemaFilterPattern: getFilterPatternData(schemaFilterPattern),
|
||||
tableFilterPattern: getFilterPatternData(tableFilterPattern),
|
||||
chartFilterPattern: getFilterPatternData(chartFilterPattern),
|
||||
dashboardFilterPattern: getFilterPatternData(dashboardFilterPattern),
|
||||
topicFilterPattern: getFilterPatternData(topicFilterPattern),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const createNewIngestion = () => {
|
||||
const ingestionDetails: CreateIngestionPipeline = {
|
||||
airflowConfig: {
|
||||
@ -266,37 +329,23 @@ const AddIngestion = ({
|
||||
type: serviceCategory.slice(0, -1),
|
||||
},
|
||||
sourceConfig: {
|
||||
config:
|
||||
pipelineType === PipelineType.Usage
|
||||
? {
|
||||
queryLogDuration,
|
||||
resultLimit,
|
||||
stageFileLocation,
|
||||
type: usageIngestionType,
|
||||
}
|
||||
: {
|
||||
enableDataProfiler: enableDataProfiler,
|
||||
generateSampleData: ingestSampleData,
|
||||
includeViews: includeView,
|
||||
schemaFilterPattern: getFilterPatternData(schemaFilterPattern),
|
||||
tableFilterPattern: getFilterPatternData(tableFilterPattern),
|
||||
chartFilterPattern: getFilterPatternData(chartFilterPattern),
|
||||
dashboardFilterPattern: getFilterPatternData(
|
||||
dashboardFilterPattern
|
||||
),
|
||||
topicFilterPattern: getFilterPatternData(topicFilterPattern),
|
||||
},
|
||||
config: getConfigData(pipelineType),
|
||||
},
|
||||
};
|
||||
|
||||
onAddIngestionSave &&
|
||||
onAddIngestionSave(ingestionDetails).then(() => {
|
||||
if (showSuccessScreen) {
|
||||
setActiveIngestionStep(3);
|
||||
} else {
|
||||
onSuccessSave?.();
|
||||
}
|
||||
});
|
||||
if (onAddIngestionSave) {
|
||||
setSaveState('waiting');
|
||||
onAddIngestionSave(ingestionDetails)
|
||||
.then(() => {
|
||||
setSaveState('success');
|
||||
if (showSuccessScreen) {
|
||||
setActiveIngestionStep(3);
|
||||
} else {
|
||||
onSuccessSave?.();
|
||||
}
|
||||
})
|
||||
.finally(() => setTimeout(() => setSaveState('initial'), 500));
|
||||
}
|
||||
};
|
||||
|
||||
const updateIngestion = () => {
|
||||
@ -314,40 +363,25 @@ const AddIngestion = ({
|
||||
sourceConfig: {
|
||||
config: {
|
||||
...(data.source.sourceConfig.config as ConfigClass),
|
||||
...(pipelineType === PipelineType.Usage
|
||||
? {
|
||||
queryLogDuration,
|
||||
resultLimit,
|
||||
stageFileLocation,
|
||||
type: usageIngestionType,
|
||||
}
|
||||
: {
|
||||
enableDataProfiler: enableDataProfiler,
|
||||
generateSampleData: ingestSampleData,
|
||||
includeViews: includeView,
|
||||
schemaFilterPattern:
|
||||
getFilterPatternData(schemaFilterPattern),
|
||||
tableFilterPattern:
|
||||
getFilterPatternData(tableFilterPattern),
|
||||
chartFilterPattern:
|
||||
getFilterPatternData(chartFilterPattern),
|
||||
dashboardFilterPattern: getFilterPatternData(
|
||||
dashboardFilterPattern
|
||||
),
|
||||
topicFilterPattern:
|
||||
getFilterPatternData(topicFilterPattern),
|
||||
}),
|
||||
...getConfigData(pipelineType),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
onUpdateIngestion &&
|
||||
onUpdateIngestion(updatedData, data, data.id as string, data.name).then(
|
||||
() => {
|
||||
onSuccessSave?.();
|
||||
}
|
||||
);
|
||||
if (onUpdateIngestion) {
|
||||
setSaveState('waiting');
|
||||
onUpdateIngestion(updatedData, data, data.id as string, data.name)
|
||||
.then(() => {
|
||||
setSaveState('success');
|
||||
if (showSuccessScreen) {
|
||||
setActiveIngestionStep(3);
|
||||
} else {
|
||||
onSuccessSave?.();
|
||||
}
|
||||
})
|
||||
.finally(() => setTimeout(() => setSaveState('initial'), 500));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -375,14 +409,18 @@ const AddIngestion = ({
|
||||
<ConfigureIngestion
|
||||
chartFilterPattern={chartFilterPattern}
|
||||
dashboardFilterPattern={dashboardFilterPattern}
|
||||
description={description}
|
||||
enableDataProfiler={enableDataProfiler}
|
||||
fqnFilterPattern={fqnFilterPattern}
|
||||
getExcludeValue={getExcludeValue}
|
||||
getIncludeValue={getIncludeValue}
|
||||
handleDescription={(val) => setDescription(val)}
|
||||
handleEnableDataProfiler={() =>
|
||||
setEnableDataProfiler((pre) => !pre)
|
||||
}
|
||||
handleIncludeView={() => setIncludeView((pre) => !pre)}
|
||||
handleIngestSampleData={() => setIngestSampleData((pre) => !pre)}
|
||||
handleIngestionName={(val) => setIngestionName(val)}
|
||||
handleQueryLogDuration={(val) => setQueryLogDuration(val)}
|
||||
handleResultLimit={(val) => setResultLimit(val)}
|
||||
handleShowFilter={handleShowFilter}
|
||||
@ -397,6 +435,7 @@ const AddIngestion = ({
|
||||
serviceCategory={serviceCategory}
|
||||
showChartFilter={showChartFilter}
|
||||
showDashboardFilter={showDashboardFilter}
|
||||
showFqnFilter={showFqnFilter}
|
||||
showSchemaFilter={showSchemaFilter}
|
||||
showTableFilter={showTableFilter}
|
||||
showTopicFilter={showTopicFilter}
|
||||
@ -418,8 +457,9 @@ const AddIngestion = ({
|
||||
handleStartDateChange={(value: string) => setStartDate(value)}
|
||||
repeatFrequency={repeatFrequency}
|
||||
startDate={startDate as string}
|
||||
status={saveState}
|
||||
onBack={handleScheduleIntervalBackClick}
|
||||
onDeloy={handleScheduleIntervalDeployClick}
|
||||
onDeploy={handleScheduleIntervalDeployClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -50,6 +50,10 @@ const mockConfigureIngestion: ConfigureIngestionProps = {
|
||||
includes: [],
|
||||
excludes: [],
|
||||
},
|
||||
fqnFilterPattern: {
|
||||
includes: [],
|
||||
excludes: [],
|
||||
},
|
||||
includeView: false,
|
||||
pipelineType: PipelineType.Metadata,
|
||||
queryLogDuration: 1,
|
||||
@ -62,7 +66,9 @@ const mockConfigureIngestion: ConfigureIngestionProps = {
|
||||
showTableFilter: false,
|
||||
showTopicFilter: false,
|
||||
showChartFilter: false,
|
||||
showFqnFilter: false,
|
||||
handleIncludeView: jest.fn(),
|
||||
handleIngestionName: jest.fn(),
|
||||
handleEnableDataProfiler: jest.fn(),
|
||||
handleIngestSampleData: jest.fn(),
|
||||
handleQueryLogDuration: jest.fn(),
|
||||
|
@ -11,23 +11,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import { EditorContentRef } from 'Models';
|
||||
import React, { Fragment, useRef } from 'react';
|
||||
import { FilterPatternEnum } from '../../../enums/filterPattern.enum';
|
||||
import { ServiceCategory } from '../../../enums/service.enum';
|
||||
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 ToggleSwitchV1 from '../../common/toggle-switch/ToggleSwitchV1';
|
||||
import { Field } from '../../Field/Field';
|
||||
import { ConfigureIngestionProps } from '../addIngestion.interface';
|
||||
|
||||
const ConfigureIngestion = ({
|
||||
ingestionName,
|
||||
description = '',
|
||||
dashboardFilterPattern,
|
||||
schemaFilterPattern,
|
||||
tableFilterPattern,
|
||||
topicFilterPattern,
|
||||
chartFilterPattern,
|
||||
fqnFilterPattern,
|
||||
includeView,
|
||||
serviceCategory,
|
||||
enableDataProfiler,
|
||||
@ -38,11 +43,14 @@ const ConfigureIngestion = ({
|
||||
showTableFilter,
|
||||
showTopicFilter,
|
||||
showChartFilter,
|
||||
showFqnFilter,
|
||||
queryLogDuration,
|
||||
stageFileLocation,
|
||||
resultLimit,
|
||||
getExcludeValue,
|
||||
getIncludeValue,
|
||||
handleIngestionName,
|
||||
handleDescription,
|
||||
handleShowFilter,
|
||||
handleEnableDataProfiler,
|
||||
handleIncludeView,
|
||||
@ -53,7 +61,9 @@ const ConfigureIngestion = ({
|
||||
onCancel,
|
||||
onNext,
|
||||
}: ConfigureIngestionProps) => {
|
||||
const getFilterPatternField = () => {
|
||||
const markdownRef = useRef<EditorContentRef>();
|
||||
|
||||
const getMetadataFilterPatternField = () => {
|
||||
switch (serviceCategory) {
|
||||
case ServiceCategory.DATABASE_SERVICES:
|
||||
return (
|
||||
@ -132,10 +142,27 @@ const ConfigureIngestion = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getProfilerFilterPatternField = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<FilterPattern
|
||||
checked={showFqnFilter}
|
||||
excludePattern={fqnFilterPattern?.excludes ?? []}
|
||||
getExcludeValue={getExcludeValue}
|
||||
getIncludeValue={getIncludeValue}
|
||||
handleChecked={(value) =>
|
||||
handleShowFilter(value, FilterPatternEnum.FQN)
|
||||
}
|
||||
includePattern={fqnFilterPattern?.includes ?? []}
|
||||
type={FilterPatternEnum.FQN}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
const getMetadataFields = () => {
|
||||
return (
|
||||
<>
|
||||
<div>{getFilterPatternField()}</div>
|
||||
<div>{getMetadataFilterPatternField()}</div>
|
||||
{getSeparator('')}
|
||||
<div>
|
||||
<Field>
|
||||
@ -252,14 +279,72 @@ const ConfigureIngestion = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getProfilerFields = () => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Field>
|
||||
<label className="tw-block tw-form-label tw-mb-1" htmlFor="name">
|
||||
Name
|
||||
</label>
|
||||
<p className="tw-text-grey-muted tw-mt-1 tw-mb-2 tw-text-sm">
|
||||
Name that identifies this pipeline instance uniquely.
|
||||
</p>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
data-testid="name"
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
value={ingestionName}
|
||||
onChange={(e) => handleIngestionName(e.target.value)}
|
||||
/>
|
||||
{getSeparator('')}
|
||||
</Field>
|
||||
</div>
|
||||
<div>{getProfilerFilterPatternField()}</div>
|
||||
{getSeparator('')}
|
||||
<div>
|
||||
<Field>
|
||||
<label className="tw-block tw-form-label tw-mb-1" htmlFor="name">
|
||||
Description
|
||||
</label>
|
||||
<p className="tw-text-grey-muted tw-mt-1 tw-mb-2 tw-text-sm">
|
||||
Description of the pipeline.
|
||||
</p>
|
||||
<RichTextEditor
|
||||
data-testid="description"
|
||||
initialValue={description}
|
||||
ref={markdownRef}
|
||||
/>
|
||||
{getSeparator('')}
|
||||
</Field>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getIngestionPipelineFields = () => {
|
||||
if (pipelineType === PipelineType.Usage) {
|
||||
return getUsageFields();
|
||||
} else {
|
||||
return getMetadataFields();
|
||||
switch (pipelineType) {
|
||||
case PipelineType.Usage: {
|
||||
return getUsageFields();
|
||||
}
|
||||
case PipelineType.Profiler: {
|
||||
return getProfilerFields();
|
||||
}
|
||||
case PipelineType.Metadata:
|
||||
default: {
|
||||
return getMetadataFields();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
handleDescription &&
|
||||
handleDescription(markdownRef.current?.getEditorContent() || '');
|
||||
onNext();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tw-px-2" data-testid="configure-ingestion-container">
|
||||
{getIngestionPipelineFields()}
|
||||
@ -280,7 +365,7 @@ const ConfigureIngestion = ({
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={onNext}>
|
||||
onClick={handleNext}>
|
||||
<span>Next</span>
|
||||
</Button>
|
||||
</Field>
|
||||
|
@ -27,6 +27,7 @@ jest.mock('../../common/toggle-switch/ToggleSwitchV1', () => {
|
||||
});
|
||||
|
||||
const mockScheduleIntervalProps: ScheduleIntervalProps = {
|
||||
status: 'initial',
|
||||
repeatFrequency: '',
|
||||
handleRepeatFrequencyChange: jest.fn(),
|
||||
startDate: '',
|
||||
@ -34,7 +35,7 @@ const mockScheduleIntervalProps: ScheduleIntervalProps = {
|
||||
endDate: '',
|
||||
handleEndDateChange: jest.fn(),
|
||||
onBack: jest.fn(),
|
||||
onDeloy: jest.fn(),
|
||||
onDeploy: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Test ScheduleInterval component', () => {
|
||||
|
@ -11,13 +11,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React from 'react';
|
||||
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 = ({
|
||||
status,
|
||||
repeatFrequency,
|
||||
handleRepeatFrequencyChange,
|
||||
startDate,
|
||||
@ -25,7 +28,7 @@ const ScheduleInterval = ({
|
||||
endDate,
|
||||
handleEndDateChange,
|
||||
onBack,
|
||||
onDeloy,
|
||||
onDeploy,
|
||||
}: ScheduleIntervalProps) => {
|
||||
return (
|
||||
<div data-testid="schedule-intervel-container">
|
||||
@ -75,14 +78,34 @@ const ScheduleInterval = ({
|
||||
<span>Back</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
data-testid="deploy-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={onDeloy}>
|
||||
<span>Deploy</span>
|
||||
</Button>
|
||||
{status === 'waiting' ? (
|
||||
<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">
|
||||
<FontAwesomeIcon icon="check" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
data-testid="deploy-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={onDeploy}>
|
||||
<span>Deploy</span>
|
||||
</Button>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
);
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LoadingState } from 'Models';
|
||||
import { FilterPatternEnum } from '../../enums/filterPattern.enum';
|
||||
import { FormSubmitType } from '../../enums/form.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
@ -47,12 +48,14 @@ export interface AddIngestionProps {
|
||||
|
||||
export interface ConfigureIngestionProps {
|
||||
ingestionName: string;
|
||||
description?: string;
|
||||
serviceCategory: ServiceCategory;
|
||||
dashboardFilterPattern: FilterPattern;
|
||||
schemaFilterPattern: FilterPattern;
|
||||
tableFilterPattern: FilterPattern;
|
||||
topicFilterPattern: FilterPattern;
|
||||
chartFilterPattern: FilterPattern;
|
||||
fqnFilterPattern: FilterPattern;
|
||||
includeView: boolean;
|
||||
enableDataProfiler: boolean;
|
||||
ingestSampleData: boolean;
|
||||
@ -62,9 +65,12 @@ export interface ConfigureIngestionProps {
|
||||
showTableFilter: boolean;
|
||||
showTopicFilter: boolean;
|
||||
showChartFilter: boolean;
|
||||
showFqnFilter: boolean;
|
||||
queryLogDuration: number;
|
||||
stageFileLocation: string;
|
||||
resultLimit: number;
|
||||
handleIngestionName: (value: string) => void;
|
||||
handleDescription?: (value: string) => void;
|
||||
handleIncludeView: () => void;
|
||||
handleEnableDataProfiler: () => void;
|
||||
handleIngestSampleData: () => void;
|
||||
@ -79,6 +85,7 @@ export interface ConfigureIngestionProps {
|
||||
}
|
||||
|
||||
export type ScheduleIntervalProps = {
|
||||
status: LoadingState;
|
||||
repeatFrequency: string;
|
||||
handleRepeatFrequencyChange: (value: string) => void;
|
||||
startDate: string;
|
||||
@ -86,5 +93,5 @@ export type ScheduleIntervalProps = {
|
||||
endDate: string;
|
||||
handleEndDateChange: (value: string) => void;
|
||||
onBack: () => void;
|
||||
onDeloy: () => void;
|
||||
onDeploy: () => void;
|
||||
};
|
||||
|
@ -15,8 +15,8 @@ import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
import cronstrue from 'cronstrue';
|
||||
import { capitalize, isNil, lowerCase } from 'lodash';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { capitalize, isNil, lowerCase, startCase } from 'lodash';
|
||||
import React, { Fragment, useCallback, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
|
||||
import {
|
||||
@ -33,13 +33,14 @@ import {
|
||||
getAddIngestionPath,
|
||||
getEditIngestionPath,
|
||||
} from '../../utils/RouterUtils';
|
||||
import { dropdownIcon as DropdownIcon } from '../../utils/svgconstant';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import { showInfoToast } from '../../utils/ToastUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import NextPrevious from '../common/next-previous/NextPrevious';
|
||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||
import PopOver from '../common/popover/PopOver';
|
||||
import Searchbar from '../common/searchbar/Searchbar';
|
||||
import DropDownList from '../dropdown/DropDownList';
|
||||
import Loader from '../Loader/Loader';
|
||||
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
|
||||
import { IngestionProps, ModifiedConfig } from './ingestion.interface';
|
||||
@ -61,6 +62,7 @@ const Ingestion: React.FC<IngestionProps> = ({
|
||||
const { isAdminUser } = useAuth();
|
||||
const { isAuthDisabled } = useAuthContext();
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
const [currTriggerId, setCurrTriggerId] = useState({ id: '', state: '' });
|
||||
const [currDeployId, setCurrDeployId] = useState({ id: '', state: '' });
|
||||
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
|
||||
@ -84,9 +86,13 @@ const Ingestion: React.FC<IngestionProps> = ({
|
||||
pipelineType.push(PipelineType.Metadata);
|
||||
ingestion?.supportsUsageExtraction &&
|
||||
pipelineType.push(PipelineType.Usage);
|
||||
ingestion?.supportsProfiler && pipelineType.push(PipelineType.Profiler);
|
||||
|
||||
return pipelineType.reduce((prev, curr) => {
|
||||
if (ingestionList.find((d) => d.pipelineType === curr)) {
|
||||
if (
|
||||
curr !== PipelineType.Profiler &&
|
||||
ingestionList.find((d) => d.pipelineType === curr)
|
||||
) {
|
||||
return prev;
|
||||
} else {
|
||||
return [...prev, curr];
|
||||
@ -94,7 +100,7 @@ const Ingestion: React.FC<IngestionProps> = ({
|
||||
}, [] as PipelineType[]);
|
||||
}
|
||||
|
||||
return [PipelineType.Metadata, PipelineType.Usage];
|
||||
return [PipelineType.Metadata, PipelineType.Usage, PipelineType.Profiler];
|
||||
};
|
||||
|
||||
const handleTriggerIngestion = (id: string, displayName: string) => {
|
||||
@ -160,57 +166,84 @@ const Ingestion: React.FC<IngestionProps> = ({
|
||||
setIsConfirmationModalOpen(true);
|
||||
};
|
||||
|
||||
const handleAddIngestionClick = () => {
|
||||
const types = getIngestionPipelineTypeOption();
|
||||
if (!types.length) {
|
||||
showInfoToast(
|
||||
`${serviceName} already has all the supported ingestion jobs added.`
|
||||
);
|
||||
} else {
|
||||
history.push(getAddIngestionPath(serviceCategory, serviceName, types[0]));
|
||||
const handleAddIngestionClick = (type?: PipelineType) => {
|
||||
setShowActions(false);
|
||||
if (type) {
|
||||
history.push(getAddIngestionPath(serviceCategory, serviceName, type));
|
||||
}
|
||||
};
|
||||
|
||||
const getAddIngestionButton = () => {
|
||||
const types = getIngestionPipelineTypeOption();
|
||||
let buttonText;
|
||||
const getAddIngestionButton = (type: PipelineType) => {
|
||||
return (
|
||||
<Button
|
||||
className={classNames('tw-h-8 tw-rounded tw-mb-2')}
|
||||
data-testid="add-new-ingestion-button"
|
||||
size="small"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={() => handleAddIngestionClick(type)}>
|
||||
Add {startCase(type)} Ingestion
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
switch (types[0]) {
|
||||
case PipelineType.Metadata: {
|
||||
buttonText = 'Add Metadata Ingestion';
|
||||
|
||||
break;
|
||||
}
|
||||
case PipelineType.Usage: {
|
||||
buttonText = 'Add Usage Ingestion';
|
||||
|
||||
break;
|
||||
}
|
||||
case PipelineType.Profiler:
|
||||
default: {
|
||||
buttonText = '';
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return buttonText ? (
|
||||
<NonAdminAction position="bottom" title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
const getAddIngestionDropdown = (types: PipelineType[]) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
className={classNames('tw-h-8 tw-rounded tw-mb-2')}
|
||||
data-testid="add-new-ingestion-button"
|
||||
disabled={
|
||||
getIngestionPipelineTypeOption().length === 0 ||
|
||||
(!isAdminUser && !isAuthDisabled)
|
||||
}
|
||||
size="small"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={handleAddIngestionClick}>
|
||||
{buttonText}
|
||||
onClick={() => setShowActions((pre) => !pre)}>
|
||||
Add Ingestion{' '}
|
||||
{showActions ? (
|
||||
<DropdownIcon
|
||||
style={{
|
||||
transform: 'rotate(180deg)',
|
||||
marginTop: '2px',
|
||||
color: '#fff',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<DropdownIcon
|
||||
style={{
|
||||
marginTop: '2px',
|
||||
color: '#fff',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</NonAdminAction>
|
||||
) : null;
|
||||
{showActions && (
|
||||
<DropDownList
|
||||
horzPosRight
|
||||
dropDownList={types.map((type) => ({
|
||||
name: `Add ${startCase(type)} Ingestion`,
|
||||
value: type,
|
||||
}))}
|
||||
onSelect={(_e, value) =>
|
||||
handleAddIngestionClick(value as PipelineType)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getAddIngestionElement = () => {
|
||||
const types = getIngestionPipelineTypeOption();
|
||||
let element: JSX.Element | null = null;
|
||||
|
||||
if (types.length) {
|
||||
if (types[0] === PipelineType.Metadata || types.length === 1) {
|
||||
element = getAddIngestionButton(types[0]);
|
||||
} else {
|
||||
element = getAddIngestionDropdown(types);
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
const getSearchedIngestions = useCallback(() => {
|
||||
@ -324,7 +357,7 @@ const Ingestion: React.FC<IngestionProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="tw-flex">
|
||||
<div className="tw-flex tw-justify-between">
|
||||
<div className="tw-w-4/12">
|
||||
{searchText || getSearchedIngestions().length > 0 ? (
|
||||
<Searchbar
|
||||
@ -335,8 +368,10 @@ const Ingestion: React.FC<IngestionProps> = ({
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="tw-w-8/12 tw-flex tw-justify-end">
|
||||
{isRequiredDetailsAvailable && getAddIngestionButton()}
|
||||
<div className="tw-relative">
|
||||
{isRequiredDetailsAvailable &&
|
||||
(isAdminUser || isAuthDisabled) &&
|
||||
getAddIngestionElement()}
|
||||
</div>
|
||||
</div>
|
||||
{getSearchedIngestions().length ? (
|
||||
|
@ -17,4 +17,5 @@ export enum FilterPatternEnum {
|
||||
CHART = 'chart',
|
||||
DASHBOARD = 'dashboard',
|
||||
TOPIC = 'topic',
|
||||
FQN = 'fqn',
|
||||
}
|
||||
|
@ -210,6 +210,25 @@ const ServicePage: FunctionComponent = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getAirflowEndpoint = () => {
|
||||
fetchAirflowConfig()
|
||||
.then((res) => {
|
||||
if (res.data?.apiEndpoint) {
|
||||
setAirflowEndpoint(res.data.apiEndpoint);
|
||||
} else {
|
||||
setAirflowEndpoint('');
|
||||
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-airflow-config-error']
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getAllIngestionWorkflows = (paging?: string) => {
|
||||
setIsloading(true);
|
||||
getIngestionPipelines(['owner', 'pipelineStatuses'], serviceFQN, paging)
|
||||
@ -230,25 +249,11 @@ const ServicePage: FunctionComponent = () => {
|
||||
jsonData['api-error-messages']['fetch-ingestion-error']
|
||||
);
|
||||
})
|
||||
.finally(() => setIsloading(false));
|
||||
};
|
||||
|
||||
const getAirflowEndpoint = () => {
|
||||
fetchAirflowConfig()
|
||||
.then((res) => {
|
||||
if (res.data?.apiEndpoint) {
|
||||
setAirflowEndpoint(res.data.apiEndpoint);
|
||||
} else {
|
||||
setAirflowEndpoint('');
|
||||
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
.finally(() => {
|
||||
setIsloading(false);
|
||||
if (!airflowEndpoint) {
|
||||
getAirflowEndpoint();
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-airflow-config-error']
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -678,7 +683,6 @@ const ServicePage: FunctionComponent = () => {
|
||||
// getDatabaseServices();
|
||||
getAllIngestionWorkflows();
|
||||
}
|
||||
getAirflowEndpoint();
|
||||
}, []);
|
||||
|
||||
const onCancel = () => {
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { AxiosResponse } from 'axios';
|
||||
import cryptoRandomString from 'crypto-random-string-with-promisify-polyfill';
|
||||
import {
|
||||
Bucket,
|
||||
DynamicFormFieldType,
|
||||
@ -543,3 +544,17 @@ export const getServiceIngestionStepGuide = (
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getIngestionName = (
|
||||
serviceName: string,
|
||||
type: IngestionPipelineType
|
||||
) => {
|
||||
if (type === IngestionPipelineType.Profiler) {
|
||||
return `${serviceName}_${type}_${cryptoRandomString({
|
||||
length: 8,
|
||||
type: 'alphanumeric',
|
||||
})}`;
|
||||
} else {
|
||||
return `${serviceName}_${type}`;
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user