Skip to content

Commit 7936528

Browse files
committed
[ui] Add Integrations Marketplace to app
1 parent cbcfed6 commit 7936528

14 files changed

+296
-88
lines changed

js_modules/dagster-ui/packages/ui-core/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"chart.js": "^3.4.1",
4949
"chartjs-adapter-date-fns": "^2.0.0",
5050
"chartjs-plugin-zoom": "^1.1.1",
51+
"clsx": "^2.1.1",
5152
"codemirror": "^5.65.2",
5253
"cronstrue": "^2.51.0",
5354
"dagre": "dagster-io/dagre#0.8.5",

js_modules/dagster-ui/packages/ui-core/src/app/AppTopNav/AppTopNav.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {NavLink} from 'react-router-dom';
44
import {AppTopNavRightOfLogo} from 'shared/app/AppTopNav/AppTopNavRightOfLogo.oss';
55
import styled from 'styled-components';
66

7+
import {useFeatureFlags} from '../Flags';
78
import {GhostDaggyWithTooltip} from './GhostDaggy';
9+
import {MarketplaceLink} from '../../integrations/MarketplaceLink';
810
import {
911
reloadFnForWorkspace,
1012
useRepositoryLocationReload,
@@ -20,6 +22,7 @@ interface Props {
2022
}
2123

2224
export const AppTopNav = ({children, allowGlobalReload = false}: Props) => {
25+
const {flagMarketplace} = useFeatureFlags();
2326
const {reloading, tryReload} = useRepositoryLocationReload({
2427
scope: 'workspace',
2528
reloadFn: reloadFnForWorkspace,
@@ -46,6 +49,7 @@ export const AppTopNav = ({children, allowGlobalReload = false}: Props) => {
4649
</ShortcutHandler>
4750
) : null}
4851
<SearchDialog />
52+
{flagMarketplace ? <MarketplaceLink /> : null}
4953
{children}
5054
</Box>
5155
</AppTopNavContainer>

js_modules/dagster-ui/packages/ui-core/src/app/ContentRoot.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {AssetsOverviewRoot} from 'shared/assets/AssetsOverviewRoot.oss';
66
import {Route} from './Route';
77
import {AssetFeatureProvider} from '../assets/AssetFeatureContext';
88
import {RunsFeedBackfillPage} from '../instance/backfill/RunsFeedBackfillPage';
9+
import {IntegrationsRoot} from '../integrations/IntegrationsRoot';
910
import RunsFeedRoot from '../runs/RunsFeedRoot';
1011
import {lazy} from '../util/lazy';
1112

@@ -91,6 +92,9 @@ export const ContentRoot = memo(() => {
9192
<Route path="/deployment">
9293
<SettingsRoot />
9394
</Route>
95+
<Route path="/integrations">
96+
<IntegrationsRoot />
97+
</Route>
9498
<Route path="*" isNestingRoute>
9599
<FallthroughRoot />
96100
</Route>

js_modules/dagster-ui/packages/ui-core/src/app/FeatureFlags.oss.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export enum FeatureFlag {
66
flagSelectionSyntax = 'flagSelectionSyntax-always-on',
77
flagAssetSelectionWorker = 'flagAssetSelectionWorker',
88
flagUseNewObserveUIs = 'flagUseNewObserveUIs',
9+
flagMarketplace = 'flagMarketplace',
910

1011
// Flags for tests
1112
__TestFlagDefaultNone = '__TestFlagDefaultNone',

js_modules/dagster-ui/packages/ui-core/src/integrations/IntegrationPage.tsx

+28-21
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {Body, Box, Colors, Heading, PageHeader} from '@dagster-io/ui-components';
1+
import {Body, Box, Colors} from '@dagster-io/ui-components';
2+
import clsx from 'clsx';
23
import {useLayoutEffect, useRef, useState} from 'react';
34
import ReactMarkdown from 'react-markdown';
4-
import {CodeComponent} from 'react-markdown/lib/ast-to-react';
5-
import {Link} from 'react-router-dom';
5+
import {Components} from 'react-markdown/lib/ast-to-react';
66
import rehypeHighlight from 'rehype-highlight';
77
import remarkGfm from 'remark-gfm';
88

@@ -24,22 +24,7 @@ export const IntegrationPage = ({integration}: Props) => {
2424

2525
return (
2626
<div>
27-
<PageHeader
28-
title={
29-
<Heading>
30-
<Box flex={{direction: 'row', gap: 8}}>
31-
<Link to="/integrations">Integrations Marketplace</Link>
32-
<span> / </span>
33-
<div>{title}</div>
34-
</Box>
35-
</Heading>
36-
}
37-
/>
38-
<Box
39-
padding={{vertical: 24}}
40-
flex={{direction: 'column', gap: 24}}
41-
style={{width: '1100px', margin: '0 auto'}}
42-
>
27+
<Box padding={{vertical: 24}} flex={{direction: 'column', gap: 12}}>
4328
<Box flex={{direction: 'row', gap: 12, alignItems: 'flex-start'}}>
4429
<IntegrationIcon name={name} logo={logo} />
4530
<Box flex={{direction: 'column', gap: 2}} margin={{top: 4}}>
@@ -54,6 +39,7 @@ export const IntegrationPage = ({integration}: Props) => {
5439
rehypePlugins={[[rehypeHighlight, {ignoreMissing: true}]]}
5540
components={{
5641
code: Code,
42+
a: Anchor,
5743
}}
5844
>
5945
{content}
@@ -64,15 +50,36 @@ export const IntegrationPage = ({integration}: Props) => {
6450
);
6551
};
6652

67-
const Code: CodeComponent = (props) => {
68-
const {children, className, ...rest} = props;
53+
const DOCS_ORIGIN = 'https://docs.dagster.io';
54+
55+
const Anchor: Components['a'] = (props) => {
56+
const {children, href, ...rest} = props;
57+
const finalHref = href?.startsWith('/') ? `${DOCS_ORIGIN}${href}` : href;
58+
return (
59+
<a href={finalHref} target="_blank" rel="noreferrer" {...rest}>
60+
{children}
61+
</a>
62+
);
63+
};
64+
65+
const Code: Components['code'] = (props) => {
66+
const {children, className, inline, ...rest} = props;
67+
6968
const codeRef = useRef<HTMLElement>(null);
7069
const [value, setValue] = useState('');
7170

7271
useLayoutEffect(() => {
7372
setValue(codeRef.current?.textContent?.trim() ?? '');
7473
}, [children]);
7574

75+
if (inline) {
76+
return (
77+
<code className={clsx(className, styles.inlineCode)} {...rest}>
78+
{children}
79+
</code>
80+
);
81+
}
82+
7683
return (
7784
<div className={styles.codeBlock}>
7885
<code className={className} {...rest} ref={codeRef}>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {Box, Heading, Icon, NonIdealState, PageHeader} from '@dagster-io/ui-components';
2+
import {useMemo} from 'react';
3+
import {Link, useParams} from 'react-router-dom';
4+
5+
import {IntegrationPage} from './IntegrationPage';
6+
import * as allIntegrations from './__generated__';
7+
import {AnchorButton} from '../ui/AnchorButton';
8+
9+
export const IntegrationRoot = () => {
10+
const {integrationName} = useParams<{integrationName: string}>();
11+
12+
const matchingIntegration = useMemo(() => {
13+
const match = Object.entries(allIntegrations).find(
14+
([key]) => key.toLocaleLowerCase() === integrationName.toLocaleLowerCase(),
15+
);
16+
return match ? match[1] : null;
17+
}, [integrationName]);
18+
19+
const content = () => {
20+
if (!matchingIntegration) {
21+
return (
22+
<Box padding={64} flex={{direction: 'column', gap: 12}}>
23+
<NonIdealState
24+
icon="search"
25+
title="Integration not found"
26+
description="This integration could not be found."
27+
action={
28+
<AnchorButton to="/integrations" icon={<Icon name="arrow_back" />}>
29+
Back to Integrations Marketplace
30+
</AnchorButton>
31+
}
32+
/>
33+
</Box>
34+
);
35+
}
36+
37+
return <IntegrationPage integration={matchingIntegration} />;
38+
};
39+
40+
return (
41+
<Box flex={{direction: 'column'}} style={{height: '100%', overflow: 'hidden'}}>
42+
<PageHeader
43+
title={
44+
<Heading>
45+
<Box flex={{direction: 'row', gap: 8}}>
46+
<Link to="/integrations">Integrations Marketplace</Link>
47+
{matchingIntegration ? (
48+
<>
49+
<span> / </span>
50+
<div>{matchingIntegration?.frontmatter.name}</div>
51+
</>
52+
) : null}
53+
</Box>
54+
</Heading>
55+
}
56+
/>
57+
<div style={{flex: 1, overflowY: 'auto'}}>
58+
<div
59+
style={{
60+
width: '80vw',
61+
maxWidth: '1100px',
62+
minWidth: '900px',
63+
margin: '0 auto',
64+
}}
65+
>
66+
{content()}
67+
</div>
68+
</div>
69+
</Box>
70+
);
71+
};

js_modules/dagster-ui/packages/ui-core/src/integrations/IntegrationTag.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import {IconName} from '@dagster-io/ui-components';
33
export enum IntegrationTag {
44
Alerting = 'alerting',
55
BiTools = 'bi-tools',
6-
ComponentReady = 'component-ready',
76
Compute = 'compute',
8-
EltTools = 'elt-tools',
7+
DagsterSupported = 'dagster-supported',
8+
EtlTools = 'etl',
99
Metadata = 'metadata',
1010
Monitoring = 'monitoring',
1111
Notifications = 'notifications',
@@ -15,9 +15,9 @@ export enum IntegrationTag {
1515
export const IntegrationTagLabel: Record<IntegrationTag, string> = {
1616
[IntegrationTag.Alerting]: 'Alerting',
1717
[IntegrationTag.BiTools]: 'BI tools',
18-
[IntegrationTag.ComponentReady]: 'Component-ready',
1918
[IntegrationTag.Compute]: 'Compute',
20-
[IntegrationTag.EltTools]: 'ELT tools',
19+
[IntegrationTag.DagsterSupported]: 'Dagster Supported',
20+
[IntegrationTag.EtlTools]: 'ETL tools',
2121
[IntegrationTag.Metadata]: 'Metadata',
2222
[IntegrationTag.Monitoring]: 'Monitoring',
2323
[IntegrationTag.Notifications]: 'Notifications',
@@ -27,9 +27,9 @@ export const IntegrationTagLabel: Record<IntegrationTag, string> = {
2727
export const IntegrationTagIcon: Record<IntegrationTag, IconName> = {
2828
[IntegrationTag.Alerting]: 'alert',
2929
[IntegrationTag.BiTools]: 'chart_bar',
30-
[IntegrationTag.ComponentReady]: 'repo',
3130
[IntegrationTag.Compute]: 'speed',
32-
[IntegrationTag.EltTools]: 'transform',
31+
[IntegrationTag.DagsterSupported]: 'dagster_solid',
32+
[IntegrationTag.EtlTools]: 'transform',
3333
[IntegrationTag.Metadata]: 'metadata',
3434
[IntegrationTag.Monitoring]: 'visibility',
3535
[IntegrationTag.Notifications]: 'notifications',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {Redirect, Route, Switch} from 'react-router';
2+
3+
import {IntegrationRoot} from './IntegrationRoot';
4+
import {MarketplaceRoot} from './MarketplaceRoot';
5+
import {useFeatureFlags} from '../app/Flags';
6+
7+
export const IntegrationsRoot = () => {
8+
const {flagMarketplace} = useFeatureFlags();
9+
10+
if (!flagMarketplace) {
11+
return <Redirect to="/deployment" />;
12+
}
13+
14+
return (
15+
<Switch>
16+
<Route path="/integrations" component={MarketplaceRoot} exact />
17+
<Route path="/integrations/:integrationName" component={IntegrationRoot} />
18+
</Switch>
19+
);
20+
};

js_modules/dagster-ui/packages/ui-core/src/integrations/MarketplaceHome.tsx

+17-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@ interface Props {
1414
export const MarketplaceHome = (props: Props) => {
1515
const {integrations} = props;
1616
const [searchQuery, setSearchQuery] = useState('');
17+
const [filters, setFilters] = useState<IntegrationTag | null>(null);
1718

18-
const filteredIntegrations = integrations.filter((integration) => {
19+
const filteredByTag = integrations.filter((integration) => {
20+
const {tags} = integration.frontmatter;
21+
return filters === null || tags.some((tag) => filters === tag);
22+
});
23+
24+
const filteredIntegrations = filteredByTag.filter((integration) => {
1925
return integration.frontmatter.name.toLowerCase().includes(searchQuery.toLowerCase());
2026
});
2127

@@ -30,10 +36,18 @@ export const MarketplaceHome = (props: Props) => {
3036
placeholder="Search for integrations"
3137
icon="search"
3238
/>
33-
<Box flex={{direction: 'row', gap: 12, alignItems: 'center'}}>
39+
<Box
40+
flex={{direction: 'row', gap: 12, alignItems: 'center', wrap: 'wrap'}}
41+
margin={{bottom: 12}}
42+
>
3443
<div style={{fontSize: 16}}>Filters</div>
3544
{Object.values(IntegrationTag).map((tag) => (
36-
<Button key={tag} icon={<Icon name={IntegrationTagIcon[tag]} />}>
45+
<Button
46+
key={tag}
47+
icon={<Icon name={IntegrationTagIcon[tag]} />}
48+
onClick={() => setFilters(filters === tag ? null : tag)}
49+
style={{backgroundColor: filters === tag ? Colors.backgroundBlue() : 'transparent'}}
50+
>
3751
{IntegrationTagLabel[tag]}
3852
</Button>
3953
))}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {Box, Icon} from '@dagster-io/ui-components';
2+
import {Link} from 'react-router-dom';
3+
4+
import styles from './css/MarketplaceLink.module.css';
5+
6+
export const MarketplaceLink = () => {
7+
return (
8+
<Link className={styles.marketplaceLink} to="/integrations">
9+
<Box
10+
flex={{direction: 'row', alignItems: 'center', gap: 8, justifyContent: 'center'}}
11+
className={styles.marketplaceLinkContent}
12+
>
13+
<Icon name="compute_kind" />
14+
Integrations
15+
</Box>
16+
</Link>
17+
);
18+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {Box, Colors, Page} from '@dagster-io/ui-components';
2+
3+
import {MarketplaceHome} from './MarketplaceHome';
4+
import * as allIntegrations from './__generated__';
5+
6+
export const MarketplaceRoot = () => {
7+
const integrations = Object.values(allIntegrations);
8+
return (
9+
<Page style={{backgroundColor: Colors.backgroundLight()}}>
10+
<Box
11+
padding={{vertical: 32}}
12+
style={{width: '80vw', maxWidth: '1200px', minWidth: '800px', margin: '0 auto'}}
13+
>
14+
<MarketplaceHome integrations={integrations} />
15+
</Box>
16+
</Page>
17+
);
18+
};

0 commit comments

Comments
 (0)