Skip to content

Commit

Permalink
[ui] Evaluations table: Don't load all partition keys up front
Browse files Browse the repository at this point in the history
  • Loading branch information
hellendag committed Aug 13, 2024
1 parent 1f67aed commit f358ecb
Show file tree
Hide file tree
Showing 14 changed files with 467 additions and 605 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import styled from 'styled-components';

import {StatusDot} from './AutomaterializeLeftPanel';
import {AutomaterializeRunsTable} from './AutomaterializeRunsTable';
import {PartitionSubsetList} from './PartitionSegmentWithPopover';
import {PartitionSubsetList} from './PartitionSubsetList';
import {PolicyEvaluationTable} from './PolicyEvaluationTable';
import {
FullPartitionsQuery,
Expand Down Expand Up @@ -57,17 +57,20 @@ export const AutomaterializeMiddlePanelWithData = ({
() => evaluation?.evaluationNodes.find((node) => node.uniqueId === evaluation.rootUniqueId),
[evaluation],
);
const partitionDefinition = definition?.partitionDefinition;
const numRequested = selectedEvaluation?.numRequested;
const rootUniqueId = evaluation?.rootUniqueId;

const statusTag = useMemo(() => {
const trueSubset =
const partitionDefinition = definition?.partitionDefinition;
const assetKeyPath = definition?.assetKey.path || [];
const numRequested = selectedEvaluation?.numRequested;

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

if (numRequested) {
if (partitionDefinition && trueSubset) {
if (partitionDefinition && rootUniqueId && numTrue) {
return (
<Box flex={{direction: 'row', gap: 4, alignItems: 'center'}}>
<Popover
Expand All @@ -78,7 +81,9 @@ export const AutomaterializeMiddlePanelWithData = ({
content={
<PartitionSubsetList
description="Requested assets"
subset={trueSubset}
assetKeyPath={assetKeyPath}
evaluationId={selectedEvaluation.evaluationId}
nodeUniqueId={rootUniqueId}
selectPartition={selectPartition}
/>
}
Expand All @@ -90,9 +95,6 @@ export const AutomaterializeMiddlePanelWithData = ({
</Box>
</Tag>
</Popover>
{numRequested === 1 ? (
<Tag icon="partition">{trueSubset.subsetValue.partitionKeys![0]}</Tag>
) : null}
</Box>
);
}
Expand All @@ -115,7 +117,7 @@ export const AutomaterializeMiddlePanelWithData = ({
</Box>
</Tag>
);
}, [partitionDefinition, selectPartition, numRequested, rootEvaluationNode]);
}, [definition, rootEvaluationNode, selectedEvaluation, rootUniqueId, selectPartition]);

const fullPartitionsQueryResult = useQuery<FullPartitionsQuery, FullPartitionsQueryVariables>(
FULL_PARTITIONS_QUERY,
Expand Down Expand Up @@ -256,6 +258,8 @@ export const AutomaterializeMiddlePanelWithData = ({
</Box>
) : null}
<PolicyEvaluationTable
assetKeyPath={definition?.assetKey.path ?? null}
evaluationId={selectedEvaluation.evaluationId}
evaluationNodes={
!selectedEvaluation.isLegacy
? selectedEvaluation.evaluationNodes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,6 @@ import {gql} from '@apollo/client';

import {METADATA_ENTRY_FRAGMENT} from '../../metadata/MetadataEntryFragment';

const AssetSubsetFragment = gql`
fragment AssetSubsetFragment on AssetSubset {
subsetValue {
isPartitioned
partitionKeys
partitionKeyRanges {
start
end
}
}
}
`;

const SpecificPartitionAssetConditionEvaluationNodeFragment = gql`
fragment SpecificPartitionAssetConditionEvaluationNodeFragment on SpecificPartitionAssetConditionEvaluationNode {
description
Expand Down Expand Up @@ -48,18 +35,11 @@ const PartitionedAssetConditionEvaluationNodeFragment = gql`
startTimestamp
endTimestamp
numTrue
numFalse
numSkipped
trueSubset {
...AssetSubsetFragment
}
candidateSubset {
...AssetSubsetFragment
}
uniqueId
childUniqueIds
numTrue
numCandidates
}
${AssetSubsetFragment}
`;

const NEW_EVALUATION_NODE_FRAGMENT = gql`
Expand All @@ -69,18 +49,11 @@ const NEW_EVALUATION_NODE_FRAGMENT = gql`
userLabel
startTimestamp
endTimestamp
numCandidates
numTrue
isPartitioned
childUniqueIds
trueSubset {
...AssetSubsetFragment
}
candidateSubset {
...AssetSubsetFragment
}
}
${AssetSubsetFragment}
`;

const AssetConditionEvaluationRecordFragment = gql`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,33 @@
import {
Box,
Colors,
Menu,
MenuItem,
MiddleTruncate,
Popover,
Tag,
TextInput,
TextInputContainer,
} from '@dagster-io/ui-components';
import {useVirtualizer} from '@tanstack/react-virtual';
import {useMemo, useRef, useState} from 'react';
import styled from 'styled-components';
import {Popover, Tag} from '@dagster-io/ui-components';

import {PolicyEvaluationStatusTag} from './PolicyEvaluationStatusTag';
import {assertUnreachable} from '../../app/Util';
import {AssetConditionEvaluationStatus, AssetSubsetValue} from '../../graphql/types';
import {Container, Inner, Row} from '../../ui/VirtualizedTable';
import {PartitionSubsetList} from './PartitionSubsetList';
import {AssetConditionEvaluationStatus} from '../../graphql/types';
import {numberFormatter} from '../../ui/formatters';

const statusToColors = (status: AssetConditionEvaluationStatus) => {
switch (status) {
case AssetConditionEvaluationStatus.TRUE:
return {color: Colors.accentGreen(), hoverColor: Colors.accentGreenHover()};
case AssetConditionEvaluationStatus.FALSE:
return {color: Colors.accentYellow(), hoverColor: Colors.accentYellowHover()};
case AssetConditionEvaluationStatus.SKIPPED:
return {color: Colors.accentGray(), hoverColor: Colors.accentGrayHover()};
default:
return assertUnreachable(status);
}
};

type AssetSusbsetWithoutTypenames = {
subsetValue: Omit<AssetSubsetValue, '__typename' | 'boolValue'>;
};

interface Props {
description: string;
subset: AssetSusbsetWithoutTypenames | null;
numTrue: number;
assetKeyPath: string[];
evaluationId: number;
nodeUniqueId: string;
selectPartition?: (partitionKey: string | null) => void;
}

export const PartitionSegmentWithPopover = ({description, selectPartition, subset}: Props) => {
if (!subset) {
return null;
}

const count = subset.subsetValue.partitionKeys?.length || 0;

export const PartitionSegmentWithPopover = ({
description,
selectPartition,
assetKeyPath,
evaluationId,
nodeUniqueId,
numTrue,
}: Props) => {
const tag = (
<Tag intent={count > 0 ? 'success' : 'none'} icon={count > 0 ? 'check_circle' : undefined}>
{numberFormatter.format(count)} True
<Tag intent={numTrue > 0 ? 'success' : 'none'} icon={numTrue > 0 ? 'check_circle' : undefined}>
{numberFormatter.format(numTrue)} True
</Tag>
);

if (count === 0) {
if (numTrue === 0) {
return tag;
}

Expand All @@ -69,7 +41,9 @@ export const PartitionSegmentWithPopover = ({description, selectPartition, subse
<PartitionSubsetList
description={description}
status={AssetConditionEvaluationStatus.TRUE}
subset={subset}
assetKeyPath={assetKeyPath}
evaluationId={evaluationId}
nodeUniqueId={nodeUniqueId}
selectPartition={selectPartition}
/>
}
Expand All @@ -78,120 +52,3 @@ export const PartitionSegmentWithPopover = ({description, selectPartition, subse
</Popover>
);
};

interface ListProps {
description: string;
status?: AssetConditionEvaluationStatus;
subset: AssetSusbsetWithoutTypenames;
selectPartition?: (partitionKey: string | null) => void;
}

const ITEM_HEIGHT = 32;
const MAX_ITEMS_BEFORE_TRUNCATION = 4;

export const PartitionSubsetList = ({description, status, subset, selectPartition}: ListProps) => {
const container = useRef<HTMLDivElement | null>(null);
const [searchValue, setSearchValue] = useState('');

const {color, hoverColor} = useMemo(
() => statusToColors(status ?? AssetConditionEvaluationStatus.TRUE),
[status],
);

const partitionKeys = useMemo(() => subset.subsetValue.partitionKeys || [], [subset]);

const filteredKeys = useMemo(() => {
const searchLower = searchValue.toLocaleLowerCase();
return partitionKeys.filter((key) => key.toLocaleLowerCase().includes(searchLower));
}, [partitionKeys, searchValue]);

const count = filteredKeys.length;

const rowVirtualizer = useVirtualizer({
count: filteredKeys.length,
getScrollElement: () => container.current,
estimateSize: () => ITEM_HEIGHT,
overscan: 10,
});

const totalHeight = rowVirtualizer.getTotalSize();
const virtualItems = rowVirtualizer.getVirtualItems();

return (
<div style={{width: '292px'}}>
<Box
padding={{vertical: 8, left: 12, right: 8}}
border="bottom"
flex={{direction: 'row', alignItems: 'center', justifyContent: 'space-between'}}
style={{display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', gap: 8}}
>
<strong>
<MiddleTruncate text={description} />
</strong>
{status ? <PolicyEvaluationStatusTag status={status} /> : null}
</Box>
{partitionKeys.length > MAX_ITEMS_BEFORE_TRUNCATION ? (
<SearchContainer padding={{vertical: 4, horizontal: 8}}>
<TextInput
icon="search"
placeholder="Filter partitions…"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
/>
</SearchContainer>
) : null}
<div
style={{
height: count > MAX_ITEMS_BEFORE_TRUNCATION ? '150px' : count * ITEM_HEIGHT + 16,
overflow: 'hidden',
}}
>
<Container ref={container}>
<Menu>
<Inner $totalHeight={totalHeight}>
{virtualItems.map(({index, key, size, start}) => {
const partitionKey = filteredKeys[index]!;
return (
<Row $height={size} $start={start} key={key}>
<MenuItem
onClick={() => {
selectPartition && selectPartition(partitionKey);
}}
text={
<Box flex={{direction: 'row', alignItems: 'center', gap: 8}}>
<PartitionStatusDot $color={color} $hoverColor={hoverColor} />
<div>
<MiddleTruncate text={partitionKey} />
</div>
</Box>
}
/>
</Row>
);
})}
</Inner>
</Menu>
</Container>
</div>
</div>
);
};

const SearchContainer = styled(Box)`
display: flex;
${TextInputContainer} {
flex: 1;
}
`;

const PartitionStatusDot = styled.div<{$color: string; $hoverColor: string}>`
background-color: ${({$color}) => $color};
height: 8px;
width: 8px;
border-radius: 50%;
transition: background-color 100ms linear;
:hover {
background-color: ${({$hoverColor}) => $hoverColor};
}
`;
Loading

0 comments on commit f358ecb

Please sign in to comment.