mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-18 22:28:01 +00:00
feat(propagation): Add Documentation Propagation Settings (#11038)
This commit is contained in:
parent
6f09b96b1d
commit
d6be56f9d9
@ -8,6 +8,7 @@ import {
|
|||||||
FilterOutlined,
|
FilterOutlined,
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
PushpinOutlined,
|
PushpinOutlined,
|
||||||
|
ControlOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Redirect, Route, useHistory, useLocation, useRouteMatch, Switch } from 'react-router';
|
import { Redirect, Route, useHistory, useLocation, useRouteMatch, Switch } from 'react-router';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@ -17,11 +18,17 @@ import { ManagePermissions } from '../permissions/ManagePermissions';
|
|||||||
import { useAppConfig } from '../useAppConfig';
|
import { useAppConfig } from '../useAppConfig';
|
||||||
import { AccessTokens } from './AccessTokens';
|
import { AccessTokens } from './AccessTokens';
|
||||||
import { Preferences } from './Preferences';
|
import { Preferences } from './Preferences';
|
||||||
|
import { Features } from './features/Features';
|
||||||
import { ManageViews } from '../entity/view/ManageViews';
|
import { ManageViews } from '../entity/view/ManageViews';
|
||||||
import { useUserContext } from '../context/useUserContext';
|
import { useUserContext } from '../context/useUserContext';
|
||||||
import { ManageOwnership } from '../entity/ownership/ManageOwnership';
|
import { ManageOwnership } from '../entity/ownership/ManageOwnership';
|
||||||
import ManagePosts from './posts/ManagePosts';
|
import ManagePosts from './posts/ManagePosts';
|
||||||
|
|
||||||
|
const MenuItem = styled(Menu.Item)`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
const PageContainer = styled.div`
|
const PageContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@ -59,6 +66,17 @@ const ItemTitle = styled.span`
|
|||||||
|
|
||||||
const menuStyle = { width: 256, 'margin-top': 8, overflow: 'hidden auto' };
|
const menuStyle = { width: 256, 'margin-top': 8, overflow: 'hidden auto' };
|
||||||
|
|
||||||
|
const NewTag = styled.span`
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #f1fbfe;
|
||||||
|
|
||||||
|
color: #09739a;
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL Paths for each settings page.
|
* URL Paths for each settings page.
|
||||||
*/
|
*/
|
||||||
@ -70,6 +88,7 @@ const PATHS = [
|
|||||||
{ path: 'views', content: <ManageViews /> },
|
{ path: 'views', content: <ManageViews /> },
|
||||||
{ path: 'ownership', content: <ManageOwnership /> },
|
{ path: 'ownership', content: <ManageOwnership /> },
|
||||||
{ path: 'posts', content: <ManagePosts /> },
|
{ path: 'posts', content: <ManagePosts /> },
|
||||||
|
{ path: 'features', content: <Features /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,6 +99,7 @@ const DEFAULT_PATH = PATHS[0];
|
|||||||
export const SettingsPage = () => {
|
export const SettingsPage = () => {
|
||||||
const { path, url } = useRouteMatch();
|
const { path, url } = useRouteMatch();
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const subRoutes = PATHS.map((p) => p.path.replace('/', ''));
|
const subRoutes = PATHS.map((p) => p.path.replace('/', ''));
|
||||||
const currPathName = pathname.replace(path, '');
|
const currPathName = pathname.replace(path, '');
|
||||||
@ -101,6 +121,7 @@ export const SettingsPage = () => {
|
|||||||
const showViews = isViewsEnabled || false;
|
const showViews = isViewsEnabled || false;
|
||||||
const showOwnershipTypes = me && me?.platformPrivileges?.manageOwnershipTypes;
|
const showOwnershipTypes = me && me?.platformPrivileges?.manageOwnershipTypes;
|
||||||
const showHomePagePosts = me && me?.platformPrivileges?.manageGlobalAnnouncements && !readOnlyModeEnabled;
|
const showHomePagePosts = me && me?.platformPrivileges?.manageGlobalAnnouncements && !readOnlyModeEnabled;
|
||||||
|
const showFeatures = true; // TODO: Add feature flag for this
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
@ -143,6 +164,13 @@ export const SettingsPage = () => {
|
|||||||
)}
|
)}
|
||||||
{(showViews || showOwnershipTypes || showHomePagePosts) && (
|
{(showViews || showOwnershipTypes || showHomePagePosts) && (
|
||||||
<Menu.ItemGroup title="Manage">
|
<Menu.ItemGroup title="Manage">
|
||||||
|
{showFeatures && (
|
||||||
|
<MenuItem key="features">
|
||||||
|
<ControlOutlined />
|
||||||
|
<ItemTitle>Features</ItemTitle>
|
||||||
|
<NewTag>New!</NewTag>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
{showViews && (
|
{showViews && (
|
||||||
<Menu.Item key="views">
|
<Menu.Item key="views">
|
||||||
<FilterOutlined /> <ItemTitle>My Views</ItemTitle>
|
<FilterOutlined /> <ItemTitle>My Views</ItemTitle>
|
||||||
|
179
datahub-web-react/src/app/settings/features/Feature.tsx
Normal file
179
datahub-web-react/src/app/settings/features/Feature.tsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { Divider, Typography, Switch, Card, Button, Tooltip } from 'antd';
|
||||||
|
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||||
|
import { ANTD_GRAY } from '../../entity/shared/constants';
|
||||||
|
|
||||||
|
const Title = styled(Typography.Title)`
|
||||||
|
&& {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FeatureRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FeatureOptionRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SettingsOptionRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DescriptionText = styled(Typography.Text)`
|
||||||
|
color: ${ANTD_GRAY[7]};
|
||||||
|
font-size: 11px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SettingTitle = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const OptionTitle = styled(Typography.Text)`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const learnMoreLinkStyle = {
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
color: '#1890FF',
|
||||||
|
fontSize: '12px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewTag = styled.div`
|
||||||
|
padding: 4px 8px;
|
||||||
|
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #f1fbfe;
|
||||||
|
|
||||||
|
color: #09739a;
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DataHubOnlyTag = styled.div`
|
||||||
|
padding: 2px 8px;
|
||||||
|
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #c9fff2;
|
||||||
|
|
||||||
|
color: #50a494;
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface FeatureType {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
settings: Array<{
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
isAvailable: boolean;
|
||||||
|
buttonText: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
}>;
|
||||||
|
options: Array<{
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
isAvailable: boolean;
|
||||||
|
checked: boolean;
|
||||||
|
onChange?: (checked: boolean) => void;
|
||||||
|
}>;
|
||||||
|
isNew: boolean;
|
||||||
|
learnMoreLink?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Feature = ({ key, title, description, settings, options, isNew, learnMoreLink }: FeatureType) => (
|
||||||
|
<Card style={{ marginBottom: '1rem' }} key={key}>
|
||||||
|
<FeatureRow>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<SettingTitle>
|
||||||
|
<Title level={5} style={{ marginBottom: 0 }}>
|
||||||
|
{title}
|
||||||
|
</Title>
|
||||||
|
{isNew && <NewTag>New!</NewTag>}
|
||||||
|
</SettingTitle>
|
||||||
|
<div>
|
||||||
|
<Typography.Paragraph type="secondary">{description}</Typography.Paragraph>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{learnMoreLink && (
|
||||||
|
<a href={learnMoreLink} target="_blank" style={learnMoreLinkStyle} rel="noreferrer">
|
||||||
|
Learn more <ArrowRightOutlined />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</FeatureRow>
|
||||||
|
<Divider style={{ margin: `8px 0 24px 0` }} />
|
||||||
|
{settings.map((option) => (
|
||||||
|
<>
|
||||||
|
<SettingsOptionRow key={option.key}>
|
||||||
|
<span>
|
||||||
|
<OptionTitle>
|
||||||
|
<span>{option.title}</span>
|
||||||
|
</OptionTitle>
|
||||||
|
</span>
|
||||||
|
<Tooltip title={option.isAvailable ? '' : 'Only available on DataHub Cloud'}>
|
||||||
|
<Button onClick={option.onClick} disabled={!option.isAvailable}>
|
||||||
|
{option.buttonText}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</SettingsOptionRow>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
<Card style={{ margin: `16px auto` }}>
|
||||||
|
{options.map((option, index) => (
|
||||||
|
<>
|
||||||
|
<FeatureOptionRow key={option.key}>
|
||||||
|
<span>
|
||||||
|
<OptionTitle>
|
||||||
|
<span>{option.title}</span>
|
||||||
|
{!option.isAvailable && (
|
||||||
|
<DataHubOnlyTag>Only available on DataHub Cloud</DataHubOnlyTag>
|
||||||
|
)}
|
||||||
|
</OptionTitle>
|
||||||
|
<div>
|
||||||
|
<DescriptionText>{option.description}</DescriptionText>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<Switch
|
||||||
|
checked={option.checked}
|
||||||
|
onChange={(checked) => (option.onChange ? option.onChange(checked) : null)}
|
||||||
|
disabled={!option.isAvailable}
|
||||||
|
/>
|
||||||
|
</FeatureOptionRow>
|
||||||
|
{index !== options.length - 1 && <Divider />}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
</Card>
|
||||||
|
);
|
110
datahub-web-react/src/app/settings/features/Features.tsx
Normal file
110
datahub-web-react/src/app/settings/features/Features.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { Divider, Typography } from 'antd';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
import { Feature, FeatureType } from './Feature';
|
||||||
|
|
||||||
|
import { useGetDocPropagationSettings, useUpdateDocPropagationSettings } from './useDocPropagationSettings';
|
||||||
|
|
||||||
|
const Page = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SourceContainer = styled.div`
|
||||||
|
width: 80%;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-right: 40px;
|
||||||
|
padding-left: 40px;
|
||||||
|
`;
|
||||||
|
const Container = styled.div`
|
||||||
|
padding-top: 0px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled(Typography.Title)`
|
||||||
|
&& {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Features = () => {
|
||||||
|
/*
|
||||||
|
* Note: When adding new features, make sure to update the features array below
|
||||||
|
* and create a hook file for the new feature in the same directory
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Hooks to get and update the document propagation settings
|
||||||
|
const { isColPropagateChecked, setIsColPropagateChecked } = useGetDocPropagationSettings();
|
||||||
|
const { updateDocPropagation } = useUpdateDocPropagationSettings();
|
||||||
|
|
||||||
|
// Features to display
|
||||||
|
const features: FeatureType[] = [
|
||||||
|
{
|
||||||
|
key: uuidv4(),
|
||||||
|
title: 'Documentation Propagation',
|
||||||
|
description: 'Automatically propagate documentation from upstream to downstream columns and assets.',
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
key: uuidv4(),
|
||||||
|
title: 'Rollback Propagation Changes',
|
||||||
|
isAvailable: false,
|
||||||
|
buttonText: 'Rollback',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: uuidv4(),
|
||||||
|
title: 'Backfill existing documentation from upstream to downstream columns/assets',
|
||||||
|
isAvailable: false,
|
||||||
|
buttonText: 'Initialize',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
key: uuidv4(),
|
||||||
|
title: 'Column Level Propagation',
|
||||||
|
description:
|
||||||
|
'Propagate new documentation from upstream to downstream columns based on column-level lineage relationships.',
|
||||||
|
isAvailable: true,
|
||||||
|
checked: isColPropagateChecked,
|
||||||
|
onChange: (checked: boolean) => {
|
||||||
|
setIsColPropagateChecked(checked);
|
||||||
|
updateDocPropagation(checked);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: uuidv4(),
|
||||||
|
title: 'Asset Level Propagation',
|
||||||
|
description:
|
||||||
|
'Propagate new documentation from upstream to downstream assets based on data lineage relationships.',
|
||||||
|
isAvailable: false,
|
||||||
|
checked: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isNew: true,
|
||||||
|
learnMoreLink: 'https://datahubproject.io/docs/automations/doc-propagation',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Render
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<SourceContainer>
|
||||||
|
<Container>
|
||||||
|
<div>
|
||||||
|
<Title level={2}>Features</Title>
|
||||||
|
<Typography.Paragraph type="secondary">
|
||||||
|
Explore and configure specific features
|
||||||
|
</Typography.Paragraph>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
<Divider />
|
||||||
|
{features.map((feature) => (
|
||||||
|
<Feature {...feature} />
|
||||||
|
))}
|
||||||
|
</SourceContainer>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,50 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { message } from 'antd';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useGetDocPropagationSettingsQuery,
|
||||||
|
useUpdateDocPropagationSettingsMutation,
|
||||||
|
} from '../../../graphql/app.generated';
|
||||||
|
|
||||||
|
// Hook to get the document propagation settings & manage state
|
||||||
|
export const useGetDocPropagationSettings = () => {
|
||||||
|
const { data, refetch } = useGetDocPropagationSettingsQuery();
|
||||||
|
const [isColPropagateChecked, setIsColPropagateChecked] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const docPropSetting = data?.docPropagationSettings?.docColumnPropagation;
|
||||||
|
if (docPropSetting !== undefined) setIsColPropagateChecked(!!docPropSetting);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isColPropagateChecked,
|
||||||
|
setIsColPropagateChecked,
|
||||||
|
refetch,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hook to update the document propagation settings
|
||||||
|
export const useUpdateDocPropagationSettings = () => {
|
||||||
|
const [updateDocPropagationSettings] = useUpdateDocPropagationSettingsMutation();
|
||||||
|
const { refetch } = useGetDocPropagationSettingsQuery();
|
||||||
|
|
||||||
|
const updateDocPropagation = async (checked: boolean) => {
|
||||||
|
try {
|
||||||
|
await updateDocPropagationSettings({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
docColumnPropagation: checked,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
message.success('Successfully updated documentation propagation settings');
|
||||||
|
} catch (e) {
|
||||||
|
message.error('Failed to update documentation propagation settings');
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { updateDocPropagation };
|
||||||
|
};
|
@ -89,6 +89,16 @@ query getGlobalViewsSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query getDocPropagationSettings {
|
||||||
|
docPropagationSettings {
|
||||||
|
docColumnPropagation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutation updateGlobalViewsSettings($input: UpdateGlobalViewsSettingsInput!) {
|
mutation updateGlobalViewsSettings($input: UpdateGlobalViewsSettingsInput!) {
|
||||||
updateGlobalViewsSettings(input: $input)
|
updateGlobalViewsSettings(input: $input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation updateDocPropagationSettings($input: UpdateDocPropagationSettingsInput!) {
|
||||||
|
updateDocPropagationSettings(input: $input)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user