Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui] Add tabs to evaluation dialog #26960

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
</>
);
};
Loading