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] Open evaluation detail dialog from automaterialize tag #26963

Merged
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 @@ -3,12 +3,13 @@ import {
Button,
Dialog,
DialogFooter,
DialogHeader,
Icon,
Mono,
NonIdealState,
SpinnerWithText,
Tag,
} from '@dagster-io/ui-components';
import {ReactNode, useState} from 'react';
import {ReactNode, useMemo, useState} from 'react';

import {GET_SLIM_EVALUATIONS_QUERY} from './GetEvaluationsQuery';
import {PartitionTagSelector} from './PartitionTagSelector';
Expand All @@ -21,6 +22,7 @@ import {usePartitionsForAssetKey} from './usePartitionsForAssetKey';
import {useQuery} from '../../apollo-client';
import {DEFAULT_TIME_FORMAT} from '../../app/time/TimestampFormat';
import {TimestampDisplay} from '../../schedules/TimestampDisplay';
import {AnchorButton} from '../../ui/AnchorButton';

interface Props {
isOpen: boolean;
Expand Down Expand Up @@ -81,10 +83,24 @@ const EvaluationDetailDialogContents = ({
const {partitions: allPartitions, loading: partitionsLoading} =
usePartitionsForAssetKey(assetKeyPath);

const viewAllPath = useMemo(() => {
// todo dish: I don't think the asset check evaluations list is permalinkable yet.
if (assetCheckName) {
return null;
}

const queryString = new URLSearchParams({
view: 'automation',
evaluation: evaluationID,
}).toString();

return `/assets/${assetKeyPath.join('/')}?${queryString}`;
}, [assetCheckName, evaluationID, assetKeyPath]);

if (loading || partitionsLoading) {
return (
<DialogContents
header={<DialogHeader icon="automation" label="Evaluation details" />}
header={<DialogHeader assetKeyPath={assetKeyPath} assetCheckName={assetCheckName} />}
body={
<Box padding={{top: 64}} flex={{direction: 'row', justifyContent: 'center'}}>
<SpinnerWithText label="Loading evaluation details..." />
Expand All @@ -100,7 +116,7 @@ const EvaluationDetailDialogContents = ({
if (record?.__typename === 'AutoMaterializeAssetEvaluationNeedsMigrationError') {
return (
<DialogContents
header={<DialogHeader icon="automation" label="Evaluation details" />}
header={<DialogHeader assetKeyPath={assetKeyPath} assetCheckName={assetCheckName} />}
body={
<Box margin={{top: 64}}>
<NonIdealState
Expand All @@ -120,7 +136,7 @@ const EvaluationDetailDialogContents = ({
if (!evaluation) {
return (
<DialogContents
header={<DialogHeader icon="automation" label="Evaluation details" />}
header={<DialogHeader assetKeyPath={assetKeyPath} assetCheckName={assetCheckName} />}
body={
<Box margin={{top: 64}}>
<NonIdealState
Expand All @@ -144,16 +160,9 @@ const EvaluationDetailDialogContents = ({
header={
<>
<DialogHeader
icon="automation"
label={
<div>
Evaluation details:{' '}
<TimestampDisplay
timestamp={evaluation.timestamp}
timeFormat={{...DEFAULT_TIME_FORMAT, showSeconds: true}}
/>
</div>
}
assetKeyPath={assetKeyPath}
assetCheckName={assetCheckName}
timestamp={evaluation.timestamp}
/>
{allPartitions.length > 0 && evaluation.isLegacy ? (
<Box padding={{vertical: 12, right: 20}} flex={{justifyContent: 'flex-end'}}>
Expand All @@ -174,25 +183,76 @@ const EvaluationDetailDialogContents = ({
setSelectedPartition={setSelectedPartition}
/>
}
viewAllButton={
viewAllPath ? (
<AnchorButton to={viewAllPath} icon={<Icon name="automation_condition" />}>
View evaluations for this asset
</AnchorButton>
) : null
}
onDone={onClose}
/>
);
};

const DialogHeader = ({
assetKeyPath,
assetCheckName,
timestamp,
}: {
assetKeyPath: string[];
assetCheckName?: string;
timestamp?: number;
}) => {
const assetKeyPathString = assetKeyPath.join('/');
const assetDetailsTag = assetCheckName ? (
<Tag icon="asset_check">
{assetCheckName} on {assetKeyPathString}
</Tag>
) : (
<Tag icon="asset">{assetKeyPathString}</Tag>
);

const timestampDisplay = timestamp ? (
<TimestampDisplay
timestamp={timestamp}
timeFormat={{...DEFAULT_TIME_FORMAT, showSeconds: true}}
/>
) : null;

return (
<Box
padding={{vertical: 16, horizontal: 20}}
flex={{direction: 'row', alignItems: 'center', justifyContent: 'space-between'}}
border="bottom"
>
<Box flex={{direction: 'row', alignItems: 'center', gap: 8}}>
<Icon name="automation" />
<strong>
<span>Evaluation details</span>
{timestampDisplay ? <span>: {timestampDisplay}</span> : ''}
</strong>
</Box>
{assetDetailsTag}
</Box>
);
};

interface BasicContentProps {
header: ReactNode;
body: ReactNode;
viewAllButton?: ReactNode;
onDone: () => void;
}

// Dialog contents for which the body container is scrollable and expands to fill the height.
const DialogContents = ({header, body, onDone}: BasicContentProps) => {
const DialogContents = ({header, body, onDone, viewAllButton}: BasicContentProps) => {
return (
<Box flex={{direction: 'column'}} style={{height: '100%'}}>
{header}
<div style={{flex: 1, overflowY: 'auto'}}>{body}</div>
<div style={{flexGrow: 0}}>
<DialogFooter topBorder>
<DialogFooter topBorder left={viewAllButton}>
<Button onClick={onDone}>Done</Button>
</DialogFooter>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,71 +1,89 @@
import {Box, Icon, MiddleTruncate, Popover, Tag} from '@dagster-io/ui-components';
import {useMemo} from 'react';
import {Link} from 'react-router-dom';
import {Box, ButtonLink, Icon, MiddleTruncate, Popover, Tag} from '@dagster-io/ui-components';
import {useMemo, useState} from 'react';

import {EvaluationDetailDialog} from './AutoMaterializePolicyPage/EvaluationDetailDialog';
import {assetDetailsPathForKey} from './assetDetailsPathForKey';
import {AssetKey} from './types';

const COLLATOR = new Intl.Collator(navigator.language, {sensitivity: 'base'});

type OpenEvaluation = {
assetKeyPath: string[];
evaluationId: string;
};

interface Props {
assetKeys: AssetKey[];
evaluationId: string;
}

export const AutomaterializeTagWithEvaluation = ({assetKeys, evaluationId}: Props) => {
const [openEvaluation, setOpenEvaluation] = useState<OpenEvaluation | null>(null);

const sortedKeys = useMemo(() => {
return [...assetKeys].sort((a, b) => COLLATOR.compare(a.path.join('/'), b.path.join('/')));
}, [assetKeys]);

return (
<Popover
placement="bottom"
content={
<div style={{width: '340px'}}>
<Box padding={{vertical: 8, horizontal: 12}} border="bottom" style={{fontWeight: 600}}>
Automation condition
</Box>
<Box
flex={{direction: 'column', gap: 12}}
padding={{vertical: 12}}
style={{maxHeight: '220px', overflowY: 'auto'}}
>
{sortedKeys.map((assetKey) => {
const url = assetDetailsPathForKey(assetKey, {
view: 'automation',
evaluation: evaluationId,
});
return (
<Box
key={url}
padding={{vertical: 8, left: 12, right: 16}}
flex={{
direction: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 8,
}}
style={{overflow: 'hidden'}}
>
<>
<Popover
placement="bottom"
content={
<div style={{width: '400px'}}>
<Box padding={{vertical: 8, horizontal: 12}} border="bottom" style={{fontWeight: 600}}>
Automation condition
</Box>
<Box
flex={{direction: 'column', gap: 16}}
padding={{vertical: 12}}
style={{maxHeight: '220px', overflowY: 'auto'}}
>
{sortedKeys.map((assetKey) => {
const url = assetDetailsPathForKey(assetKey, {
view: 'automation',
evaluation: evaluationId,
});
return (
<Box
flex={{direction: 'row', alignItems: 'center', gap: 8}}
key={url}
padding={{vertical: 8, left: 12, right: 16}}
flex={{
direction: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gap: 16,
}}
style={{overflow: 'hidden'}}
>
<Icon name="asset" />
<MiddleTruncate text={assetKey.path.join('/')} />
<Box
flex={{direction: 'row', alignItems: 'center', gap: 8}}
style={{overflow: 'hidden'}}
>
<Icon name="asset" />
<MiddleTruncate text={assetKey.path.join('/')} />
</Box>
<ButtonLink
onClick={() => setOpenEvaluation({assetKeyPath: assetKey.path, evaluationId})}
style={{whiteSpace: 'nowrap'}}
>
View evaluation
</ButtonLink>
</Box>
<Link to={url} style={{whiteSpace: 'nowrap'}}>
View evaluation
</Link>
</Box>
);
})}
</Box>
</div>
}
interactionKind="hover"
>
<Tag icon="automation_condition">Automation condition</Tag>
</Popover>
);
})}
</Box>
</div>
}
interactionKind="hover"
>
<Tag icon="automation_condition">Automation condition</Tag>
</Popover>
<EvaluationDetailDialog
assetKeyPath={openEvaluation?.assetKeyPath ?? []}
isOpen={!!openEvaluation}
onClose={() => setOpenEvaluation(null)}
evaluationID={openEvaluation?.evaluationId ?? ''}
/>
</>
);
};
Loading