Skip to content

Commit

Permalink
[ui] Allow deleting dynamic partitions from asset pages (#23462)
Browse files Browse the repository at this point in the history
## Summary & Motivation

Fixes #14026

This PR adds a new "delete partitions" modal in both OSS and Plus that
allows you to remove dynamic partitions from the UI. The UI immediately
updates to reflect the change.

During testing I also found that our current UI for adding dynamic
partitions didn't work quite right in cases where the dynamic partition
was one of the dimensions of a multi-dimensional partition def, because
the `dimension.name` and the `dimension.dynamicPartitionsDefinitionName`
are not the same, and the Add Partition mutation requires the latter.

The new dropdown option in these screenshots only appears if the asset
has a dynamic partition dimension, so non-dynamic assets are unimpacted.

<img width="1728" alt="image"
src="https://github.com/user-attachments/assets/91812d57-a972-40f5-b11d-13eba0603ee3">

<img width="498" alt="image"
src="https://github.com/user-attachments/assets/eb2a9aff-846f-456b-9fbb-72f9648dedd4">

<img width="877" alt="image"
src="https://github.com/user-attachments/assets/d3ca68eb-b9a6-4f61-a917-7f3afae37832">

## How I Tested These Changes

There's a new test file covering this modal!

I also tested this manually using both assets that have dynamic
partitions and assets that have multi-dimensional partition defs where
one axis is dynamic. Also verified the changes in the report
materialization events modal and the wipe assets implementation on the
asset table didn't have any impact.

---------

Co-authored-by: bengotow <[email protected]>
  • Loading branch information
bengotow and bengotow authored Aug 19, 2024
1 parent 0827970 commit dfeac61
Show file tree
Hide file tree
Showing 27 changed files with 633 additions and 134 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import {
Button,
Icon,
Menu,
MenuDivider,
MenuItem,
Popover,
Spinner,
Tooltip,
} from '@dagster-io/ui-components';
import {Button, Icon, Menu, MenuItem, Popover, Spinner, Tooltip} from '@dagster-io/ui-components';
import {useContext} from 'react';

import {
Expand All @@ -17,11 +8,11 @@ import {
import {useObserveAction} from './LaunchAssetObservationButton';
import {assetDetailsPathForKey} from './assetDetailsPathForKey';
import {AssetTableDefinitionFragment} from './types/AssetTableFragment.types';
import {useDeleteDynamicPartitionsDialog} from './useDeleteDynamicPartitionsDialog';
import {useReportEventsModal} from './useReportEventsModal';
import {useWipeModal} from './useWipeModal';
import {CloudOSSContext} from '../app/CloudOSSContext';
import {showSharedToaster} from '../app/DomUtils';
import {usePermissionsForLocation} from '../app/Permissions';
import {AssetKeyInput} from '../graphql/types';
import {MenuLink} from '../ui/MenuLink';
import {RepoAddress} from '../workspace/types';
import {workspacePathFromAddress} from '../workspace/workspacePath';
Expand All @@ -30,34 +21,42 @@ interface Props {
path: string[];
definition: AssetTableDefinitionFragment | null;
repoAddress: RepoAddress | null;
onWipe?: (assets: AssetKeyInput[]) => void;
onRefresh?: () => void;
}

export const AssetActionMenu = (props: Props) => {
const {repoAddress, path, definition, onWipe} = props;
const {
permissions: {canWipeAssets},
} = usePermissionsForLocation(repoAddress?.location);

const {repoAddress, path, definition, onRefresh} = props;
const {
featureContext: {canSeeWipeMaterializationAction, canSeeMaterializeAction},
featureContext: {canSeeMaterializeAction},
} = useContext(CloudOSSContext);

const {executeItem, launchpadElement} = useExecuteAssetMenuItem(path, definition);

const deletePartitions = useDeleteDynamicPartitionsDialog(
repoAddress && definition ? {repoAddress, assetKey: {path}, definition} : null,
onRefresh,
);

const wipe = useWipeModal(
repoAddress && definition ? {repository: definition.repository, assetKey: {path}} : null,
onRefresh,
);

const reportEvents = useReportEventsModal(
repoAddress
? {
assetKey: {path},
isPartitioned: !!definition?.partitionDefinition,
repository: {name: repoAddress.name, location: {name: repoAddress.location}},
repoAddress,
}
: null,
);
return (
<>
{launchpadElement}
{wipe.element}
{reportEvents.element}
{deletePartitions.element}
<Popover
position="bottom-right"
content={
Expand Down Expand Up @@ -108,16 +107,8 @@ export const AssetActionMenu = (props: Props) => {
/>
))
: undefined}
{canSeeWipeMaterializationAction ? <MenuDivider /> : undefined}
{canSeeWipeMaterializationAction ? (
<MenuItem
text="Wipe materializations"
icon="delete"
disabled={!onWipe || !canWipeAssets}
intent="danger"
onClick={() => canWipeAssets && onWipe && onWipe([{path}])}
/>
) : null}
{wipe.dropdownOptions}
{deletePartitions.dropdownOptions}
</Menu>
}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export const AssetPartitions = ({
{timeDimensionIdx !== -1 && (
<Box padding={{vertical: 16, horizontal: 24}} border="bottom">
<DimensionRangeWizard
dimensionType={selections[timeDimensionIdx]!.dimension.type}
partitionKeys={selections[timeDimensionIdx]!.dimension.partitionKeys}
health={{ranges: rangesForEachDimension[timeDimensionIdx]!}}
selected={selections[timeDimensionIdx]!.selectedKeys}
Expand All @@ -211,7 +212,6 @@ export const AssetPartitions = ({
selections.map((r, idx) => (idx === timeDimensionIdx ? {...r, selectedKeys} : r)),
)
}
dimensionType={selections[timeDimensionIdx]!.dimension.type}
/>
</Box>
)}
Expand Down
12 changes: 2 additions & 10 deletions js_modules/dagster-ui/packages/ui-core/src/assets/AssetTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {AssetViewType} from './useAssetView';
import {CloudOSSContext} from '../app/CloudOSSContext';
import {useUnscopedPermissions} from '../app/Permissions';
import {QueryRefreshCountdown, RefreshState} from '../app/QueryRefresh';
import {AssetKeyInput, DefinitionTag} from '../graphql/types';
import {DefinitionTag} from '../graphql/types';
import {useSelectionReducer} from '../hooks/useSelectionReducer';
import {testId} from '../testing/testId';
import {StaticSetFilter} from '../ui/BaseFilters/useStaticSetFilter';
Expand Down Expand Up @@ -57,8 +57,6 @@ export const AssetTable = ({
computeKindFilter,
storageKindFilter,
}: Props) => {
const [toWipe, setToWipe] = React.useState<AssetKeyInput[] | undefined>();

const groupedByDisplayKey = useMemo(
() => groupBy(assets, (a) => JSON.stringify(displayPathForAsset(a))),
[assets, displayPathForAsset],
Expand Down Expand Up @@ -139,9 +137,9 @@ export const AssetTable = ({
groups={groupedByDisplayKey}
checkedDisplayKeys={checkedDisplayKeys}
onToggleFactory={onToggleFactory}
onRefresh={() => refreshState.refetch()}
showRepoColumn
view={view}
onWipe={(assetKeys: AssetKeyInput[]) => setToWipe(assetKeys)}
computeKindFilter={computeKindFilter}
storageKindFilter={storageKindFilter}
/>
Expand Down Expand Up @@ -188,12 +186,6 @@ export const AssetTable = ({
{belowActionBarComponents}
{content()}
</Box>
<AssetWipeDialog
assetKeys={toWipe || []}
isOpen={!!toWipe}
onClose={() => setToWipe(undefined)}
onComplete={() => refreshState.refetch()}
/>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const ASSET_TABLE_DEFINITION_FRAGMENT = gql`
hasMaterializePermission
partitionDefinition {
description
dimensionTypes {
type
dynamicPartitionsDefinitionName
}
}
description
owners {
Expand Down
40 changes: 28 additions & 12 deletions js_modules/dagster-ui/packages/ui-core/src/assets/AssetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
AssetViewDefinitionQuery,
AssetViewDefinitionQueryVariables,
} from './types/AssetView.types';
import {useDeleteDynamicPartitionsDialog} from './useDeleteDynamicPartitionsDialog';
import {healthRefreshHintFromLiveData} from './usePartitionHealthData';
import {useReportEventsModal} from './useReportEventsModal';
import {useWipeModal} from './useWipeModal';
Expand All @@ -46,6 +47,7 @@ import {useAssetGraphData} from '../asset-graph/useAssetGraphData';
import {StaleReasonsTag} from '../assets/Stale';
import {useQueryPersistedState} from '../hooks/useQueryPersistedState';
import {useBlockTraceOnQueryResult} from '../performance/TraceContext';
import {buildRepoAddress} from '../workspace/buildRepoAddress';

interface Props {
assetKey: AssetKey;
Expand Down Expand Up @@ -257,28 +259,36 @@ export const AssetView = ({assetKey, headerBreadcrumbs, writeAssetVisit, current
}
};

const repoAddress = useMemo(
() =>
definition
? buildRepoAddress(definition.repository.name, definition.repository.location.name)
: null,
[definition],
);

const setCurrentPage = useSetRecoilState(currentPageAtom);
const {path} = useRouteMatch();
useEffect(() => {
setCurrentPage(({specificPath}) => ({specificPath, path: `${path}?view=${selectedTab}`}));
}, [path, selectedTab, setCurrentPage]);

const wipe = useWipeModal(
definition
? {
assetKey: definition.assetKey,
repository: definition.repository,
}
: null,
definition ? {assetKey: definition.assetKey, repository: definition.repository} : null,
refresh,
);

const dynamicPartitionsDelete = useDeleteDynamicPartitionsDialog(
definition && repoAddress ? {assetKey: definition.assetKey, definition, repoAddress} : null,
() => {
definitionQueryResult.refetch();
refresh();
},
);

const reportEvents = useReportEventsModal(
definition
? {
assetKey: definition.assetKey,
isPartitioned: definition.isPartitioned,
repository: definition.repository,
}
definition && repoAddress
? {assetKey: definition.assetKey, isPartitioned: definition.isPartitioned, repoAddress}
: null,
refresh,
);
Expand Down Expand Up @@ -326,11 +336,13 @@ export const AssetView = ({assetKey, headerBreadcrumbs, writeAssetVisit, current
additionalDropdownOptions={[
...reportEvents.dropdownOptions,
...wipe.dropdownOptions,
...dynamicPartitionsDelete.dropdownOptions,
]}
/>
) : undefined}
{reportEvents.element}
{wipe.element}
{dynamicPartitionsDelete.element}
</Box>
}
/>
Expand Down Expand Up @@ -470,6 +482,10 @@ export const ASSET_VIEW_DEFINITION_QUERY = gql`
groupName
partitionDefinition {
description
dimensionTypes {
type
dynamicPartitionsDefinitionName
}
}
partitionKeysByDimension {
name
Expand Down
Loading

1 comment on commit dfeac61

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for dagit-core-storybook ready!

✅ Preview
https://dagit-core-storybook-8zaheqiii-elementl.vercel.app

Built with commit dfeac61.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.