Skip to content

Commit

Permalink
[ui] New Evaluations list table
Browse files Browse the repository at this point in the history
  • Loading branch information
hellendag committed Nov 13, 2024
1 parent e500fa9 commit ab4f68f
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {Table} from '@dagster-io/ui-components';

import {EvaluationListRow} from './EvaluationListRow';
import {AssetViewDefinitionNodeFragment} from '../types/AssetView.types';
import {AssetConditionEvaluationRecordFragment} from './types/GetEvaluationsQuery.types';

interface Props {
definition: AssetViewDefinitionNodeFragment;
evaluations: AssetConditionEvaluationRecordFragment[];
}

export const EvaluationList = ({definition, evaluations}: Props) => {
return (
<Table>
<thead>
<tr>
<th>Timestamp</th>
<th style={{width: '240px'}}>Evaluation result</th>
<th style={{width: '240px'}}>Run(s)</th>
</tr>
</thead>
<tbody>
{evaluations.map((evaluation) => {
return (
<EvaluationListRow
key={evaluation.id}
evaluation={evaluation}
definition={definition}
/>
);
})}
</tbody>
</Table>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {
Box,
Button,
ButtonLink,
Colors,
Dialog,
DialogFooter,
DialogHeader,
Mono,
} from '@dagster-io/ui-components';
import {useState} from 'react';

import {EvaluationStatusTag} from './EvaluationStatusTag';
import {PolicyEvaluationTable} from './PolicyEvaluationTable';
import {AssetConditionEvaluationRecordFragment} from './types/GetEvaluationsQuery.types';
import {DEFAULT_TIME_FORMAT} from '../../app/time/TimestampFormat';
import {RunsFeedTableWithFilters} from '../../runs/RunsFeedTable';
import {TimestampDisplay} from '../../schedules/TimestampDisplay';
import {AssetViewDefinitionNodeFragment} from '../types/AssetView.types';

interface Props {
definition: AssetViewDefinitionNodeFragment;
evaluation: AssetConditionEvaluationRecordFragment;
}

export const EvaluationListRow = ({evaluation, definition}: Props) => {
const [isOpen, setIsOpen] = useState(false);
// const [selectedPartition, setSelectedPartition] = useState<string | null>(null);

return (
<>
<tr>
<td style={{verticalAlign: 'middle'}}>
<ButtonLink onClick={() => setIsOpen(true)}>
<TimestampDisplay
timestamp={evaluation.timestamp}
timeFormat={{...DEFAULT_TIME_FORMAT, showSeconds: true}}
/>
</ButtonLink>
</td>
<td style={{verticalAlign: 'middle'}}>
<EvaluationStatusTag
definition={definition}
selectedEvaluation={evaluation}
selectPartition={() => {}}
/>
</td>
<td style={{verticalAlign: 'middle'}}>
<EvaluationRunInfo evaluation={evaluation} />
</td>
</tr>
<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
icon="automation"
label={
<div>
Evaluation details:{' '}
<TimestampDisplay
timestamp={evaluation.timestamp}
timeFormat={{...DEFAULT_TIME_FORMAT, showSeconds: true}}
/>
</div>
}
/>
<div style={{flex: 1, overflowY: 'auto'}}>
<PolicyEvaluationTable
assetKeyPath={definition?.assetKey.path ?? null}
evaluationId={evaluation.evaluationId}
evaluationNodes={
!evaluation.isLegacy
? evaluation.evaluationNodes
: // : selectedPartition && specificPartitionData?.assetConditionEvaluationForPartition
// ? specificPartitionData.assetConditionEvaluationForPartition.evaluationNodes
evaluation.evaluation.evaluationNodes
}
isLegacyEvaluation={evaluation.isLegacy}
rootUniqueId={evaluation.evaluation.rootUniqueId}
// todo dish
selectPartition={() => {}}
/>
</div>
<div style={{flexGrow: 0}}>
<DialogFooter topBorder>
<Button onClick={() => setIsOpen(false)}>Done</Button>
</DialogFooter>
</div>
</Box>
</Dialog>
</>
);
};

const EvaluationRunInfo = ({evaluation}: {evaluation: AssetConditionEvaluationRecordFragment}) => {
const {runIds} = evaluation;
const [isOpen, setIsOpen] = useState(false);

if (runIds.length === 0) {
return <span style={{color: Colors.textDisabled()}}>None</span>;
}

if (runIds.length === 1) {
return (
<Box flex={{direction: 'row', gap: 4}}>
<Mono>{runIds[0]}</Mono>
</Box>
);
}

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={evaluation.timestamp}
timeFormat={{...DEFAULT_TIME_FORMAT, showSeconds: true}}
/>
</>
}
/>
<div style={{flex: 1, overflowY: 'auto'}}>
<RunsFeedTableWithFilters filter={{runIds}} />
</div>
<DialogFooter topBorder>
<Button onClick={() => setIsOpen(false)}>Done</Button>
</DialogFooter>
</Box>
</Dialog>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {Box, Colors, Icon, Popover, Tag} from '@dagster-io/ui-components';
import {useMemo} from 'react';

import {PartitionSubsetList} from './PartitionSubsetList';
import {AssetConditionEvaluationRecordFragment} from './types/GetEvaluationsQuery.types';
import {AssetViewDefinitionNodeFragment} from '../types/AssetView.types';

interface Props {
definition: AssetViewDefinitionNodeFragment;
selectedEvaluation: AssetConditionEvaluationRecordFragment;
selectPartition: (partitionKey: string | null) => void;
}

export const EvaluationStatusTag = ({definition, selectedEvaluation, selectPartition}: Props) => {
const evaluation = selectedEvaluation?.evaluation;
const rootEvaluationNode = useMemo(
() => evaluation?.evaluationNodes.find((node) => node.uniqueId === evaluation.rootUniqueId),
[evaluation],
);
const rootUniqueId = evaluation?.rootUniqueId;

const partitionDefinition = definition?.partitionDefinition;
const assetKeyPath = definition?.assetKey.path || [];
const numRequested = selectedEvaluation?.numRequested;

const numTrue =
rootEvaluationNode?.__typename === 'PartitionedAssetConditionEvaluationNode'
? rootEvaluationNode.numTrue
: null;

if (numRequested) {
if (partitionDefinition && rootUniqueId && numTrue) {
return (
<Box flex={{direction: 'row', gap: 4, alignItems: 'center'}}>
<Popover
interactionKind="hover"
placement="bottom"
hoverOpenDelay={50}
hoverCloseDelay={50}
content={
<PartitionSubsetList
description="Requested assets"
assetKeyPath={assetKeyPath}
evaluationId={selectedEvaluation.evaluationId}
nodeUniqueId={rootUniqueId}
selectPartition={selectPartition}
/>
}
>
<Tag intent="success">
<Box flex={{direction: 'row', gap: 4, alignItems: 'center'}}>
<Icon name="check_filled" color={Colors.accentGreen()} />
{numRequested} requested
</Box>
</Tag>
</Popover>
</Box>
);
}

return (
<Tag intent="success">
<Box flex={{direction: 'row', gap: 4, alignItems: 'center'}}>
<Icon name="check_filled" color={Colors.accentGreen()} />
Requested
</Box>
</Tag>
);
}

return (
<Tag>
<Box flex={{direction: 'row', gap: 4, alignItems: 'center'}}>
<Icon name="check_missing" color={Colors.accentGray()} />
Not requested
</Box>
</Tag>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import faker from 'faker';

import {
buildAssetConditionEvaluation,
buildAssetConditionEvaluationRecord,
buildAutomationConditionEvaluationNode,
} from '../../../graphql/types';

const ONE_MINUTE = 60 * 1000;

export const buildEvaluationRecordsForList = (length: number) => {
const now = Date.now();
const evaluationId = 100;
return new Array(length).fill(null).map((_, ii) => {
const evaluationNodes = new Array(30).fill(null).map((_, jj) => {
const id = faker.lorem.word();
return buildAutomationConditionEvaluationNode({
startTimestamp: 0 + jj,
endTimestamp: 10 + jj,
uniqueId: id,
userLabel: faker.lorem.word(),
isPartitioned: false,
numTrue: 0,
});
});

return buildAssetConditionEvaluationRecord({
id: `evaluation-${ii}`,
evaluationId: `${evaluationId + ii}`,
evaluation: buildAssetConditionEvaluation({
rootUniqueId: 'my-root',
}),
timestamp: (now - ONE_MINUTE * ii) / 1000,
numRequested: Math.random() > 0.5 ? 1 : 0,
runIds: Array.from({length: Math.floor(Math.random() * 5)}).map(() =>
faker.datatype.uuid().slice(0, 8),
),
isLegacy: false,
rootUniqueId: 'my-root',
evaluationNodes: [
buildAutomationConditionEvaluationNode({
startTimestamp: 0,
endTimestamp: 1000,
uniqueId: 'my-root',
userLabel: faker.lorem.word(),
isPartitioned: false,
numTrue: 0,
childUniqueIds: evaluationNodes.map((node) => node.uniqueId),
}),
...evaluationNodes,
],
});
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {MockedProvider} from '@apollo/client/testing';

import {buildAssetNode} from '../../../graphql/types';
import {EvaluationList} from '../EvaluationList';
import {buildEvaluationRecordsForList} from '../__fixtures__/EvaluationList.fixtures';

// eslint-disable-next-line import/no-default-export
export default {
title: 'Asset Details/Automaterialize/EvaluationList',
component: EvaluationList,
};

export const Default = () => {
const definition = buildAssetNode({
id: '1',
groupName: '1',
isMaterializable: true,
partitionDefinition: null,
partitionKeysByDimension: [],
});

const evaluations = buildEvaluationRecordsForList(25);

return (
<MockedProvider>
<EvaluationList definition={definition} evaluations={evaluations} />
</MockedProvider>
);
};

0 comments on commit ab4f68f

Please sign in to comment.