This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 2cb78c4263 Filter datasets graph by dag_id (#37464)
2cb78c4263 is described below
commit 2cb78c4263ff06a70fee9f79c97e1702920b49d3
Author: Brent Bovenzi <[email protected]>
AuthorDate: Thu Feb 22 15:46:50 2024 -0500
Filter datasets graph by dag_id (#37464)
* Initial filter datasets graph by dag_id
* Fix bug with merging dataset subgraphs
* Fix index for dataset group merging
* Add tooltip
---
.../www/static/js/api/useDatasetDependencies.ts | 121 ++++++++++++++-------
airflow/www/static/js/datasets/DagFilter.tsx | 117 ++++++++++++++++++++
airflow/www/static/js/datasets/Graph/DagNode.tsx | 37 +++++--
airflow/www/static/js/datasets/Graph/Node.tsx | 11 +-
airflow/www/static/js/datasets/Graph/index.tsx | 100 ++++++++++-------
airflow/www/static/js/datasets/List.test.tsx | 18 ++-
airflow/www/static/js/datasets/List.tsx | 29 ++++-
airflow/www/static/js/datasets/Main.tsx | 49 +++++++--
8 files changed, 371 insertions(+), 111 deletions(-)
diff --git a/airflow/www/static/js/api/useDatasetDependencies.ts
b/airflow/www/static/js/api/useDatasetDependencies.ts
index 2680ee9a78..42308f87a4 100644
--- a/airflow/www/static/js/api/useDatasetDependencies.ts
+++ b/airflow/www/static/js/api/useDatasetDependencies.ts
@@ -26,7 +26,7 @@ import { getTextWidth } from "src/utils/graph";
import type { NodeType, DepEdge, DepNode } from "src/types";
-interface DatasetDependencies {
+export interface DatasetDependencies {
edges: DepEdge[];
nodes: DepNode[];
}
@@ -41,16 +41,11 @@ interface GenerateProps {
font: string;
}
-interface Graph extends ElkShape {
+export interface DatasetGraph extends ElkShape {
children: NodeType[];
edges: ElkExtendedEdge[];
}
-interface Data {
- fullGraph: Graph;
- subGraphs: Graph[];
-}
-
const generateGraph = ({ nodes, edges, font }: GenerateProps) => ({
id: "root",
layoutOptions: {
@@ -91,32 +86,43 @@ const findDownstreamGraph = ({
}: SeparateGraphsProps): EdgeGroup[] => {
let unassignedEdges = [...edges];
+ const otherIndexes: number[] = [];
+
const mergedGraphs = graphs
.reduce((newGraphs, graph) => {
- const otherGroupIndex = newGraphs.findIndex((otherGroup) =>
- otherGroup.edges.some((otherEdge) =>
- graph.edges.some((edge) => edge.target === otherEdge.target)
- )
+ // Find all overlapping graphs where at least one edge in each graph has
the same target node
+ const otherGroups = newGraphs.filter((otherGroup, i) =>
+ otherGroup.edges.some((otherEdge) => {
+ if (graph.edges.some((edge) => edge.target === otherEdge.target)) {
+ otherIndexes.push(i);
+ return true;
+ }
+ return false;
+ })
);
- if (otherGroupIndex === -1) {
+ if (!otherGroups.length) {
return [...newGraphs, graph];
}
- const mergedEdges = [
- ...newGraphs[otherGroupIndex].edges,
- ...graph.edges,
- ].filter(
- (edge, edgeIndex, otherEdges) =>
- edgeIndex ===
- otherEdges.findIndex(
- (otherEdge) =>
- otherEdge.source === edge.source &&
- otherEdge.target === edge.target
- )
- );
+ // Merge the edges of every overlapping group
+ const mergedEdges = otherGroups
+ .reduce(
+ (totalEdges, group) => [...totalEdges, ...group.edges],
+ [...graph.edges]
+ )
+ .filter(
+ (edge, edgeIndex, otherEdges) =>
+ edgeIndex ===
+ otherEdges.findIndex(
+ (otherEdge) =>
+ otherEdge.source === edge.source &&
+ otherEdge.target === edge.target
+ )
+ );
return [
+ // filter out the merged graphs
...newGraphs.filter(
- (_, newGraphIndex) => newGraphIndex !== otherGroupIndex
+ (_, newGraphIndex) => !otherIndexes.includes(newGraphIndex)
),
{ edges: mergedEdges },
];
@@ -180,33 +186,64 @@ const separateGraphs = ({
const formatDependencies = async ({ edges, nodes }: DatasetDependencies) => {
const elk = new ELK();
- const graphs = separateGraphs({ edges, nodes });
-
// get computed style to calculate how large each node should be
const font = `bold ${16}px ${
window.getComputedStyle(document.body).fontFamily
}`;
- // Finally generate the graph data with elk
- const subGraphs = await Promise.all(
- graphs.map(async (g) =>
- elk.layout(generateGraph({ nodes: g.nodes, edges: g.edges, font }))
- )
- );
- const fullGraph = await elk.layout(generateGraph({ nodes, edges, font }));
+ const graph = await elk.layout(generateGraph({ nodes, edges, font }));
- return {
- fullGraph,
- subGraphs,
- } as Data;
+ return graph as DatasetGraph;
};
export default function useDatasetDependencies() {
return useQuery("datasetDependencies", async () => {
const datasetDepsUrl = getMetaValue("dataset_dependencies_url");
- const rawData = await axios.get<AxiosResponse, DatasetDependencies>(
- datasetDepsUrl
- );
- return formatDependencies(rawData);
+ return axios.get<AxiosResponse, DatasetDependencies>(datasetDepsUrl);
});
}
+
+interface GraphsProps {
+ dagIds?: string[];
+ selectedUri: string | null;
+}
+
+export const useDatasetGraphs = ({ dagIds, selectedUri }: GraphsProps) => {
+ const { data: datasetDependencies } = useDatasetDependencies();
+ return useQuery(
+ ["datasetGraphs", datasetDependencies, dagIds, selectedUri],
+ () => {
+ if (datasetDependencies) {
+ let graph = datasetDependencies;
+ const subGraphs = datasetDependencies
+ ? separateGraphs(datasetDependencies)
+ : [];
+
+ // Filter by dataset URI takes precedence
+ if (selectedUri) {
+ graph =
+ subGraphs.find((g) =>
+ g.nodes.some((n) => n.value.label === selectedUri)
+ ) || graph;
+ } else if (dagIds?.length) {
+ const filteredSubGraphs = subGraphs.filter((sg) =>
+ dagIds.some((dagId) =>
+ sg.nodes.some((c) => c.value.label === dagId)
+ )
+ );
+
+ graph = filteredSubGraphs.reduce(
+ (graphs, subGraph) => ({
+ edges: [...graphs.edges, ...subGraph.edges],
+ nodes: [...graphs.nodes, ...subGraph.nodes],
+ }),
+ { edges: [], nodes: [] }
+ );
+ }
+
+ return formatDependencies(graph);
+ }
+ return undefined;
+ }
+ );
+};
diff --git a/airflow/www/static/js/datasets/DagFilter.tsx
b/airflow/www/static/js/datasets/DagFilter.tsx
new file mode 100644
index 0000000000..71b923594d
--- /dev/null
+++ b/airflow/www/static/js/datasets/DagFilter.tsx
@@ -0,0 +1,117 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from "react";
+import { HStack } from "@chakra-ui/react";
+import { Size, useChakraSelectProps } from "chakra-react-select";
+
+import type { DatasetDependencies } from "src/api/useDatasetDependencies";
+import MultiSelect from "src/components/MultiSelect";
+import InfoTooltip from "src/components/InfoTooltip";
+
+interface Props {
+ datasetDependencies?: DatasetDependencies;
+ filteredDagIds: string[];
+ onFilterDags: (dagIds: string[]) => void;
+}
+
+const transformArrayToMultiSelectOptions = (
+ options: string[] | null
+): { label: string; value: string }[] =>
+ options === null
+ ? []
+ : options.map((option) => ({ label: option, value: option }));
+
+const DagFilter = ({
+ datasetDependencies,
+ filteredDagIds,
+ onFilterDags,
+}: Props) => {
+ const dagIds = (datasetDependencies?.nodes || [])
+ .filter((node) => node.value.class === "dag")
+ .map((dag) => dag.value.label);
+ const options = dagIds.map((dagId) => ({ label: dagId, value: dagId }));
+
+ const inputStyles: { backgroundColor: string; size: Size } = {
+ backgroundColor: "white",
+ size: "lg",
+ };
+ const multiSelectStyles = useChakraSelectProps({
+ ...inputStyles,
+ isMulti: true,
+ tagVariant: "solid",
+ hideSelectedOptions: false,
+ isClearable: false,
+ selectedOptionStyle: "check",
+ chakraStyles: {
+ container: (p) => ({
+ ...p,
+ width: "100%",
+ }),
+ placeholder: (p) => ({
+ ...p,
+ color: "gray.700",
+ fontSize: "md",
+ }),
+ inputContainer: (p) => ({
+ ...p,
+ color: "gray.700",
+ fontSize: "md",
+ }),
+ downChevron: (p) => ({
+ ...p,
+ fontSize: "lg",
+ }),
+ control: (p) => ({
+ ...p,
+ cursor: "pointer",
+ }),
+ option: (p) => ({
+ ...p,
+ transition: "background-color 0.2s",
+ _hover: {
+ bg: "gray.100",
+ },
+ }),
+ },
+ });
+
+ return (
+ <HStack width="100%">
+ <MultiSelect
+ {...multiSelectStyles}
+ isDisabled={!datasetDependencies}
+ value={transformArrayToMultiSelectOptions(filteredDagIds)}
+ onChange={(dagOptions) => {
+ if (
+ Array.isArray(dagOptions) &&
+ dagOptions.every((dagOption) => "value" in dagOption)
+ ) {
+ onFilterDags(dagOptions.map((option) => option.value));
+ }
+ }}
+ options={options}
+ placeholder="Filter graph by DAG ID"
+ />
+ <InfoTooltip label="Filter Datasets graph by anything that may be
connected to one or more DAGs. Does not filter the datasets list." />
+ </HStack>
+ );
+};
+
+export default DagFilter;
diff --git a/airflow/www/static/js/datasets/Graph/DagNode.tsx
b/airflow/www/static/js/datasets/Graph/DagNode.tsx
index aa12c57522..8a686f8acc 100644
--- a/airflow/www/static/js/datasets/Graph/DagNode.tsx
+++ b/airflow/www/static/js/datasets/Graph/DagNode.tsx
@@ -19,13 +19,14 @@
import React from "react";
import {
+ Button,
Flex,
Link,
Popover,
PopoverArrow,
- PopoverBody,
PopoverCloseButton,
PopoverContent,
+ PopoverFooter,
PopoverHeader,
PopoverTrigger,
Portal,
@@ -40,9 +41,13 @@ import { getMetaValue } from "src/utils";
const DagNode = ({
dagId,
isHighlighted,
+ isSelected,
+ onSelect,
}: {
dagId: string;
isHighlighted?: boolean;
+ isSelected?: boolean;
+ onSelect?: (dagId: string, type: string) => void;
}) => {
const { colors } = useTheme();
const containerRef = useContainerRef();
@@ -52,9 +57,12 @@ const DagNode = ({
<Popover>
<PopoverTrigger>
<Flex
- borderWidth={2}
- borderColor={isHighlighted ? colors.blue[400] : undefined}
+ borderColor={
+ isHighlighted || isSelected ? colors.blue[400] : undefined
+ }
borderRadius={5}
+ borderWidth={isSelected ? 4 : 2}
+ fontWeight={isSelected ? "bold" : "normal"}
p={2}
height="100%"
width="100%"
@@ -72,11 +80,26 @@ const DagNode = ({
<PopoverArrow bg="gray.100" />
<PopoverCloseButton />
<PopoverHeader>{dagId}</PopoverHeader>
- <PopoverBody>
- <Link color="blue" href={gridUrl}>
+ <PopoverFooter as={Flex} justifyContent="space-between">
+ <Button
+ variant="outline"
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (onSelect) onSelect(dagId, "dag");
+ }}
+ >
+ Filter by DAG
+ </Button>
+ <Button
+ as={Link}
+ href={gridUrl}
+ variant="outline"
+ colorScheme="blue"
+ >
View DAG
- </Link>
- </PopoverBody>
+ </Button>
+ </PopoverFooter>
</PopoverContent>
</Portal>
</Popover>
diff --git a/airflow/www/static/js/datasets/Graph/Node.tsx
b/airflow/www/static/js/datasets/Graph/Node.tsx
index ed02653139..425f25e3c9 100644
--- a/airflow/www/static/js/datasets/Graph/Node.tsx
+++ b/airflow/www/static/js/datasets/Graph/Node.tsx
@@ -32,7 +32,7 @@ export interface CustomNodeProps {
width?: number;
isSelected?: boolean;
isHighlighted?: boolean;
- onSelect: (datasetUri: string) => void;
+ onSelect: (datasetUri: string, type: string) => void;
isOpen?: boolean;
isActive?: boolean;
}
@@ -45,7 +45,12 @@ const BaseNode = ({
return (
<Box bg="white">
{type === "dag" && (
- <DagNode dagId={label} isHighlighted={isHighlighted} />
+ <DagNode
+ dagId={label}
+ isHighlighted={isHighlighted}
+ isSelected={isSelected}
+ onSelect={onSelect}
+ />
)}
{type !== "dag" && (
<Flex
@@ -57,7 +62,7 @@ const BaseNode = ({
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
- if (type === "dataset") onSelect(label);
+ onSelect(label, "dataset");
}}
cursor="pointer"
fontSize={16}
diff --git a/airflow/www/static/js/datasets/Graph/index.tsx
b/airflow/www/static/js/datasets/Graph/index.tsx
index f137be36d1..d40c53bc6a 100644
--- a/airflow/www/static/js/datasets/Graph/index.tsx
+++ b/airflow/www/static/js/datasets/Graph/index.tsx
@@ -31,9 +31,9 @@ import ReactFlow, {
import { Box, Tooltip, useTheme } from "@chakra-ui/react";
import { RiFocus3Line } from "react-icons/ri";
-import { useDatasetDependencies } from "src/api";
import Edge from "src/components/Graph/Edge";
import { useContainerRef } from "src/context/containerRef";
+import { useDatasetGraphs } from "src/api/useDatasetDependencies";
import Node, { CustomNodeProps } from "./Node";
import Legend from "./Legend";
@@ -41,17 +41,28 @@ import Legend from "./Legend";
interface Props {
onSelect: (datasetId: string) => void;
selectedUri: string | null;
+ filteredDagIds: string[];
+ onFilterDags: (dagIds: string[]) => void;
}
const nodeTypes = { custom: Node };
const edgeTypes = { custom: Edge };
-const Graph = ({ onSelect, selectedUri }: Props) => {
- const { data } = useDatasetDependencies();
+const Graph = ({
+ onSelect,
+ selectedUri,
+ filteredDagIds,
+ onFilterDags,
+}: Props) => {
const { colors } = useTheme();
const { setCenter, setViewport } = useReactFlow();
const containerRef = useContainerRef();
+ const { data: graph } = useDatasetGraphs({
+ dagIds: filteredDagIds,
+ selectedUri,
+ });
+
useEffect(() => {
setViewport({ x: 0, y: 0, zoom: 1 });
}, [selectedUri, setViewport]);
@@ -61,46 +72,51 @@ const Graph = ({ onSelect, selectedUri }: Props) => {
}: ReactFlowNode<CustomNodeProps>) =>
isSelected ? colors.blue["300"] : colors.gray["300"];
- if (!data || !data.fullGraph || !data.subGraphs) return null;
- const graph = selectedUri
- ? data.subGraphs.find((g) =>
- g.children.some((n) => n.id === `dataset:${selectedUri}`)
- )
- : data.fullGraph;
- if (!graph) return null;
-
- const edges = graph.edges.map((e) => ({
- id: e.id,
- source: e.sources[0],
- target: e.targets[0],
- type: "custom",
- data: {
- rest: {
- ...e,
- isSelected: selectedUri && e.id.includes(selectedUri),
+ const edges =
+ graph?.edges?.map((e) => ({
+ id: e.id,
+ source: e.sources[0],
+ target: e.targets[0],
+ type: "custom",
+ data: {
+ rest: {
+ ...e,
+ isSelected: selectedUri && e.id.includes(selectedUri),
+ },
+ },
+ })) || [];
+
+ const handleSelect = (id: string, type: string) => {
+ if (type === "dataset") onSelect(id);
+ if (type === "dag") {
+ if (filteredDagIds.includes(id))
+ onFilterDags(filteredDagIds.filter((dagId) => dagId !== id));
+ else onFilterDags([...filteredDagIds, id]);
+ }
+ };
+
+ const nodes: ReactFlowNode<CustomNodeProps>[] =
+ graph?.children?.map((c) => ({
+ id: c.id,
+ data: {
+ label: c.value.label,
+ type: c.value.class,
+ width: c.width,
+ height: c.height,
+ onSelect: handleSelect,
+ isSelected:
+ selectedUri === c.value.label ||
+ (c.value.class === "dag" && filteredDagIds.includes(c.value.label)),
+ isHighlighted: edges.some(
+ (e) => e.data.rest.isSelected && e.id.includes(c.id)
+ ),
+ },
+ type: "custom",
+ position: {
+ x: c.x || 0,
+ y: c.y || 0,
},
- },
- }));
-
- const nodes: ReactFlowNode<CustomNodeProps>[] = graph.children.map((c) => ({
- id: c.id,
- data: {
- label: c.value.label,
- type: c.value.class,
- width: c.width,
- height: c.height,
- onSelect,
- isSelected: selectedUri === c.value.label,
- isHighlighted: edges.some(
- (e) => e.data.rest.isSelected && e.id.includes(c.id)
- ),
- },
- type: "custom",
- position: {
- x: c.x || 0,
- y: c.y || 0,
- },
- }));
+ })) || [];
const focusNode = () => {
if (selectedUri) {
diff --git a/airflow/www/static/js/datasets/List.test.tsx
b/airflow/www/static/js/datasets/List.test.tsx
index f0c1523029..64a5e3a449 100644
--- a/airflow/www/static/js/datasets/List.test.tsx
+++ b/airflow/www/static/js/datasets/List.test.tsx
@@ -87,7 +87,11 @@ describe("Test Datasets List", () => {
.mockImplementation(() => returnValue);
const { getByText, queryAllByTestId } = render(
- <DatasetsList onSelect={() => {}} />,
+ <DatasetsList
+ onSelect={() => {}}
+ filteredDagIds={[]}
+ onFilterDags={() => {}}
+ />,
{ wrapper: Wrapper }
);
@@ -111,7 +115,11 @@ describe("Test Datasets List", () => {
.mockImplementation(() => emptyReturnValue);
const { getByText, queryAllByTestId, getByTestId } = render(
- <DatasetsList onSelect={() => {}} />,
+ <DatasetsList
+ onSelect={() => {}}
+ filteredDagIds={[]}
+ onFilterDags={() => {}}
+ />,
{ wrapper: Wrapper }
);
@@ -130,7 +138,11 @@ describe("Test Datasets List", () => {
const { getByDisplayValue } = render(
<Wrapper initialEntries={["/datasets?search=s3%253A%252F%252F"]}>
- <DatasetsList onSelect={() => {}} />
+ <DatasetsList
+ onSelect={() => {}}
+ filteredDagIds={[]}
+ onFilterDags={() => {}}
+ />
</Wrapper>
);
diff --git a/airflow/www/static/js/datasets/List.tsx
b/airflow/www/static/js/datasets/List.tsx
index 9d83406d7f..69b945d192 100644
--- a/airflow/www/static/js/datasets/List.tsx
+++ b/airflow/www/static/js/datasets/List.tsx
@@ -42,9 +42,14 @@ import { Table, TimeCell } from "src/components/Table";
import type { API } from "src/types";
import { getMetaValue } from "src/utils";
import type { DateOption } from "src/api/useDatasetsSummary";
+import type { DatasetDependencies } from "src/api/useDatasetDependencies";
+import DagFilter from "./DagFilter";
interface Props {
onSelect: (datasetId: string) => void;
+ datasetDependencies?: DatasetDependencies;
+ filteredDagIds: string[];
+ onFilterDags: (dagIds: string[]) => void;
}
interface CellProps {
@@ -80,7 +85,12 @@ const dateOptions: Record<string, DateOption> = {
hour: { count: 1, unit: "hour" },
};
-const DatasetsList = ({ onSelect }: Props) => {
+const DatasetsList = ({
+ onSelect,
+ datasetDependencies,
+ onFilterDags,
+ filteredDagIds,
+}: Props) => {
const limit = 25;
const [offset, setOffset] = useState(0);
@@ -133,7 +143,11 @@ const DatasetsList = ({ onSelect }: Props) => {
const docsUrl = getMetaValue("datasets_docs");
const onSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
- searchParams.set(SEARCH_PARAM, encodeURIComponent(e.target.value));
+ if (e.target.value) {
+ searchParams.set(SEARCH_PARAM, encodeURIComponent(e.target.value));
+ } else {
+ searchParams.delete(SEARCH_PARAM);
+ }
setSearchParams(searchParams);
};
@@ -158,7 +172,7 @@ const DatasetsList = ({ onSelect }: Props) => {
to learn how to create a dataset.
</Text>
)}
- <Flex wrap="wrap">
+ <Flex wrap="wrap" mb={2}>
<Text mr={2}>Filter datasets with updates in the past:</Text>
<ButtonGroup size="sm" isAttached variant="outline">
<Button
@@ -194,7 +208,7 @@ const DatasetsList = ({ onSelect }: Props) => {
})}
</ButtonGroup>
</Flex>
- <InputGroup my={2} px={1}>
+ <InputGroup my={2}>
<InputLeftElement pointerEvents="none">
<MdSearch />
</InputLeftElement>
@@ -215,7 +229,12 @@ const DatasetsList = ({ onSelect }: Props) => {
</InputRightElement>
)}
</InputGroup>
- <Box borderWidth={1}>
+ <DagFilter
+ datasetDependencies={datasetDependencies}
+ filteredDagIds={filteredDagIds}
+ onFilterDags={onFilterDags}
+ />
+ <Box borderWidth={1} mt={2}>
<Table
data={data}
columns={columns}
diff --git a/airflow/www/static/js/datasets/Main.tsx
b/airflow/www/static/js/datasets/Main.tsx
index 70f7629804..6bb815e83e 100644
--- a/airflow/www/static/js/datasets/Main.tsx
+++ b/airflow/www/static/js/datasets/Main.tsx
@@ -20,13 +20,16 @@
import React, { useCallback, useEffect, useRef } from "react";
import { useSearchParams } from "react-router-dom";
import { Flex, Box } from "@chakra-ui/react";
+
import { useOffsetTop } from "src/utils";
+import { useDatasetDependencies } from "src/api";
import DatasetsList from "./List";
import DatasetDetails from "./Details";
import Graph from "./Graph";
-const DATASET_URI = "uri";
+const DATASET_URI_PARAM = "uri";
+const DAG_ID_PARAM = "dag_id";
const minPanelWidth = 300;
const Datasets = () => {
@@ -40,16 +43,36 @@ const Datasets = () => {
const resizeRef = useRef<HTMLDivElement>(null);
+ const datasetUriSearch = decodeURIComponent(
+ searchParams.get(DATASET_URI_PARAM) || ""
+ );
+ const filteredDagIds = searchParams
+ .getAll(DAG_ID_PARAM)
+ .map((param) => decodeURIComponent(param));
+
+ // We need to load in the raw dependencies in order to generate the list of
dagIds
+ const { data: datasetDependencies } = useDatasetDependencies();
+
const onBack = () => {
- searchParams.delete(DATASET_URI);
+ searchParams.delete(DATASET_URI_PARAM);
setSearchParams(searchParams);
};
const onSelect = (datasetUri: string) => {
- searchParams.set(DATASET_URI, encodeURIComponent(datasetUri));
+ searchParams.set(DATASET_URI_PARAM, encodeURIComponent(datasetUri));
setSearchParams(searchParams);
};
+ const onFilterDags = (dagIds: string[]) => {
+ const params = new URLSearchParams(
+ dagIds.map((dagId) => [DAG_ID_PARAM, dagId])
+ );
+ if (datasetUriSearch) {
+ params.append(DATASET_URI_PARAM, encodeURIComponent(datasetUriSearch));
+ }
+ setSearchParams(params);
+ };
+
const resize = useCallback(
(e: MouseEvent) => {
const listEl = listRef.current;
@@ -85,8 +108,6 @@ const Datasets = () => {
return () => {};
}, [resize]);
- const datasetUri = decodeURIComponent(searchParams.get(DATASET_URI) || "");
-
return (
<Flex
alignItems="flex-start"
@@ -100,10 +121,15 @@ const Datasets = () => {
ref={listRef}
mr={3}
>
- {datasetUri ? (
- <DatasetDetails uri={datasetUri} onBack={onBack} />
+ {datasetUriSearch ? (
+ <DatasetDetails uri={datasetUriSearch} onBack={onBack} />
) : (
- <DatasetsList onSelect={onSelect} />
+ <DatasetsList
+ onSelect={onSelect}
+ datasetDependencies={datasetDependencies}
+ filteredDagIds={filteredDagIds}
+ onFilterDags={onFilterDags}
+ />
)}
</Box>
<Box
@@ -121,7 +147,12 @@ const Datasets = () => {
borderColor="gray.200"
borderWidth={1}
>
- <Graph selectedUri={datasetUri} onSelect={onSelect} />
+ <Graph
+ selectedUri={datasetUriSearch}
+ onSelect={onSelect}
+ filteredDagIds={filteredDagIds}
+ onFilterDags={onFilterDags}
+ />
</Box>
</Flex>
);