Skip to content

Commit

Permalink
[2/n] add frontend code link protocol customization in user settings (#…
Browse files Browse the repository at this point in the history
…21467)

## Summary

Adds a new user settings option that allows users to specify the protocol to use for links to the editor. Defaults to VSCode but also allows custom protocols to be specified.

![Capture-2024-04-26-154042](https://github.com/dagster-io/dagster/assets/10215173/8e1bf2a9-119f-4913-ad61-19522e02fa2d)
  • Loading branch information
benpankow authored May 8, 2024
1 parent f8ba248 commit 0d61b46
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 33 deletions.
41 changes: 22 additions & 19 deletions js_modules/dagster-ui/packages/ui-core/src/app/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {migrateLocalStorageKeys} from './migrateLocalStorageKeys';
import {TimeProvider} from './time/TimeContext';
import {AssetLiveDataProvider} from '../asset-data/AssetLiveDataProvider';
import {AssetRunLogObserver} from '../asset-graph/AssetRunLogObserver';
import {CodeLinkProtocolProvider} from '../code-links/CodeLinkProtocol';
import {DeploymentStatusProvider, DeploymentStatusType} from '../instance/DeploymentStatusProvider';
import {InstancePageContext} from '../instance/InstancePageContext';
import {PerformancePageNavigationListener} from '../performance';
Expand Down Expand Up @@ -139,25 +140,27 @@ export const AppProvider = (props: AppProviderProps) => {
<CompatRouter>
<PerformancePageNavigationListener />
<TimeProvider>
<WorkspaceProvider>
<DeploymentStatusProvider include={statusPolling}>
<CustomConfirmationProvider>
<AnalyticsContext.Provider value={analytics}>
<InstancePageContext.Provider value={instancePageValue}>
<JobFeatureProvider>
<LayoutProvider>
<DagsterPlusLaunchPromotion />
{props.children}
</LayoutProvider>
</JobFeatureProvider>
</InstancePageContext.Provider>
</AnalyticsContext.Provider>
</CustomConfirmationProvider>
<CustomTooltipProvider />
<CustomAlertProvider />
<AssetRunLogObserver />
</DeploymentStatusProvider>
</WorkspaceProvider>
<CodeLinkProtocolProvider>
<WorkspaceProvider>
<DeploymentStatusProvider include={statusPolling}>
<CustomConfirmationProvider>
<AnalyticsContext.Provider value={analytics}>
<InstancePageContext.Provider value={instancePageValue}>
<JobFeatureProvider>
<LayoutProvider>
<DagsterPlusLaunchPromotion />
{props.children}
</LayoutProvider>
</JobFeatureProvider>
</InstancePageContext.Provider>
</AnalyticsContext.Provider>
</CustomConfirmationProvider>
<CustomTooltipProvider />
<CustomAlertProvider />
<AssetRunLogObserver />
</DeploymentStatusProvider>
</WorkspaceProvider>
</CodeLinkProtocolProvider>
</TimeProvider>
</CompatRouter>
</BrowserRouter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import {
Box,
Button,
Checkbox,
Colors,
Dialog,
DialogBody,
DialogFooter,
Icon,
Subheading,
Tooltip,
} from '@dagster-io/ui-components';
import * as React from 'react';

import {UserPreferences} from './UserPreferences';
import {CodeLinkProtocolSelect} from '../../code-links/CodeLinkProtocol';
import {FeatureFlagType, getFeatureFlags, setFeatureFlags} from '../Flags';

type OnCloseFn = (event: React.SyntheticEvent<HTMLElement>) => void;
Expand Down Expand Up @@ -71,6 +75,52 @@ const UserSettingsDialogContent = ({onClose, visibleFlags}: DialogContentProps)
}
};

const experimentalSettings = visibleFlags.map(({key, label, flagType}) => (
<Box
padding={{vertical: 8}}
flex={{justifyContent: 'space-between', alignItems: 'center'}}
key={key}
>
<div>{label || key}</div>
<Checkbox
format="switch"
checked={flags.includes(flagType)}
onChange={() => toggleFlag(flagType)}
/>
</Box>
));

experimentalSettings.push(
<Box
padding={{vertical: 8}}
flex={{
justifyContent: 'space-between',
alignItems: 'flex-start',
direction: 'column',
gap: 8,
}}
key="code-link"
>
<Box flex={{direction: 'row', gap: 4, alignItems: 'center'}}>
Editor link protocol
<Tooltip
content={
<>
URL protocol to use when linking to definitions in code
<br /> <br />
{'{'}FILE{'}'} and {'{'}LINE{'}'} replaced by filepath and line
<br />
number, respectively
</>
}
>
<Icon name="info" color={Colors.accentGray()} />
</Tooltip>
</Box>
<CodeLinkProtocolSelect />
</Box>,
);

return (
<>
<DialogBody>
Expand All @@ -81,20 +131,7 @@ const UserSettingsDialogContent = ({onClose, visibleFlags}: DialogContentProps)
<Box padding={{bottom: 8}}>
<Subheading>Experimental features</Subheading>
</Box>
{visibleFlags.map(({key, label, flagType}) => (
<Box
padding={{vertical: 8}}
flex={{justifyContent: 'space-between', alignItems: 'center'}}
key={key}
>
<div>{label || key}</div>
<Checkbox
format="switch"
checked={flags.includes(flagType)}
onChange={() => toggleFlag(flagType)}
/>
</Box>
))}
{experimentalSettings}
</Box>
</DialogBody>
<DialogFooter topBorder>
Expand Down
18 changes: 18 additions & 0 deletions js_modules/dagster-ui/packages/ui-core/src/code-links/CodeLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {ExternalAnchorButton} from '@dagster-io/ui-components/src/components/Button';
import {Icon} from '@dagster-io/ui-components/src/components/Icon';
import * as React from 'react';

import {CodeLinkProtocolContext} from './CodeLinkProtocol';

export const CodeLink = ({file, lineNumber}: {file: string; lineNumber: number}) => {
const [codeLinkProtocol, _] = React.useContext(CodeLinkProtocolContext);

const codeLink = codeLinkProtocol.protocol
.replace('{FILE}', file)
.replace('{LINE}', lineNumber.toString());
return (
<ExternalAnchorButton icon={<Icon name="open_in_new" />} href={codeLink}>
Open in editor
</ExternalAnchorButton>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
Box,
Button,
CaptionMono,
Icon,
Menu,
MenuItem,
Select,
TextInput,
} from '@dagster-io/ui-components';
import * as React from 'react';

import {useStateWithStorage} from '../hooks/useStateWithStorage';

export const CodeLinkProtocolKey = 'CodeLinkProtocolPreference';

const POPULAR_PROTOCOLS: {[name: string]: string} = {
'vscode://file/{FILE}:{LINE}': 'Visual Studio Code',
'': 'Custom',
};

const DEFAULT_PROTOCOL = {protocol: Object.keys(POPULAR_PROTOCOLS)[0]!, custom: false};

type ProtocolData = {
protocol: string;
custom: boolean;
};

export const CodeLinkProtocolContext = React.createContext<
[ProtocolData, React.Dispatch<React.SetStateAction<ProtocolData | undefined>>]
>([DEFAULT_PROTOCOL, () => '']);

export const CodeLinkProtocolProvider = ({children}: {children: React.ReactNode}) => {
const state = useStateWithStorage<ProtocolData>(
CodeLinkProtocolKey,
(x) => x ?? DEFAULT_PROTOCOL,
);

return (
<CodeLinkProtocolContext.Provider value={state}>{children}</CodeLinkProtocolContext.Provider>
);
};

export const CodeLinkProtocolSelect = ({}) => {
const [codeLinkProtocol, setCodeLinkProtocol] = React.useContext(CodeLinkProtocolContext);
const isCustom = codeLinkProtocol.custom;

return (
<Box
flex={{direction: 'column', gap: 4, alignItems: 'stretch'}}
style={{width: 225, height: 55}}
>
<Select<string>
popoverProps={{
position: 'bottom-left',
modifiers: {offset: {enabled: true, offset: '-12px, 8px'}},
}}
activeItem={isCustom ? '' : codeLinkProtocol.protocol}
inputProps={{style: {width: '300px'}}}
items={Object.keys(POPULAR_PROTOCOLS)}
itemPredicate={(query: string, protocol: string) =>
protocol.toLowerCase().includes(query.toLowerCase())
}
itemRenderer={(protocol: string, props: any) => (
<MenuItem
active={props.modifiers.active}
onClick={props.handleClick}
label={protocol}
key={protocol}
text={POPULAR_PROTOCOLS[protocol]}
/>
)}
itemListRenderer={({renderItem, filteredItems}) => {
const renderedItems = filteredItems.map(renderItem).filter(Boolean);
return <Menu>{renderedItems}</Menu>;
}}
noResults={<MenuItem disabled text="No results." />}
onItemSelect={(protocol: string) =>
setCodeLinkProtocol({protocol, custom: protocol === ''})
}
>
<Button rightIcon={<Icon name="expand_more" />} style={{width: '225px'}}>
<div style={{width: '225px', textAlign: 'left'}}>
{isCustom ? 'Custom' : POPULAR_PROTOCOLS[codeLinkProtocol.protocol]}
</div>
</Button>
</Select>
{isCustom ? (
<TextInput
value={codeLinkProtocol.protocol}
onChange={(e) =>
setCodeLinkProtocol({
protocol: e.target.value,
custom: true,
})
}
placeholder="protocol://{FILE}:{LINE}"
/>
) : (
<Box padding={{left: 8, top: 2}}>
<CaptionMono>{codeLinkProtocol.protocol}</CaptionMono>
</Box>
)}
</Box>
);
};

1 comment on commit 0d61b46

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for dagit-core-storybook ready!

✅ Preview
https://dagit-core-storybook-chxzsy5ii-elementl.vercel.app

Built with commit 0d61b46.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.