Fix #4362: Deploy Profiler Workflow UI (#4427)

This commit is contained in:
darth-coder00 2022-04-24 05:49:35 +05:30 committed by GitHub
parent a9570a21c6
commit a4ac73e867
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 363 additions and 146 deletions

View File

@ -12,6 +12,7 @@
*/ */
import { isEmpty, isUndefined } from 'lodash'; import { isEmpty, isUndefined } from 'lodash';
import { LoadingState } from 'Models';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { import {
INGESTION_SCHEDULER_INITIAL_VALUE, INGESTION_SCHEDULER_INITIAL_VALUE,
@ -31,6 +32,7 @@ import {
IngestionPipeline, IngestionPipeline,
} from '../../generated/entity/services/ingestionPipelines/ingestionPipeline'; } from '../../generated/entity/services/ingestionPipelines/ingestionPipeline';
import { getCurrentDate, getCurrentUserId } from '../../utils/CommonUtils'; import { getCurrentDate, getCurrentUserId } from '../../utils/CommonUtils';
import { getIngestionName } from '../../utils/ServiceUtils';
import SuccessScreen from '../common/success-screen/SuccessScreen'; import SuccessScreen from '../common/success-screen/SuccessScreen';
import IngestionStepper from '../IngestionStepper/IngestionStepper.component'; import IngestionStepper from '../IngestionStepper/IngestionStepper.component';
import { AddIngestionProps } from './addIngestion.interface'; import { AddIngestionProps } from './addIngestion.interface';
@ -53,9 +55,11 @@ const AddIngestion = ({
handleCancelClick, handleCancelClick,
handleViewServiceClick, handleViewServiceClick,
}: AddIngestionProps) => { }: AddIngestionProps) => {
const [ingestionName] = useState( const [saveState, setSaveState] = useState<LoadingState>('initial');
data?.name ?? `${serviceData.name}_${pipelineType}` const [ingestionName, setIngestionName] = useState(
data?.name ?? getIngestionName(serviceData.name, pipelineType)
); );
const [description, setDescription] = useState(data?.description ?? '');
const [repeatFrequency, setRepeatFrequency] = useState( const [repeatFrequency, setRepeatFrequency] = useState(
data?.airflowConfig.scheduleInterval ?? INGESTION_SCHEDULER_INITIAL_VALUE data?.airflowConfig.scheduleInterval ?? INGESTION_SCHEDULER_INITIAL_VALUE
); );
@ -89,6 +93,11 @@ const AddIngestion = ({
(data?.source.sourceConfig.config as ConfigClass)?.chartFilterPattern (data?.source.sourceConfig.config as ConfigClass)?.chartFilterPattern
) )
); );
const [showFqnFilter, setShowFqnFilter] = useState(
!isUndefined(
(data?.source.sourceConfig.config as ConfigClass)?.fqnFilterPattern
)
);
const [includeView, setIncludeView] = useState( const [includeView, setIncludeView] = useState(
Boolean((data?.source.sourceConfig.config as ConfigClass)?.includeViews) Boolean((data?.source.sourceConfig.config as ConfigClass)?.includeViews)
); );
@ -121,6 +130,10 @@ const AddIngestion = ({
(data?.source.sourceConfig.config as ConfigClass)?.chartFilterPattern ?? (data?.source.sourceConfig.config as ConfigClass)?.chartFilterPattern ??
INITIAL_FILTER_PATTERN INITIAL_FILTER_PATTERN
); );
const [fqnFilterPattern, setFqnFilterPattern] = useState<FilterPattern>(
(data?.source.sourceConfig.config as ConfigClass)?.fqnFilterPattern ??
INITIAL_FILTER_PATTERN
);
const [queryLogDuration, setQueryLogDuration] = useState<number>( const [queryLogDuration, setQueryLogDuration] = useState<number>(
(data?.source.sourceConfig.config as ConfigClass)?.queryLogDuration ?? 1 (data?.source.sourceConfig.config as ConfigClass)?.queryLogDuration ?? 1
@ -138,6 +151,12 @@ const AddIngestion = ({
ConfigType.DatabaseUsage ConfigType.DatabaseUsage
); );
}, [data]); }, [data]);
const profilerIngestionType = useMemo(() => {
return (
(data?.source.sourceConfig.config as ConfigClass)?.type ??
ConfigType.Profiler
);
}, [data]);
const getIncludeValue = (value: Array<string>, type: FilterPatternEnum) => { const getIncludeValue = (value: Array<string>, type: FilterPatternEnum) => {
switch (type) { switch (type) {
@ -163,6 +182,10 @@ const AddIngestion = ({
case FilterPatternEnum.CHART: case FilterPatternEnum.CHART:
setChartFilterPattern({ ...topicFilterPattern, includes: value }); setChartFilterPattern({ ...topicFilterPattern, includes: value });
break;
case FilterPatternEnum.FQN:
setFqnFilterPattern({ ...fqnFilterPattern, includes: value });
break; break;
} }
}; };
@ -190,6 +213,10 @@ const AddIngestion = ({
case FilterPatternEnum.CHART: case FilterPatternEnum.CHART:
setChartFilterPattern({ ...topicFilterPattern, excludes: value }); setChartFilterPattern({ ...topicFilterPattern, excludes: value });
break;
case FilterPatternEnum.FQN:
setFqnFilterPattern({ ...fqnFilterPattern, excludes: value });
break; break;
} }
}; };
@ -215,6 +242,10 @@ const AddIngestion = ({
case FilterPatternEnum.CHART: case FilterPatternEnum.CHART:
setShowChartFilter(value); setShowChartFilter(value);
break;
case FilterPatternEnum.FQN:
setShowFqnFilter(value);
break; break;
} }
}; };
@ -246,6 +277,38 @@ const AddIngestion = ({
return filterPattern; 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 createNewIngestion = () => {
const ingestionDetails: CreateIngestionPipeline = { const ingestionDetails: CreateIngestionPipeline = {
airflowConfig: { airflowConfig: {
@ -266,37 +329,23 @@ const AddIngestion = ({
type: serviceCategory.slice(0, -1), type: serviceCategory.slice(0, -1),
}, },
sourceConfig: { sourceConfig: {
config: config: getConfigData(pipelineType),
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),
},
}, },
}; };
onAddIngestionSave && if (onAddIngestionSave) {
onAddIngestionSave(ingestionDetails).then(() => { setSaveState('waiting');
if (showSuccessScreen) { onAddIngestionSave(ingestionDetails)
setActiveIngestionStep(3); .then(() => {
} else { setSaveState('success');
onSuccessSave?.(); if (showSuccessScreen) {
} setActiveIngestionStep(3);
}); } else {
onSuccessSave?.();
}
})
.finally(() => setTimeout(() => setSaveState('initial'), 500));
}
}; };
const updateIngestion = () => { const updateIngestion = () => {
@ -314,40 +363,25 @@ const AddIngestion = ({
sourceConfig: { sourceConfig: {
config: { config: {
...(data.source.sourceConfig.config as ConfigClass), ...(data.source.sourceConfig.config as ConfigClass),
...(pipelineType === PipelineType.Usage ...getConfigData(pipelineType),
? {
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),
}),
}, },
}, },
}, },
}; };
onUpdateIngestion && if (onUpdateIngestion) {
onUpdateIngestion(updatedData, data, data.id as string, data.name).then( setSaveState('waiting');
() => { onUpdateIngestion(updatedData, data, data.id as string, data.name)
onSuccessSave?.(); .then(() => {
} setSaveState('success');
); if (showSuccessScreen) {
setActiveIngestionStep(3);
} else {
onSuccessSave?.();
}
})
.finally(() => setTimeout(() => setSaveState('initial'), 500));
}
} }
}; };
@ -375,14 +409,18 @@ const AddIngestion = ({
<ConfigureIngestion <ConfigureIngestion
chartFilterPattern={chartFilterPattern} chartFilterPattern={chartFilterPattern}
dashboardFilterPattern={dashboardFilterPattern} dashboardFilterPattern={dashboardFilterPattern}
description={description}
enableDataProfiler={enableDataProfiler} enableDataProfiler={enableDataProfiler}
fqnFilterPattern={fqnFilterPattern}
getExcludeValue={getExcludeValue} getExcludeValue={getExcludeValue}
getIncludeValue={getIncludeValue} getIncludeValue={getIncludeValue}
handleDescription={(val) => setDescription(val)}
handleEnableDataProfiler={() => handleEnableDataProfiler={() =>
setEnableDataProfiler((pre) => !pre) setEnableDataProfiler((pre) => !pre)
} }
handleIncludeView={() => setIncludeView((pre) => !pre)} handleIncludeView={() => setIncludeView((pre) => !pre)}
handleIngestSampleData={() => setIngestSampleData((pre) => !pre)} handleIngestSampleData={() => setIngestSampleData((pre) => !pre)}
handleIngestionName={(val) => setIngestionName(val)}
handleQueryLogDuration={(val) => setQueryLogDuration(val)} handleQueryLogDuration={(val) => setQueryLogDuration(val)}
handleResultLimit={(val) => setResultLimit(val)} handleResultLimit={(val) => setResultLimit(val)}
handleShowFilter={handleShowFilter} handleShowFilter={handleShowFilter}
@ -397,6 +435,7 @@ const AddIngestion = ({
serviceCategory={serviceCategory} serviceCategory={serviceCategory}
showChartFilter={showChartFilter} showChartFilter={showChartFilter}
showDashboardFilter={showDashboardFilter} showDashboardFilter={showDashboardFilter}
showFqnFilter={showFqnFilter}
showSchemaFilter={showSchemaFilter} showSchemaFilter={showSchemaFilter}
showTableFilter={showTableFilter} showTableFilter={showTableFilter}
showTopicFilter={showTopicFilter} showTopicFilter={showTopicFilter}
@ -418,8 +457,9 @@ const AddIngestion = ({
handleStartDateChange={(value: string) => setStartDate(value)} handleStartDateChange={(value: string) => setStartDate(value)}
repeatFrequency={repeatFrequency} repeatFrequency={repeatFrequency}
startDate={startDate as string} startDate={startDate as string}
status={saveState}
onBack={handleScheduleIntervalBackClick} onBack={handleScheduleIntervalBackClick}
onDeloy={handleScheduleIntervalDeployClick} onDeploy={handleScheduleIntervalDeployClick}
/> />
)} )}

View File

@ -50,6 +50,10 @@ const mockConfigureIngestion: ConfigureIngestionProps = {
includes: [], includes: [],
excludes: [], excludes: [],
}, },
fqnFilterPattern: {
includes: [],
excludes: [],
},
includeView: false, includeView: false,
pipelineType: PipelineType.Metadata, pipelineType: PipelineType.Metadata,
queryLogDuration: 1, queryLogDuration: 1,
@ -62,7 +66,9 @@ const mockConfigureIngestion: ConfigureIngestionProps = {
showTableFilter: false, showTableFilter: false,
showTopicFilter: false, showTopicFilter: false,
showChartFilter: false, showChartFilter: false,
showFqnFilter: false,
handleIncludeView: jest.fn(), handleIncludeView: jest.fn(),
handleIngestionName: jest.fn(),
handleEnableDataProfiler: jest.fn(), handleEnableDataProfiler: jest.fn(),
handleIngestSampleData: jest.fn(), handleIngestSampleData: jest.fn(),
handleQueryLogDuration: jest.fn(), handleQueryLogDuration: jest.fn(),

View File

@ -11,23 +11,28 @@
* limitations under the License. * 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 { FilterPatternEnum } from '../../../enums/filterPattern.enum';
import { ServiceCategory } from '../../../enums/service.enum'; import { ServiceCategory } from '../../../enums/service.enum';
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 { 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 ToggleSwitchV1 from '../../common/toggle-switch/ToggleSwitchV1'; import ToggleSwitchV1 from '../../common/toggle-switch/ToggleSwitchV1';
import { Field } from '../../Field/Field'; import { Field } from '../../Field/Field';
import { ConfigureIngestionProps } from '../addIngestion.interface'; import { ConfigureIngestionProps } from '../addIngestion.interface';
const ConfigureIngestion = ({ const ConfigureIngestion = ({
ingestionName,
description = '',
dashboardFilterPattern, dashboardFilterPattern,
schemaFilterPattern, schemaFilterPattern,
tableFilterPattern, tableFilterPattern,
topicFilterPattern, topicFilterPattern,
chartFilterPattern, chartFilterPattern,
fqnFilterPattern,
includeView, includeView,
serviceCategory, serviceCategory,
enableDataProfiler, enableDataProfiler,
@ -38,11 +43,14 @@ const ConfigureIngestion = ({
showTableFilter, showTableFilter,
showTopicFilter, showTopicFilter,
showChartFilter, showChartFilter,
showFqnFilter,
queryLogDuration, queryLogDuration,
stageFileLocation, stageFileLocation,
resultLimit, resultLimit,
getExcludeValue, getExcludeValue,
getIncludeValue, getIncludeValue,
handleIngestionName,
handleDescription,
handleShowFilter, handleShowFilter,
handleEnableDataProfiler, handleEnableDataProfiler,
handleIncludeView, handleIncludeView,
@ -53,7 +61,9 @@ const ConfigureIngestion = ({
onCancel, onCancel,
onNext, onNext,
}: ConfigureIngestionProps) => { }: ConfigureIngestionProps) => {
const getFilterPatternField = () => { const markdownRef = useRef<EditorContentRef>();
const getMetadataFilterPatternField = () => {
switch (serviceCategory) { switch (serviceCategory) {
case ServiceCategory.DATABASE_SERVICES: case ServiceCategory.DATABASE_SERVICES:
return ( 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 = () => { const getMetadataFields = () => {
return ( return (
<> <>
<div>{getFilterPatternField()}</div> <div>{getMetadataFilterPatternField()}</div>
{getSeparator('')} {getSeparator('')}
<div> <div>
<Field> <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 = () => { const getIngestionPipelineFields = () => {
if (pipelineType === PipelineType.Usage) { switch (pipelineType) {
return getUsageFields(); case PipelineType.Usage: {
} else { return getUsageFields();
return getMetadataFields(); }
case PipelineType.Profiler: {
return getProfilerFields();
}
case PipelineType.Metadata:
default: {
return getMetadataFields();
}
} }
}; };
const handleNext = () => {
handleDescription &&
handleDescription(markdownRef.current?.getEditorContent() || '');
onNext();
};
return ( return (
<div className="tw-px-2" data-testid="configure-ingestion-container"> <div className="tw-px-2" data-testid="configure-ingestion-container">
{getIngestionPipelineFields()} {getIngestionPipelineFields()}
@ -280,7 +365,7 @@ const ConfigureIngestion = ({
size="regular" size="regular"
theme="primary" theme="primary"
variant="contained" variant="contained"
onClick={onNext}> onClick={handleNext}>
<span>Next</span> <span>Next</span>
</Button> </Button>
</Field> </Field>

View File

@ -27,6 +27,7 @@ jest.mock('../../common/toggle-switch/ToggleSwitchV1', () => {
}); });
const mockScheduleIntervalProps: ScheduleIntervalProps = { const mockScheduleIntervalProps: ScheduleIntervalProps = {
status: 'initial',
repeatFrequency: '', repeatFrequency: '',
handleRepeatFrequencyChange: jest.fn(), handleRepeatFrequencyChange: jest.fn(),
startDate: '', startDate: '',
@ -34,7 +35,7 @@ const mockScheduleIntervalProps: ScheduleIntervalProps = {
endDate: '', endDate: '',
handleEndDateChange: jest.fn(), handleEndDateChange: jest.fn(),
onBack: jest.fn(), onBack: jest.fn(),
onDeloy: jest.fn(), onDeploy: jest.fn(),
}; };
describe('Test ScheduleInterval component', () => { describe('Test ScheduleInterval component', () => {

View File

@ -11,13 +11,16 @@
* limitations under the License. * limitations under the License.
*/ */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React from 'react'; import React from 'react';
import { Button } from '../../buttons/Button/Button'; import { Button } from '../../buttons/Button/Button';
import CronEditor from '../../common/CronEditor/CronEditor'; import CronEditor from '../../common/CronEditor/CronEditor';
import { Field } from '../../Field/Field'; import { Field } from '../../Field/Field';
import Loader from '../../Loader/Loader';
import { ScheduleIntervalProps } from '../addIngestion.interface'; import { ScheduleIntervalProps } from '../addIngestion.interface';
const ScheduleInterval = ({ const ScheduleInterval = ({
status,
repeatFrequency, repeatFrequency,
handleRepeatFrequencyChange, handleRepeatFrequencyChange,
startDate, startDate,
@ -25,7 +28,7 @@ const ScheduleInterval = ({
endDate, endDate,
handleEndDateChange, handleEndDateChange,
onBack, onBack,
onDeloy, onDeploy,
}: ScheduleIntervalProps) => { }: ScheduleIntervalProps) => {
return ( return (
<div data-testid="schedule-intervel-container"> <div data-testid="schedule-intervel-container">
@ -75,14 +78,34 @@ const ScheduleInterval = ({
<span>Back</span> <span>Back</span>
</Button> </Button>
<Button {status === 'waiting' ? (
data-testid="deploy-button" <Button
size="regular" disabled
theme="primary" className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
variant="contained" size="regular"
onClick={onDeloy}> theme="primary"
<span>Deploy</span> variant="contained">
</Button> <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> </Field>
</div> </div>
); );

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { LoadingState } from 'Models';
import { FilterPatternEnum } from '../../enums/filterPattern.enum'; import { FilterPatternEnum } from '../../enums/filterPattern.enum';
import { FormSubmitType } from '../../enums/form.enum'; import { FormSubmitType } from '../../enums/form.enum';
import { ServiceCategory } from '../../enums/service.enum'; import { ServiceCategory } from '../../enums/service.enum';
@ -47,12 +48,14 @@ export interface AddIngestionProps {
export interface ConfigureIngestionProps { export interface ConfigureIngestionProps {
ingestionName: string; ingestionName: string;
description?: string;
serviceCategory: ServiceCategory; serviceCategory: ServiceCategory;
dashboardFilterPattern: FilterPattern; dashboardFilterPattern: FilterPattern;
schemaFilterPattern: FilterPattern; schemaFilterPattern: FilterPattern;
tableFilterPattern: FilterPattern; tableFilterPattern: FilterPattern;
topicFilterPattern: FilterPattern; topicFilterPattern: FilterPattern;
chartFilterPattern: FilterPattern; chartFilterPattern: FilterPattern;
fqnFilterPattern: FilterPattern;
includeView: boolean; includeView: boolean;
enableDataProfiler: boolean; enableDataProfiler: boolean;
ingestSampleData: boolean; ingestSampleData: boolean;
@ -62,9 +65,12 @@ export interface ConfigureIngestionProps {
showTableFilter: boolean; showTableFilter: boolean;
showTopicFilter: boolean; showTopicFilter: boolean;
showChartFilter: boolean; showChartFilter: boolean;
showFqnFilter: boolean;
queryLogDuration: number; queryLogDuration: number;
stageFileLocation: string; stageFileLocation: string;
resultLimit: number; resultLimit: number;
handleIngestionName: (value: string) => void;
handleDescription?: (value: string) => void;
handleIncludeView: () => void; handleIncludeView: () => void;
handleEnableDataProfiler: () => void; handleEnableDataProfiler: () => void;
handleIngestSampleData: () => void; handleIngestSampleData: () => void;
@ -79,6 +85,7 @@ export interface ConfigureIngestionProps {
} }
export type ScheduleIntervalProps = { export type ScheduleIntervalProps = {
status: LoadingState;
repeatFrequency: string; repeatFrequency: string;
handleRepeatFrequencyChange: (value: string) => void; handleRepeatFrequencyChange: (value: string) => void;
startDate: string; startDate: string;
@ -86,5 +93,5 @@ export type ScheduleIntervalProps = {
endDate: string; endDate: string;
handleEndDateChange: (value: string) => void; handleEndDateChange: (value: string) => void;
onBack: () => void; onBack: () => void;
onDeloy: () => void; onDeploy: () => void;
}; };

View File

@ -15,8 +15,8 @@ import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames'; import classNames from 'classnames';
import cronstrue from 'cronstrue'; import cronstrue from 'cronstrue';
import { capitalize, isNil, lowerCase } from 'lodash'; import { capitalize, isNil, lowerCase, startCase } from 'lodash';
import React, { useCallback, useState } from 'react'; import React, { Fragment, useCallback, useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'; import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { import {
@ -33,13 +33,14 @@ import {
getAddIngestionPath, getAddIngestionPath,
getEditIngestionPath, getEditIngestionPath,
} from '../../utils/RouterUtils'; } from '../../utils/RouterUtils';
import { dropdownIcon as DropdownIcon } from '../../utils/svgconstant';
import SVGIcons, { Icons } from '../../utils/SvgUtils'; import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showInfoToast } from '../../utils/ToastUtils';
import { Button } from '../buttons/Button/Button'; import { Button } from '../buttons/Button/Button';
import NextPrevious from '../common/next-previous/NextPrevious'; import NextPrevious from '../common/next-previous/NextPrevious';
import NonAdminAction from '../common/non-admin-action/NonAdminAction'; import NonAdminAction from '../common/non-admin-action/NonAdminAction';
import PopOver from '../common/popover/PopOver'; import PopOver from '../common/popover/PopOver';
import Searchbar from '../common/searchbar/Searchbar'; import Searchbar from '../common/searchbar/Searchbar';
import DropDownList from '../dropdown/DropDownList';
import Loader from '../Loader/Loader'; import Loader from '../Loader/Loader';
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal'; import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
import { IngestionProps, ModifiedConfig } from './ingestion.interface'; import { IngestionProps, ModifiedConfig } from './ingestion.interface';
@ -61,6 +62,7 @@ const Ingestion: React.FC<IngestionProps> = ({
const { isAdminUser } = useAuth(); const { isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext(); const { isAuthDisabled } = useAuthContext();
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
const [showActions, setShowActions] = useState(false);
const [currTriggerId, setCurrTriggerId] = useState({ id: '', state: '' }); const [currTriggerId, setCurrTriggerId] = useState({ id: '', state: '' });
const [currDeployId, setCurrDeployId] = useState({ id: '', state: '' }); const [currDeployId, setCurrDeployId] = useState({ id: '', state: '' });
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false); const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
@ -84,9 +86,13 @@ const Ingestion: React.FC<IngestionProps> = ({
pipelineType.push(PipelineType.Metadata); pipelineType.push(PipelineType.Metadata);
ingestion?.supportsUsageExtraction && ingestion?.supportsUsageExtraction &&
pipelineType.push(PipelineType.Usage); pipelineType.push(PipelineType.Usage);
ingestion?.supportsProfiler && pipelineType.push(PipelineType.Profiler);
return pipelineType.reduce((prev, curr) => { return pipelineType.reduce((prev, curr) => {
if (ingestionList.find((d) => d.pipelineType === curr)) { if (
curr !== PipelineType.Profiler &&
ingestionList.find((d) => d.pipelineType === curr)
) {
return prev; return prev;
} else { } else {
return [...prev, curr]; return [...prev, curr];
@ -94,7 +100,7 @@ const Ingestion: React.FC<IngestionProps> = ({
}, [] as PipelineType[]); }, [] as PipelineType[]);
} }
return [PipelineType.Metadata, PipelineType.Usage]; return [PipelineType.Metadata, PipelineType.Usage, PipelineType.Profiler];
}; };
const handleTriggerIngestion = (id: string, displayName: string) => { const handleTriggerIngestion = (id: string, displayName: string) => {
@ -160,57 +166,84 @@ const Ingestion: React.FC<IngestionProps> = ({
setIsConfirmationModalOpen(true); setIsConfirmationModalOpen(true);
}; };
const handleAddIngestionClick = () => { const handleAddIngestionClick = (type?: PipelineType) => {
const types = getIngestionPipelineTypeOption(); setShowActions(false);
if (!types.length) { if (type) {
showInfoToast( history.push(getAddIngestionPath(serviceCategory, serviceName, type));
`${serviceName} already has all the supported ingestion jobs added.`
);
} else {
history.push(getAddIngestionPath(serviceCategory, serviceName, types[0]));
} }
}; };
const getAddIngestionButton = () => { const getAddIngestionButton = (type: PipelineType) => {
const types = getIngestionPipelineTypeOption(); return (
let buttonText; <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]) { const getAddIngestionDropdown = (types: PipelineType[]) => {
case PipelineType.Metadata: { return (
buttonText = 'Add Metadata Ingestion'; <Fragment>
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}>
<Button <Button
className={classNames('tw-h-8 tw-rounded tw-mb-2')} className={classNames('tw-h-8 tw-rounded tw-mb-2')}
data-testid="add-new-ingestion-button" data-testid="add-new-ingestion-button"
disabled={
getIngestionPipelineTypeOption().length === 0 ||
(!isAdminUser && !isAuthDisabled)
}
size="small" size="small"
theme="primary" theme="primary"
variant="contained" variant="contained"
onClick={handleAddIngestionClick}> onClick={() => setShowActions((pre) => !pre)}>
{buttonText} Add Ingestion{' '}
{showActions ? (
<DropdownIcon
style={{
transform: 'rotate(180deg)',
marginTop: '2px',
color: '#fff',
}}
/>
) : (
<DropdownIcon
style={{
marginTop: '2px',
color: '#fff',
}}
/>
)}
</Button> </Button>
</NonAdminAction> {showActions && (
) : null; <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(() => { const getSearchedIngestions = useCallback(() => {
@ -324,7 +357,7 @@ const Ingestion: React.FC<IngestionProps> = ({
</div> </div>
)} )}
</div> </div>
<div className="tw-flex"> <div className="tw-flex tw-justify-between">
<div className="tw-w-4/12"> <div className="tw-w-4/12">
{searchText || getSearchedIngestions().length > 0 ? ( {searchText || getSearchedIngestions().length > 0 ? (
<Searchbar <Searchbar
@ -335,8 +368,10 @@ const Ingestion: React.FC<IngestionProps> = ({
/> />
) : null} ) : null}
</div> </div>
<div className="tw-w-8/12 tw-flex tw-justify-end"> <div className="tw-relative">
{isRequiredDetailsAvailable && getAddIngestionButton()} {isRequiredDetailsAvailable &&
(isAdminUser || isAuthDisabled) &&
getAddIngestionElement()}
</div> </div>
</div> </div>
{getSearchedIngestions().length ? ( {getSearchedIngestions().length ? (

View File

@ -17,4 +17,5 @@ export enum FilterPatternEnum {
CHART = 'chart', CHART = 'chart',
DASHBOARD = 'dashboard', DASHBOARD = 'dashboard',
TOPIC = 'topic', TOPIC = 'topic',
FQN = 'fqn',
} }

View File

@ -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) => { const getAllIngestionWorkflows = (paging?: string) => {
setIsloading(true); setIsloading(true);
getIngestionPipelines(['owner', 'pipelineStatuses'], serviceFQN, paging) getIngestionPipelines(['owner', 'pipelineStatuses'], serviceFQN, paging)
@ -230,25 +249,11 @@ const ServicePage: FunctionComponent = () => {
jsonData['api-error-messages']['fetch-ingestion-error'] jsonData['api-error-messages']['fetch-ingestion-error']
); );
}) })
.finally(() => setIsloading(false)); .finally(() => {
}; setIsloading(false);
if (!airflowEndpoint) {
const getAirflowEndpoint = () => { 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']
);
}); });
}; };
@ -678,7 +683,6 @@ const ServicePage: FunctionComponent = () => {
// getDatabaseServices(); // getDatabaseServices();
getAllIngestionWorkflows(); getAllIngestionWorkflows();
} }
getAirflowEndpoint();
}, []); }, []);
const onCancel = () => { const onCancel = () => {

View File

@ -12,6 +12,7 @@
*/ */
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import cryptoRandomString from 'crypto-random-string-with-promisify-polyfill';
import { import {
Bucket, Bucket,
DynamicFormFieldType, 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}`;
}
};