2025-07-11 02:39:34 +05:30
|
|
|
import { LoadingOutlined } from '@ant-design/icons';
|
2025-06-04 21:05:24 +05:30
|
|
|
import { Modal } from '@components';
|
2025-07-11 02:39:34 +05:30
|
|
|
import { Spin, Steps } from 'antd';
|
2025-05-20 22:32:24 +05:30
|
|
|
import { isEqual } from 'lodash';
|
2025-09-01 17:41:48 +05:30
|
|
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
2025-05-20 22:32:24 +05:30
|
|
|
import styled from 'styled-components';
|
|
|
|
|
|
2025-09-01 17:41:48 +05:30
|
|
|
import analytics, { EventType } from '@app/analytics';
|
2025-05-20 22:32:24 +05:30
|
|
|
import { CreateScheduleStep } from '@app/ingestV2/source/builder/CreateScheduleStep';
|
|
|
|
|
import { DefineRecipeStep } from '@app/ingestV2/source/builder/DefineRecipeStep';
|
|
|
|
|
import { NameSourceStep } from '@app/ingestV2/source/builder/NameSourceStep';
|
|
|
|
|
import { SelectTemplateStep } from '@app/ingestV2/source/builder/SelectTemplateStep';
|
|
|
|
|
import sourcesJson from '@app/ingestV2/source/builder/sources.json';
|
|
|
|
|
import { SourceBuilderState, StepProps } from '@app/ingestV2/source/builder/types';
|
|
|
|
|
|
2025-06-04 21:05:24 +05:30
|
|
|
import { IngestionSource } from '@types';
|
2025-05-20 22:32:24 +05:30
|
|
|
|
|
|
|
|
const StepsContainer = styled.div`
|
|
|
|
|
margin-right: 20px;
|
|
|
|
|
margin-left: 20px;
|
|
|
|
|
margin-bottom: 40px;
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mapping from the step type to the title for the step
|
|
|
|
|
*/
|
|
|
|
|
export enum IngestionSourceBuilderStepTitles {
|
|
|
|
|
SELECT_TEMPLATE = 'Choose Data Source',
|
|
|
|
|
DEFINE_RECIPE = 'Configure Connection',
|
|
|
|
|
CREATE_SCHEDULE = 'Sync Schedule',
|
|
|
|
|
NAME_SOURCE = 'Finish up',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mapping from the step type to the component implementing that step.
|
|
|
|
|
*/
|
|
|
|
|
export const IngestionSourceBuilderStepComponent = {
|
|
|
|
|
SELECT_TEMPLATE: SelectTemplateStep,
|
|
|
|
|
DEFINE_RECIPE: DefineRecipeStep,
|
|
|
|
|
CREATE_SCHEDULE: CreateScheduleStep,
|
|
|
|
|
NAME_SOURCE: NameSourceStep,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Steps of the Ingestion Source Builder flow.
|
|
|
|
|
*/
|
|
|
|
|
export enum IngestionSourceBuilderStep {
|
|
|
|
|
SELECT_TEMPLATE = 'SELECT_TEMPLATE',
|
|
|
|
|
DEFINE_RECIPE = 'DEFINE_RECIPE',
|
|
|
|
|
CREATE_SCHEDULE = 'CREATE_SCHEDULE',
|
|
|
|
|
NAME_SOURCE = 'NAME_SOURCE',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
|
initialState?: SourceBuilderState;
|
|
|
|
|
open: boolean;
|
|
|
|
|
onSubmit?: (input: SourceBuilderState, resetState: () => void, shouldRun?: boolean) => void;
|
2025-06-04 21:05:24 +05:30
|
|
|
onCancel: () => void;
|
|
|
|
|
sourceRefetch?: () => Promise<any>;
|
|
|
|
|
selectedSource?: IngestionSource;
|
2025-07-11 02:39:34 +05:30
|
|
|
loading?: boolean;
|
2025-09-01 17:41:48 +05:30
|
|
|
selectedSourceType?: string;
|
|
|
|
|
setSelectedSourceType?: (sourceType: string) => void;
|
2025-05-20 22:32:24 +05:30
|
|
|
};
|
|
|
|
|
|
2025-06-04 21:05:24 +05:30
|
|
|
export const IngestionSourceBuilderModal = ({
|
|
|
|
|
initialState,
|
|
|
|
|
open,
|
|
|
|
|
onSubmit,
|
|
|
|
|
onCancel,
|
|
|
|
|
sourceRefetch,
|
|
|
|
|
selectedSource,
|
2025-07-11 02:39:34 +05:30
|
|
|
loading,
|
2025-09-01 17:41:48 +05:30
|
|
|
selectedSourceType,
|
|
|
|
|
setSelectedSourceType,
|
2025-06-04 21:05:24 +05:30
|
|
|
}: Props) => {
|
2025-05-20 22:32:24 +05:30
|
|
|
const isEditing = initialState !== undefined;
|
|
|
|
|
const titleText = isEditing ? 'Edit Data Source' : 'Connect Data Source';
|
|
|
|
|
const initialStep = isEditing
|
|
|
|
|
? IngestionSourceBuilderStep.DEFINE_RECIPE
|
|
|
|
|
: IngestionSourceBuilderStep.SELECT_TEMPLATE;
|
|
|
|
|
|
|
|
|
|
const [stepStack, setStepStack] = useState([initialStep]);
|
|
|
|
|
const [ingestionBuilderState, setIngestionBuilderState] = useState<SourceBuilderState>({
|
|
|
|
|
schedule: {
|
|
|
|
|
interval: '0 0 * * *',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const ingestionSources = JSON.parse(JSON.stringify(sourcesJson)); // TODO: replace with call to server once we have access to dynamic list of sources
|
|
|
|
|
|
2025-09-01 17:41:48 +05:30
|
|
|
const sendAnalyticsStepViewedEvent = useCallback(
|
|
|
|
|
(step: IngestionSourceBuilderStep) => {
|
|
|
|
|
if (open) {
|
|
|
|
|
analytics.event({
|
|
|
|
|
type: EventType.IngestionSourceConfigurationImpressionEvent,
|
|
|
|
|
viewedSection: step,
|
|
|
|
|
sourceType: selectedSource?.type || selectedSourceType,
|
|
|
|
|
sourceUrn: selectedSource?.urn,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[selectedSource?.type, selectedSource?.urn, selectedSourceType, open],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Reset the modal state when initialState changes or modal opens
|
2025-05-20 22:32:24 +05:30
|
|
|
const prevInitialState = useRef(initialState);
|
2025-09-01 17:41:48 +05:30
|
|
|
const prevOpen = useRef(open);
|
2025-05-20 22:32:24 +05:30
|
|
|
useEffect(() => {
|
2025-09-01 17:41:48 +05:30
|
|
|
const stateChanged = !isEqual(prevInitialState.current, initialState);
|
|
|
|
|
const modalOpened = !prevOpen.current && open;
|
|
|
|
|
|
|
|
|
|
if (stateChanged) {
|
2025-05-20 22:32:24 +05:30
|
|
|
setIngestionBuilderState(initialState || {});
|
2025-09-01 17:41:48 +05:30
|
|
|
setStepStack([initialStep]);
|
|
|
|
|
setSelectedSourceType?.('');
|
|
|
|
|
prevInitialState.current = initialState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fire event when modal opens
|
|
|
|
|
if (modalOpened) {
|
|
|
|
|
setStepStack([initialStep]); // Ensure correct step when modal opens
|
|
|
|
|
sendAnalyticsStepViewedEvent(initialStep);
|
2025-05-20 22:32:24 +05:30
|
|
|
}
|
|
|
|
|
|
2025-09-01 17:41:48 +05:30
|
|
|
prevOpen.current = open;
|
|
|
|
|
}, [initialState, initialStep, open, sendAnalyticsStepViewedEvent, setSelectedSourceType]);
|
2025-05-20 22:32:24 +05:30
|
|
|
|
|
|
|
|
const goTo = (step: IngestionSourceBuilderStep) => {
|
|
|
|
|
setStepStack([...stepStack, step]);
|
2025-09-01 17:41:48 +05:30
|
|
|
sendAnalyticsStepViewedEvent(step);
|
2025-05-20 22:32:24 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const prev = () => {
|
|
|
|
|
setStepStack(stepStack.slice(0, -1));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cancel = () => {
|
|
|
|
|
onCancel?.();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const submit = (shouldRun?: boolean) => {
|
|
|
|
|
onSubmit?.(
|
|
|
|
|
ingestionBuilderState,
|
|
|
|
|
() => {
|
|
|
|
|
setStepStack([initialStep]);
|
|
|
|
|
setIngestionBuilderState({});
|
|
|
|
|
},
|
|
|
|
|
shouldRun,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const currentStep = stepStack[stepStack.length - 1];
|
|
|
|
|
const currentStepIndex = Object.values(IngestionSourceBuilderStep)
|
|
|
|
|
.map((value, index) => ({
|
|
|
|
|
value,
|
|
|
|
|
index,
|
|
|
|
|
}))
|
|
|
|
|
.filter((obj) => obj.value === currentStep)[0].index;
|
|
|
|
|
const StepComponent: React.FC<StepProps> = IngestionSourceBuilderStepComponent[currentStep];
|
|
|
|
|
|
|
|
|
|
return (
|
2025-06-04 21:05:24 +05:30
|
|
|
<Modal width="64%" title={titleText} open={open} onCancel={onCancel} buttons={[]}>
|
2025-07-11 02:39:34 +05:30
|
|
|
<Spin spinning={loading} indicator={<LoadingOutlined />}>
|
|
|
|
|
{currentStepIndex > 0 ? (
|
|
|
|
|
<StepsContainer>
|
|
|
|
|
<Steps current={currentStepIndex}>
|
|
|
|
|
{Object.keys(IngestionSourceBuilderStep).map((item) => (
|
|
|
|
|
<Steps.Step key={item} title={IngestionSourceBuilderStepTitles[item]} />
|
|
|
|
|
))}
|
|
|
|
|
</Steps>
|
|
|
|
|
</StepsContainer>
|
|
|
|
|
) : null}
|
|
|
|
|
<StepComponent
|
|
|
|
|
state={ingestionBuilderState}
|
|
|
|
|
updateState={setIngestionBuilderState}
|
|
|
|
|
isEditing={isEditing}
|
|
|
|
|
goTo={goTo}
|
|
|
|
|
prev={stepStack.length > 1 ? prev : undefined}
|
|
|
|
|
submit={submit}
|
|
|
|
|
cancel={cancel}
|
|
|
|
|
ingestionSources={ingestionSources}
|
|
|
|
|
sourceRefetch={sourceRefetch}
|
|
|
|
|
selectedSource={selectedSource}
|
2025-09-01 17:41:48 +05:30
|
|
|
selectedSourceType={selectedSourceType}
|
|
|
|
|
setSelectedSourceType={setSelectedSourceType}
|
2025-07-11 02:39:34 +05:30
|
|
|
/>
|
|
|
|
|
</Spin>
|
2025-06-04 21:05:24 +05:30
|
|
|
</Modal>
|
2025-05-20 22:32:24 +05:30
|
|
|
);
|
|
|
|
|
};
|