diff --git a/airflow/www/package.json b/airflow/www/package.json index bbce06550edea..ed4b94db9a154 100644 --- a/airflow/www/package.json +++ b/airflow/www/package.json @@ -100,6 +100,7 @@ "codemirror": "^5.59.1", "color": "^4.2.3", "d3": "^3.4.4", + "d3-7": "npm:d3@7", "d3-shape": "^2.1.0", "d3-tip": "^0.9.1", "dagre-d3": "^0.6.4", @@ -122,6 +123,7 @@ "react-router-dom": "^6.3.0", "react-table": "^7.8.0", "react-textarea-autosize": "^8.3.4", + "reactflow": "^11.4.0", "redoc": "^2.0.0-rc.72", "remark-gfm": "^3.0.1", "swagger-ui-dist": "4.1.3", diff --git a/airflow/www/static/declarations.d.ts b/airflow/www/static/declarations.d.ts new file mode 100644 index 0000000000000..9e9f4ea9f9f9a --- /dev/null +++ b/airflow/www/static/declarations.d.ts @@ -0,0 +1,20 @@ +/*! + * 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. + */ + +declare module "*.css"; diff --git a/airflow/www/static/js/api/index.ts b/airflow/www/static/js/api/index.ts index 0749565f6f5d2..2bbfe10b66e78 100644 --- a/airflow/www/static/js/api/index.ts +++ b/airflow/www/static/js/api/index.ts @@ -29,6 +29,7 @@ import useMarkFailedTask from "./useMarkFailedTask"; import useMarkSuccessTask from "./useMarkSuccessTask"; import useExtraLinks from "./useExtraLinks"; import useConfirmMarkTask from "./useConfirmMarkTask"; +import useGraphData from "./useGraphData"; import useGridData from "./useGridData"; import useMappedInstances from "./useMappedInstances"; import useDatasets from "./useDatasets"; @@ -55,6 +56,7 @@ export { useDatasetEvents, useDatasets, useExtraLinks, + useGraphData, useGridData, useMappedInstances, useMarkFailedRun, diff --git a/airflow/www/static/js/api/useDatasetDependencies.ts b/airflow/www/static/js/api/useDatasetDependencies.ts index 12e46c266bd57..3760cbfded139 100644 --- a/airflow/www/static/js/api/useDatasetDependencies.ts +++ b/airflow/www/static/js/api/useDatasetDependencies.ts @@ -25,6 +25,8 @@ import { getMetaValue } from "src/utils"; import type { DepEdge, DepNode } from "src/types"; import type { NodeType } from "src/datasets/Graph/Node"; +import { getTextWidth } from "src/utils/graph"; + interface DatasetDependencies { edges: DepEdge[]; nodes: DepNode[]; @@ -50,17 +52,6 @@ interface Data { subGraphs: Graph[]; } -// Take text and font to calculate how long each node should be -function getTextWidth(text: string, font: string) { - const context = document.createElement("canvas").getContext("2d"); - if (context) { - context.font = font; - const metrics = context.measureText(text); - return metrics.width; - } - return text.length * 9; -} - const generateGraph = ({ nodes, edges, font }: GenerateProps) => ({ id: "root", layoutOptions: { diff --git a/airflow/www/static/js/api/useGraphData.ts b/airflow/www/static/js/api/useGraphData.ts new file mode 100644 index 0000000000000..ab318fbb07ebb --- /dev/null +++ b/airflow/www/static/js/api/useGraphData.ts @@ -0,0 +1,54 @@ +/*! + * 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 { useQuery } from "react-query"; +import axios, { AxiosResponse } from "axios"; + +import { getMetaValue } from "src/utils"; +import type { DepNode } from "src/types"; + +const DAG_ID_PARAM = "dag_id"; + +const dagId = getMetaValue(DAG_ID_PARAM); +const graphDataUrl = getMetaValue("graph_data_url"); +const urlRoot = getMetaValue("root"); + +interface GraphData { + edges: WebserverEdge[]; + nodes: DepNode; + arrange: string; +} +export interface WebserverEdge { + label?: string; + sourceId: string; + targetId: string; +} + +const useGraphData = () => + useQuery("graphData", async () => { + const params = { + [DAG_ID_PARAM]: dagId, + root: urlRoot || undefined, + filter_upstream: true, + filter_downstream: true, + }; + return axios.get(graphDataUrl, { params }); + }); + +export default useGraphData; diff --git a/airflow/www/static/js/dag/InstanceTooltip.tsx b/airflow/www/static/js/dag/InstanceTooltip.tsx index 28dc9aedb728f..f49b55a4fcdda 100644 --- a/airflow/www/static/js/dag/InstanceTooltip.tsx +++ b/airflow/www/static/js/dag/InstanceTooltip.tsx @@ -20,7 +20,7 @@ import React from "react"; import { Box, Text } from "@chakra-ui/react"; -import { finalStatesMap } from "src/utils"; +import { getGroupAndMapSummary } from "src/utils"; import { formatDuration, getDuration } from "src/datetime_utils"; import type { TaskInstance, Task } from "src/types"; import Time from "src/components/Time"; @@ -36,33 +36,16 @@ const InstanceTooltip = ({ }: Props) => { if (!group) return null; const isGroup = !!group.children; + const { isMapped } = group; const summary: React.ReactNode[] = []; - const isMapped = group?.isMapped; - - const numMap = finalStatesMap(); - let numMapped = 0; - if (isGroup && group.children && !isMapped) { - group.children.forEach((child) => { - const taskInstance = child.instances.find((ti) => ti.runId === runId); - if (taskInstance) { - const stateKey = - taskInstance.state == null ? "no_status" : taskInstance.state; - if (numMap.has(stateKey)) - numMap.set(stateKey, (numMap.get(stateKey) || 0) + 1); - } - }); - } - - if (isMapped && mappedStates) { - Object.keys(mappedStates).forEach((stateKey) => { - const num = mappedStates[stateKey]; - numMapped += num; - numMap.set(stateKey || "no_status", num); - }); - } + const { totalTasks, childTaskMap } = getGroupAndMapSummary({ + group, + runId, + mappedStates, + }); - numMap.forEach((key, val) => { + childTaskMap.forEach((key, val) => { if (key > 0) { summary.push( // eslint-disable-next-line react/no-array-index-key @@ -78,15 +61,15 @@ const InstanceTooltip = ({ return ( {group.tooltip && {group.tooltip}} - {isMapped && numMapped > 0 && ( + {isMapped && totalTasks > 0 && ( - {numMapped} mapped task + {totalTasks} mapped task {isGroup && " group"} - {numMapped > 1 && "s"} + {totalTasks > 1 && "s"} )} - {isGroup || isMapped ? "Overall " : ""} + {isGroup || totalTasks ? "Overall " : ""} Status: {state || "no status"} {(isGroup || isMapped) && summary} diff --git a/airflow/www/static/js/dag/Main.tsx b/airflow/www/static/js/dag/Main.tsx index aaa20e6a0eced..7c8a0311d6c74 100644 --- a/airflow/www/static/js/dag/Main.tsx +++ b/airflow/www/static/js/dag/Main.tsx @@ -31,6 +31,7 @@ import Details from "./details"; import Grid from "./grid"; import FilterBar from "./nav/FilterBar"; import LegendRow from "./nav/LegendRow"; +import useToggleGroups from "./useToggleGroups"; const detailsPanelKey = "hideDetailsPanel"; const minPanelWidth = 300; @@ -69,6 +70,7 @@ const Main = () => { const [hoveredTaskState, setHoveredTaskState] = useState< string | null | undefined >(); + const { openGroupIds, onToggleGroups } = useToggleGroups(); // Add a debounced delay to not constantly trigger highlighting certain task states const onStatusHover = debounce( @@ -157,6 +159,8 @@ const Main = () => { isPanelOpen={isOpen} onPanelToggle={onPanelToggle} hoveredTaskState={hoveredTaskState} + openGroupIds={openGroupIds} + onToggleGroups={onToggleGroups} /> {isOpen && ( @@ -175,7 +179,10 @@ const Main = () => { bg="white" height="100%" > -
+
)} diff --git a/airflow/www/static/js/dag/details/Dag.tsx b/airflow/www/static/js/dag/details/Dag.tsx index 45fad8231d338..7c641d98190b4 100644 --- a/airflow/www/static/js/dag/details/Dag.tsx +++ b/airflow/www/static/js/dag/details/Dag.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { ReactNode, useRef } from "react"; +import React, { useRef, ReactNode } from "react"; import { Table, Tbody, @@ -90,90 +90,127 @@ const Dag = () => { const lastStart = dagRuns[dagRuns.length - 1]?.startDate; return ( - <> + - - - - {durations.length > 0 && ( - <> - +
+ + {durations.length > 0 && ( + <> + + + + + + + + {stateSummary} + {firstStart && ( + + - - - - - {stateSummary} - {firstStart && ( - - - - - )} - {lastStart && ( - - - - - )} + )} + {lastStart && ( - - - - - - - - - - + + - - )} - + )} + + + + + + + + + + + + + + )} + + + + + + + + {stateSummary} + {firstStart && ( + + - + )} + {lastStart && ( - - + + - {!!taskSummary.groupCount && ( - - - - - )} - {Object.entries(taskSummary.operators).map(([key, value]) => ( - - - - - ))} - -
+ DAG Runs Summary + +
Total Runs Displayed{durations.length}
First Run Start - DAG Runs Summary + -
Total Runs Displayed{durations.length}
First Run Start -
Last Run Start -
Max Run Duration{formatDuration(max)}
Mean Run Duration{formatDuration(avg)}
Min Run Duration{formatDuration(min)}Last Run Start +
Max Run Duration{formatDuration(max)}
Mean Run Duration{formatDuration(avg)}
Min Run Duration{formatDuration(min)}
+ DAG Runs Summary + +
Total Runs Displayed{durations.length}
First Run Start - DAG Summary +
Total Tasks{taskSummary.taskCount}Last Run Start +
Total Task Groups{taskSummary.groupCount}
- {key} - {value > 1 && "s"} - {value}
-
- + )} + + Max Run Duration + {formatDuration(max)} + + + Mean Run Duration + {formatDuration(avg)} + + + Min Run Duration + {formatDuration(min)} + + + + DAG Summary + + + + + Total Tasks + {taskSummary.taskCount} + + {!!taskSummary.groupCount && ( + + Total Task Groups + {taskSummary.groupCount} + + )} + {Object.entries(taskSummary.operators).map(([key, value]) => ( + + + {key} + {value > 1 && "s"} + + {value} + + ))} + + +
); }; diff --git a/airflow/www/static/js/dag/details/Header.tsx b/airflow/www/static/js/dag/details/Header.tsx index a0593c21caaae..d17f04618ae5c 100644 --- a/airflow/www/static/js/dag/details/Header.tsx +++ b/airflow/www/static/js/dag/details/Header.tsx @@ -83,8 +83,8 @@ const Header = () => { const isDagDetails = !runId && !taskId; const isRunDetails = !!(runId && !taskId); - const isTaskDetails = runId && taskId && mapIndex === null; - const isMappedTaskDetails = runId && taskId && mapIndex !== null; + const isTaskDetails = runId && taskId && mapIndex === undefined; + const isMappedTaskDetails = runId && taskId && mapIndex !== undefined; return ( /}> @@ -119,7 +119,7 @@ const Header = () => { )} - {mapIndex !== null && ( + {mapIndex !== undefined && ( { return ( <> - - - - - + + + + Re-run: @@ -113,137 +99,130 @@ const DagRun = ({ runId }: Props) => { - + + + + - - - - - - - + +
+ + + + + + + + + + + + + + {startDate && ( + + + + + )} + {lastSchedulingDecision && ( - + + )} + {queuedAt && ( - + + )} + {startDate && ( - + - {startDate && ( - - - - - )} - {lastSchedulingDecision && ( - - - - - )} - {queuedAt && ( - - - - - )} - {startDate && ( + )} + {endDate && ( + + + + + )} + {dataIntervalStart && dataIntervalEnd && ( + <> - + - )} - {endDate && ( - + + + )} + + + + + + + {confIsJson ? ( + + ) : ( + )} - {dataIntervalStart && dataIntervalEnd && ( - <> - - - - - - - - - - )} - - - - - - - {confIsJson ? ( - - ) : ( - - )} - - -
Status + + + {state || "no status"} + +
Run ID + +
Run type + + {runType} +
Run duration{formatDuration(getDuration(startDate, endDate))}
StatusLast scheduling decision - - - {state || "no status"} - +
Run IDQueued at - +
Run typeStarted - - {runType} +
Run duration{formatDuration(getDuration(startDate, endDate))}
Last scheduling decision -
Queued at -
Ended +
StartedData interval start -
EndedData interval end -
Externally triggered{externalTrigger ? "True" : "False"}
Run config + + + + + + {conf ?? "None"}
Data interval start -
Data interval end -
Externally triggered{externalTrigger ? "True" : "False"}
Run config - - - - - - {conf ?? "None"}
- {runType === "dataset_triggered" && ( - - )} -
- + + + + {runType === "dataset_triggered" && ( + + )} +
); }; diff --git a/airflow/www/static/js/dag/details/graph/Edge.tsx b/airflow/www/static/js/dag/details/graph/Edge.tsx new file mode 100644 index 0000000000000..561048f79821c --- /dev/null +++ b/airflow/www/static/js/dag/details/graph/Edge.tsx @@ -0,0 +1,66 @@ +/*! + * 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 { Text } from "@chakra-ui/react"; +import type { ElkEdgeSection, ElkLabel, ElkPoint, LayoutOptions } from "elkjs"; + +import Edge from "src/datasets/Graph/Edge"; +import { Group } from "@visx/group"; + +interface EdgeProps { + data?: { + rest: { + isSelected: boolean; + sources: string[]; + targets: string[]; + sections: ElkEdgeSection[]; + junctionPoints?: ElkPoint[]; + id: string; + labels?: ElkLabel[]; + layoutOptions?: LayoutOptions; + }; + }; +} + +const CustomEdge = ({ data }: EdgeProps) => { + if (!data) return null; + const { rest } = data; + return ( + <> + {rest?.labels?.map(({ id, x, y, text, width, height }) => { + if (!y || !x) return null; + return ( + + + {text} + + + ); + })} + + + ); +}; + +export default CustomEdge; diff --git a/airflow/www/static/js/dag/details/graph/Node.tsx b/airflow/www/static/js/dag/details/graph/Node.tsx new file mode 100644 index 0000000000000..7bdae04e09cab --- /dev/null +++ b/airflow/www/static/js/dag/details/graph/Node.tsx @@ -0,0 +1,178 @@ +/*! + * 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 { Box, Text, Flex } from "@chakra-ui/react"; +import { Handle, NodeProps, Position } from "reactflow"; + +import { SimpleStatus } from "src/dag/StatusBox"; +import useSelection from "src/dag/useSelection"; +import type { DagRun, Task, TaskInstance } from "src/types"; +import { getGroupAndMapSummary, hoverDelay } from "src/utils"; +import Tooltip from "src/components/Tooltip"; +import InstanceTooltip from "src/dag/InstanceTooltip"; +import { useContainerRef } from "src/context/containerRef"; + +export interface CustomNodeProps { + label: string; + height?: number; + width?: number; + isJoinNode?: boolean; + instance?: TaskInstance; + task?: Task | null; + isSelected: boolean; + latestDagRunId: DagRun["runId"]; + childCount?: number; + onToggleCollapse: () => void; + isOpen?: boolean; +} + +const Node = ({ + id, + data: { + label, + childCount, + height, + width, + isJoinNode, + instance, + task, + isSelected, + latestDagRunId, + onToggleCollapse, + isOpen, + }, +}: NodeProps) => { + const { onSelect } = useSelection(); + const containerRef = useContainerRef(); + + if (isJoinNode) { + return ( + <> + + + + + ); + } + + if (!task) return null; + + const { isMapped } = task; + const mappedStates = instance?.mappedStates; + + const { totalTasks } = getGroupAndMapSummary({ group: task, mappedStates }); + + const taskName = isMapped + ? `${label} [${instance ? totalTasks : " "}]` + : label; + + return ( + <> + + + ) : null + } + portalProps={{ containerRef }} + hasArrow + placement="top" + openDelay={hoverDelay} + > + { + if (latestDagRunId) { + onSelect({ + runId: instance?.runId || latestDagRunId, + taskId: isSelected ? undefined : id, + }); + } + }} + > + + + + {taskName} + + {!!instance && instance.state && ( + + + + {instance.state} + + + )} + {task?.operator && ( + + {task.operator} + + )} + + {!!childCount && ( + { + e.stopPropagation(); + onToggleCollapse(); + }} + > + {isOpen ? "- " : "+ "} + {childCount} tasks + + )} + + + + + + ); +}; + +export default Node; diff --git a/airflow/www/static/js/dag/details/graph/index.tsx b/airflow/www/static/js/dag/details/graph/index.tsx new file mode 100644 index 0000000000000..da92a061083c1 --- /dev/null +++ b/airflow/www/static/js/dag/details/graph/index.tsx @@ -0,0 +1,183 @@ +/*! + * 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, { useRef, useState, useEffect } from "react"; +import { Box, useTheme, Select, Text } from "@chakra-ui/react"; +import ReactFlow, { + ReactFlowProvider, + Controls, + Background, + MiniMap, + Node as ReactFlowNode, + useReactFlow, + ControlButton, + Panel, +} from "reactflow"; +import { RiFocus3Line } from "react-icons/ri"; + +import { useGraphData, useGridData } from "src/api"; +import useSelection from "src/dag/useSelection"; +import { useOffsetTop } from "src/utils"; +import { useGraphLayout } from "src/utils/graph"; +import Tooltip from "src/components/Tooltip"; +import { useContainerRef } from "src/context/containerRef"; + +import Edge from "./Edge"; +import Node, { CustomNodeProps } from "./Node"; +import { buildEdges, nodeStrokeColor, nodeColor, flattenNodes } from "./utils"; + +const nodeTypes = { custom: Node }; +const edgeTypes = { custom: Edge }; + +interface Props { + openGroupIds: string[]; + onToggleGroups: (groupIds: string[]) => void; +} + +const Graph = ({ openGroupIds, onToggleGroups }: Props) => { + const graphRef = useRef(null); + const containerRef = useContainerRef(); + const { data } = useGraphData(); + const [arrange, setArrange] = useState(data?.arrange || "LR"); + + useEffect(() => { + setArrange(data?.arrange || "LR"); + }, [data?.arrange]); + + const { data: graphData } = useGraphLayout({ + edges: data?.edges, + nodes: data?.nodes, + openGroupIds, + arrange, + }); + const { selected } = useSelection(); + const { + data: { dagRuns, groups }, + } = useGridData(); + const { colors } = useTheme(); + const { setCenter } = useReactFlow(); + const latestDagRunId = dagRuns[dagRuns.length - 1]?.runId; + + const offsetTop = useOffsetTop(graphRef); + + let nodes: ReactFlowNode[] = []; + + if (graphData?.children) { + nodes = flattenNodes({ + children: graphData.children, + selected, + openGroupIds, + onToggleGroups, + latestDagRunId, + groups, + }); + } + + const focusNode = () => { + if (selected.taskId) { + const node = nodes.find((n) => n.id === selected.taskId); + const x = node?.positionAbsolute?.x || node?.position.x; + const y = node?.positionAbsolute?.y || node?.position.y; + if (!x || !y) return; + setCenter(x, y, { duration: 1000 }); + } + }; + + const edges = buildEdges({ + edges: graphData?.edges, + nodes, + selectedTaskId: selected.taskId, + }); + + return ( + + {!!offsetTop && ( + + + + Layout: + + + + + + + + + + + + + + nodeStrokeColor(props, colors)} + nodeColor={nodeColor} + zoomable + pannable + /> + + )} + + ); +}; + +const GraphWrapper = ({ openGroupIds, onToggleGroups }: Props) => ( + + + +); + +export default GraphWrapper; diff --git a/airflow/www/static/js/dag/details/graph/utils.ts b/airflow/www/static/js/dag/details/graph/utils.ts new file mode 100644 index 0000000000000..b9fde2b8beec2 --- /dev/null +++ b/airflow/www/static/js/dag/details/graph/utils.ts @@ -0,0 +1,208 @@ +/*! + * 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 Color from "color"; +import type { Node as ReactFlowNode } from "reactflow"; +import type { ElkExtendedEdge } from "elkjs"; + +import type { SelectionProps } from "src/dag/useSelection"; +import { getTask } from "src/utils"; +import type { Task, TaskInstance } from "src/types"; +import type { NodeType } from "src/datasets/Graph/Node"; + +import type { CustomNodeProps } from "./Node"; + +interface FlattenNodesProps { + children: NodeType[]; + selected: SelectionProps; + groups: Task; + latestDagRunId: string; + parent?: ReactFlowNode; + openGroupIds: string[]; + onToggleGroups: (groupIds: string[]) => void; +} + +// Generate a flattened list of nodes for react-flow to render +export const flattenNodes = ({ + children, + selected, + groups, + latestDagRunId, + onToggleGroups, + openGroupIds, + parent, +}: FlattenNodesProps) => { + let nodes: ReactFlowNode[] = []; + const parentNode = parent ? { parentNode: parent.id } : undefined; + children.forEach((node) => { + let instance: TaskInstance | undefined; + const group = getTask({ taskId: node.id, task: groups }); + if (!node.id.includes("join_id") && selected.runId) { + instance = group?.instances.find((ti) => ti.runId === selected.runId); + } + const isSelected = node.id === selected.taskId && !!instance; + + const newNode = { + id: node.id, + data: { + width: node.width, + height: node.height, + task: group, + instance, + isSelected, + latestDagRunId, + onToggleCollapse: () => { + let newGroupIds = []; + if (!node.value.isOpen) { + newGroupIds = [...openGroupIds, node.value.label]; + } else { + newGroupIds = openGroupIds.filter((g) => g !== node.value.label); + } + onToggleGroups(newGroupIds); + }, + ...node.value, + }, + type: "custom", + position: { + x: node.x || 0, + y: node.y || 0, + }, + positionAbsolute: { + x: (parent?.positionAbsolute?.x || 0) + (node.x || 0), + y: (parent?.positionAbsolute?.y || 0) + (node.y || 0), + }, + ...parentNode, + }; + + nodes.push(newNode); + + if (node.children) { + const childNodes = flattenNodes({ + children: node.children, + selected, + groups, + latestDagRunId, + onToggleGroups, + openGroupIds, + parent: newNode, + }); + nodes = [...nodes, ...childNodes]; + } + }); + return nodes; +}; + +export const nodeColor = ({ + data: { height, width, instance }, +}: ReactFlowNode) => { + if (!height || !width) return ""; + if (width > 200 || height > 60) { + return "#cccccc50"; + } + if (instance?.state) { + return `${Color(stateColors[instance.state]).hex()}90`; + } + return "#cccccc90"; +}; + +export const nodeStrokeColor = ( + { data: { isSelected } }: ReactFlowNode, + colors: any +) => (isSelected ? colors.blue[500] : ""); + +interface BuildEdgesProps { + edges?: ElkExtendedEdge[]; + nodes: ReactFlowNode[]; + selectedTaskId?: string | null; +} + +// Format edge data to what react-flow needs to render successfully +export const buildEdges = ({ + edges = [], + nodes, + selectedTaskId, +}: BuildEdgesProps) => + edges + .map((edge) => ({ + id: edge.id, + source: edge.sources[0], + target: edge.targets[0], + data: { rest: edge }, + type: "custom", + })) + .map((e) => { + const sourceIds = e.source.split("."); + const targetIds = e.target.split("."); + const isSelected = + selectedTaskId && + (e.source === selectedTaskId || e.target === selectedTaskId); + + if ( + sourceIds.length === targetIds.length && + sourceIds[0] === targetIds[0] + ) { + const parentIds = + sourceIds.length > targetIds.length ? sourceIds : targetIds; + parentIds.pop(); + let parentX = 0; + let parentY = 0; + + nodes + .filter((n) => parentIds.some((p) => p === n.data.label)) + .forEach((p) => { + parentX += p.position.x; + parentY += p.position.y; + }); + + return { + ...e, + data: { + rest: { + ...e.data.rest, + isSelected, + sections: e.data.rest.sections.map((s) => ({ + ...s, + startPoint: { + x: s.startPoint.x + parentX, + y: s.startPoint.y + parentY, + }, + endPoint: { + x: s.endPoint.x + parentX, + y: s.endPoint.y + parentY, + }, + bendPoints: (s.bendPoints || []).map((bp) => ({ + x: bp.x + parentX, + y: bp.y + parentY, + })), + })), + }, + }, + }; + } + + return { + ...e, + data: { + rest: { + ...e.data.rest, + isSelected, + }, + }, + }; + }); diff --git a/airflow/www/static/js/dag/details/index.tsx b/airflow/www/static/js/dag/details/index.tsx index 446c889e7e417..82027fb37f8e9 100644 --- a/airflow/www/static/js/dag/details/index.tsx +++ b/airflow/www/static/js/dag/details/index.tsx @@ -17,38 +17,182 @@ * under the License. */ -import React from "react"; -import { Flex, Box, Divider } from "@chakra-ui/react"; +import React, { useEffect, useState } from "react"; +import { + Flex, + Divider, + Tabs, + TabList, + TabPanel, + TabPanels, + Tab, + Text, +} from "@chakra-ui/react"; import useSelection from "src/dag/useSelection"; +import { getTask, getMetaValue } from "src/utils"; +import { useGridData, useTaskInstance } from "src/api"; +import { MdDetails, MdAccountTree, MdReorder } from "react-icons/md"; +import { BiBracket } from "react-icons/bi"; import Header from "./Header"; import TaskInstanceContent from "./taskInstance"; import DagRunContent from "./dagRun"; import DagContent from "./Dag"; +import Graph from "./graph"; +import MappedInstances from "./taskInstance/MappedInstances"; +import Logs from "./taskInstance/Logs"; +import BackToTaskSummary from "./taskInstance/BackToTaskSummary"; -const Details = () => { +const dagId = getMetaValue("dag_id")!; + +interface Props { + openGroupIds: string[]; + onToggleGroups: (groupIds: string[]) => void; +} + +const Details = ({ openGroupIds, onToggleGroups }: Props) => { const { selected: { runId, taskId, mapIndex }, onSelect, } = useSelection(); + const isDag = !runId && !taskId; + const isDagRun = runId && !taskId; + const isTaskInstance = taskId && runId; + const [tabIndex, setTabIndex] = useState(0); + + const { + data: { dagRuns, groups }, + } = useGridData(); + const group = getTask({ taskId, task: groups }); + const children = group?.children; + const isMapped = group?.isMapped; + + const isMappedTaskSummary = isMapped && mapIndex === undefined && taskId; + const isGroup = !!children; + const isGroupOrMappedTaskSummary = isGroup || isMappedTaskSummary; + const showLogs = !!(isTaskInstance && !isGroupOrMappedTaskSummary); + const showMappedTasks = !!(isTaskInstance && isMappedTaskSummary && !isGroup); + + useEffect(() => { + if ((!taskId || isGroup) && tabIndex > 1) { + setTabIndex(1); + } + }, [runId, taskId, tabIndex, setTabIndex, isGroup]); + + const run = dagRuns.find((r) => r.runId === runId); + const { data: mappedTaskInstance } = useTaskInstance({ + dagId, + dagRunId: runId || "", + taskId: taskId || "", + mapIndex, + enabled: mapIndex !== undefined, + }); + + const instance = + mapIndex !== undefined && mapIndex > -1 + ? mappedTaskInstance + : group?.instances.find((ti) => ti.runId === runId); return (
- - {!runId && !taskId && } - {runId && !taskId && } - {taskId && runId && ( - - )} - + + + + + + Details + + + + + + Graph + + + {showLogs && ( + + + + Logs + + + )} + {showMappedTasks && ( + + + + Mapped Tasks + + + )} + + + + {isDag && } + {isDagRun && } + {isTaskInstance && ( + <> + -1} + onClick={() => onSelect({ runId, taskId })} + /> + + + )} + + + + + {showLogs && run && ( + + onSelect({ runId, taskId })} + /> + + + )} + {showMappedTasks && ( + + + onSelect({ runId, taskId, mapIndex: row.values.mapIndex }) + } + /> + + )} + + ); }; diff --git a/airflow/www/static/js/dag/details/taskInstance/Details.tsx b/airflow/www/static/js/dag/details/taskInstance/Details.tsx index de92561d059a8..942910879390e 100644 --- a/airflow/www/static/js/dag/details/taskInstance/Details.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/Details.tsx @@ -20,7 +20,7 @@ import React from "react"; import { Text, Flex, Table, Tbody, Tr, Td, Divider } from "@chakra-ui/react"; -import { finalStatesMap } from "src/utils"; +import { getGroupAndMapSummary } from "src/utils"; import { getDuration, formatDuration } from "src/datetime_utils"; import { SimpleStatus } from "src/dag/StatusBox"; import Time from "src/components/Time"; @@ -52,27 +52,13 @@ const Details = ({ instance, group, dagId }: Props) => { enabled: !isGroup && !isMapped, }); - const numMap = finalStatesMap(); - let numMapped = 0; - if (isGroup && !isMapped) { - group.children?.forEach((child) => { - const taskInstance = child.instances.find((ti) => ti.runId === runId); - if (taskInstance) { - const stateKey = - taskInstance.state == null ? "no_status" : taskInstance.state; - if (numMap.has(stateKey)) - numMap.set(stateKey, (numMap.get(stateKey) || 0) + 1); - } - }); - } else if (isMapped && mappedStates) { - Object.keys(mappedStates).forEach((stateKey) => { - const num = mappedStates[stateKey]; - numMapped += num; - numMap.set(stateKey || "no_status", num); - }); - } + const { totalTasks, childTaskMap } = getGroupAndMapSummary({ + group, + runId, + mappedStates, + }); - numMap.forEach((key, val) => { + childTaskMap.forEach((key, val) => { if (key > 0) { summary.push( // eslint-disable-next-line react/no-array-index-key @@ -146,11 +132,11 @@ const Details = ({ instance, group, dagId }: Props) => { - {mappedStates && numMapped > 0 && ( + {mappedStates && totalTasks > 0 && ( - {numMapped} {isGroup ? "Task Group" : "Task"} - {numMapped === 1 ? " " : "s "} + {totalTasks} {isGroup ? "Task Group" : "Task"} + {totalTasks === 1 ? " " : "s "} Mapped diff --git a/airflow/www/static/js/dag/details/taskInstance/Logs/LogBlock.tsx b/airflow/www/static/js/dag/details/taskInstance/Logs/LogBlock.tsx index 36698b7057d6d..f92e8a80a4e3e 100644 --- a/airflow/www/static/js/dag/details/taskInstance/Logs/LogBlock.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/Logs/LogBlock.tsx @@ -43,13 +43,13 @@ const LogBlock = ({ parsedLogs, wrap, tryNumber }: Props) => { useEffect(() => { // Always scroll to bottom when wrap or tryNumber change - scrollToBottom(); - }, [wrap, tryNumber]); + if (offsetTop) scrollToBottom(); + }, [wrap, tryNumber, offsetTop]); useEffect(() => { // When logs change, only scroll if autoScroll is enabled - if (autoScroll) scrollToBottom(); - }, [parsedLogs, autoScroll]); + if (autoScroll && offsetTop) scrollToBottom(); + }, [parsedLogs, autoScroll, offsetTop]); const onScroll = (e: React.UIEvent) => { if (e.currentTarget) { diff --git a/airflow/www/static/js/dag/details/taskInstance/Nav.tsx b/airflow/www/static/js/dag/details/taskInstance/Nav.tsx index 8ed6e329d63b5..192f4eda38f86 100644 --- a/airflow/www/static/js/dag/details/taskInstance/Nav.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/Nav.tsx @@ -18,7 +18,7 @@ */ import React, { forwardRef } from "react"; -import { Flex, Divider } from "@chakra-ui/react"; +import { Flex } from "@chakra-ui/react"; import { getMetaValue, appendSearchParams } from "src/utils"; import LinkButton from "src/components/LinkButton"; @@ -33,7 +33,6 @@ const taskInstancesUrl = getMetaValue("task_instances_list_url"); const renderedK8sUrl = getMetaValue("rendered_k8s_url"); const renderedTemplatesUrl = getMetaValue("rendered_templates_url"); const xcomUrl = getMetaValue("xcom_url"); -const logUrl = getMetaValue("log_url"); const taskUrl = getMetaValue("task_url"); const gridUrl = getMetaValue("grid_url"); const gridUrlNoRoot = getMetaValue("grid_url_no_root"); @@ -60,7 +59,6 @@ const Nav = forwardRef( }); const detailsLink = `${taskUrl}&${params}`; const renderedLink = `${renderedTemplatesUrl}&${params}`; - const logLink = `${logUrl}&${params}`; const xcomLink = `${xcomUrl}&${params}`; const k8sLink = `${renderedK8sUrl}&${params}`; const listParams = new URLSearchParamsWrapper({ @@ -78,6 +76,8 @@ const Nav = forwardRef( root: taskId, }); + if (mapIndex !== undefined && mapIndex >= 0) + listParams.append("_flt_0_map_index", mapIndex.toString()); if (baseDate) filterParams.append("base_date", baseDate); if (numRuns) filterParams.append("num_runs", numRuns); @@ -96,32 +96,28 @@ const Nav = forwardRef( const isSubDag = operator === "SubDagOperator"; return ( - <> - - {(!isMapped || mapIndex !== undefined) && ( - <> - Task Instance Details - Rendered Template - {isK8sExecutor && ( - K8s Pod Spec - )} - {isSubDag && ( - Zoom into SubDag - )} - Log - XCom - - )} - - List Instances, all runs - - Filter Upstream - - - + + {(!isMapped || mapIndex !== undefined) && ( + <> + More Details + Rendered Template + {isK8sExecutor && ( + K8s Pod Spec + )} + {isSubDag && ( + Zoom into SubDag + )} + XCom + + )} + + List Instances, all runs + + Filter Upstream + ); } ); diff --git a/airflow/www/static/js/dag/details/taskInstance/index.tsx b/airflow/www/static/js/dag/details/taskInstance/index.tsx index be290670c7af4..e95389fc65be1 100644 --- a/airflow/www/static/js/dag/details/taskInstance/index.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/index.tsx @@ -17,34 +17,18 @@ * under the License. */ -/* global localStorage */ - -import React, { useRef, useState } from "react"; -import { - Box, - Text, - Tabs, - TabList, - Tab, - TabPanels, - TabPanel, -} from "@chakra-ui/react"; +import React, { useRef } from "react"; +import { Box } from "@chakra-ui/react"; import { useGridData, useTaskInstance } from "src/api"; import { getMetaValue, getTask, useOffsetTop } from "src/utils"; import type { DagRun, TaskInstance as TaskInstanceType } from "src/types"; -import type { SelectionProps } from "src/dag/useSelection"; import NotesAccordion from "src/dag/details/NotesAccordion"; -import ExtraLinks from "./ExtraLinks"; -import Logs from "./Logs"; import TaskNav from "./Nav"; +import ExtraLinks from "./ExtraLinks"; import Details from "./Details"; -import MappedInstances from "./MappedInstances"; import TaskActions from "./taskActions"; -import BackToTaskSummary from "./BackToTaskSummary"; - -const detailsPanelActiveTabIndex = "detailsPanelActiveTabIndex"; const dagId = getMetaValue("dag_id")!; @@ -52,22 +36,16 @@ interface Props { taskId: string; runId: DagRun["runId"]; mapIndex: TaskInstanceType["mapIndex"]; - onSelect: (selectionProps: SelectionProps) => void; } -const TaskInstance = ({ taskId, runId, mapIndex, onSelect }: Props) => { - const taskInstancesRef = useRef(null); - const offsetTop = useOffsetTop(taskInstancesRef); +const TaskInstance = ({ taskId, runId, mapIndex }: Props) => { + const taskInstanceRef = useRef(null); + const offsetTop = useOffsetTop(taskInstanceRef); const isMapIndexDefined = !(mapIndex === undefined); const actionsMapIndexes = isMapIndexDefined ? [mapIndex] : []; const { data: { dagRuns, groups }, } = useGridData(); - const storageTabIndex = parseInt( - localStorage.getItem(detailsPanelActiveTabIndex) || "0", - 10 - ); - const [preferedTabIndex, setPreferedTabIndex] = useState(storageTabIndex); const group = getTask({ taskId, task: groups }); const run = dagRuns.find((r) => r.runId === runId); @@ -92,28 +70,8 @@ const TaskInstance = ({ taskId, runId, mapIndex, onSelect }: Props) => { ? mappedTaskInstance : group?.instances.find((ti) => ti.runId === runId); - const handleTabsChange = (index: number) => { - localStorage.setItem(detailsPanelActiveTabIndex, index.toString()); - setPreferedTabIndex(index); - }; - if (!group || !run || !instance) return null; - let isPreferedTabDisplayed = false; - - switch (preferedTabIndex) { - case 0: - isPreferedTabDisplayed = true; - break; - case 1: - isPreferedTabDisplayed = !isGroup || (isGroup && !!isMapped); - break; - default: - isPreferedTabDisplayed = false; - } - - const selectedTabIndex = isPreferedTabDisplayed ? preferedTabIndex : 0; - const { executionDate } = run; let taskActionsTitle = `${isGroup ? "Task Group" : "Task"} Actions`; @@ -124,7 +82,13 @@ const TaskInstance = ({ taskId, runId, mapIndex, onSelect }: Props) => { } return ( - + {!isGroup && ( { operator={operator} /> )} - - - - Details - - {isMappedTaskSummary && !isGroup && ( - - Mapped Tasks - - )} - {!isGroupOrMappedTaskSummary && ( - - Logs - - )} - - - onSelect({ runId, taskId })} + {!isGroupOrMappedTaskSummary && ( + + )} + + - - - {/* Details Tab */} - - - {!isGroupOrMappedTaskSummary && ( - - )} - - - - {!isMapped && group.extraLinks && ( - - )} -
- - - - {/* Logs Tab */} - {!isGroupOrMappedTaskSummary && ( - - - - )} - - {/* Mapped Task Instances Tab */} - {isMappedTaskSummary && !isGroup && ( - - - onSelect({ runId, taskId, mapIndex: row.values.mapIndex }) - } - /> - - )} - - + + {!isMapped && group.extraLinks && ( + + )} +
); }; diff --git a/airflow/www/static/js/dag/grid/index.test.tsx b/airflow/www/static/js/dag/grid/index.test.tsx index 6de26d2e8d0f2..1b3888ffae3fc 100644 --- a/airflow/www/static/js/dag/grid/index.test.tsx +++ b/airflow/www/static/js/dag/grid/index.test.tsx @@ -25,6 +25,7 @@ import { render, fireEvent, waitFor } from "@testing-library/react"; import { Wrapper } from "src/utils/testUtils"; import * as useGridDataModule from "src/api/useGridData"; +import useToggleGroups from "src/dag/useToggleGroups"; import Grid from "."; @@ -112,6 +113,11 @@ const mockGridData = { const EXPAND = "Expand all task groups"; const COLLAPSE = "Collapse all task groups"; +const GridWithGroups = () => { + const { openGroupIds, onToggleGroups } = useToggleGroups(); + return ; +}; + describe("Test ToggleGroups", () => { beforeAll(() => { class ResizeObserver { @@ -137,9 +143,10 @@ describe("Test ToggleGroups", () => { }); test("Group defaults to closed", () => { - const { getByTestId, getByText, getAllByTestId } = render(, { - wrapper: Wrapper, - }); + const { getByTestId, getByText, getAllByTestId } = render( + {}} />, + { wrapper: Wrapper } + ); const groupName = getByText("group_1"); @@ -149,7 +156,9 @@ describe("Test ToggleGroups", () => { }); test("Buttons are disabled if all groups are expanded or collapsed", () => { - const { getByTitle } = render(, { wrapper: Wrapper }); + const { getByTitle, queryAllByTestId } = render(, { + wrapper: Wrapper, + }); const expandButton = getByTitle(EXPAND); const collapseButton = getByTitle(COLLAPSE); @@ -157,16 +166,23 @@ describe("Test ToggleGroups", () => { expect(expandButton).toBeEnabled(); expect(collapseButton).toBeDisabled(); + const taskElements = queryAllByTestId("task-instance"); + expect(taskElements).toHaveLength(1); + fireEvent.click(expandButton); + const newTaskElements = queryAllByTestId("task-instance"); + expect(newTaskElements).toHaveLength(3); + expect(collapseButton).toBeEnabled(); expect(expandButton).toBeDisabled(); }); test("Expand/collapse buttons toggle nested groups", async () => { - const { getByText, queryAllByTestId, getByTitle } = render(, { - wrapper: Wrapper, - }); + const { getByText, queryAllByTestId, getByTitle } = render( + , + { wrapper: Wrapper } + ); const expandButton = getByTitle(EXPAND); const collapseButton = getByTitle(COLLAPSE); @@ -198,9 +214,11 @@ describe("Test ToggleGroups", () => { }); test("Hovered effect on task state", async () => { - const { rerender, queryAllByTestId } = render(, { - wrapper: Wrapper, - }); + const openGroupIds = ["group_1", "task_1"]; + const { rerender, queryAllByTestId } = render( + {}} />, + { wrapper: Wrapper } + ); const taskElements = queryAllByTestId("task-instance"); expect(taskElements).toHaveLength(3); @@ -209,13 +227,25 @@ describe("Test ToggleGroups", () => { expect(taskElement).toHaveStyle("opacity: 1"); }); - rerender(); + rerender( + {}} + /> + ); taskElements.forEach((taskElement) => { expect(taskElement).toHaveStyle("opacity: 1"); }); - rerender(); + rerender( + {}} + /> + ); taskElements.forEach((taskElement) => { expect(taskElement).toHaveStyle("opacity: 0.3"); diff --git a/airflow/www/static/js/dag/grid/index.tsx b/airflow/www/static/js/dag/grid/index.tsx index a76e349a22521..f0c8ea1b08d60 100644 --- a/airflow/www/static/js/dag/grid/index.tsx +++ b/airflow/www/static/js/dag/grid/index.tsx @@ -17,31 +17,33 @@ * under the License. */ -/* global localStorage, ResizeObserver */ +/* global ResizeObserver */ -import React, { useState, useRef, useEffect } from "react"; +import React, { useRef, useEffect } from "react"; import { Table, Tbody, Box, Thead, IconButton } from "@chakra-ui/react"; import { MdDoubleArrow } from "react-icons/md"; import { useGridData } from "src/api"; -import { getMetaValue, useOffsetTop } from "src/utils"; +import { useOffsetTop } from "src/utils"; import renderTaskRows from "./renderTaskRows"; import DagRuns from "./dagRuns"; -const dagId = getMetaValue("dag_id"); - interface Props { isPanelOpen?: boolean; onPanelToggle?: () => void; hoveredTaskState?: string | null; + openGroupIds: string[]; + onToggleGroups: (groupIds: string[]) => void; } const Grid = ({ isPanelOpen = false, onPanelToggle, hoveredTaskState, + openGroupIds, + onToggleGroups, }: Props) => { const scrollRef = useRef(null); const tableRef = useRef(null); @@ -52,15 +54,6 @@ const Grid = ({ } = useGridData(); const dagRunIds = dagRuns.map((dr) => dr.runId); - const openGroupsKey = `${dagId}/open-groups`; - const storedGroups = JSON.parse(localStorage.getItem(openGroupsKey) || "[]"); - const [openGroupIds, setOpenGroupIds] = useState(storedGroups); - - const onToggleGroups = (groupIds: string[]) => { - localStorage.setItem(openGroupsKey, JSON.stringify(groupIds)); - setOpenGroupIds(groupIds); - }; - useEffect(() => { const scrollOnResize = new ResizeObserver(() => { const runsContainer = scrollRef.current; diff --git a/airflow/www/static/js/dag/index.tsx b/airflow/www/static/js/dag/index.tsx index d1b9819fe4d95..47ed13c9a02bb 100644 --- a/airflow/www/static/js/dag/index.tsx +++ b/airflow/www/static/js/dag/index.tsx @@ -22,6 +22,7 @@ import React from "react"; import { createRoot } from "react-dom/client"; import createCache from "@emotion/cache"; +import reactFlowStyle from "reactflow/dist/style.css"; import App from "src/App"; @@ -30,6 +31,7 @@ import Main from "./Main"; // create shadowRoot const root = document.querySelector("#root"); const shadowRoot = root?.attachShadow({ mode: "open" }); + const cache = createCache({ container: shadowRoot, key: "c", @@ -37,6 +39,10 @@ const cache = createCache({ const mainElement = document.getElementById("react-container"); if (mainElement) { + const styleTag = document.createElement("style"); + const style = reactFlowStyle.toString(); + styleTag.innerHTML = style; + shadowRoot?.appendChild(styleTag); shadowRoot?.appendChild(mainElement); const reactRoot = createRoot(mainElement); diff --git a/airflow/www/static/js/dag/useSelection.test.tsx b/airflow/www/static/js/dag/useSelection.test.tsx index 400634beca51b..db9ff67c3221c 100644 --- a/airflow/www/static/js/dag/useSelection.test.tsx +++ b/airflow/www/static/js/dag/useSelection.test.tsx @@ -38,12 +38,12 @@ describe("Test useSelection hook", () => { expect(runId).toBeNull(); expect(taskId).toBeNull(); - expect(mapIndex).toBeNull(); + expect(mapIndex).toBeUndefined(); }); test.each([ { taskId: "task_1", runId: "run_1", mapIndex: 2 }, - { taskId: null, runId: "run_1", mapIndex: null }, + { taskId: null, runId: "run_1", mapIndex: undefined }, { taskId: "task_2", runId: null, mapIndex: 1 }, { taskId: "task_3", runId: null, mapIndex: -1 }, { taskId: "task_4", runId: null, mapIndex: 0 }, @@ -65,6 +65,6 @@ describe("Test useSelection hook", () => { expect(result.current.selected.taskId).toBeNull(); expect(result.current.selected.runId).toBeNull(); - expect(result.current.selected.mapIndex).toBeNull(); + expect(result.current.selected.mapIndex).toBeUndefined(); }); }); diff --git a/airflow/www/static/js/dag/useSelection.ts b/airflow/www/static/js/dag/useSelection.ts index 3c0a011389048..c36fbc5640ed4 100644 --- a/airflow/www/static/js/dag/useSelection.ts +++ b/airflow/www/static/js/dag/useSelection.ts @@ -27,7 +27,7 @@ const MAP_INDEX = "map_index"; export interface SelectionProps { runId?: string | null; taskId?: string | null; - mapIndex?: number | null; + mapIndex?: number; } const useSelection = () => { @@ -59,7 +59,8 @@ const useSelection = () => { const runId = searchParams.get(RUN_ID); const taskId = searchParams.get(TASK_ID); const mapIndexParam = searchParams.get(MAP_INDEX); - const mapIndex = mapIndexParam !== null ? parseInt(mapIndexParam, 10) : null; + const mapIndex = + mapIndexParam !== null ? parseInt(mapIndexParam, 10) : undefined; return { selected: { diff --git a/airflow/www/static/js/dag/useToggleGroups.ts b/airflow/www/static/js/dag/useToggleGroups.ts new file mode 100644 index 0000000000000..6b9eadaa4c46c --- /dev/null +++ b/airflow/www/static/js/dag/useToggleGroups.ts @@ -0,0 +1,43 @@ +/*! + * 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. + */ + +/* global localStorage */ + +import { useState } from "react"; +import { getMetaValue } from "src/utils"; + +const dagId = getMetaValue("dag_id"); + +const useToggleGroups = () => { + const openGroupsKey = `${dagId}/open-groups`; + const storedGroups = JSON.parse(localStorage.getItem(openGroupsKey) || "[]"); + const [openGroupIds, setOpenGroupIds] = useState(storedGroups); + + const onToggleGroups = (groupIds: string[]) => { + localStorage.setItem(openGroupsKey, JSON.stringify(groupIds)); + setOpenGroupIds(groupIds); + }; + + return { + openGroupIds, + onToggleGroups, + }; +}; + +export default useToggleGroups; diff --git a/airflow/www/static/js/datasets/Graph/Edge.tsx b/airflow/www/static/js/datasets/Graph/Edge.tsx index dd1b9ae4cd4d2..a5ef5fbfed3d2 100644 --- a/airflow/www/static/js/datasets/Graph/Edge.tsx +++ b/airflow/www/static/js/datasets/Graph/Edge.tsx @@ -29,24 +29,27 @@ interface Props { targets: string[]; sections: Record[]; }; - isSelected: boolean; + isSelected?: boolean; + showMarker?: boolean; } -const Edge = ({ edge, isSelected }: Props) => { +const Edge = ({ edge, isSelected = false, showMarker = true }: Props) => { const { colors } = useTheme(); return ( <> - - {edge.sections.map((s) => ( + {showMarker && ( + + )} + {(edge.sections || []).map((s) => ( d.x || 0} y={(d) => d.y || 0} data={[s.startPoint, ...(s.bendPoints || []), s.endPoint]} diff --git a/airflow/www/static/js/datasets/Graph/Node.tsx b/airflow/www/static/js/datasets/Graph/Node.tsx index 8c8799a0b21d7..d692861b66b4b 100644 --- a/airflow/www/static/js/datasets/Graph/Node.tsx +++ b/airflow/www/static/js/datasets/Graph/Node.tsx @@ -30,6 +30,7 @@ import DagNode from "./DagNode"; export interface NodeType extends ElkShape { value: DepNode["value"]; + children?: NodeType[]; } interface Props { diff --git a/airflow/www/static/js/types/index.ts b/airflow/www/static/js/types/index.ts index e3248137ac636..dc45f79460158 100644 --- a/airflow/www/static/js/types/index.ts +++ b/airflow/www/static/js/types/index.ts @@ -112,7 +112,11 @@ interface DepNode { label: string; rx: number; ry: number; + isOpen?: boolean; + isJoinNode?: boolean; + childCount?: number; }; + children?: DepNode[]; } interface DepEdge { diff --git a/airflow/www/static/js/utils/graph.ts b/airflow/www/static/js/utils/graph.ts new file mode 100644 index 0000000000000..8c8da293cec06 --- /dev/null +++ b/airflow/www/static/js/utils/graph.ts @@ -0,0 +1,216 @@ +/*! + * 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 ELK, { ElkExtendedEdge, ElkShape } from "elkjs"; + +import type { DepNode } from "src/types"; +import type { NodeType } from "src/datasets/Graph/Node"; +import { useQuery } from "react-query"; + +interface GenerateProps { + nodes: DepNode[]; + edges: WebserverEdge[]; + font: string; + openGroupIds?: string[]; + arrange: string; +} + +interface WebserverEdge { + label?: string; + sourceId: string; + targetId: string; +} + +interface Graph extends ElkShape { + children: NodeType[]; + edges: ElkExtendedEdge[]; +} + +interface LayoutProps { + edges?: WebserverEdge[]; + nodes?: DepNode; + openGroupIds?: string[]; + arrange?: string; +} + +// Take text and font to calculate how long each node should be +export function getTextWidth(text: string, font: string) { + const context = document.createElement("canvas").getContext("2d"); + if (context) { + context.font = font; + const metrics = context.measureText(text); + return metrics.width; + } + return text.length * 9; +} + +const getDirection = (arrange: string) => { + switch (arrange) { + case "RL": + return "LEFT"; + case "TB": + return "DOWN"; + case "BT": + return "UP"; + case "LR": + default: + return "RIGHT"; + } +}; + +const generateGraph = ({ + nodes, + edges: unformattedEdges, + font, + openGroupIds, + arrange, +}: GenerateProps) => { + const closedGroupIds: string[] = []; + + const formatChildNode = (node: any) => { + const { id, value, children } = node; + const isOpen = openGroupIds?.includes(value.label); + if (isOpen && children.length) { + return { + id, + value: { + ...value, + childCount: children.length, + isOpen: true, + }, + label: value.label, + layoutOptions: { + "elk.padding": "[top=60,left=10,bottom=10,right=10]", + }, + children: children.map(formatChildNode), + }; + } + const isJoinNode = id.includes("join_id"); + if (children?.length) closedGroupIds.push(value.label); + return { + id, + label: value.label, + value: { + ...value, + isJoinNode, + childCount: children?.length || 0, + }, + width: isJoinNode ? 10 : 200, + height: isJoinNode ? 10 : 60, + }; + }; + const children = nodes.map(formatChildNode); + + const edges = unformattedEdges + .map((edge) => { + let { sourceId, targetId } = edge; + const splitSource = sourceId.split("."); + const splitTarget = targetId.split("."); + + if (closedGroupIds.includes(splitSource[splitSource.length - 2])) { + splitSource.pop(); + sourceId = splitSource.join("."); + } + if (closedGroupIds.includes(splitTarget[splitTarget.length - 2])) { + splitTarget.pop(); + targetId = splitTarget.join("."); + } + return { + ...edge, + targetId, + sourceId, + }; + }) + // Deduplicate edges + .filter( + (value, index, self) => + index === + self.findIndex( + (t) => t.sourceId === value.sourceId && t.targetId === value.targetId + ) + ) + .filter((edge) => { + const splitSource = edge.sourceId.split("."); + const splitTarget = edge.targetId.split("."); + if ( + splitSource + .slice(0, splitSource.length - 1) + .some((id) => closedGroupIds.includes(id)) || + splitTarget + .slice(0, splitTarget.length - 1) + .some((id) => closedGroupIds.includes(id)) + ) { + return false; + } + if (edge.sourceId === edge.targetId) return false; + return true; + }) + .map((e) => ({ + id: `${e.sourceId}-${e.targetId}`, + sources: [e.sourceId], + targets: [e.targetId], + labels: e.label + ? [ + { + id: e.label, + text: e.label, + height: 16, + width: getTextWidth(e.label, font), + }, + ] + : [], + })); + + return { + id: "root", + layoutOptions: { + hierarchyHandling: "INCLUDE_CHILDREN", + "elk.direction": getDirection(arrange), + "spacing.edgeLabel": "10.0", + }, + children, + edges, + }; +}; + +export const useGraphLayout = ({ + edges = [], + nodes, + openGroupIds, + arrange = "LR", +}: LayoutProps) => + useQuery( + ["graphLayout", !!nodes?.children, openGroupIds, arrange], + async () => { + const font = `bold ${16}px ${ + window.getComputedStyle(document.body).fontFamily + }`; + const elk = new ELK(); + const data = await elk.layout( + generateGraph({ + nodes: nodes?.children || [], + edges, + font, + openGroupIds, + arrange, + }) + ); + return data as Graph; + } + ); diff --git a/airflow/www/static/js/utils/index.ts b/airflow/www/static/js/utils/index.ts index a6d35811b4d93..7526adfff74cf 100644 --- a/airflow/www/static/js/utils/index.ts +++ b/airflow/www/static/js/utils/index.ts @@ -18,8 +18,7 @@ */ import Color from "color"; - -import type { DagRun, RunOrdering, Task } from "src/types"; +import type { DagRun, RunOrdering, Task, TaskInstance } from "src/types"; import useOffsetTop from "./useOffsetTop"; @@ -49,6 +48,43 @@ const finalStatesMap = () => ["no_status", 0], ]); +interface GroupSummaryProps { + group: Task; + runId?: string; + mappedStates: TaskInstance["mappedStates"]; +} + +const getGroupAndMapSummary = ({ + group, + runId, + mappedStates, +}: GroupSummaryProps) => { + let totalTasks = 0; + const childTaskMap = finalStatesMap(); + if (!!group.children && !group.isMapped) { + group.children?.forEach((child) => { + const taskInstance = child.instances.find((ti) => ti.runId === runId); + if (taskInstance) { + const stateKey = + taskInstance.state == null ? "no_status" : taskInstance.state; + if (childTaskMap.has(stateKey)) { + childTaskMap.set(stateKey, (childTaskMap.get(stateKey) || 0) + 1); + } + } + }); + } else if (group.isMapped && mappedStates) { + Object.entries(mappedStates).forEach(([key, value]) => { + totalTasks += value; + childTaskMap.set(key || "no_status", value); + }); + } + + return { + totalTasks, + childTaskMap, + }; +}; + const appendSearchParams = ( url: string | null, params: URLSearchParams | string @@ -144,6 +180,7 @@ const getStatusBackgroundColor = (color: string, hasNote: boolean) => export { hoverDelay, finalStatesMap, + getGroupAndMapSummary, getMetaValue, appendSearchParams, getTask, diff --git a/airflow/www/templates/airflow/dag.html b/airflow/www/templates/airflow/dag.html index 2ed843bb7887a..0fdc5473435dd 100644 --- a/airflow/www/templates/airflow/dag.html +++ b/airflow/www/templates/airflow/dag.html @@ -55,6 +55,7 @@ + diff --git a/airflow/www/views.py b/airflow/www/views.py index 8e6ad1981e271..f428116309eb8 100644 --- a/airflow/www/views.py +++ b/airflow/www/views.py @@ -3530,6 +3530,42 @@ def extra_links(self, *, session: Session = NEW_SESSION): else: return {"url": None, "error": f"No URL found for {link_name}"}, 404 + @expose("/object/graph_data") + @auth.has_access( + [ + (permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG), + (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_INSTANCE), + (permissions.ACTION_CAN_READ, permissions.RESOURCE_TASK_LOG), + ] + ) + @gzipped + @action_logging + @provide_session + def graph_data(self, session=None): + """Get Graph Data""" + dag_id = request.args.get("dag_id") + dag = get_airflow_app().dag_bag.get_dag(dag_id, session=session) + root = request.args.get("root") + if root: + filter_upstream = request.args.get("filter_upstream") == "true" + filter_downstream = request.args.get("filter_downstream") == "true" + dag = dag.partial_subset( + task_ids_or_regex=root, include_upstream=filter_upstream, include_downstream=filter_downstream + ) + + nodes = task_group_to_dict(dag.task_group) + edges = dag_edges(dag) + + data = { + "arrange": dag.orientation, + "nodes": nodes, + "edges": edges, + } + return ( + htmlsafe_json_dumps(data, separators=(",", ":"), dumps=flask.json.dumps), + {"Content-Type": "application/json; charset=utf-8"}, + ) + @expose("/object/task_instances") @auth.has_access( [ diff --git a/airflow/www/webpack.config.js b/airflow/www/webpack.config.js index 8732fbb74a9b0..82344e3b03e96 100644 --- a/airflow/www/webpack.config.js +++ b/airflow/www/webpack.config.js @@ -123,6 +123,7 @@ const config = { { test: /\.css$/, include: CSS_DIR, + exclude: /node_modules/, use: [ { loader: MiniCssExtractPlugin.loader, @@ -133,6 +134,13 @@ const config = { "css-loader", ], }, + // Extract css files + { + test: /\.css$/, + exclude: CSS_DIR, + include: /node_modules/, + use: ["css-loader"], + }, /* for css linking images */ { test: /\.(png|jpg|gif)$/i, @@ -199,7 +207,6 @@ const config = { from: "node_modules/nvd3/build/*.min.*", flatten: true, }, - // Update this when upgrade d3 package, as the path in new D3 is different { from: "node_modules/d3/d3.min.*", flatten: true, diff --git a/airflow/www/yarn.lock b/airflow/www/yarn.lock index 47ef127a98d65..d92f7c0ca2ab1 100644 --- a/airflow/www/yarn.lock +++ b/airflow/www/yarn.lock @@ -1354,6 +1354,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.18.9": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3" + integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/runtime@^7.7.6": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" @@ -2537,6 +2544,64 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9" integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA== +"@reactflow/background@11.1.0": + version "11.1.0" + resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.1.0.tgz#943c799d9f251340e9867ad8f4c6ac291163e401" + integrity sha512-EgDn3rhK+l8jKmE6KGUZvesRjdh7fOqsz5Hj7STUU5/uGsvgN9KFuudY/Ka8m+yCQxqNK8MAJcRMOZd0mvNFMQ== + dependencies: + "@babel/runtime" "^7.18.9" + "@reactflow/core" "11.4.0" + classcat "^5.0.3" + zustand "^4.1.1" + +"@reactflow/controls@11.1.0": + version "11.1.0" + resolved "https://registry.yarnpkg.com/@reactflow/controls/-/controls-11.1.0.tgz#6d6f6dd6e53557579c6cfcea3c7376d2d00c2953" + integrity sha512-5nH1TQ9mkveUOnq7QgohzeAdGR4WwKQJMrWjb5u3Dnm5D5+oRxTE3eGBoaw6B6nYaK1rDrPCcMAuGmEPdEC+Mg== + dependencies: + "@babel/runtime" "^7.18.9" + "@reactflow/core" "11.4.0" + classcat "^5.0.3" + +"@reactflow/core@11.4.0": + version "11.4.0" + resolved "https://registry.yarnpkg.com/@reactflow/core/-/core-11.4.0.tgz#9af0c812eb9968b75cf55427c6be4a9205d0db48" + integrity sha512-AfFp685kmxWs2Iiq35TatG9Q8u5W+eftXECQ0ea55Oi37nrMe5jfWhjnGnnl3bSFcHqAe6avqNiFDwqugU6kzQ== + dependencies: + "@types/d3" "^7.4.0" + "@types/d3-drag" "^3.0.1" + "@types/d3-selection" "^3.0.3" + "@types/d3-zoom" "^3.0.1" + classcat "^5.0.3" + d3-drag "^3.0.0" + d3-selection "^3.0.0" + d3-zoom "^3.0.0" + zustand "^4.1.1" + +"@reactflow/minimap@11.3.0": + version "11.3.0" + resolved "https://registry.yarnpkg.com/@reactflow/minimap/-/minimap-11.3.0.tgz#2f89dbab4c10b754c452f70857172d959cca60aa" + integrity sha512-nvb4qmbsogjhrn7GWXpvLMtmAyE7mjs0BXvtbpcFVpKqQ3Lbf76zCa8c2krUMnBBqu+9yF0Ftkn7mMCTV2gPLQ== + dependencies: + "@babel/runtime" "^7.18.9" + "@reactflow/core" "11.4.0" + "@types/d3-selection" "^3.0.3" + "@types/d3-zoom" "^3.0.1" + classcat "^5.0.3" + d3-selection "^3.0.0" + d3-zoom "^3.0.0" + zustand "^4.1.1" + +"@reactflow/node-toolbar@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@reactflow/node-toolbar/-/node-toolbar-1.1.0.tgz#c7de2fb5c7aef02a1e575ce12a35d23cd45c18bd" + integrity sha512-kibrTGGvwhFGndVSgwr9E6l9Uddr44csr06X+PJ7FJ0SXgeOHbSw4MaM/9dSFxkFoCi77fPXSdMONNTReSBnIg== + dependencies: + "@babel/runtime" "^7.18.9" + "@reactflow/core" "11.4.0" + classcat "^5.0.3" + zustand "^4.1.1" + "@redocly/ajv@^8.6.4": version "8.6.4" resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" @@ -2697,11 +2762,111 @@ dependencies: "@types/color-convert" "*" +"@types/d3-array@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.3.tgz#87d990bf504d14ad6b16766979d04e943c046dac" + integrity sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ== + +"@types/d3-axis@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.1.tgz#6afc20744fa5cc0cbc3e2bd367b140a79ed3e7a8" + integrity sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.1.tgz#ae5f17ce391935ca88b29000e60ee20452c6357c" + integrity sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.1.tgz#54c8856c19c8e4ab36a53f73ba737de4768ad248" + integrity sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw== + +"@types/d3-color@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.0.tgz#6594da178ded6c7c3842f3cc0ac84b156f12f2d4" + integrity sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA== + "@types/d3-color@^1": version "1.4.2" resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.4.2.tgz#944f281d04a0f06e134ea96adbb68303515b2784" integrity sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA== +"@types/d3-contour@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.1.tgz#9ff4e2fd2a3910de9c5097270a7da8a6ef240017" + integrity sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz#006b7bd838baec1511270cb900bf4fc377bbbf41" + integrity sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ== + +"@types/d3-dispatch@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz#a1b18ae5fa055a6734cb3bd3cbc6260ef19676e3" + integrity sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw== + +"@types/d3-drag@*", "@types/d3-drag@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.1.tgz#fb1e3d5cceeee4d913caa59dedf55c94cb66e80f" + integrity sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.0.tgz#f3c61fb117bd493ec0e814856feb804a14cfc311" + integrity sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A== + +"@types/d3-ease@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.0.tgz#c29926f8b596f9dadaeca062a32a45365681eae0" + integrity sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA== + +"@types/d3-fetch@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.1.tgz#f9fa88b81aa2eea5814f11aec82ecfddbd0b8fe0" + integrity sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.3.tgz#76cb20d04ae798afede1ea6e41750763ff5a9c82" + integrity sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA== + +"@types/d3-format@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.1.tgz#194f1317a499edd7e58766f96735bdc0216bb89d" + integrity sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg== + +"@types/d3-geo@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.0.2.tgz#e7ec5f484c159b2c404c42d260e6d99d99f45d9a" + integrity sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz#4561bb7ace038f247e108295ef77b6a82193ac25" + integrity sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ== + +"@types/d3-interpolate@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz#e7d17fa4a5830ad56fe22ce3b4fac8541a9572dc" + integrity sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw== + dependencies: + "@types/d3-color" "*" + "@types/d3-interpolate@^1.3.1": version "1.4.2" resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz#88902a205f682773a517612299a44699285eed7b" @@ -2709,11 +2874,43 @@ dependencies: "@types/d3-color" "^1" +"@types/d3-path@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.0.tgz#939e3a784ae4f80b1fde8098b91af1776ff1312b" + integrity sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg== + "@types/d3-path@^1", "@types/d3-path@^1.0.8": version "1.0.9" resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c" integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ== +"@types/d3-polygon@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.0.tgz#5200a3fa793d7736fa104285fa19b0dbc2424b93" + integrity sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw== + +"@types/d3-quadtree@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz#433112a178eb7df123aab2ce11c67f51cafe8ff5" + integrity sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw== + +"@types/d3-random@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.1.tgz#5c8d42b36cd4c80b92e5626a252f994ca6bfc953" + integrity sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ== + +"@types/d3-scale-chromatic@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#103124777e8cdec85b20b51fd3397c682ee1e954" + integrity sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw== + +"@types/d3-scale@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.2.tgz#41be241126af4630524ead9cb1008ab2f0f26e69" + integrity sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA== + dependencies: + "@types/d3-time" "*" + "@types/d3-scale@^3.3.0": version "3.3.2" resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-3.3.2.tgz#18c94e90f4f1c6b1ee14a70f14bfca2bd1c61d06" @@ -2721,6 +2918,18 @@ dependencies: "@types/d3-time" "^2" +"@types/d3-selection@*", "@types/d3-selection@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.3.tgz#57be7da68e7d9c9b29efefd8ea5a9ef1171e42ba" + integrity sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA== + +"@types/d3-shape@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.0.tgz#1d87a6ddcf28285ef1e5c278ca4bdbc0658f3505" + integrity sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA== + dependencies: + "@types/d3-path" "*" + "@types/d3-shape@^1.3.1": version "1.3.8" resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.8.tgz#c3c15ec7436b4ce24e38de517586850f1fea8e89" @@ -2728,11 +2937,77 @@ dependencies: "@types/d3-path" "^1" +"@types/d3-time-format@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.0.tgz#ee7b6e798f8deb2d9640675f8811d0253aaa1946" + integrity sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw== + +"@types/d3-time@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.0.tgz#e1ac0f3e9e195135361fa1a1d62f795d87e6e819" + integrity sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg== + "@types/d3-time@^2", "@types/d3-time@^2.0.0": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-2.1.1.tgz#743fdc821c81f86537cbfece07093ac39b4bc342" integrity sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg== +"@types/d3-timer@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.0.tgz#e2505f1c21ec08bda8915238e397fb71d2fc54ce" + integrity sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g== + +"@types/d3-transition@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.2.tgz#393dc3e3d55009a43cc6f252e73fccab6d78a8a4" + integrity sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*", "@types/d3-zoom@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.1.tgz#4bfc7e29625c4f79df38e2c36de52ec3e9faf826" + integrity sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.0.tgz#fc5cac5b1756fc592a3cf1f3dc881bf08225f515" + integrity sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + "@types/debug@^4.0.0": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" @@ -2761,6 +3036,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/geojson@*": + version "7946.0.10" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249" + integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA== + "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" @@ -4083,6 +4363,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +classcat@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.4.tgz#e12d1dfe6df6427f260f03b80dc63571a5107ba6" + integrity sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g== + classnames@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" @@ -4226,7 +4511,7 @@ commander@2, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.0.0, commander@^7.2.0: +commander@7, commander@^7.0.0, commander@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== @@ -4518,6 +4803,42 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== +"d3-7@npm:d3@7": + version "7.8.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.0.tgz#c9441f0ea9266b1003a97c2ffd53e79e9e14b1fc" + integrity sha512-a5rNemRadWkEfqnY5NsD4RdCP9vn8EIJ4I5Rl14U0uKH1SXqcNmk/h9aGaAF1O98lz6L9M0IeUcuPa9GUYbI5A== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0: version "1.2.4" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" @@ -4530,11 +4851,23 @@ d3-array@2, d3-array@^2.3.0: dependencies: internmap "^1.0.0" +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.1.tgz#39331ea706f5709417d31bbb6ec152e0328b39b3" + integrity sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ== + dependencies: + internmap "1 - 2" + d3-axis@1: version "1.0.12" resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.12.tgz#cdf20ba210cfbb43795af33756886fb3638daac9" integrity sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ== +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + d3-brush@1: version "1.1.6" resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.1.6.tgz#b0a22c7372cabec128bdddf9bddc058592f89e9b" @@ -4546,6 +4879,17 @@ d3-brush@1: d3-selection "1" d3-transition "1" +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + d3-chord@1: version "1.0.6" resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.6.tgz#309157e3f2db2c752f0280fedd35f2067ccbb15f" @@ -4554,12 +4898,19 @@ d3-chord@1: d3-array "1" d3-path "1" +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + d3-collection@1, d3-collection@^1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== -d3-color@1, "d3-color@1 - 2", d3-color@^3.1.0: +d3-color@1, "d3-color@1 - 2", "d3-color@1 - 3", d3-color@3, d3-color@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== @@ -4571,11 +4922,30 @@ d3-contour@1: dependencies: d3-array "^1.1.1" +d3-contour@4: + version "4.0.0" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.0.tgz#5a1337c6da0d528479acdb5db54bc81a0ff2ec6b" + integrity sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92" + integrity sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ== + dependencies: + delaunator "5" + d3-dispatch@1: version "1.0.6" resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58" integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA== +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + d3-drag@1: version "1.2.5" resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70" @@ -4584,6 +4954,14 @@ d3-drag@1: d3-dispatch "1" d3-selection "1" +"d3-drag@2 - 3", d3-drag@3, d3-drag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + d3-dsv@1: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c" @@ -4593,11 +4971,25 @@ d3-dsv@1: iconv-lite "0.4" rw "1" +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + d3-ease@1: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2" integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ== +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + d3-fetch@1: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-1.2.0.tgz#15ce2ecfc41b092b1db50abd2c552c2316cf7fc7" @@ -4605,6 +4997,13 @@ d3-fetch@1: dependencies: d3-dsv "1" +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + d3-force@1: version "1.2.1" resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b" @@ -4615,6 +5014,15 @@ d3-force@1: d3-quadtree "1" d3-timer "1" +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + d3-format@1: version "1.4.5" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" @@ -4625,6 +5033,11 @@ d3-format@1: resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767" integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + d3-geo@1: version "1.12.1" resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f" @@ -4632,11 +5045,23 @@ d3-geo@1: dependencies: d3-array "1" +d3-geo@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.0.tgz#74fd54e1f4cebd5185ac2039217a98d39b0a4c0e" + integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA== + dependencies: + d3-array "2.5.0 - 3" + d3-hierarchy@1: version "1.1.9" resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83" integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ== +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + d3-interpolate@1, d3-interpolate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987" @@ -4644,6 +5069,13 @@ d3-interpolate@1, d3-interpolate@^1.4.0: dependencies: d3-color "1" +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + "d3-interpolate@1.2.0 - 2": version "2.0.1" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" @@ -4661,21 +5093,41 @@ d3-path@1, d3-path@^1.0.5: resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8" integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA== +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + d3-polygon@1: version "1.0.6" resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.6.tgz#0bf8cb8180a6dc107f518ddf7975e12abbfbd38e" integrity sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ== +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + d3-quadtree@1: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135" integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA== +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + d3-random@1: version "1.1.2" resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.2.tgz#2833be7c124360bf9e2d3fd4f33847cfe6cab291" integrity sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ== +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + d3-scale-chromatic@1: version "1.5.0" resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz#54e333fc78212f439b14641fb55801dd81135a98" @@ -4684,6 +5136,14 @@ d3-scale-chromatic@1: d3-color "1" d3-interpolate "1" +d3-scale-chromatic@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" + integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + d3-scale@2: version "2.2.2" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f" @@ -4696,6 +5156,17 @@ d3-scale@2: d3-time "1" d3-time-format "2" +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + d3-scale@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3" @@ -4712,6 +5183,11 @@ d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.3.0: resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c" integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg== +"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + d3-shape@1, d3-shape@^1.0.6, d3-shape@^1.2.0: version "1.3.7" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" @@ -4719,6 +5195,13 @@ d3-shape@1, d3-shape@^1.0.6, d3-shape@^1.2.0: dependencies: d3-path "1" +d3-shape@3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + d3-shape@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-2.1.0.tgz#3b6a82ccafbc45de55b57fcf956c584ded3b666f" @@ -4740,6 +5223,13 @@ d3-time-format@2: dependencies: d3-time "1 - 2" +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + d3-time@1: version "1.1.0" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" @@ -4752,11 +5242,23 @@ d3-time@1: dependencies: d3-array "2" +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + d3-timer@1: version "1.0.10" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + d3-tip@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/d3-tip/-/d3-tip-0.9.1.tgz#84e6d331c4e6650d80c5228a07e41820609ab64b" @@ -4777,6 +5279,17 @@ d3-transition@1: d3-selection "^1.1.0" d3-timer "1" +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + d3-voronoi@1: version "1.1.4" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297" @@ -4793,10 +5306,21 @@ d3-zoom@1: d3-selection "1" d3-transition "1" +d3-zoom@3, d3-zoom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + d3@^3.4.4: version "3.5.17" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" - integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g= + integrity sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg== d3@^5.14: version "5.16.0" @@ -5000,6 +5524,13 @@ del@^4.1.1: pify "^4.0.1" rimraf "^2.6.3" +delaunator@5: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" + integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw== + dependencies: + robust-predicates "^3.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -6311,6 +6842,13 @@ iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.6: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -6404,6 +6942,11 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + internmap@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" @@ -8305,11 +8848,6 @@ nano-time@1.0.0: dependencies: big-integer "^1.6.16" -nanoid@^3.1.23: - version "3.3.2" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" - integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== - nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" @@ -8452,7 +8990,7 @@ num2fraction@^1.2.2: nvd3@^1.8.6: version "1.8.6" resolved "https://registry.yarnpkg.com/nvd3/-/nvd3-1.8.6.tgz#2d3eba74bf33363b5101ebf1d093c59a53ae73c4" - integrity sha1-LT66dL8zNjtRAevx0JPFmlOuc8Q= + integrity sha512-YGQ9hAQHuQCF0JmYkT2GhNMHb5pA+vDfQj6C2GdpQPzdRPj/srPG3mh/3fZzUFt+at1NusLk/RqICUWkxm4viQ== nwsapi@^2.2.0: version "2.2.0" @@ -9108,7 +9646,15 @@ postcss-scss@^2.1.1: dependencies: postcss "^7.0.6" -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5: +postcss-selector-parser@^6.0.2: + version "6.0.11" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" + integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5: version "6.0.6" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== @@ -9163,13 +9709,13 @@ postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0. source-map "^0.6.1" postcss@^8.2.15: - version "8.3.4" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.4.tgz#41ece1c43f2f7c74dc7d90144047ce052757b822" - integrity sha512-/tZY0PXExXXnNhKv3TOvZAOUYRyuqcCbBm2c17YMDK0PlVII3K7/LKdt3ScHL+hhouddjUWi+1sKDf9xXW+8YA== + version "8.4.20" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56" + integrity sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g== dependencies: - colorette "^1.2.2" - nanoid "^3.1.23" - source-map-js "^0.6.2" + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" postcss@^8.4.13: version "8.4.14" @@ -9511,6 +10057,17 @@ react@^18.0.0: dependencies: loose-envify "^1.1.0" +reactflow@^11.4.0: + version "11.4.0" + resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.4.0.tgz#aeb4b030ba93e8e656094f59226e55ec538f55b4" + integrity sha512-Y+LZ3XZX7UejW4vukeyLwDDfqNT0RxyNNSHD1FJOIu2IvyVMkj+wKTcbp3ehm7brBkMOOaPyugcEWlLwFXcrjg== + dependencies: + "@reactflow/background" "11.1.0" + "@reactflow/controls" "11.1.0" + "@reactflow/core" "11.4.0" + "@reactflow/minimap" "11.3.0" + "@reactflow/node-toolbar" "1.1.0" + read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" @@ -9609,6 +10166,11 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -9797,6 +10359,11 @@ rimraf@^2.6.3: dependencies: glob "^7.1.3" +robust-predicates@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" + integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -9807,7 +10374,7 @@ run-parallel@^1.1.9: rw@1: version "1.3.3" resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" - integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== sade@^1.7.3: version "1.8.1" @@ -9826,7 +10393,7 @@ safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -9909,7 +10476,14 @@ semver@^7.3.2, semver@^7.3.4: dependencies: lru-cache "^6.0.0" -semver@^7.3.5, semver@^7.3.7: +semver@^7.3.5: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.7: version "7.3.7" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== @@ -10053,11 +10627,6 @@ source-list-map@^2.0.0, source-list-map@^2.0.1: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" - integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== - source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -11013,6 +11582,11 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" +use-sync-external-store@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -11396,6 +11970,13 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zustand@^4.1.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.5.tgz#7402b511f5b23ccb0f9ba6d20ae01ec817e16eb6" + integrity sha512-PsdRT8Bvq22Yyh1tvpgdHNE7OAeFKqJXUxtJvj1Ixw2B9O2YZ1M34ImQ+xyZah4wZrR4lENMoDUutKPpyXCQ/Q== + dependencies: + use-sync-external-store "1.2.0" + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"