Skip to content

bulk select & delete #727

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

Draft
wants to merge 4 commits into
base: staging
Choose a base branch
from
Draft
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
6 changes: 3 additions & 3 deletions src/app/components/[componentId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StackComponentsDetailHeader } from "../../../components/stack-component
import { StackComponentTabs } from "@/components/stack-components/component-detail/Tabs";
import { StackList } from "../../stacks/StackList";
import { RunsBody } from "../../pipelines/RunsTab/RunsBody";
import { RunsSelectorProvider } from "../../pipelines/RunsTab/RunsSelectorContext";
import { RunsDataTableContextProvider } from "../../pipelines/RunsTab/RunsDataTableContext";

export default function ComponentDetailPage() {
const { componentId } = useParams() as { componentId: string };
Expand All @@ -15,9 +15,9 @@ export default function ComponentDetailPage() {
isPanel={false}
stacksTabContent={<StackList fixedQueryParams={{ component_id: componentId }} />}
runsTabContent={
<RunsSelectorProvider>
<RunsDataTableContextProvider>
<RunsBody fixedQueryParams={{ stack_component: componentId }} />
</RunsSelectorProvider>
</RunsDataTableContextProvider>
}
componentId={componentId}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/app/pipelines/PipelinesTab/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { DeletePipelineAlert } from "./DeletePipelineAlert";
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
import { usePipelineDataTableContext } from "./PipelineSelectorContext";

export function PipelinesButtonGroup() {
const { selectedPipelines } = usePipelinesSelectorContext();
const { selectedRowIDs } = usePipelineDataTableContext();
return (
<div className="flex items-center divide-x divide-theme-border-moderate overflow-hidden rounded-md border border-theme-border-moderate">
<div className="bg-primary-25 px-2 py-1 font-semibold text-theme-text-brand">{`${selectedPipelines?.length} Pipeline${selectedPipelines?.length > 1 ? "s" : ""} selected`}</div>
<div className="bg-primary-25 px-2 py-1 font-semibold text-theme-text-brand">{`${selectedRowIDs.length} Pipeline${selectedRowIDs.length > 1 ? "s" : ""} selected`}</div>
<DeletePipelineAlert />
</div>
);
Expand Down
10 changes: 6 additions & 4 deletions src/app/pipelines/PipelinesTab/DeletePipelineAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import Trash from "@/assets/icons/trash.svg?react";
import { DeleteAlertContent, DeleteAlertContentBody } from "@/components/DeleteAlertDialog";
import { AlertDialog, AlertDialogTrigger, Button } from "@zenml-io/react-component-library";
import { useState } from "react";
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
import { usePipelineDataTableContext } from "./PipelineSelectorContext";
import { useBulkDeletePipelines } from "./bulk";

export function DeletePipelineAlert() {
const [isOpen, setIsOpen] = useState(false);
const { bulkDeletePipelines, selectedPipelines } = usePipelinesSelectorContext();
const { selectedRowIDs } = usePipelineDataTableContext();
const { bulkDelete } = useBulkDeletePipelines();

async function handleDelete() {
await bulkDeletePipelines(selectedPipelines);
await bulkDelete(selectedRowIDs);
setIsOpen(false);
}

Expand All @@ -27,7 +29,7 @@ export function DeletePipelineAlert() {
</Button>
</AlertDialogTrigger>
<DeleteAlertContent
title={`Delete Pipeline${selectedPipelines.length >= 2 ? "s" : ""}`}
title={`Delete Pipeline${selectedRowIDs.length >= 2 ? "s" : ""}`}
handleDelete={handleDelete}
>
<DeleteAlertContentBody>
Expand Down
6 changes: 3 additions & 3 deletions src/app/pipelines/PipelinesTab/PipelineDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
DropdownMenuTrigger
} from "@zenml-io/react-component-library";
import { ElementRef, useRef, useState } from "react";
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
import { useBulkDeletePipelines } from "./bulk";

type Props = {
id: string;
Expand All @@ -20,10 +20,10 @@ export function PipelineDropdown({ id }: Props) {
const dropdownTriggerRef = useRef<ElementRef<typeof AlertDialogTrigger> | null>(null);
const focusRef = useRef<HTMLElement | null>(null);

const { bulkDeletePipelines } = usePipelinesSelectorContext();
const { bulkDelete } = useBulkDeletePipelines();

async function handleDelete() {
await bulkDeletePipelines([id]);
await bulkDelete([id]);
handleDialogItemOpenChange(false);
}

Expand Down
61 changes: 6 additions & 55 deletions src/app/pipelines/PipelinesTab/PipelineSelectorContext.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,7 @@
import { pipelineQueries } from "@/data/pipelines";
import { useDeletePipeline } from "@/data/pipelines/delete-pipeline";
import { useQueryClient } from "@tanstack/react-query";
import { useToast } from "@zenml-io/react-component-library";
import { SetStateAction, createContext, useContext, useState } from "react";
import { createDataTableConsumerContext } from "@/components/table/DataTableConsumerContext";

type PipelinesSelectorContextProps = {
selectedPipelines: string[];
setSelectedPipelines: (actions: SetStateAction<string[]>) => void;
bulkDeletePipelines: (runIds: string[]) => Promise<void>;
};

const PipelinesSelectorContext = createContext<PipelinesSelectorContextProps | null>(null);

export function PipelinesSelectorProvider({ children }: { children: React.ReactNode }) {
const [selectedPipelines, setSelectedPipelines] = useState<string[]>([]);
const queryClient = useQueryClient();

const { toast } = useToast();

const deleteRunMutation = useDeletePipeline();

const bulkDeletePipelines = async (runIds: string[]) => {
try {
// Use mutateAsync to handle each delete operation
const deletePromises = runIds.map((id) => deleteRunMutation.mutateAsync({ pipelineId: id }));

await Promise.all(deletePromises);
toast({
description: "Deleted successfully.",
status: "success",
emphasis: "subtle",
rounded: true
});
await queryClient.invalidateQueries({ queryKey: pipelineQueries.all });
setSelectedPipelines([]);
} catch (error) {
console.error("Failed to delete some pipelines:", error);
}
};

return (
<PipelinesSelectorContext.Provider
value={{ selectedPipelines, setSelectedPipelines, bulkDeletePipelines }}
>
{children}
</PipelinesSelectorContext.Provider>
);
}

export function usePipelinesSelectorContext() {
const context = useContext(PipelinesSelectorContext);
if (!context)
throw new Error("usePipelinesSelectorContext must be used within a PipelinesSelectorProvider");
return context;
}
export const {
Context: PipelineDataTableContext,
ContextProvider: PipelineDataTableContextProvider,
useContext: usePipelineDataTableContext
} = createDataTableConsumerContext();
6 changes: 3 additions & 3 deletions src/app/pipelines/PipelinesTab/PipelinesBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { useQuery } from "@tanstack/react-query";
import { Button, DataTable, Skeleton } from "@zenml-io/react-component-library";
import { PipelinesButtonGroup } from "./ButtonGroup";
import { getPipelineColumns } from "./columns";
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
import { usePipelineDataTableContext } from "./PipelineSelectorContext";
import { usePipelineOverviewSearchParams } from "./service";

export function PipelinesBody() {
const queryParams = usePipelineOverviewSearchParams();
const { selectedPipelines } = usePipelinesSelectorContext();
const { selectedRowIDs } = usePipelineDataTableContext();
const { data, refetch } = useQuery({
...pipelineQueries.pipelineList({ ...queryParams, sort_by: "desc:latest_run" }),
throwOnError: true
Expand All @@ -20,7 +20,7 @@ export function PipelinesBody() {
return (
<div className="flex flex-col gap-5">
<div className="flex items-center justify-between">
{selectedPipelines.length ? (
{selectedRowIDs.length ? (
<PipelinesButtonGroup />
) : (
<SearchField searchParams={queryParams} />
Expand Down
27 changes: 0 additions & 27 deletions src/app/pipelines/PipelinesTab/PipelinesSelector.tsx

This file was deleted.

11 changes: 11 additions & 0 deletions src/app/pipelines/PipelinesTab/bulk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useDeletePipeline } from "../../../data/pipelines/delete-pipeline";
import { usePipelineDataTableContext } from "./PipelineSelectorContext";
import { createUseBulkDelete } from "@/lib/bulk-delete";
import { pipelineQueries } from "../../../data/pipelines";

export const useBulkDeletePipelines = createUseBulkDelete({
useMutation: useDeletePipeline,
useQueryKeys: () => pipelineQueries,
useDataTableContext: usePipelineDataTableContext,
getParams: (pipelineId) => ({ pipelineId })
});
22 changes: 19 additions & 3 deletions src/app/pipelines/PipelinesTab/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ExecutionStatus } from "@/types/pipeline-runs";
import { Pipeline } from "@/types/pipelines";
import { ColumnDef } from "@tanstack/react-table";
import {
Checkbox,
Tag,
Tooltip,
TooltipContent,
Expand All @@ -19,18 +20,33 @@ import {
} from "@zenml-io/react-component-library";
import { Link } from "react-router-dom";
import { PipelineDropdown } from "./PipelineDropdown";
import { PipelinesSelector } from "./PipelinesSelector";

export function getPipelineColumns(): ColumnDef<Pipeline>[] {
return [
{
id: "check",
header: "",
header: ({ table }) => {
return (
<Checkbox
id="check-all"
checked={table.getIsAllRowsSelected()}
onCheckedChange={(state) =>
table.toggleAllRowsSelected(state === "indeterminate" ? true : state)
}
/>
);
},
meta: {
width: "1%"
},
cell: ({ row }) => {
return <PipelinesSelector id={row.original.id} />;
return (
<Checkbox
id={`check-${row.id}`}
checked={row.getIsSelected()}
onCheckedChange={row.getToggleSelectedHandler()}
/>
);
}
},
{
Expand Down
6 changes: 3 additions & 3 deletions src/app/pipelines/RunsTab/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { DeleteRunAlert } from "./DeleteRunAlert";
import { useRunsSelectorContext } from "./RunsSelectorContext";
import { useRunsDataTableContext } from "./RunsDataTableContext";

export function RunsButtonGroup() {
const { selectedRuns } = useRunsSelectorContext();
const { selectedRowCount } = useRunsDataTableContext();
return (
<div className="flex items-center divide-x divide-theme-border-moderate overflow-hidden rounded-md border border-theme-border-moderate">
<div className="bg-primary-25 px-2 py-1 font-semibold text-theme-text-brand">{`${selectedRuns?.length} Run${selectedRuns?.length > 1 ? "s" : ""} selected`}</div>
<div className="bg-primary-25 px-2 py-1 font-semibold text-theme-text-brand">{`${selectedRowCount} Run${selectedRowCount > 1 ? "s" : ""} selected`}</div>
<DeleteRunAlert />
</div>
);
Expand Down
10 changes: 6 additions & 4 deletions src/app/pipelines/RunsTab/DeleteRunAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import Trash from "@/assets/icons/trash.svg?react";
import { DeleteAlertContent, DeleteAlertContentBody } from "@/components/DeleteAlertDialog";
import { AlertDialog, AlertDialogTrigger, Button } from "@zenml-io/react-component-library";
import { useState } from "react";
import { useRunsSelectorContext } from "./RunsSelectorContext";
import { useRunsDataTableContext } from "./RunsDataTableContext";
import { useBulkDeleteRuns } from "./bulk";

export function DeleteRunAlert() {
const [isOpen, setIsOpen] = useState(false);
const { bulkDeleteRuns, selectedRuns } = useRunsSelectorContext();
const { selectedRowIDs } = useRunsDataTableContext();
const { bulkDelete } = useBulkDeleteRuns();

async function handleDelete() {
await bulkDeleteRuns(selectedRuns);
await bulkDelete(selectedRowIDs);
setIsOpen(false);
}

Expand All @@ -27,7 +29,7 @@ export function DeleteRunAlert() {
</Button>
</AlertDialogTrigger>
<DeleteAlertContent
title={`Delete Run${selectedRuns.length >= 2 ? "s" : ""}`}
title={`Delete Run${selectedRowIDs.length >= 2 ? "s" : ""}`}
handleDelete={handleDelete}
>
<DeleteAlertContentBody>
Expand Down
6 changes: 3 additions & 3 deletions src/app/pipelines/RunsTab/RunDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
DropdownMenuTrigger
} from "@zenml-io/react-component-library";
import { ElementRef, useRef, useState } from "react";
import { useRunsSelectorContext } from "./RunsSelectorContext";
import { useBulkDeleteRuns } from "./bulk";

type Props = {
id: string;
Expand All @@ -21,10 +21,10 @@ export function RunDropdown({ id }: Props) {
const dropdownTriggerRef = useRef<ElementRef<typeof AlertDialogTrigger> | null>(null);
const focusRef = useRef<HTMLElement | null>(null);

const { bulkDeleteRuns } = useRunsSelectorContext();
const { bulkDelete } = useBulkDeleteRuns();

async function handleDelete() {
await bulkDeleteRuns([id]);
await bulkDelete([id]);
handleDialogItemOpenChange(false);
}

Expand Down
27 changes: 0 additions & 27 deletions src/app/pipelines/RunsTab/RunSelector.tsx

This file was deleted.

6 changes: 3 additions & 3 deletions src/app/pipelines/RunsTab/RunsBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { PipelineRunOvervieweParams } from "@/types/pipeline-runs";
import { Button, DataTable, Skeleton } from "@zenml-io/react-component-library";
import { RunsButtonGroup } from "./ButtonGroup";
import { runsColumns } from "./columns";
import { useRunsSelectorContext } from "./RunsSelectorContext";
import { useRunsDataTableContext } from "./RunsDataTableContext";
import { useRunsOverviewSearchParams } from "./service";

type Props = {
fixedQueryParams?: PipelineRunOvervieweParams;
};

export function RunsBody({ fixedQueryParams = {} }: Props) {
const { selectedRuns } = useRunsSelectorContext();
const { selectedRowIDs } = useRunsDataTableContext();
const queryParams = useRunsOverviewSearchParams();
const { data, refetch } = useAllPipelineRuns({
params: {
Expand All @@ -27,7 +27,7 @@ export function RunsBody({ fixedQueryParams = {} }: Props) {
return (
<div className="mt-5 flex flex-col gap-5">
<div className="flex items-center justify-between">
{selectedRuns.length ? <RunsButtonGroup /> : <SearchField searchParams={queryParams} />}
{selectedRowIDs.length ? <RunsButtonGroup /> : <SearchField searchParams={queryParams} />}
<div className="flex justify-between">
<Button intent="primary" emphasis="subtle" size="md" onClick={() => refetch()}>
<Refresh className="h-5 w-5 fill-theme-text-brand" />
Expand Down
7 changes: 7 additions & 0 deletions src/app/pipelines/RunsTab/RunsDataTableContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createDataTableConsumerContext } from "@/components/table/DataTableConsumerContext";

export const {
Context: RunsDataTableContext,
ContextProvider: RunsDataTableContextProvider,
useContext: useRunsDataTableContext
} = createDataTableConsumerContext();
Loading