Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
bengotow committed Jul 24, 2024
1 parent 55857c3 commit ba433d5
Show file tree
Hide file tree
Showing 11 changed files with 621 additions and 343 deletions.
16 changes: 15 additions & 1 deletion js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from './types/AssetView.types';
import {healthRefreshHintFromLiveData} from './usePartitionHealthData';
import {useReportEventsModal} from './useReportEventsModal';
import {useWipeModal} from './useWipeModal';
import {currentPageAtom} from '../app/analytics';
import {Timestamp} from '../app/time/Timestamp';
import {AssetLiveDataRefreshButton, useAssetLiveData} from '../asset-data/AssetLiveDataProvider';
Expand Down Expand Up @@ -276,6 +277,15 @@ export const AssetView = ({
setCurrentPage(({specificPath}) => ({specificPath, path: `${path}?view=${selectedTab}`}));
}, [path, selectedTab, setCurrentPage]);

const wipe = useWipeModal(
definition
? {
assetKey: definition.assetKey,
repository: definition.repository,
}
: null,
refresh,
);
const reportEvents = useReportEventsModal(
definition
? {
Expand Down Expand Up @@ -327,10 +337,14 @@ export const AssetView = ({
<LaunchAssetExecutionButton
scope={{all: [definition]}}
showChangedAndMissingOption={false}
additionalDropdownOptions={reportEvents.dropdownOptions}
additionalDropdownOptions={[
...reportEvents.dropdownOptions,
...wipe.dropdownOptions,
]}
/>
) : undefined}
{reportEvents.element}
{wipe.element}
</Box>
}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {RefetchQueriesFunction, gql, useMutation} from '@apollo/client';
import {RefetchQueriesFunction} from '@apollo/client';
// eslint-disable-next-line no-restricted-imports
import {ProgressBar} from '@blueprintjs/core';
import {
Expand All @@ -11,27 +11,18 @@ import {
Group,
ifPlural,
} from '@dagster-io/ui-components';
import {useVirtualizer} from '@tanstack/react-virtual';
import {memo, useLayoutEffect, useMemo, useRef, useState} from 'react';
import {memo, useMemo} from 'react';

import {VirtualizedSimpleAssetKeyList} from './VirtualizedSimpleAssetKeyList';
import {asAssetPartitionRangeInput} from './asInput';
import {AssetWipeMutation, AssetWipeMutationVariables} from './types/AssetWipeDialog.types';
import {showCustomAlert} from '../app/CustomAlertProvider';
import {PYTHON_ERROR_FRAGMENT} from '../app/PythonErrorFragment';
import {displayNameForAssetKey} from '../asset-graph/Utils';
import {useWipeAssets} from './useWipeAssets';
import {AssetKeyInput} from '../graphql/types';
import {NavigationBlock} from '../runs/NavigationBlock';
import {Inner, Row} from '../ui/VirtualizedTable';
import {numberFormatter} from '../ui/formatters';

interface AssetKey {
path: string[];
}

const CHUNK_SIZE = 100;

export const AssetWipeDialog = memo(
(props: {
assetKeys: AssetKey[];
assetKeys: AssetKeyInput[];
isOpen: boolean;
onClose: () => void;
onComplete?: () => void;
Expand All @@ -57,76 +48,17 @@ export const AssetWipeDialogInner = memo(
onComplete,
requery,
}: {
assetKeys: AssetKey[];
assetKeys: AssetKeyInput[];
onClose: () => void;
onComplete?: () => void;
requery?: RefetchQueriesFunction;
}) => {
const [requestWipe] = useMutation<AssetWipeMutation, AssetWipeMutationVariables>(
ASSET_WIPE_MUTATION,
{refetchQueries: requery},
);

const [isWiping, setIsWiping] = useState(false);
const [wipedCount, setWipedCount] = useState(0);
const [failedCount, setFailedCount] = useState(0);

const isDone = !isWiping && (wipedCount || failedCount);

const didCancel = useRef(false);
const wipe = async () => {
if (!assetKeys.length) {
return;
}
setIsWiping(true);
for (let i = 0, l = assetKeys.length; i < l; i += CHUNK_SIZE) {
if (didCancel.current) {
return;
}
const nextChunk = assetKeys.slice(i, i + CHUNK_SIZE);
const result = await requestWipe({
variables: {assetPartitionRanges: nextChunk.map((x) => asAssetPartitionRangeInput(x))},
});
const data = result.data?.wipeAssets;
switch (data?.__typename) {
case 'AssetNotFoundError':
case 'PythonError':
setFailedCount((failed) => failed + nextChunk.length);
break;
case 'AssetWipeSuccess':
setWipedCount((wiped) => wiped + nextChunk.length);
break;
case 'UnauthorizedError':
showCustomAlert({
title: 'Could not wipe asset materializations',
body: 'You do not have permission to do this.',
});
onClose();
return;
}
}
onComplete?.();
setIsWiping(false);
};

useLayoutEffect(() => {
return () => {
didCancel.current = true;
};
}, []);

const parentRef = useRef<HTMLDivElement>(null);

const rowVirtualizer = useVirtualizer({
count: assetKeys.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 24,
overscan: 10,
const {wipeAssets, isWiping, isDone, wipedCount, failedCount} = useWipeAssets({
refetchQueries: requery,
onClose,
onComplete,
});

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

const content = useMemo(() => {
if (isDone) {
return (
Expand All @@ -143,18 +75,7 @@ export const AssetWipeDialogInner = memo(
{numberFormatter.format(assetKeys.length)}{' '}
{ifPlural(assetKeys.length, 'asset', 'assets')}?
</div>
<div style={{maxHeight: '50vh', overflowY: 'scroll'}} ref={parentRef}>
<Inner $totalHeight={totalHeight}>
{items.map(({index, key, size, start}) => {
const assetKey = assetKeys[index]!;
return (
<Row key={key} $height={size} $start={start}>
{displayNameForAssetKey(assetKey)}
</Row>
);
})}
</Inner>
</div>
<VirtualizedSimpleAssetKeyList assetKeys={assetKeys} style={{maxHeight: '50vh'}} />
<div>
Assets defined only by their historical materializations will disappear from the Asset
Catalog. Software-defined assets will remain unless their definition is also deleted.
Expand All @@ -171,7 +92,7 @@ export const AssetWipeDialogInner = memo(
<NavigationBlock message="Wiping in progress, please do not navigate away yet." />
</Box>
);
}, [isDone, isWiping, assetKeys, wipedCount, failedCount, totalHeight, items]);
}, [isDone, isWiping, assetKeys, wipedCount, failedCount]);

return (
<>
Expand All @@ -181,7 +102,12 @@ export const AssetWipeDialogInner = memo(
{isDone ? 'Done' : 'Cancel'}
</Button>
{isDone ? null : (
<Button intent="danger" onClick={wipe} disabled={isWiping} loading={isWiping}>
<Button
intent="danger"
onClick={() => wipeAssets(assetKeys.map((key) => asAssetPartitionRangeInput(key)))}
disabled={isWiping}
loading={isWiping}
>
Wipe
</Button>
)}
Expand All @@ -190,24 +116,3 @@ export const AssetWipeDialogInner = memo(
);
},
);

const ASSET_WIPE_MUTATION = gql`
mutation AssetWipeMutation($assetPartitionRanges: [PartitionsByAssetSelector!]!) {
wipeAssets(assetPartitionRanges: $assetPartitionRanges) {
... on AssetWipeSuccess {
assetPartitionRanges {
assetKey {
path
}
partitionRange {
start
end
}
}
}
...PythonErrorFragment
}
}
${PYTHON_ERROR_FRAGMENT}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ import {
showBackfillErrorToast,
showBackfillSuccessToast,
} from '../partitions/BackfillMessaging';
import {DimensionRangeWizard} from '../partitions/DimensionRangeWizard';
import {DimensionRangeWizards} from '../partitions/DimensionRangeWizards';
import {assembleIntoSpans, stringForSpan} from '../partitions/SpanRepresentation';
import {DagsterTag} from '../runs/RunTag';
import {testId} from '../testing/testId';
Expand Down Expand Up @@ -502,50 +502,14 @@ const LaunchAssetChoosePartitionsDialogBody = ({
<Subheading>{displayNameForAssetKey(target.anchorAssetKey)}</Subheading>
</Box>
)}
{selections.map((range, idx) => (
<Box
key={range.dimension.name}
border={idx < selections.length - 1 ? 'bottom' : undefined}
padding={{vertical: 12, horizontal: 20}}
>
<Box as={Subheading} flex={{alignItems: 'center', gap: 8}}>
<Icon name="partition" />
{range.dimension.name}
</Box>
<Box>
Select partitions to materialize.{' '}
{range.dimension.type === PartitionDefinitionType.TIME_WINDOW
? 'Click and drag to select a range on the timeline.'
: null}
</Box>
<DimensionRangeWizard
partitionKeys={range.dimension.partitionKeys}
health={{
ranges: displayedHealth.rangesForSingleDimension(
idx,
selections.length === 2 ? selections[1 - idx]!.selectedRanges : undefined,
),
}}
dimensionType={range.dimension.type}
selected={range.selectedKeys}
setSelected={(selectedKeys) =>
setSelections((selections) =>
selections.map((r) =>
r.dimension === range.dimension ? {...r, selectedKeys} : r,
),
)
}
partitionDefinitionName={
displayedPartitionDefinition?.name ||
displayedBaseAsset?.partitionDefinition?.dimensionTypes.find(
(d) => d.name === range.dimension.name,
)?.dynamicPartitionsDefinitionName
}
repoAddress={repoAddress}
refetch={refetch}
/>
</Box>
))}
<DimensionRangeWizards
repoAddress={repoAddress}
refetch={refetch}
selections={selections}
setSelections={setSelections}
displayedHealth={displayedHealth}
displayedPartitionDefinition={displayedPartitionDefinition}
/>
</ToggleableSection>
)}
<ToggleableSection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,14 @@ export const LaunchAssetExecutionButton = ({
showChangedAndMissingOption?: boolean;
primary?: boolean;
preferredJobName?: string;
additionalDropdownOptions?: {
label: string;
icon?: JSX.Element;
onClick: () => void;
}[];
additionalDropdownOptions?: (
| JSX.Element
| {
label: string;
icon?: JSX.Element;
onClick: () => void;
}
)[];
}) => {
const {onClick, loading, launchpadElement} = useMaterializationAction(preferredJobName);
const [isOpen, setIsOpen] = React.useState(false);
Expand Down Expand Up @@ -290,14 +293,18 @@ export const LaunchAssetExecutionButton = ({
onClick(firstOption.assetKeys, e, true);
}}
/>
{additionalDropdownOptions?.map((option) => (
<MenuItem
key={option.label}
text={option.label}
icon={option.icon}
onClick={option.onClick}
/>
))}
{additionalDropdownOptions?.map((option) =>
'label' in option ? (
<MenuItem
key={option.label}
text={option.label}
icon={option.icon}
onClick={option.onClick}
/>
) : (
option
),
)}
</Menu>
}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// eslint-disable-next-line no-restricted-imports

import {useVirtualizer} from '@tanstack/react-virtual';
import {CSSProperties, useRef} from 'react';

import {displayNameForAssetKey} from '../asset-graph/Utils';
import {AssetKeyInput} from '../graphql/types';
import {Inner, Row} from '../ui/VirtualizedTable';

export const VirtualizedSimpleAssetKeyList = ({
assetKeys,
style,
}: {
assetKeys: AssetKeyInput[];
style: CSSProperties;
}) => {
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: assetKeys.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 24,
overscan: 10,
});

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

return (
<div style={{...style, overflowY: 'auto'}} ref={parentRef}>
<Inner $totalHeight={totalHeight}>
{items.map(({index, key, size, start}) => {
const assetKey = assetKeys[index]!;
return (
<Row key={key} $height={size} $start={start}>
{displayNameForAssetKey(assetKey)}
</Row>
);
})}
</Inner>
</div>
);
};
Loading

0 comments on commit ba433d5

Please sign in to comment.