Skip to content

Commit 0be5c39

Browse files
feat(ingestion-ui) Display ingestion sources in UI more dynamically (#5789)
1 parent dfeced8 commit 0be5c39

File tree

19 files changed

+479
-47
lines changed

19 files changed

+479
-47
lines changed

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import com.linkedin.datahub.graphql.generated.GlossaryNode;
5050
import com.linkedin.datahub.graphql.generated.GlossaryTerm;
5151
import com.linkedin.datahub.graphql.generated.GlossaryTermAssociation;
52+
import com.linkedin.datahub.graphql.generated.IngestionSource;
5253
import com.linkedin.datahub.graphql.generated.InstitutionalMemoryMetadata;
5354
import com.linkedin.datahub.graphql.generated.LineageRelationship;
5455
import com.linkedin.datahub.graphql.generated.ListAccessTokenResult;
@@ -1475,6 +1476,13 @@ private <T, K> DataLoader<K, DataFetcherResult<T>> createDataLoader(final Loadab
14751476
}
14761477

14771478
private void configureIngestionSourceResolvers(final RuntimeWiring.Builder builder) {
1478-
builder.type("IngestionSource", typeWiring -> typeWiring.dataFetcher("executions", new IngestionSourceExecutionRequestsResolver(entityClient)));
1479+
builder.type("IngestionSource", typeWiring -> typeWiring
1480+
.dataFetcher("executions", new IngestionSourceExecutionRequestsResolver(entityClient))
1481+
.dataFetcher("platform", new LoadableTypeResolver<>(dataPlatformType,
1482+
(env) -> {
1483+
final IngestionSource ingestionSource = env.getSource();
1484+
return ingestionSource.getPlatform() != null ? ingestionSource.getPlatform().getUrn() : null;
1485+
})
1486+
));
14791487
}
14801488
}

datahub-graphql-core/src/main/resources/ingestion.graphql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,11 @@ type IngestionSource {
389389
"""
390390
schedule: IngestionSchedule
391391

392+
"""
393+
The data platform associated with this ingestion source
394+
"""
395+
platform: DataPlatform
396+
392397
"""
393398
An type-specific set of configurations for the ingestion source
394399
"""

datahub-web-react/src/app/ingest/source/IngestionSourceTable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ function IngestionSourceTable({
108108
urn: source.urn,
109109
type: source.type,
110110
name: source.name,
111+
platformUrn: source.platform?.urn,
111112
schedule: source.schedule?.interval,
112113
timezone: source.schedule?.timezone,
113114
execCount: source.executions?.total || 0,

datahub-web-react/src/app/ingest/source/IngestionSourceTableColumns.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import React from 'react';
66
import styled from 'styled-components/macro';
77
import { ANTD_GRAY } from '../../entity/shared/constants';
88
import { capitalizeFirstLetter } from '../../shared/textUtil';
9+
import useGetSourceLogoUrl from './builder/useGetSourceLogoUrl';
910
import {
1011
getExecutionRequestStatusDisplayColor,
1112
getExecutionRequestStatusDisplayText,
1213
getExecutionRequestStatusIcon,
1314
RUNNING,
14-
sourceTypeToIconUrl,
1515
} from './utils';
1616

1717
const PreviewImage = styled(Image)`
@@ -63,7 +63,7 @@ const CliBadge = styled.span`
6363
`;
6464

6565
export function TypeColumn(type: string, record: any) {
66-
const iconUrl = sourceTypeToIconUrl(type);
66+
const iconUrl = useGetSourceLogoUrl(type);
6767
const typeDisplayName = capitalizeFirstLetter(type);
6868

6969
return (

datahub-web-react/src/app/ingest/source/builder/DefineRecipeStep.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Alert, Button, Space, Typography } from 'antd';
22
import React, { useEffect, useState } from 'react';
33
import styled from 'styled-components';
44
import { StepProps } from './types';
5-
import { getSourceConfigs, jsonToYaml } from '../utils';
5+
import { getPlaceholderRecipe, getSourceConfigs, jsonToYaml } from '../utils';
66
import { YamlEditor } from './YamlEditor';
77
import { ANTD_GRAY } from '../../../entity/shared/constants';
88
import { IngestionSourceBuilderStep } from './steps';
@@ -37,13 +37,14 @@ const ControlsContainer = styled.div`
3737
/**
3838
* The step for defining a recipe
3939
*/
40-
export const DefineRecipeStep = ({ state, updateState, goTo, prev }: StepProps) => {
40+
export const DefineRecipeStep = ({ state, updateState, goTo, prev, ingestionSources }: StepProps) => {
4141
const existingRecipeJson = state.config?.recipe;
4242
const existingRecipeYaml = existingRecipeJson && jsonToYaml(existingRecipeJson);
4343
const { type } = state;
44-
const sourceConfigs = getSourceConfigs(type as string);
44+
const sourceConfigs = getSourceConfigs(ingestionSources, type as string);
45+
const placeholderRecipe = getPlaceholderRecipe(ingestionSources, type);
4546

46-
const [stagedRecipeYml, setStagedRecipeYml] = useState(existingRecipeYaml || sourceConfigs.placeholderRecipe);
47+
const [stagedRecipeYml, setStagedRecipeYml] = useState(existingRecipeYaml || placeholderRecipe);
4748

4849
useEffect(() => {
4950
if (existingRecipeYaml) {
@@ -54,12 +55,12 @@ export const DefineRecipeStep = ({ state, updateState, goTo, prev }: StepProps)
5455
const [stepComplete, setStepComplete] = useState(false);
5556

5657
const isEditing: boolean = prev === undefined;
57-
const displayRecipe = stagedRecipeYml || sourceConfigs.placeholderRecipe;
58-
const sourceDisplayName = sourceConfigs.displayName;
59-
const sourceDocumentationUrl = sourceConfigs.docsUrl; // Maybe undefined (in case of "custom")
58+
const displayRecipe = stagedRecipeYml || placeholderRecipe;
59+
const sourceDisplayName = sourceConfigs?.displayName;
60+
const sourceDocumentationUrl = sourceConfigs?.docsUrl;
6061

6162
// TODO: Delete LookML banner specific code
62-
const isSourceLooker: boolean = sourceConfigs.type === 'looker';
63+
const isSourceLooker: boolean = sourceConfigs?.name === 'looker';
6364
const [showLookerBanner, setShowLookerBanner] = useState(isSourceLooker && !isEditing);
6465

6566
useEffect(() => {
@@ -90,6 +91,7 @@ export const DefineRecipeStep = ({ state, updateState, goTo, prev }: StepProps)
9091
type={type}
9192
isEditing={isEditing}
9293
displayRecipe={displayRecipe}
94+
sourceConfigs={sourceConfigs}
9395
setStagedRecipe={setStagedRecipeYml}
9496
onClickNext={onClickNext}
9597
goToPrevious={prev}

datahub-web-react/src/app/ingest/source/builder/IngestionSourceBuilderModal.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CreateScheduleStep } from './CreateScheduleStep';
88
import { DefineRecipeStep } from './DefineRecipeStep';
99
import { NameSourceStep } from './NameSourceStep';
1010
import { SelectTemplateStep } from './SelectTemplateStep';
11+
import sourcesJson from './sources.json';
1112

1213
const ExpandButton = styled(Button)`
1314
&& {
@@ -74,6 +75,8 @@ export const IngestionSourceBuilderModal = ({ initialState, visible, onSubmit, o
7475
const [modalExpanded, setModalExpanded] = useState(false);
7576
const [ingestionBuilderState, setIngestionBuilderState] = useState<SourceBuilderState>({});
7677

78+
const ingestionSources = JSON.parse(JSON.stringify(sourcesJson)); // TODO: replace with call to server once we have access to dynamic list of sources
79+
7780
// Reset the ingestion builder modal state when the modal is re-opened.
7881
const prevInitialState = useRef(initialState);
7982
useEffect(() => {
@@ -148,6 +151,7 @@ export const IngestionSourceBuilderModal = ({ initialState, visible, onSubmit, o
148151
prev={stepStack.length > 1 ? prev : undefined}
149152
submit={submit}
150153
cancel={cancel}
154+
ingestionSources={ingestionSources}
151155
/>
152156
</Modal>
153157
);

datahub-web-react/src/app/ingest/source/builder/RecipeBuilder.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import styled from 'styled-components/macro';
66
import { ANTD_GRAY } from '../../../entity/shared/constants';
77
import { YamlEditor } from './YamlEditor';
88
import RecipeForm from './RecipeForm/RecipeForm';
9+
import { SourceConfig } from './types';
910

1011
export const ControlsContainer = styled.div`
1112
display: flex;
@@ -41,13 +42,14 @@ interface Props {
4142
type: string;
4243
isEditing: boolean;
4344
displayRecipe: string;
45+
sourceConfigs?: SourceConfig;
4446
setStagedRecipe: (recipe: string) => void;
4547
onClickNext: () => void;
4648
goToPrevious?: () => void;
4749
}
4850

4951
function RecipeBuilder(props: Props) {
50-
const { type, isEditing, displayRecipe, setStagedRecipe, onClickNext, goToPrevious } = props;
52+
const { type, isEditing, displayRecipe, sourceConfigs, setStagedRecipe, onClickNext, goToPrevious } = props;
5153

5254
const [isViewingForm, setIsViewingForm] = useState(true);
5355

@@ -78,6 +80,7 @@ function RecipeBuilder(props: Props) {
7880
type={type}
7981
isEditing={isEditing}
8082
displayRecipe={displayRecipe}
83+
sourceConfigs={sourceConfigs}
8184
setStagedRecipe={setStagedRecipe}
8285
onClickNext={onClickNext}
8386
goToPrevious={goToPrevious}

datahub-web-react/src/app/ingest/source/builder/RecipeForm/RecipeForm.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import FormField from './FormField';
1010
import TestConnectionButton from './TestConnection/TestConnectionButton';
1111
import { useListSecretsQuery } from '../../../../../graphql/ingestion.generated';
1212
import { RecipeField, setFieldValueOnRecipe } from './common';
13+
import { SourceConfig } from '../types';
1314

1415
export const ControlsContainer = styled.div`
1516
display: flex;
@@ -91,13 +92,14 @@ interface Props {
9192
type: string;
9293
isEditing: boolean;
9394
displayRecipe: string;
95+
sourceConfigs?: SourceConfig;
9496
setStagedRecipe: (recipe: string) => void;
9597
onClickNext: () => void;
9698
goToPrevious?: () => void;
9799
}
98100

99101
function RecipeForm(props: Props) {
100-
const { type, isEditing, displayRecipe, setStagedRecipe, onClickNext, goToPrevious } = props;
102+
const { type, isEditing, displayRecipe, sourceConfigs, setStagedRecipe, onClickNext, goToPrevious } = props;
101103
const { fields, advancedFields, filterFields, filterSectionTooltip } = RECIPE_FIELDS[type];
102104
const allFields = [...fields, ...advancedFields, ...filterFields];
103105
const { data, refetch: refetchSecrets } = useListSecretsQuery({
@@ -146,7 +148,7 @@ function RecipeForm(props: Props) {
146148
))}
147149
{CONNECTORS_WITH_TEST_CONNECTION.has(type) && (
148150
<TestConnectionWrapper>
149-
<TestConnectionButton type={type} recipe={displayRecipe} />
151+
<TestConnectionButton recipe={displayRecipe} sourceConfigs={sourceConfigs} />
150152
</TestConnectionWrapper>
151153
)}
152154
</Collapse.Panel>

datahub-web-react/src/app/ingest/source/builder/RecipeForm/TestConnection/TestConnectionButton.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import {
66
useCreateTestConnectionRequestMutation,
77
useGetIngestionExecutionRequestLazyQuery,
88
} from '../../../../../../graphql/ingestion.generated';
9-
import { FAILURE, getSourceConfigs, RUNNING, yamlToJson } from '../../../utils';
9+
import { FAILURE, RUNNING, yamlToJson } from '../../../utils';
1010
import { TestConnectionResult } from './types';
1111
import TestConnectionModal from './TestConnectionModal';
12+
import { SourceConfig } from '../../types';
1213

1314
export function getRecipeJson(recipeYaml: string) {
1415
// Convert the recipe into it's json representation, and catch + report exceptions while we do it.
@@ -26,21 +27,19 @@ export function getRecipeJson(recipeYaml: string) {
2627
}
2728

2829
interface Props {
29-
type: string;
3030
recipe: string;
31+
sourceConfigs?: SourceConfig;
3132
}
3233

3334
function TestConnectionButton(props: Props) {
34-
const { type, recipe } = props;
35+
const { recipe, sourceConfigs } = props;
3536
const [isLoading, setIsLoading] = useState(false);
3637
const [isModalVisible, setIsModalVisible] = useState(false);
3738
const [pollingInterval, setPollingInterval] = useState<null | NodeJS.Timeout>(null);
3839
const [testConnectionResult, setTestConnectionResult] = useState<null | TestConnectionResult>(null);
3940
const [createTestConnectionRequest, { data: requestData }] = useCreateTestConnectionRequestMutation();
4041
const [getIngestionExecutionRequest, { data: resultData, loading }] = useGetIngestionExecutionRequestLazyQuery();
4142

42-
const sourceConfigs = getSourceConfigs(type);
43-
4443
useEffect(() => {
4544
if (requestData && requestData.createTestConnectionRequest) {
4645
const interval = setInterval(

datahub-web-react/src/app/ingest/source/builder/RecipeForm/TestConnection/TestConnectionModal.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import styled from 'styled-components/macro';
66
import { ReactComponent as LoadingSvg } from '../../../../../../images/datahub-logo-color-loading_pendulum.svg';
77
import { ANTD_GRAY } from '../../../../../entity/shared/constants';
88
import ConnectionCapabilityView from './ConnectionCapabilityView';
9-
import { SourceConfig } from '../../../conf/types';
109
import { CapabilityReport, SourceCapability, TestConnectionResult } from './types';
10+
import { SourceConfig } from '../../types';
11+
import useGetSourceLogoUrl from '../../useGetSourceLogoUrl';
1112

1213
const LoadingWrapper = styled.div`
1314
display: flex;
@@ -84,7 +85,7 @@ const StyledClose = styled(CloseOutlined)`
8485
interface Props {
8586
isLoading: boolean;
8687
testConnectionFailed: boolean;
87-
sourceConfig: SourceConfig;
88+
sourceConfig?: SourceConfig;
8889
testConnectionResult: TestConnectionResult | null;
8990
hideModal: () => void;
9091
}
@@ -96,15 +97,17 @@ function TestConnectionModal({
9697
testConnectionResult,
9798
hideModal,
9899
}: Props) {
100+
const logoUrl = useGetSourceLogoUrl(sourceConfig?.name || '');
101+
99102
return (
100103
<Modal
101104
visible
102105
onCancel={hideModal}
103106
footer={<Button onClick={hideModal}>Done</Button>}
104107
title={
105108
<ModalHeader style={{ margin: 0 }}>
106-
<SourceIcon alt="source logo" src={sourceConfig.logoUrl} />
107-
{sourceConfig.displayName} Connection Test
109+
<SourceIcon alt="source logo" src={logoUrl} />
110+
{sourceConfig?.displayName} Connection Test
108111
</ModalHeader>
109112
}
110113
width={750}
@@ -133,8 +136,8 @@ function TestConnectionModal({
133136
</ResultsHeader>
134137
<ResultsSubHeader>
135138
{testConnectionFailed
136-
? `A connection was not able to be established with ${sourceConfig.displayName}.`
137-
: `A connection was successfully established with ${sourceConfig.displayName}.`}
139+
? `A connection was not able to be established with ${sourceConfig?.displayName}.`
140+
: `A connection was successfully established with ${sourceConfig?.displayName}.`}
138141
</ResultsSubHeader>
139142
<Divider />
140143
{testConnectionResult?.internal_failure ? (

0 commit comments

Comments
 (0)