diff --git a/api/src/main/java/marquez/db/JobDao.java b/api/src/main/java/marquez/db/JobDao.java index 7d9378c2dd..c1a92680df 100644 --- a/api/src/main/java/marquez/db/JobDao.java +++ b/api/src/main/java/marquez/db/JobDao.java @@ -283,7 +283,6 @@ INSERT INTO jobs_view AS j ( namespace_name, name, description, - current_job_context_uuid, current_location, current_inputs, symlink_target_uuid, @@ -297,7 +296,6 @@ INSERT INTO jobs_view AS j ( :namespaceName, :name, :description, - null, :location, :inputs, :symlinkTargetId, @@ -332,7 +330,6 @@ INSERT INTO jobs_view AS j ( namespace_name, name, description, - current_job_context_uuid, current_location, current_inputs, symlink_target_uuid @@ -346,7 +343,6 @@ INSERT INTO jobs_view AS j ( :namespaceName, :name, :description, - null, :location, :inputs, :symlinkTargetId diff --git a/api/src/main/java/marquez/db/LineageDao.java b/api/src/main/java/marquez/db/LineageDao.java index f71f24e562..c45a06e5a9 100644 --- a/api/src/main/java/marquez/db/LineageDao.java +++ b/api/src/main/java/marquez/db/LineageDao.java @@ -73,10 +73,9 @@ WHERE j.uuid IN () OR j.symlink_target_uuid IN () WHERE io.job_uuid != l.job_uuid AND array_cat(io.inputs, io.outputs) && array_cat(l.inputs, l.outputs) AND depth < :depth) - SELECT DISTINCT ON (j.uuid) j.*, inputs AS input_uuids, outputs AS output_uuids, jc.context + SELECT DISTINCT ON (j.uuid) j.*, inputs AS input_uuids, outputs AS output_uuids FROM lineage l2 - INNER JOIN jobs_view j ON j.uuid=l2.job_uuid - LEFT JOIN job_contexts jc on jc.uuid = j.current_job_context_uuid; + INNER JOIN jobs_view j ON j.uuid=l2.job_uuid; """) Set getLineage(@BindList Set jobIds, int depth); @@ -116,11 +115,10 @@ WHERE ds.uuid IN ()""") + " WHERE j.uuid in () OR j.symlink_target_uuid IN ()\n" + " ORDER BY r.job_name, r.namespace_name, created_at DESC\n" + ")\n" - + "SELECT r.*, ra.args, ctx.context, f.facets,\n" + + "SELECT r.*, ra.args, f.facets,\n" + " r.version AS job_version, ri.input_versions, ro.output_versions\n" + " from latest_runs AS r\n" + "LEFT JOIN run_args AS ra ON ra.uuid = r.run_args_uuid\n" - + "LEFT JOIN job_contexts AS ctx ON r.job_context_uuid = ctx.uuid\n" + "LEFT JOIN LATERAL (\n" + " SELECT im.run_uuid,\n" + " JSON_AGG(json_build_object('namespace', dv.namespace_name,\n" diff --git a/api/src/main/java/marquez/db/RunDao.java b/api/src/main/java/marquez/db/RunDao.java index 2d3b6e403a..fa22eba479 100644 --- a/api/src/main/java/marquez/db/RunDao.java +++ b/api/src/main/java/marquez/db/RunDao.java @@ -207,8 +207,7 @@ LEFT OUTER JOIN ( + "transitioned_at, " + "namespace_name, " + "job_name, " - + "location, " - + "job_context_uuid " + + "location " + ") VALUES ( " + ":runUuid, " + ":parentRunUuid, " @@ -224,8 +223,7 @@ LEFT OUTER JOIN ( + ":runStateTime, " + ":namespaceName, " + ":jobName, " - + ":location, " - + "null " + + ":location " + ") ON CONFLICT(uuid) DO " + "UPDATE SET " + "external_id = EXCLUDED.external_id, " @@ -266,8 +264,7 @@ RunRow upsert( + "nominal_end_time, " + "namespace_name, " + "job_name, " - + "location, " - + "job_context_uuid " + + "location " + ") VALUES ( " + ":runUuid, " + ":parentRunUuid, " @@ -281,8 +278,7 @@ RunRow upsert( + ":nominalEndTime, " + ":namespaceName, " + ":jobName, " - + ":location, " - + "null" + + ":location " + ") ON CONFLICT(uuid) DO " + "UPDATE SET " + "external_id = EXCLUDED.external_id, " diff --git a/api/src/main/java/marquez/db/migrations/V44_3_BackfillJobsWithParents.java b/api/src/main/java/marquez/db/migrations/V44_3_BackfillJobsWithParents.java index 80cb4e0098..525008233f 100644 --- a/api/src/main/java/marquez/db/migrations/V44_3_BackfillJobsWithParents.java +++ b/api/src/main/java/marquez/db/migrations/V44_3_BackfillJobsWithParents.java @@ -51,10 +51,10 @@ LEFT JOIN LATERAL ( public static final String INSERT_NEW_JOB_WITH_PARENT = """ INSERT INTO jobs(uuid, type, created_at, updated_at, namespace_uuid, name, description, - current_version_uuid, namespace_name, current_job_context_uuid, current_location, current_inputs, + current_version_uuid, namespace_name, current_location, current_inputs, parent_job_uuid) SELECT :uuid, type, created_at, updated_at, namespace_uuid, name, description, current_version_uuid, - namespace_name, current_job_context_uuid, current_location, current_inputs, :parent_job_uuid + namespace_name, current_location, current_inputs, :parent_job_uuid FROM jobs WHERE uuid=:job_uuid ON CONFLICT (name, namespace_name, parent_job_uuid) DO NOTHING diff --git a/api/src/main/resources/marquez/db/migration/R__1_Jobs_view_and_rewrite_function.sql b/api/src/main/resources/marquez/db/migration/R__1_Jobs_view_and_rewrite_function.sql index 3c8c8e741f..eb390b9d5c 100644 --- a/api/src/main/resources/marquez/db/migration/R__1_Jobs_view_and_rewrite_function.sql +++ b/api/src/main/resources/marquez/db/migration/R__1_Jobs_view_and_rewrite_function.sql @@ -52,7 +52,7 @@ BEGIN NEW.description, NEW.current_version_uuid, NEW.namespace_name, - NEW.current_job_context_uuid, + NULL, NEW.current_location, NEW.current_inputs, NEW.symlink_target_uuid, @@ -67,7 +67,6 @@ BEGIN END, type = EXCLUDED.type, description = EXCLUDED.description, - current_job_context_uuid = EXCLUDED.current_job_context_uuid, current_location = EXCLUDED.current_location, current_inputs = EXCLUDED.current_inputs, -- update the symlink target if null. otherwise, keep the old value diff --git a/api/src/main/resources/marquez/db/migration/V64__drop_job_contexts.sql b/api/src/main/resources/marquez/db/migration/V64__drop_job_contexts.sql new file mode 100644 index 0000000000..f635e98284 --- /dev/null +++ b/api/src/main/resources/marquez/db/migration/V64__drop_job_contexts.sql @@ -0,0 +1 @@ +DROP TABLE job_contexts; diff --git a/api/src/test/java/marquez/db/BackfillTestUtils.java b/api/src/test/java/marquez/db/BackfillTestUtils.java index 6b396803bb..5a235c49f9 100644 --- a/api/src/test/java/marquez/db/BackfillTestUtils.java +++ b/api/src/test/java/marquez/db/BackfillTestUtils.java @@ -122,8 +122,8 @@ public static UUID writeJob(Jdbi jdbi, String jobName, Instant now, NamespaceRow h -> { return h.createQuery( """ - INSERT INTO jobs (uuid, type, created_at, updated_at, namespace_uuid, name, namespace_name, current_job_context_uuid, current_inputs) - VALUES (:uuid, :type, :now, :now, :namespaceUuid, :name, :namespaceName, null, :currentInputs) + INSERT INTO jobs (uuid, type, created_at, updated_at, namespace_uuid, name, namespace_name, current_inputs) + VALUES (:uuid, :type, :now, :now, :namespaceUuid, :name, :namespaceName, :currentInputs) RETURNING uuid """) .bind("uuid", UUID.randomUUID()) diff --git a/web/src/components/core/text/MqText.tsx b/web/src/components/core/text/MqText.tsx index 72e93b3080..cf918eef2a 100644 --- a/web/src/components/core/text/MqText.tsx +++ b/web/src/components/core/text/MqText.tsx @@ -30,6 +30,7 @@ interface OwnProps { small?: boolean bottomMargin?: boolean children: ReactElement | (string | ReactElement)[] | string | string[] | number | undefined | null + onClick?: () => void } type MqTextProps = OwnProps @@ -53,6 +54,7 @@ const MqText: React.FC = ({ highlight, color, small, + onClick, }) => { const theme = createTheme(useTheme()) @@ -143,6 +145,7 @@ const MqText: React.FC = ({ if (heading) { return ( = ({ ) } else if (link && linkTo) { return ( - + = ({ } else if (link && href) { return ( = ({ ) } else { return ( - + {children} ) diff --git a/web/src/components/datasets/DatasetDetailPage.tsx b/web/src/components/datasets/DatasetDetailPage.tsx index 465dd098dd..4aba1012e9 100644 --- a/web/src/components/datasets/DatasetDetailPage.tsx +++ b/web/src/components/datasets/DatasetDetailPage.tsx @@ -17,6 +17,7 @@ import { fetchDatasetVersions, resetDataset, resetDatasetVersions, + setTabIndex, } from '../../store/actionCreators' import { useNavigate } from 'react-router-dom' import CloseIcon from '@mui/icons-material/Close' @@ -29,7 +30,8 @@ import MqStatus from '../core/status/MqStatus' import MqText from '../core/text/MqText' import { useTheme } from '@emotion/react' -import React, { ChangeEvent, FunctionComponent, SetStateAction, useEffect } from 'react' +import Io from '../io/Io' +import React, { ChangeEvent, FunctionComponent, useEffect } from 'react' interface StateProps { lineageDataset: LineageDataset @@ -37,6 +39,7 @@ interface StateProps { versionsLoading: boolean datasets: IState['datasets'] display: IState['display'] + tabIndex: IState['lineage']['tabIndex'] } interface DispatchProps { @@ -45,6 +48,7 @@ interface DispatchProps { resetDataset: typeof resetDataset deleteDataset: typeof deleteDataset dialogToggle: typeof dialogToggle + setTabIndex: typeof setTabIndex } type IProps = StateProps & DispatchProps @@ -68,6 +72,8 @@ const DatasetDetailPage: FunctionComponent = (props) => { versions, versionsLoading, lineageDataset, + tabIndex, + setTabIndex, } = props const navigate = useNavigate() const i18next = require('i18next') @@ -92,9 +98,8 @@ const DatasetDetailPage: FunctionComponent = (props) => { [] ) - const [tab, setTab] = React.useState(0) - const handleChange = (event: ChangeEvent, newValue: SetStateAction) => { - setTab(newValue) + const handleChange = (_: ChangeEvent, newValue: number) => { + setTabIndex(newValue) } if (versionsLoading) { @@ -149,20 +154,26 @@ const DatasetDetailPage: FunctionComponent = (props) => { )} - + + @@ -214,15 +225,16 @@ const DatasetDetailPage: FunctionComponent = (props) => { {description} - {tab === 0 && ( + {tabIndex === 0 && ( )} - {tab === 1 && } - {tab === 2 && } + {tabIndex === 1 && } + {tabIndex === 2 && } + {tabIndex === 3 && } ) } @@ -232,6 +244,7 @@ const mapStateToProps = (state: IState) => ({ display: state.display, versions: state.datasetVersions.result.versions, versionsLoading: state.datasetVersions.isLoading, + tabIndex: state.lineage.tabIndex, }) const mapDispatchToProps = (dispatch: Redux.Dispatch) => @@ -242,6 +255,7 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch) => resetDataset: resetDataset, deleteDataset: deleteDataset, dialogToggle: dialogToggle, + setTabIndex: setTabIndex, }, dispatch ) diff --git a/web/src/components/io/Io.tsx b/web/src/components/io/Io.tsx new file mode 100644 index 0000000000..6f96f29ad3 --- /dev/null +++ b/web/src/components/io/Io.tsx @@ -0,0 +1,150 @@ +// Copyright 2018-2023 contributors to the Marquez project +// SPDX-License-Identifier: Apache-2.0 + +import * as Redux from 'redux' +import { Box } from '@mui/system' +import { IState } from '../../store/reducers' +import { LineageEdge, LineageNode } from '../lineage/types' + +import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material' +import { Undefinable } from '../../types/util/Nullable' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { encodeNode } from '../../helpers/nodes' +import { setSelectedNode } from '../../store/actionCreators' +import MqEmpty from '../core/empty/MqEmpty' +import MqText from '../core/text/MqText' +import React, { FunctionComponent } from 'react' + +export interface DispatchProps { + setSelectedNode: typeof setSelectedNode +} + +interface IOProps { + node: Undefinable + inputs: Undefinable + outputs: Undefinable +} + +function determineName(node: string) { + const colonIndex1 = node.indexOf(':') + if (colonIndex1 !== -1) { + const colonIndex2 = node.indexOf(':', colonIndex1 + 1) + if (colonIndex2 !== -1) { + return node.substring(colonIndex2 + 1) + } + } + return '' +} + +export const determineLink = (current: LineageNode, edge: string) => { + return `/lineage/${encodeNode( + current.type === 'JOB' ? 'DATASET' : 'JOB', + edge.split(':')[1], + determineName(edge) + )}` +} + +const Io: FunctionComponent = ({ + node, + inputs, + outputs, + setSelectedNode, +}) => { + const i18next = require('i18next') + if (!node) { + return null + } + + return ( + + + + + + + INPUTS + + + + + {inputs?.map((input) => ( + + + { + setSelectedNode(input.origin) + }} + > + {determineName(input.origin)} + + + + ))} + +
+ {inputs && inputs.length === 0 && ( + + + {i18next.t('lineage.no_inputs')} + + + )} +
+ + + + + + OUTPUTS + + + + + {outputs?.map((output) => ( + + + setSelectedNode(output.destination)} + > + {determineName(output.destination)} + + + + ))} + +
+ {outputs && outputs.length === 0 && ( + + + {i18next.t('lineage.no_outputs')} + + + )} +
+
+ ) +} + +const mapStateToProps = (state: IState) => { + const node = state.lineage.lineage.graph.find((node) => node.id === state.lineage.selectedNode) + return { + node: node, + inputs: node?.inEdges, + outputs: node?.outEdges, + } +} + +const mapDispatchToProps = (dispatch: Redux.Dispatch) => + bindActionCreators( + { + setSelectedNode: setSelectedNode, + }, + dispatch + ) + +export default connect(mapStateToProps, mapDispatchToProps)(Io) diff --git a/web/src/components/jobs/JobDetailPage.tsx b/web/src/components/jobs/JobDetailPage.tsx index 6f7f0e52d6..c0c71544c0 100644 --- a/web/src/components/jobs/JobDetailPage.tsx +++ b/web/src/components/jobs/JobDetailPage.tsx @@ -1,7 +1,7 @@ // Copyright 2018-2023 contributors to the Marquez project // SPDX-License-Identifier: Apache-2.0 -import React, { ChangeEvent, FunctionComponent, SetStateAction, useEffect } from 'react' +import React, { ChangeEvent, FunctionComponent, useEffect } from 'react' import '../../i18n/config' import * as Redux from 'redux' @@ -18,6 +18,7 @@ import { fetchRuns, resetJobs, resetRuns, + setTabIndex, } from '../../store/actionCreators' import { jobRunsStatus } from '../../helpers/nodes' import { useNavigate } from 'react-router-dom' @@ -25,6 +26,7 @@ import { useTheme } from '@emotion/react' import CloseIcon from '@mui/icons-material/Close' import Dialog from '../Dialog' import IconButton from '@mui/material/IconButton' +import Io from '../io/Io' import MqEmpty from '../core/empty/MqEmpty' import MqStatus from '../core/status/MqStatus' import MqText from '../core/text/MqText' @@ -37,6 +39,7 @@ interface DispatchProps { resetJobs: typeof resetJobs deleteJob: typeof deleteJob dialogToggle: typeof dialogToggle + setTabIndex: typeof setTabIndex } type IProps = { @@ -45,17 +48,28 @@ type IProps = { runs: Run[] runsLoading: boolean display: IState['display'] + tabIndex: IState['lineage']['tabIndex'] } & DispatchProps const JobDetailPage: FunctionComponent = (props) => { const theme = createTheme(useTheme()) - const { job, jobs, fetchRuns, resetRuns, deleteJob, dialogToggle, runs, display, runsLoading } = - props + const { + job, + jobs, + fetchRuns, + resetRuns, + deleteJob, + dialogToggle, + runs, + display, + runsLoading, + tabIndex, + setTabIndex, + } = props const navigate = useNavigate() - const [tab, setTab] = React.useState(0) - const handleChange = (event: ChangeEvent, newValue: SetStateAction) => { - setTab(newValue) + const handleChange = (event: ChangeEvent, newValue: number) => { + setTabIndex(newValue) } const i18next = require('i18next') @@ -96,8 +110,9 @@ const JobDetailPage: FunctionComponent = (props) => { }} > - + + @@ -158,7 +173,7 @@ const JobDetailPage: FunctionComponent = (props) => { {job.description} - {tab === 0 ? ( + {tabIndex === 0 ? ( job.latestRun ? ( ) : ( @@ -167,7 +182,8 @@ const JobDetailPage: FunctionComponent = (props) => { ) ) ) : null} - {tab === 1 && } + {tabIndex === 1 && } + {tabIndex === 2 && } ) } @@ -177,6 +193,7 @@ const mapStateToProps = (state: IState) => ({ runsLoading: state.runs.isLoading, display: state.display, jobs: state.jobs, + tabIndex: state.lineage.tabIndex, }) const mapDispatchToProps = (dispatch: Redux.Dispatch) => @@ -187,6 +204,7 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch) => resetJobs: resetJobs, deleteJob: deleteJob, dialogToggle: dialogToggle, + setTabIndex: setTabIndex, }, dispatch ) diff --git a/web/src/components/lineage/Lineage.tsx b/web/src/components/lineage/Lineage.tsx index de5733c0ec..5ef4702b6a 100644 --- a/web/src/components/lineage/Lineage.tsx +++ b/web/src/components/lineage/Lineage.tsx @@ -19,7 +19,7 @@ import { fetchLineage, resetLineage, setLineageGraphDepth, - setSelectedNode + setSelectedNode, } from '../../store/actionCreators' import { generateNodeId } from '../../helpers/nodes' import { localPoint } from '@visx/event' @@ -85,7 +85,7 @@ export function buildGraphAll( label: graph[i].id, data: graph[i].data, width: NODE_SIZE, - height: NODE_SIZE + height: NODE_SIZE, }) } @@ -110,7 +110,7 @@ export function getSelectedPaths(g: graphlib.Graph, selectedNode: string // Sets used to detect cycles and break out of the recursive loop const visitedNodes = { successors: new Set(), - predecessors: new Set() + predecessors: new Set(), } const getSuccessors = (node: string) => { @@ -121,8 +121,8 @@ export function getSelectedPaths(g: graphlib.Graph, selectedNode: string if (successors?.length) { for (let i = 0; i < successors.length; i++) { if (successors[i]) { - paths.push([node, (successors[i] as unknown) as string]) - getSuccessors((successors[i] as unknown) as string) + paths.push([node, successors[i] as unknown as string]) + getSuccessors(successors[i] as unknown as string) } } } @@ -136,8 +136,8 @@ export function getSelectedPaths(g: graphlib.Graph, selectedNode: string if (predecessors?.length) { for (let i = 0; i < predecessors.length; i++) { if (predecessors[i]) { - paths.push([(predecessors[i] as unknown) as string, node]) - getPredecessors((predecessors[i] as unknown) as string) + paths.push([predecessors[i] as unknown as string, node]) + getPredecessors(predecessors[i] as unknown as string) } } } @@ -151,7 +151,7 @@ export function getSelectedPaths(g: graphlib.Graph, selectedNode: string export function removeUnselectedNodes(g: graphlib.Graph, selectedNode: string) { const nodesInSelectedPath = new Set(getSelectedPaths(g, selectedNode).flat()) - const nodesToRemove = g.nodes().filter(n => !nodesInSelectedPath.has(n)) + const nodesToRemove = g.nodes().filter((n) => !nodesInSelectedPath.has(n)) for (const node of nodesToRemove) { g.removeNode(node) @@ -166,7 +166,7 @@ const Lineage: React.FC = (props: LineageProps) => { const [state, setState] = React.useState({ graph: g, edges: [], - nodes: [] + nodes: [], }) const { nodeName, namespace, nodeType } = useParams() const mounted = React.useRef(false) @@ -204,7 +204,7 @@ const Lineage: React.FC = (props: LineageProps) => { setState({ graph: gResult, edges: getEdges(), - nodes: gResult.nodes().map(v => gResult.node(v)) + nodes: gResult.nodes().map((v) => gResult.node(v)), }) } ) @@ -231,7 +231,7 @@ const Lineage: React.FC = (props: LineageProps) => { setState({ graph: gResult, edges: getEdges(), - nodes: gResult.nodes().map(v => gResult.node(v)) + nodes: gResult.nodes().map((v) => gResult.node(v)), }) } ) @@ -253,7 +253,7 @@ const Lineage: React.FC = (props: LineageProps) => { const getEdges = () => { const selectedPaths = getSelectedPaths(g, props.selectedNode) - return g?.edges().map(e => { + return g?.edges().map((e) => { const isSelected = selectedPaths.some((r: any) => e.v === r[0] && e.w === r[1]) return Object.assign(g.edge(e), { isSelected: isSelected }) }) @@ -265,7 +265,7 @@ const Lineage: React.FC = (props: LineageProps) => { {props.selectedNode === null && ( @@ -276,11 +276,11 @@ const Lineage: React.FC = (props: LineageProps) => { )} ({ + sx={(theme) => ({ zIndex: theme.zIndex.appBar + 1, position: 'absolute', right: 0, - margin: '1rem 3rem' + margin: '1rem 3rem', })} > @@ -288,7 +288,7 @@ const Lineage: React.FC = (props: LineageProps) => { {state?.graph && ( - {parent => ( + {(parent) => ( = (props: LineageProps) => { scaleYMax={MAX_ZOOM} initialTransformMatrix={INITIAL_TRANSFORM} > - {zoom => ( + {(zoom) => (
} > @@ -320,7 +320,7 @@ const Lineage: React.FC = (props: LineageProps) => { onTouchStart={zoom.dragStart} onTouchMove={zoom.dragMove} onTouchEnd={zoom.dragEnd} - onMouseDown={event => { + onMouseDown={(event) => { zoom.dragStart(event) }} onMouseMove={zoom.dragMove} @@ -328,21 +328,21 @@ const Lineage: React.FC = (props: LineageProps) => { onMouseLeave={() => { if (zoom.isDragging) zoom.dragEnd() }} - onDoubleClick={event => { + onDoubleClick={(event) => { const point = localPoint(event) || { x: 0, - y: 0 + y: 0, } zoom.scale({ scaleX: DOUBLE_CLICK_MAGNIFICATION, scaleY: DOUBLE_CLICK_MAGNIFICATION, - point + point, }) }} /> {/* foreground */} - {state?.nodes.map(node => ( + {state?.nodes.map((node) => ( ))} @@ -361,7 +361,7 @@ const mapStateToProps = (state: IState) => ({ lineage: state.lineage.lineage, selectedNode: state.lineage.selectedNode, depth: state.lineage.depth, - showFullGraph: state.lineage.showFullGraph + showFullGraph: state.lineage.showFullGraph, }) const mapDispatchToProps = (dispatch: Redux.Dispatch) => @@ -370,7 +370,7 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch) => setSelectedNode: setSelectedNode, fetchLineage: fetchLineage, resetLineage: resetLineage, - setDepth: setLineageGraphDepth + setDepth: setLineageGraphDepth, }, dispatch ) diff --git a/web/src/components/lineage/components/drag-bar/DragBar.tsx b/web/src/components/lineage/components/drag-bar/DragBar.tsx index d03d229a03..6719e52362 100644 --- a/web/src/components/lineage/components/drag-bar/DragBar.tsx +++ b/web/src/components/lineage/components/drag-bar/DragBar.tsx @@ -34,7 +34,6 @@ const DragBar: React.FC = ({ setBottomBarHeight }) => { }, [isResizing]) const handleMousedown = () => { - console.log('handleMousedown', isResizing) setIsResizing(true) } @@ -46,7 +45,6 @@ const DragBar: React.FC = ({ setBottomBarHeight }) => { } const handleMouseup = () => { - console.log('handleMouseup', isResizing) setIsResizing(false) } diff --git a/web/src/components/lineage/components/node/Node.tsx b/web/src/components/lineage/components/node/Node.tsx index eee2e79828..724fb805ae 100644 --- a/web/src/components/lineage/components/node/Node.tsx +++ b/web/src/components/lineage/components/node/Node.tsx @@ -33,16 +33,16 @@ interface OwnProps { type NodeProps = DispatchProps & OwnProps -const Node: React.FC = ({ node, selectedNode, setSelectedNode }) => { - const determineLink = (node: GraphNode) => { - if (isJob(node)) { - return `/lineage/${encodeNode('JOB', node.data.namespace, node.data.name)}` - } else if (isDataset(node)) { - return `/lineage/${encodeNode('DATASET', node.data.namespace, node.data.name)}` - } - return '/' +export const determineLink = (node: GraphNode) => { + if (isJob(node)) { + return `/lineage/${encodeNode('JOB', node.data.namespace, node.data.name)}` + } else if (isDataset(node)) { + return `/lineage/${encodeNode('DATASET', node.data.namespace, node.data.name)}` } + return '/' +} +const Node: React.FC = ({ node, selectedNode, setSelectedNode }) => { const addToToolTip = (inputData: GraphNode) => { return ( <> diff --git a/web/src/i18n/config.ts b/web/src/i18n/config.ts index e934f3090f..fb4118b76f 100644 --- a/web/src/i18n/config.ts +++ b/web/src/i18n/config.ts @@ -10,7 +10,7 @@ export const defaultNS = 'translation' const DETECTION_OPTIONS = { order: ['localStorage'], lookupLocalStorage: 'lng', - caches: ['localStorage'] + caches: ['localStorage'], } i18next @@ -23,7 +23,7 @@ i18next en: { translation: { header: { - docs_link: 'API Docs' + docs_link: 'API Docs', }, jobs: { search: 'Search', @@ -36,7 +36,7 @@ i18next facets_subhead_aria: 'Search', runs_subhead: 'FACETS', dialog_delete: 'DELETE', - dialog_confirmation_title: 'Are you sure?' + dialog_confirmation_title: 'Are you sure?', }, search: { filter: { @@ -44,7 +44,7 @@ i18next jobs: 'Jobs', datasets: 'Datasets', updated: 'Updated', - name: 'Name' + name: 'Name', }, status: 'Searching...', none: 'No Results', @@ -52,21 +52,24 @@ i18next search_aria: 'Search jobs and datasets', jobs: 'Jobs', and: 'and', - datasets: 'Datasets' + datasets: 'Datasets', }, lineage: { empty_title: 'No node selected', empty_body: 'Try selecting a node through search or the jobs or datasets page.', graph_depth_title: 'Depth', - full_graph_label: 'Full' + full_graph_label: 'Full', + empty: 'Empty', + no_inputs: 'No inputs for this node.', + no_outputs: 'No outputs for this node.', }, sidenav: { jobs: 'JOBS', datasets: 'DATASETS', - events: 'EVENTS' + events: 'EVENTS', }, namespace_select: { - prompt: 'ns' + prompt: 'ns', }, dataset_info: { empty_title: 'No Fields', @@ -74,14 +77,14 @@ i18next facets_subhead: 'FACETS', facets_subhead_aria: 'Search', run_subhead: 'Created by Run', - duration: 'Duration' + duration: 'Duration', }, datasets: { latest_tab: 'LATEST SCHEMA', history_tab: 'VERSION HISTORY', column_lineage_tab: 'COLUMN LINEAGE', dialog_delete: 'DELETE', - dialog_confirmation_title: 'Are you sure?' + dialog_confirmation_title: 'Are you sure?', }, datasets_route: { empty_title: 'No datasets found', @@ -91,11 +94,11 @@ i18next namespace_col: 'NAMESPACE', source_col: 'SOURCE', updated_col: 'UPDATED AT', - status_col: 'STATUS' + status_col: 'STATUS', }, datasets_column_lineage: { empty_title: 'No column lineage', - empty_body: 'Column lineage not available for the specified dataset.' + empty_body: 'Column lineage not available for the specified dataset.', }, jobs_route: { empty_title: 'No jobs found', @@ -105,7 +108,7 @@ i18next namespace_col: 'NAMESPACE', updated_col: 'UPDATED AT', latest_run_col: 'LATEST RUN DURATION', - latest_run_state_col: 'LATEST RUN STATE' + latest_run_state_col: 'LATEST RUN STATE', }, runs_columns: { id: 'ID', @@ -113,20 +116,20 @@ i18next created_at: 'CREATED AT', started_at: 'STARTED AT', ended_at: 'ENDED AT', - duration: 'DURATION' + duration: 'DURATION', }, dataset_info_columns: { name: 'NAME', type: 'TYPE', description: 'DESCRIPTION', - tags: 'TAGS' + tags: 'TAGS', }, dataset_versions_columns: { version: 'VERSION', created_at: 'CREATED AT', fields: 'FIELDS', created_by_run: 'CREATED BY RUN', - lifecycle_state: 'LIFECYCLE STATE' + lifecycle_state: 'LIFECYCLE STATE', }, events_route: { title: 'EVENTS', @@ -135,21 +138,21 @@ i18next previous_page: 'Previous page', next_page: 'Next page', empty_title: 'No events found', - empty_body: 'Try changing dates or consulting our documentation to add events.' + empty_body: 'Try changing dates or consulting our documentation to add events.', }, events_columns: { id: 'ID', state: 'STATE', name: 'NAME', namespace: 'NAMESPACE', - time: 'TIME' - } - } + time: 'TIME', + }, + }, }, fr: { translation: { header: { - docs_link: 'Documents API' + docs_link: 'Documents API', }, jobs: { search: 'Recherche', @@ -162,7 +165,7 @@ i18next runs_subhead: 'FACETTES', facets_subhead_aria: 'Recherche', dialog_delete: 'EFFACER', - dialog_confirmation_title: 'Êtes-vous sûr?' + dialog_confirmation_title: 'Êtes-vous sûr?', }, search: { filter: { @@ -170,7 +173,7 @@ i18next jobs: "d'Emplois", datasets: 'Jeux de Données', updated: 'Mis à jour', - name: 'Nom' + name: 'Nom', }, status: 'Recherche...', none: 'Aucun Résultat', @@ -178,20 +181,20 @@ i18next search_aria: 'Recherchez des emplois et des ensembles de données', jobs: "d'Emplois", and: 'et', - datasets: 'Jeux de Données' + datasets: 'Jeux de Données', }, lineage: { empty_title: 'Aucun nœud sélectionné', empty_body: - 'Essayez de sélectionner un nœud via la recherche ou la page des travaux ou des jeux de données.' + 'Essayez de sélectionner un nœud via la recherche ou la page des travaux ou des jeux de données.', }, sidenav: { jobs: 'EMPLOIS', datasets: 'JEUX DE DONNÉES', - events: 'ÉVÉNEMENTS' + events: 'ÉVÉNEMENTS', }, namespace_select: { - prompt: 'en' + prompt: 'en', }, dataset_info: { empty_title: 'Aucun jeu de données trouvé', @@ -199,14 +202,14 @@ i18next facets_subhead: 'FACETTES', facets_subhead_aria: 'Recherche', run_subhead: 'Créé par Run', - duration: 'Durée' + duration: 'Durée', }, datasets: { latest_tab: 'DERNIER SCHEMA', history_tab: 'HISTORIQUE DES VERSIONS', column_lineage_tab: 'LIGNÉE DE COLONNE', dialog_delete: 'EFFACER', - dialog_confirmation_title: 'Êtes-vous sûr?' + dialog_confirmation_title: 'Êtes-vous sûr?', }, datasets_route: { empty_title: 'Aucun jeu de données trouvé', @@ -217,11 +220,11 @@ i18next namespace_col: 'ESPACE DE NOMS', source_col: 'SOURCE', updated_col: 'MISE À JOUR À', - status_col: 'STATUT' + status_col: 'STATUT', }, datasets_column_lineage: { empty_title: 'Aucune lignée de colonne', - empty_body: "Lignage de colonne non disponible pour l'ensemble de données spécifié." + empty_body: "Lignage de colonne non disponible pour l'ensemble de données spécifié.", }, jobs_route: { empty_title: 'Aucun emploi trouvé', @@ -232,7 +235,7 @@ i18next namespace_col: 'ESPACE DE NOMS', updated_col: 'MISE À JOUR À', latest_run_col: "DERNIÈRE DURÉE D'EXÉCUTION", - latest_run_state_col: "DERNIER ÉTAT D'EXÉCUTIONE" + latest_run_state_col: "DERNIER ÉTAT D'EXÉCUTIONE", }, runs_columns: { id: 'ID', @@ -240,20 +243,20 @@ i18next created_at: 'CRÉÉ À', started_at: 'COMMENCÉ À', ended_at: 'TERMINÉ À', - duration: 'DURÉE' + duration: 'DURÉE', }, dataset_info_columns: { name: 'NOM', type: 'TAPER', description: 'DESCRIPTION', - tags: 'MOTS CLÉS' + tags: 'MOTS CLÉS', }, dataset_versions_columns: { version: 'VERSION', created_at: 'CRÉÉ À', fields: 'DOMAINES', created_by_run: 'CRÉÉ PAR RUN', - lifecycle_state: 'ÉTAT DU CYCLE DE VIE' + lifecycle_state: 'ÉTAT DU CYCLE DE VIE', }, events_route: { title: 'ÉVÉNEMENTS', @@ -263,21 +266,21 @@ i18next next_page: 'Page suivante', empty_title: 'Aucun événement trouvé', empty_body: - 'Essayez de changer les dates ou consultez notre documentation pour ajouter des événements.' + 'Essayez de changer les dates ou consultez notre documentation pour ajouter des événements.', }, events_columns: { id: 'ID', state: 'ETAT', name: 'NOM', namespace: 'ESPACE DE NOMS', - time: 'TEMPS' - } - } + time: 'TEMPS', + }, + }, }, es: { translation: { header: { - docs_link: 'Documentos API' + docs_link: 'Documentos API', }, jobs: { search: 'Buscar', @@ -290,7 +293,7 @@ i18next runs_subhead: 'FACETAS', facets_subhead_aria: 'Buscar', dialog_delete: 'ELIMINAR', - dialog_confirmation_title: 'Estás seguro?' + dialog_confirmation_title: 'Estás seguro?', }, search: { filter: { @@ -298,7 +301,7 @@ i18next jobs: 'Trabajos', datasets: 'Conjuntos de Datos', updated: 'Actualizado', - name: 'Nombre' + name: 'Nombre', }, status: 'Buscando...', none: 'No Hay Resultados', @@ -306,20 +309,20 @@ i18next search_aria: 'Buscar trabajos y conjuntos de datos', jobs: 'Trabajos', and: 'y', - datasets: 'Conjuntos de Datos' + datasets: 'Conjuntos de Datos', }, lineage: { empty_title: 'Ningún nodo seleccionado', empty_body: - 'Intente seleccionar un nodo mediante la búsqueda o la página de trabajos o conjuntos de datos.' + 'Intente seleccionar un nodo mediante la búsqueda o la página de trabajos o conjuntos de datos.', }, sidenav: { jobs: 'TRABAJOS', datasets: 'CONJUNTOS DE DATOS', - events: 'EVENTOS' + events: 'EVENTOS', }, namespace_select: { - prompt: 'en' + prompt: 'en', }, dataset_info: { empty_title: 'No se encontraron conjuntos de datos', @@ -327,14 +330,14 @@ i18next facets_subhead: 'FACETAS', facets_subhead_aria: 'Buscar', run_subhead: 'Creado por Ejecutar', - duration: 'Duración' + duration: 'Duración', }, datasets: { latest_tab: 'ESQUEMA ÚLTIMO', history_tab: 'HISTORIAL DE VERSIONES', column_lineage_tab: 'LINAJE DE COLUMNA', dialog_delete: 'ELIMINAR', - dialog_confirmation_title: 'Estás seguro?' + dialog_confirmation_title: 'Estás seguro?', }, datasets_route: { empty_title: 'No se encontraron conjuntos de datos', @@ -345,11 +348,11 @@ i18next namespace_col: 'ESPACIO DE NOMBRES', source_col: 'FUENTE', updated_col: 'ACTUALIZADO EN', - status_col: 'ESTADO' + status_col: 'ESTADO', }, datasets_column_lineage: { empty_title: 'Sin linaje de columna', - empty_body: 'Linaje de columna no disponible para el conjunto de datos especificado.' + empty_body: 'Linaje de columna no disponible para el conjunto de datos especificado.', }, jobs_route: { empty_title: 'No se encontraron trabajos', @@ -360,7 +363,7 @@ i18next namespace_col: 'ESPACIO DE NOMBRES', updated_col: 'ACTUALIZADO EN', latest_run_col: 'DURACIÓN DE LA ÚLTIMA EJECUCIÓN', - latest_run_state_col: 'ESTADO DE LA ÚLTIMA EJECUCIÓN' + latest_run_state_col: 'ESTADO DE LA ÚLTIMA EJECUCIÓN', }, runs_columns: { id: 'ID', @@ -368,20 +371,20 @@ i18next created_at: 'CREADO EN', started_at: 'EMPEZÓ A LAS', ended_at: 'TERMINÓ EN', - duration: 'DURACIÓN' + duration: 'DURACIÓN', }, dataset_info_columns: { name: 'NOMBRE', type: 'ESCRIBE', description: 'DESCRIPCIÓN', - tags: 'ETIQUETAS' + tags: 'ETIQUETAS', }, dataset_versions_columns: { version: 'VERSIÓN', created_at: 'CREADO EN', fields: 'CAMPOS', created_by_run: 'CREADO POR EJECUTAR', - lifecycle_state: 'ESTADO DEL CICLO DE VIDA' + lifecycle_state: 'ESTADO DEL CICLO DE VIDA', }, events_route: { title: 'EVENTOS', @@ -391,21 +394,21 @@ i18next next_page: 'Siguiente página', empty_title: 'No se encontraron eventos', empty_body: - 'Prueba a cambiar las fechas o consulta nuestra documentación para añadir eventos.' + 'Prueba a cambiar las fechas o consulta nuestra documentación para añadir eventos.', }, events_columns: { id: 'ID', state: 'ESTADO', name: 'NOMBRE', namespace: 'ESPACIO DE NOMBRES', - time: 'TIEMPO' - } - } + time: 'TIEMPO', + }, + }, }, pl: { translation: { header: { - docs_link: 'Dokumentacja API' + docs_link: 'Dokumentacja API', }, jobs: { search: 'Wyszukiwanie', @@ -418,7 +421,7 @@ i18next runs_subhead: 'ASPECTY', facets_subhead_aria: 'Wyszukiwanie', dialog_delete: 'USUNĄĆ', - dialog_confirmation_title: 'Jesteś pewny?' + dialog_confirmation_title: 'Jesteś pewny?', }, search: { filter: { @@ -426,7 +429,7 @@ i18next jobs: 'Zadania', datasets: 'Zbiory Danych', updated: 'Zaktualizowano', - name: 'Nazwa' + name: 'Nazwa', }, status: 'Badawczy...', none: 'Brak Wyników', @@ -434,20 +437,20 @@ i18next search_aria: 'Wyszukiwanie zadań i zbiorów danych', jobs: 'Zadania', and: 'i', - datasets: 'Zbiory Danych' + datasets: 'Zbiory Danych', }, lineage: { empty_title: 'Nie wybrano węzła', empty_body: - 'Spróbuj wybrać węzeł za pomocą wyszukiwania lub strony zadań lub zestawów danych.' + 'Spróbuj wybrać węzeł za pomocą wyszukiwania lub strony zadań lub zestawów danych.', }, sidenav: { jobs: 'ZADANIA', datasets: 'ZBIORY DANYCH', - events: 'WYDARZENIA' + events: 'WYDARZENIA', }, namespace_select: { - prompt: 'pn' + prompt: 'pn', }, dataset_info: { empty_title: 'Nie znaleziono zbiorów danych', @@ -455,14 +458,14 @@ i18next facets_subhead: 'ASPECTY', facets_subhead_aria: 'Wyszukiwanie', run_subhead: 'Stworzony przez Run', - duration: 'Czas trwania' + duration: 'Czas trwania', }, datasets: { latest_tab: 'NAJNOWSZY SCHEMAT', history_tab: 'HISTORIA WERSJI', column_lineage_tab: 'RODOWÓD KOLUMNOWY', dialog_delete: 'USUNĄĆ', - dialog_confirmation_title: 'Jesteś pewny?' + dialog_confirmation_title: 'Jesteś pewny?', }, datasets_route: { empty_title: 'Nie znaleziono zbiorów danych', @@ -473,11 +476,11 @@ i18next namespace_col: 'PRZESTRZEŃ NAZW', source_col: 'ŹRÓDŁO', updated_col: 'ZAKTUALIZOWANO', - status_col: 'STATUS' + status_col: 'STATUS', }, datasets_column_lineage: { empty_title: 'Brak rodowodu kolumny', - empty_body: 'Pochodzenie kolumny jest niedostępne dla określonego zbioru danych.' + empty_body: 'Pochodzenie kolumny jest niedostępne dla określonego zbioru danych.', }, jobs_route: { empty_title: 'Nie znaleziono ofert pracy', @@ -488,7 +491,7 @@ i18next namespace_col: 'PRZESTRZEŃ NAZW', updated_col: 'ZAKTUALIZOWANO', latest_run_col: 'NAJNOWSZY CZAS TRWANIA', - latest_run_state_col: 'NAJNOWSZY STAN URUCHOMIENIA' + latest_run_state_col: 'NAJNOWSZY STAN URUCHOMIENIA', }, runs_columns: { id: 'ID', @@ -496,20 +499,20 @@ i18next created_at: 'UTWORZONY W', started_at: 'ROZPOCZĘŁO SIĘ O GODZ', ended_at: 'ZAKOŃCZONE O GODZ', - duration: 'TRWANIE' + duration: 'TRWANIE', }, dataset_info_columns: { name: 'NAZWA', type: 'RODZAJ', description: 'OPIS', - tags: 'TAGI' + tags: 'TAGI', }, dataset_versions_columns: { version: 'WERSJA', created_at: 'UTWORZONY W', fields: 'KIERUNKI', created_by_run: 'STWORZONY PRZEZ URUCHOM', - lifecycle_state: 'STAN CYKLU ŻYCIA' + lifecycle_state: 'STAN CYKLU ŻYCIA', }, events_route: { title: 'WYDARZENIA', @@ -519,18 +522,18 @@ i18next next_page: 'Następna strona', empty_title: 'Nie znaleziono wydarzeń', empty_body: - 'Spróbuj zmienić daty lub zapoznaj się z naszą dokumentacją, aby dodać wydarzenia.' + 'Spróbuj zmienić daty lub zapoznaj się z naszą dokumentacją, aby dodać wydarzenia.', }, events_columns: { id: 'ID', state: 'PAŃSTWO', name: 'NAZWA', namespace: 'PRZESTRZEŃ NAZW', - time: 'CZAS' - } - } - } + time: 'CZAS', + }, + }, + }, }, defaultNS, - detection: DETECTION_OPTIONS + detection: DETECTION_OPTIONS, }) diff --git a/web/src/store/actionCreators/actionTypes.ts b/web/src/store/actionCreators/actionTypes.ts index 5e40a21362..b97005cf87 100644 --- a/web/src/store/actionCreators/actionTypes.ts +++ b/web/src/store/actionCreators/actionTypes.ts @@ -16,6 +16,8 @@ export const SET_SELECTED_NODE = 'SET_SELECTED_NODE' export const SET_BOTTOM_BAR_HEIGHT = 'SET_BOTTOM_BAR_HEIGHT' +export const SET_TAB_INDEX = 'SET_TAB_INDEX' + // jobs export const FETCH_JOBS = 'FETCH_JOBS' export const FETCH_JOBS_SUCCESS = 'FETCH_JOBS_SUCCESS' diff --git a/web/src/store/actionCreators/index.ts b/web/src/store/actionCreators/index.ts index e7df50d046..4e10ad460a 100644 --- a/web/src/store/actionCreators/index.ts +++ b/web/src/store/actionCreators/index.ts @@ -235,6 +235,11 @@ export const setBottomBarHeight = (height: number) => ({ payload: height, }) +export const setTabIndex = (index: number) => ({ + type: actionTypes.SET_TAB_INDEX, + payload: index, +}) + export const fetchLineage = ( nodeType: JobOrDataset, namespace: string, diff --git a/web/src/store/reducers/lineage.ts b/web/src/store/reducers/lineage.ts index 0d7b13afb7..b4f773f15e 100644 --- a/web/src/store/reducers/lineage.ts +++ b/web/src/store/reducers/lineage.ts @@ -7,7 +7,8 @@ import { SET_BOTTOM_BAR_HEIGHT, SET_LINEAGE_GRAPH_DEPTH, SET_SELECTED_NODE, - SET_SHOW_FULL_GRAPH + SET_SHOW_FULL_GRAPH, + SET_TAB_INDEX, } from '../actionCreators/actionTypes' import { HEADER_HEIGHT } from '../../helpers/theme' import { LineageGraph } from '../../types/api' @@ -19,6 +20,7 @@ export interface ILineageState { selectedNode: Nullable bottomBarHeight: number depth: number + tabIndex: number showFullGraph: boolean } @@ -27,7 +29,8 @@ const initialState: ILineageState = { selectedNode: null, bottomBarHeight: (window.innerHeight - HEADER_HEIGHT) / 3, depth: 5, - showFullGraph: true + tabIndex: 0, + showFullGraph: true, } type ILineageActions = ReturnType & @@ -41,24 +44,30 @@ export default (state = initialState, action: ILineageActions) => { case FETCH_LINEAGE_SUCCESS: return { ...state, lineage: action.payload } case SET_SELECTED_NODE: - return { ...state, selectedNode: action.payload } + // reset the selected index if we are not on the i/o tab + return { ...state, selectedNode: action.payload, tabIndex: state.tabIndex === 1 ? 1 : 0 } case SET_BOTTOM_BAR_HEIGHT: return { ...state, bottomBarHeight: Math.min( window.innerHeight - HEADER_HEIGHT - DRAG_BAR_HEIGHT, Math.max(2, action.payload) - ) + ), + } + case SET_TAB_INDEX: + return { + ...state, + tabIndex: action.payload, } case SET_LINEAGE_GRAPH_DEPTH: return { ...state, - depth: action.payload + depth: action.payload, } case SET_SHOW_FULL_GRAPH: return { ...state, - showFullGraph: action.payload + showFullGraph: action.payload, } case RESET_LINEAGE: { return { ...state, lineage: { graph: [] } } diff --git a/web/src/store/sagas/index.ts b/web/src/store/sagas/index.ts index 10cabcce14..46aeea74a8 100644 --- a/web/src/store/sagas/index.ts +++ b/web/src/store/sagas/index.ts @@ -180,7 +180,6 @@ export function* fetchEventsSaga() { payload.limit, payload.offset ) - console.log(events) yield put(fetchEventsSuccess(events)) } catch (e) { yield put(applicationError('Something went wrong while fetching event runs'))