Skip to content

Commit

Permalink
[ui] Add tabs to evaluation dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
hellendag committed Jan 9, 2025
1 parent 18fbec5 commit 9e22794
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
Mono,
NonIdealState,
SpinnerWithText,
Tab,
Tabs,
Tag,
} from '@dagster-io/ui-components';
import {ReactNode, useMemo, useState} from 'react';
Expand All @@ -21,15 +23,19 @@ import {
import {usePartitionsForAssetKey} from './usePartitionsForAssetKey';
import {useQuery} from '../../apollo-client';
import {DEFAULT_TIME_FORMAT} from '../../app/time/TimestampFormat';
import {RunsFeedTableWithFilters} from '../../runs/RunsFeedTable';
import {TimestampDisplay} from '../../schedules/TimestampDisplay';
import {AnchorButton} from '../../ui/AnchorButton';

export type Tab = 'evaluation' | 'runs';

interface Props {
isOpen: boolean;
onClose: () => void;
assetKeyPath: string[];
assetCheckName?: string;
evaluationID: string;
initialTab?: Tab;
}

export const EvaluationDetailDialog = ({
Expand All @@ -38,6 +44,7 @@ export const EvaluationDetailDialog = ({
evaluationID,
assetKeyPath,
assetCheckName,
initialTab = 'evaluation',
}: Props) => {
return (
<Dialog isOpen={isOpen} onClose={onClose} style={EvaluationDetailDialogStyle}>
Expand All @@ -46,6 +53,7 @@ export const EvaluationDetailDialog = ({
assetKeyPath={assetKeyPath}
assetCheckName={assetCheckName}
onClose={onClose}
initialTab={initialTab}
/>
</Dialog>
);
Expand All @@ -56,15 +64,18 @@ interface ContentProps {
assetKeyPath: string[];
assetCheckName?: string;
onClose: () => void;
initialTab?: Tab;
}

const EvaluationDetailDialogContents = ({
evaluationID,
assetKeyPath,
assetCheckName,
onClose,
initialTab = 'evaluation',
}: ContentProps) => {
const [selectedPartition, setSelectedPartition] = useState<string | null>(null);
const [tabId, setTabId] = useState<Tab>(initialTab);

const {data, loading} = useQuery<GetSlimEvaluationsQuery, GetSlimEvaluationsQueryVariables>(
GET_SLIM_EVALUATIONS_QUERY,
Expand Down Expand Up @@ -101,6 +112,8 @@ const EvaluationDetailDialogContents = ({
return (
<DialogContents
header={<DialogHeader assetKeyPath={assetKeyPath} assetCheckName={assetCheckName} />}
selectedTabId={tabId}
onTabChange={setTabId}
body={
<Box padding={{top: 64}} flex={{direction: 'row', justifyContent: 'center'}}>
<SpinnerWithText label="Loading evaluation details..." />
Expand All @@ -117,6 +130,8 @@ const EvaluationDetailDialogContents = ({
return (
<DialogContents
header={<DialogHeader assetKeyPath={assetKeyPath} assetCheckName={assetCheckName} />}
selectedTabId={tabId}
onTabChange={setTabId}
body={
<Box margin={{top: 64}}>
<NonIdealState
Expand All @@ -137,6 +152,9 @@ const EvaluationDetailDialogContents = ({
return (
<DialogContents
header={<DialogHeader assetKeyPath={assetKeyPath} assetCheckName={assetCheckName} />}
selectedTabId={tabId}
onTabChange={setTabId}
onDone={onClose}
body={
<Box margin={{top: 64}}>
<NonIdealState
Expand All @@ -150,13 +168,18 @@ const EvaluationDetailDialogContents = ({
/>
</Box>
}
onDone={onClose}
/>
);
}

const {runIds} = evaluation;

return (
<DialogContents
onTabChange={setTabId}
runCount={runIds.length}
selectedTabId={tabId}
onDone={onClose}
header={
<>
<DialogHeader
Expand All @@ -165,7 +188,7 @@ const EvaluationDetailDialogContents = ({
timestamp={evaluation.timestamp}
/>
{allPartitions.length > 0 && evaluation.isLegacy ? (
<Box padding={{vertical: 12, right: 20}} flex={{justifyContent: 'flex-end'}}>
<Box padding={{vertical: 12, horizontal: 20}} flex={{justifyContent: 'flex-end'}}>
<PartitionTagSelector
allPartitions={allPartitions}
selectedPartition={selectedPartition}
Expand All @@ -176,12 +199,19 @@ const EvaluationDetailDialogContents = ({
</>
}
body={
<QueryfulEvaluationDetailTable
evaluation={evaluation}
assetKeyPath={assetKeyPath}
selectedPartition={selectedPartition}
setSelectedPartition={setSelectedPartition}
/>
tabId === 'evaluation' ? (
<QueryfulEvaluationDetailTable
evaluation={evaluation}
assetKeyPath={assetKeyPath}
selectedPartition={selectedPartition}
setSelectedPartition={setSelectedPartition}
/>
) : (
<RunsFeedTableWithFilters
filter={{runIds: evaluation.runIds}}
includeRunsFromBackfills={true}
/>
)
}
viewAllButton={
viewAllPath ? (
Expand All @@ -190,7 +220,6 @@ const EvaluationDetailDialogContents = ({
</AnchorButton>
) : null
}
onDone={onClose}
/>
);
};
Expand Down Expand Up @@ -242,14 +271,43 @@ interface BasicContentProps {
header: ReactNode;
body: ReactNode;
viewAllButton?: ReactNode;
selectedTabId: Tab;
onTabChange: (tabId: Tab) => void;
onDone: () => void;
runCount?: number;
}

// Dialog contents for which the body container is scrollable and expands to fill the height.
const DialogContents = ({header, body, onDone, viewAllButton}: BasicContentProps) => {
const DialogContents = ({
header,
body,
selectedTabId,
onTabChange,
runCount = 0,
viewAllButton,
onDone,
}: BasicContentProps) => {
return (
<Box flex={{direction: 'column'}} style={{height: '100%'}}>
{header}
<Box padding={{horizontal: 20}} border="bottom">
<Tabs selectedTabId={selectedTabId} onChange={onTabChange}>
<Tab id="evaluation" title="Evaluation" />
<Tab
id="runs"
title={
runCount > 0 ? (
<span>
Runs <span style={{fontVariantNumeric: 'tabular-nums'}}>({runCount})</span>
</span>
) : (
'Runs'
)
}
disabled={runCount === 0}
/>
</Tabs>
</Box>
<div style={{flex: 1, overflowY: 'auto'}}>{body}</div>
<div style={{flexGrow: 0}}>
<DialogFooter topBorder left={viewAllButton}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import {
Box,
Button,
ButtonLink,
Colors,
Dialog,
DialogFooter,
DialogHeader,
Mono,
} from '@dagster-io/ui-components';
import {ButtonLink, Colors} from '@dagster-io/ui-components';
import {useState} from 'react';
import {Link} from 'react-router-dom';

import {AssetKey} from '../types';
import {EvaluationDetailDialog} from './EvaluationDetailDialog';
import {EvaluationDetailDialog, Tab} from './EvaluationDetailDialog';
import {EvaluationStatusTag} from './EvaluationStatusTag';
import {AssetConditionEvaluationRecordFragment} from './types/GetEvaluationsQuery.types';
import {DEFAULT_TIME_FORMAT} from '../../app/time/TimestampFormat';
import {RunsFeedTableWithFilters} from '../../runs/RunsFeedTable';
import {TimestampDisplay} from '../../schedules/TimestampDisplay';

interface Props {
Expand All @@ -28,12 +17,18 @@ interface Props {

export const EvaluationListRow = ({evaluation, assetKey, assetCheckName, isPartitioned}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [tab, setTab] = useState<Tab>('evaluation');

return (
<>
<tr>
<td style={{verticalAlign: 'middle'}}>
<ButtonLink onClick={() => setIsOpen(true)}>
<ButtonLink
onClick={() => {
setTab('evaluation');
setIsOpen(true);
}}
>
<TimestampDisplay
timestamp={evaluation.timestamp}
timeFormat={{...DEFAULT_TIME_FORMAT, showSeconds: true}}
Expand All @@ -49,7 +44,18 @@ export const EvaluationListRow = ({evaluation, assetKey, assetCheckName, isParti
/>
</td>
<td style={{verticalAlign: 'middle'}}>
<EvaluationRunInfo runIds={evaluation.runIds} timestamp={evaluation.timestamp} />
{evaluation.runIds.length > 0 ? (
<ButtonLink
onClick={() => {
setTab('runs');
setIsOpen(true);
}}
>
{evaluation.runIds.length > 1 ? `${evaluation.runIds.length} runs` : '1 run'}
</ButtonLink>
) : (
<span style={{color: Colors.textDisabled()}}>None</span>
)}
</td>
</tr>
<EvaluationDetailDialog
Expand All @@ -58,78 +64,8 @@ export const EvaluationListRow = ({evaluation, assetKey, assetCheckName, isParti
evaluationID={evaluation.evaluationId}
assetKeyPath={assetKey.path}
assetCheckName={assetCheckName}
initialTab={tab}
/>
</>
);
};

interface EvaluationRunInfoProps {
runIds: string[];
timestamp: number;
}

const EvaluationRunInfo = ({runIds, timestamp}: EvaluationRunInfoProps) => {
const [isOpen, setIsOpen] = useState(false);
const firstRun = runIds[0];

if (!firstRun) {
return <span style={{color: Colors.textDisabled()}}>None</span>;
}

if (runIds.length === 1) {
const truncated = firstRun.slice(0, 8);

// This looks like a backfill ID. Link there.
if (truncated === firstRun) {
return (
<Link to={`/runs/b/${firstRun}`}>
<Mono>{firstRun}</Mono>
</Link>
);
}

return (
<Link to={`/runs/${firstRun}`}>
<Mono>{truncated}</Mono>
</Link>
);
}

return (
<>
<ButtonLink onClick={() => setIsOpen(true)}>{runIds.length} runs</ButtonLink>
<Dialog
isOpen={isOpen}
onClose={() => setIsOpen(false)}
style={{
width: '80vw',
maxWidth: '1400px',
minWidth: '800px',
height: '80vh',
minHeight: '400px',
maxHeight: '1400px',
}}
>
<Box flex={{direction: 'column'}} style={{height: '100%'}}>
<DialogHeader
label={
<>
Runs at{' '}
<TimestampDisplay
timestamp={timestamp}
timeFormat={{...DEFAULT_TIME_FORMAT, showSeconds: true}}
/>
</>
}
/>
<div style={{flex: 1, overflowY: 'auto'}}>
<RunsFeedTableWithFilters filter={{runIds}} includeRunsFromBackfills={true} />
</div>
<DialogFooter topBorder>
<Button onClick={() => setIsOpen(false)}>Done</Button>
</DialogFooter>
</Box>
</Dialog>
</>
);
};

0 comments on commit 9e22794

Please sign in to comment.