Skip to content

Commit

Permalink
[ui] Show run queue criteria on Run page header (#22778)
Browse files Browse the repository at this point in the history
## Summary & Motivation

Add "View queue criteria" to the overflow menu on the Run detail page
header. This reuses the existing dialog from the Runs page, with a bit
of refactoring:

- Strip down what the dialog requires from the `Run` object
- Query for the values needed for the dialog, which allows us to reuse
the component without needing the exact same fragment object in both
places.
- More context for the loading state
- An empty state when the dialog query fails

Overflow menu item:

<img width="235" alt="Screenshot 2024-06-28 at 15 35 58"
src="https://github.com/dagster-io/dagster/assets/2823852/c11bfdcc-861a-41cc-971e-961cb75a69ab">

Loading state:

<img width="735" alt="Screenshot 2024-06-28 at 16 30 29"
src="https://github.com/dagster-io/dagster/assets/2823852/8c3c03c6-806e-419d-bc6f-d1808fc04f5b">

Error state:

<img width="739" alt="Screenshot 2024-06-28 at 16 30 43"
src="https://github.com/dagster-io/dagster/assets/2823852/67f85024-1619-4037-b1a1-6ab388310a42">

## How I Tested These Changes

Queue some runs, verify that the dialog works from the Runs page and
from the Run detail header.

Verify that the menu item is not present in the run is not queued.
  • Loading branch information
hellendag authored Jun 29, 2024
1 parent e8ffab8 commit bb69f3f
Show file tree
Hide file tree
Showing 14 changed files with 112 additions and 40 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export const OverviewSensors = () => {
data: cachedData,
} = useContext(WorkspaceContext);

const repoCount = allRepos.length;
const [searchValue, setSearchValue] = useQueryPersistedState<string>({
queryKey: 'search',
defaults: {search: ''},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {useQuery} from '@apollo/client';
import {
Box,
Button,
Colors,
Dialog,
DialogFooter,
Icon,
Mono,
Spinner,
NonIdealState,
SpinnerWithText,
Table,
Tag,
Tooltip,
Expand All @@ -16,25 +17,29 @@ import * as React from 'react';
import {Link} from 'react-router-dom';
import * as yaml from 'yaml';

import {RunTableRunFragment} from './types/RunTable.types';
import {QUEUED_RUN_CRITERIA_QUERY} from './QueuedRunCriteriaQuery';
import {
QueuedRunCriteriaQuery,
QueuedRunCriteriaQueryVariables,
} from './types/QueuedRunCriteriaQuery.types';
import {RunFragment} from './types/RunFragments.types';
import {useRunQueueConfig} from '../instance/useRunQueueConfig';
import {StructuredContentTable} from '../metadata/MetadataEntry';
import {numberFormatter} from '../ui/formatters';

type TagConcurrencyLimit = {
key: string;
value?: string;
limit: number;
};

export const QueuedRunCriteriaDialog = ({
isOpen,
onClose,
run,
}: {
interface DialogProps extends ContentProps {
isOpen: boolean;
onClose: () => void;
run: RunTableRunFragment;
}) => {
}

export const QueuedRunCriteriaDialog = (props: DialogProps) => {
const {isOpen, onClose, run} = props;
return (
<Dialog
isOpen={isOpen}
Expand All @@ -54,8 +59,22 @@ export const QueuedRunCriteriaDialog = ({
);
};

const QueuedRunCriteriaDialogContent = ({run}: {run: RunTableRunFragment}) => {
interface ContentProps {
run: Pick<RunFragment, 'id' | 'tags'>;
}

const QueuedRunCriteriaDialogContent = ({run}: ContentProps) => {
const runQueueConfig = useRunQueueConfig();

const {data, loading} = useQuery<QueuedRunCriteriaQuery, QueuedRunCriteriaQueryVariables>(
QUEUED_RUN_CRITERIA_QUERY,
{
variables: {
runId: run.id,
},
},
);

const runTagMap = Object.fromEntries(run.tags.map(({key, value}) => [key, value]));
const maxConcurrentRuns = runQueueConfig?.maxConcurrentRuns;
const runTagLimits = React.useMemo(() => {
Expand All @@ -77,20 +96,34 @@ const QueuedRunCriteriaDialogContent = ({run}: {run: RunTableRunFragment}) => {
}
}, [runQueueConfig, runTagMap]);

if (!runQueueConfig) {
if (!runQueueConfig || loading) {
return (
<Box padding={32} flex={{direction: 'row', justifyContent: 'center'}}>
<SpinnerWithText label="Loading run queue criteria…" />
</Box>
);
}

if (!data || data.runOrError.__typename !== 'Run') {
return (
<Box padding={32}>
<Spinner purpose="section" />
<Box padding={32} flex={{direction: 'row', justifyContent: 'center'}}>
<NonIdealState
icon="run"
title="Queue criteria not found"
description="Could not load queue criteria for this run."
/>
</Box>
);
}

const {rootConcurrencyKeys, hasUnconstrainedRootNodes} = data.runOrError;

const priority = runTagMap['dagster/priority'];
const runIsOpConcurrencyLimited =
runQueueConfig?.isOpConcurrencyAware &&
run.rootConcurrencyKeys &&
run.rootConcurrencyKeys.length > 0 &&
!run.hasUnconstrainedRootNodes;
rootConcurrencyKeys &&
rootConcurrencyKeys.length > 0 &&
!hasUnconstrainedRootNodes;

return (
<Table>
Expand All @@ -104,9 +137,7 @@ const QueuedRunCriteriaDialogContent = ({run}: {run: RunTableRunFragment}) => {
{maxConcurrentRuns !== undefined ? (
<tr>
<td>Max concurrent runs</td>
<td>
<Mono>{maxConcurrentRuns}</Mono>
</td>
<td>{numberFormatter.format(maxConcurrentRuns)}</td>
</tr>
) : null}
{runTagLimits?.length ? (
Expand Down Expand Up @@ -154,7 +185,7 @@ const QueuedRunCriteriaDialogContent = ({run}: {run: RunTableRunFragment}) => {
</Box>
</td>
<td>
{run.rootConcurrencyKeys!.map((key, i) =>
{rootConcurrencyKeys!.map((key, i) =>
runQueueConfig ? (
<Tag interactive key={`rootConcurrency:${i}`}>
<Link to={`/concurrency?key=${key}`}>{key}</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {gql} from '@apollo/client';

export const QUEUED_RUN_CRITERIA_QUERY = gql`
query QueuedRunCriteriaQuery($runId: ID!) {
runOrError(runId: $runId) {
... on Run {
id
rootConcurrencyKeys
hasUnconstrainedRootNodes
}
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {useContext, useState} from 'react';
import {useHistory} from 'react-router-dom';

import {DeletionDialog} from './DeletionDialog';
import {QueuedRunCriteriaDialog} from './QueuedRunCriteriaDialog';
import {RunConfigDialog} from './RunConfigDialog';
import {doneStatuses} from './RunStatuses';
import {DagsterTag} from './RunTag';
Expand All @@ -14,6 +15,7 @@ import {AppContext} from '../app/AppContext';
import {showSharedToaster} from '../app/DomUtils';
import {InjectedComponentContext} from '../app/InjectedComponentContext';
import {useCopyToClipboard} from '../app/browser';
import {RunStatus} from '../graphql/types';
import {FREE_CONCURRENCY_SLOTS_MUTATION} from '../instance/InstanceConcurrency';
import {
FreeConcurrencySlotsMutation,
Expand All @@ -22,7 +24,14 @@ import {
import {AnchorButton} from '../ui/AnchorButton';
import {workspacePipelineLinkForRun, workspacePipelinePath} from '../workspace/workspacePath';

type VisibleDialog = 'config' | 'delete' | 'terminate' | 'free_slots' | 'metrics' | null;
type VisibleDialog =
| 'config'
| 'delete'
| 'terminate'
| 'queue-criteria'
| 'free_slots'
| 'metrics'
| null;

export const RunHeaderActions = ({run, isJob}: {run: RunFragment; isJob: boolean}) => {
const {runConfigYaml} = run;
Expand Down Expand Up @@ -101,6 +110,15 @@ export const RunHeaderActions = ({run, isJob}: {run: RunFragment; isJob: boolean
onClick={() => window.open(`${rootServerURI}/download_debug/${run.id}`)}
/>
</Tooltip>
{run.status === RunStatus.QUEUED ? (
<MenuItem
tagName="button"
icon="history_toggle_off"
text="View queue criteria"
intent="none"
onClick={() => setVisibleDialog('queue-criteria')}
/>
) : null}
{runMetricsEnabled && RunMetricsDialog ? (
<MenuItem
tagName="button"
Expand Down Expand Up @@ -140,6 +158,13 @@ export const RunHeaderActions = ({run, isJob}: {run: RunFragment; isJob: boolean
tags={run.tags}
isJob={isJob}
/>
{run.status === RunStatus.QUEUED ? (
<QueuedRunCriteriaDialog
run={run}
isOpen={visibleDialog === 'queue-criteria'}
onClose={() => setVisibleDialog(null)}
/>
) : null}
{runMetricsEnabled && RunMetricsDialog ? (
<RunMetricsDialog
runId={run.id}
Expand Down
2 changes: 0 additions & 2 deletions js_modules/dagster-ui/packages/ui-core/src/runs/RunTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,6 @@ export const RUN_TABLE_RUN_FRAGMENT = gql`
...RunTagsFragment
}
...RunTimeFragment
rootConcurrencyKeys
hasUnconstrainedRootNodes
}
${RUN_TIME_FRAGMENT}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type RowType =
};

export const SectionedLeftNav = () => {
const {loading, visibleRepos} = React.useContext(WorkspaceContext);
const {visibleRepos} = React.useContext(WorkspaceContext);
const {basePath} = React.useContext(AppContext);
const parentRef = React.useRef<HTMLDivElement | null>(null);

Expand Down

1 comment on commit bb69f3f

@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-g8d6cqlz0-elementl.vercel.app

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

Please sign in to comment.