@truedat/dd 8.11.6 → 8.11.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/package.json +4 -3
  2. package/src/components/Graphs.js +30 -4
  3. package/src/components/LineageContextMenu.js +185 -58
  4. package/src/components/LineageDownloadButton.js +16 -6
  5. package/src/components/LineageEventsPanel.js +80 -27
  6. package/src/components/LineageEventsTable.js +7 -3
  7. package/src/components/LineageGraph.js +5166 -306
  8. package/src/components/LineageGraphToolbar.js +896 -0
  9. package/src/components/LineageInspectorPanel.js +748 -0
  10. package/src/components/LineageResourceNode.js +501 -0
  11. package/src/components/LineageRoutes.js +1 -2
  12. package/src/components/LineageSelectionPanel.js +28 -12
  13. package/src/components/StructureGraph.js +8 -1
  14. package/src/components/StructureGraphView.js +14 -0
  15. package/src/components/StructureImpact.js +2 -22
  16. package/src/components/StructureLineage.js +2 -19
  17. package/src/components/StructuresUploadEventsTable.js +0 -3
  18. package/src/components/__tests__/LineageContextMenu.spec.js +42 -0
  19. package/src/components/__tests__/LineageGraph.spec.js +896 -0
  20. package/src/components/__tests__/LineageGraphLayout.spec.js +639 -0
  21. package/src/components/__tests__/LineageGraphToolbar.spec.js +366 -0
  22. package/src/components/__tests__/LineageHierarchy.spec.js +116 -0
  23. package/src/components/__tests__/LineageInspectorPanel.spec.js +364 -0
  24. package/src/components/__tests__/LineageResourceNode.spec.js +338 -0
  25. package/src/components/__tests__/LineageRoutes.spec.js +27 -9
  26. package/src/components/__tests__/__snapshots__/LineageDownloadButton.spec.js.snap +23 -3
  27. package/src/components/__tests__/__snapshots__/LineageForm.spec.js.snap +23 -3
  28. package/src/components/__tests__/lineageEventHelpers.spec.js +60 -0
  29. package/src/components/lineageEventHelpers.js +53 -17
  30. package/src/reducers/__tests__/lineageEvents.spec.js +164 -0
  31. package/src/reducers/__tests__/lineageQuery.spec.js +11 -0
  32. package/src/reducers/lineageEvents.js +68 -2
  33. package/src/reducers/lineageQuery.js +2 -6
  34. package/src/styles/LineageGraph.less +4094 -0
  35. package/src/styles/LineageGraphColors.less +105 -0
  36. package/src/styles/LineageGraphCore.less +20 -0
  37. package/src/styles/LineageResourceNode.less +711 -0
  38. package/src/utils/__tests__/lineageGraphIndex.spec.js +52 -0
  39. package/src/utils/__tests__/lineageLayout.spec.js +252 -0
  40. package/src/utils/__tests__/lineageTopology.spec.js +256 -0
  41. package/src/utils/lineageGraphIndex.js +296 -0
  42. package/src/utils/lineageLayout.js +1050 -0
  43. package/src/styles/lineageGraph.less +0 -1236
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "8.11.6",
3
+ "version": "8.11.9",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -54,7 +54,7 @@
54
54
  "@testing-library/jest-dom": "^6.6.3",
55
55
  "@testing-library/react": "^16.3.0",
56
56
  "@testing-library/user-event": "^14.6.1",
57
- "@truedat/test": "8.11.6",
57
+ "@truedat/test": "8.11.9",
58
58
  "identity-obj-proxy": "^3.0.0",
59
59
  "jest": "^29.7.0",
60
60
  "redux-saga-test-plan": "^4.0.6"
@@ -67,6 +67,7 @@
67
67
  "is-hotkey": "^0.2.0",
68
68
  "is-url": "^1.2.4",
69
69
  "lodash": "^4.17.21",
70
+ "lucide-react": "^1.41.0",
70
71
  "match-sorter": "^8.0.1",
71
72
  "moment": "^2.30.1",
72
73
  "path-to-regexp": "^8.2.0",
@@ -89,5 +90,5 @@
89
90
  "svg-pan-zoom": "^3.6.2",
90
91
  "swr": "^2.3.3"
91
92
  },
92
- "gitHead": "bfd7e4d934a88ec244c45032ca76103f236e9462"
93
+ "gitHead": "7ecccf4e5e9e4bf052cbe4414ce196751335a235"
93
94
  }
@@ -1,5 +1,10 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
- import PropTypes from "prop-types";
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useLayoutEffect,
5
+ useRef,
6
+ useState,
7
+ } from "react";
3
8
  import { useParams } from "react-router";
4
9
  import { Segment } from "semantic-ui-react";
5
10
  import LineageHeader from "./LineageHeader";
@@ -9,6 +14,27 @@ import LineageGraph from "./LineageGraph";
9
14
 
10
15
  export const Graphs = () => {
11
16
  const { id } = useParams();
17
+ const canvasRef = useRef(null);
18
+ useLayoutEffect(() => {
19
+ const canvas = canvasRef.current;
20
+ if (!canvas) return undefined;
21
+ const updateCanvasTop = () => {
22
+ canvas.style.setProperty(
23
+ "--td-lineage-canvas-top",
24
+ `${canvas.getBoundingClientRect().top}px`,
25
+ );
26
+ };
27
+ updateCanvasTop();
28
+ const observer = new ResizeObserver(updateCanvasTop);
29
+ observer.observe(canvas.parentElement);
30
+ observer.observe(canvas.parentElement.parentElement);
31
+ window.addEventListener("resize", updateCanvasTop);
32
+ return () => {
33
+ observer.disconnect();
34
+ window.removeEventListener("resize", updateCanvasTop);
35
+ };
36
+ }, []);
37
+
12
38
  const [sidebarCollapsed, setSidebarCollapsed] = useState(Boolean(id));
13
39
  const [sidebarHintActive, setSidebarHintActive] = useState(Boolean(id));
14
40
  const [sidebarHintOn, setSidebarHintOn] = useState(Boolean(id));
@@ -97,9 +123,9 @@ export const Graphs = () => {
97
123
  sidebarHintOn={sidebarHintOn}
98
124
  sidebarHintToken={sidebarHintToken}
99
125
  />
100
- <main className="lineage-view__main">
126
+ <main className="lineage-view__main" ref={canvasRef}>
101
127
  <GraphRedirector />
102
- <LineageGraph />
128
+ <LineageGraph height="var(--td-lineage-canvas-height)" />
103
129
  </main>
104
130
  </div>
105
131
  </Segment>
@@ -2,7 +2,7 @@ import _ from "lodash/fp";
2
2
  import PropTypes from "prop-types";
3
3
  import { connect } from "react-redux";
4
4
  import { Link } from "react-router";
5
- import { Menu, Icon } from "semantic-ui-react";
5
+ import { Icon } from "semantic-ui-react";
6
6
  import { FormattedMessage } from "react-intl";
7
7
  import { linkTo } from "@truedat/core/routes";
8
8
  import { excludeNode, createGraph } from "../routines";
@@ -14,64 +14,188 @@ export const LineageContextMenu = ({
14
14
  resource,
15
15
  excludeNode,
16
16
  createGraph,
17
+ onAction,
18
+ onTogglePin,
19
+ pinned,
17
20
  structureId,
18
- }) => (
19
- <Menu fluid={_.size(name) > 25} secondary size="small" vertical>
20
- <Menu.Item header>{name}</Menu.Item>
21
- {resource && (
22
- <>
23
- <Menu.Item
24
- onClick={() =>
25
- createGraph({
26
- ids: [id],
27
- levels: levels,
28
- type: "lineage",
29
- isRedirected: true,
30
- })
31
- }
32
- >
33
- <Icon name="random" flipped="horizontally" />
34
- <FormattedMessage
35
- id="navigation.lineage.lineage"
36
- defaultMessage="Lineage"
37
- />
38
- </Menu.Item>
39
- <Menu.Item
40
- onClick={() =>
41
- createGraph({
42
- ids: [id],
43
- levels: levels,
44
- type: "impact",
45
- isRedirected: true,
46
- })
47
- }
48
- >
49
- <Icon name="random" />
50
- <FormattedMessage
51
- id="navigation.lineage.impact"
52
- defaultMessage="Impact"
53
- />
54
- </Menu.Item>
55
- <Menu.Item onClick={() => excludeNode({ id, isRedirected: true })}>
56
- <Icon name="eye slash outline" />
57
- <FormattedMessage
58
- id="navigation.lineage.exclude"
59
- defaultMessage="Exclude"
60
- />
61
- </Menu.Item>
62
- </>
63
- )}
64
- {structureId && (
65
- <Menu.Item as={Link} to={linkTo.STRUCTURE({ id: structureId })}>
66
- <Icon name="th list" />
67
- <FormattedMessage
68
- id="navigation.lineage.view"
69
- defaultMessage="View in Data Catalog"
70
- />
71
- </Menu.Item>
72
- )}
73
- </Menu>
74
- );
21
+ }) => {
22
+ const handleCreateGraph = (type) => () => {
23
+ onAction && onAction();
24
+ createGraph({
25
+ ids: [id],
26
+ levels,
27
+ type,
28
+ isRedirected: true,
29
+ });
30
+ };
31
+ const handleExclude = () => {
32
+ onAction && onAction();
33
+ excludeNode({ id, isRedirected: true });
34
+ };
35
+ const handleNavigate = () => {
36
+ onAction && onAction();
37
+ };
38
+ const handleTogglePin = () => {
39
+ onTogglePin && onTogglePin();
40
+ };
41
+
42
+ return (
43
+ <div
44
+ className={`lineage-context-menu${
45
+ _.size(name) > 25 ? " lineage-context-menu--wide" : ""
46
+ }`}
47
+ >
48
+ <div className="lineage-context-menu__header">
49
+ <span className="lineage-context-menu__header-icon" aria-hidden="true">
50
+ <Icon name={resource ? "sitemap" : "folder open outline"} />
51
+ </span>
52
+ <div className="lineage-context-menu__header-copy">
53
+ <span className="lineage-context-menu__eyebrow">
54
+ <FormattedMessage
55
+ id={
56
+ resource
57
+ ? "lineage.sidebar.tabs.lineage"
58
+ : "navigation.lineage.view"
59
+ }
60
+ defaultMessage={resource ? "Lineage" : "View detail"}
61
+ />
62
+ </span>
63
+ <span className="lineage-context-menu__title" title={name}>
64
+ {name}
65
+ </span>
66
+ </div>
67
+ </div>
68
+ <div className="lineage-context-menu__actions">
69
+ {onTogglePin ? (
70
+ <button
71
+ aria-pressed={pinned}
72
+ className={`lineage-context-menu__action lineage-context-menu__action--pin${
73
+ pinned ? " lineage-context-menu__action--active" : ""
74
+ }`}
75
+ onClick={handleTogglePin}
76
+ type="button"
77
+ >
78
+ <span className="lineage-context-menu__action-icon">
79
+ <Icon
80
+ className={pinned ? "lineage-unpin-icon" : undefined}
81
+ name="pin"
82
+ />
83
+ </span>
84
+ <span className="lineage-context-menu__action-copy">
85
+ <span className="lineage-context-menu__action-title">
86
+ <FormattedMessage
87
+ id={
88
+ pinned
89
+ ? "lineage.context_menu.unpin_focus"
90
+ : "lineage.context_menu.pin_focus"
91
+ }
92
+ defaultMessage={pinned ? "Unpin focus" : "Pin focus"}
93
+ />
94
+ </span>
95
+ </span>
96
+ </button>
97
+ ) : null}
98
+ {resource ? (
99
+ <>
100
+ <button
101
+ className="lineage-context-menu__action"
102
+ onClick={handleCreateGraph("lineage")}
103
+ type="button"
104
+ >
105
+ <span className="lineage-context-menu__action-icon">
106
+ <Icon name="random" flipped="horizontally" />
107
+ </span>
108
+ <span className="lineage-context-menu__action-copy">
109
+ <span className="lineage-context-menu__action-title">
110
+ <FormattedMessage
111
+ id="navigation.lineage.lineage"
112
+ defaultMessage="Lineage"
113
+ />
114
+ </span>
115
+ </span>
116
+ </button>
117
+ <button
118
+ className="lineage-context-menu__action"
119
+ onClick={handleCreateGraph("impact")}
120
+ type="button"
121
+ >
122
+ <span className="lineage-context-menu__action-icon">
123
+ <Icon name="random" />
124
+ </span>
125
+ <span className="lineage-context-menu__action-copy">
126
+ <span className="lineage-context-menu__action-title">
127
+ <FormattedMessage
128
+ id="navigation.lineage.impact"
129
+ defaultMessage="Impact"
130
+ />
131
+ </span>
132
+ </span>
133
+ </button>
134
+ <button
135
+ className="lineage-context-menu__action"
136
+ onClick={handleExclude}
137
+ type="button"
138
+ >
139
+ <span className="lineage-context-menu__action-icon">
140
+ <Icon name="eye slash outline" />
141
+ </span>
142
+ <span className="lineage-context-menu__action-copy">
143
+ <span className="lineage-context-menu__action-title">
144
+ <FormattedMessage
145
+ id="navigation.lineage.exclude"
146
+ defaultMessage="Exclude"
147
+ />
148
+ </span>
149
+ </span>
150
+ </button>
151
+ </>
152
+ ) : null}
153
+ {structureId ? (
154
+ <Link
155
+ className="lineage-context-menu__action lineage-context-menu__action--link"
156
+ onClick={handleNavigate}
157
+ target="_blank"
158
+ rel="noopener noreferrer"
159
+ to={linkTo.STRUCTURE({ id: structureId })}
160
+ >
161
+ <span className="lineage-context-menu__action-icon">
162
+ <Icon name="th list" />
163
+ </span>
164
+ <span className="lineage-context-menu__action-copy">
165
+ <span className="lineage-context-menu__action-title">
166
+ <FormattedMessage
167
+ id="navigation.lineage.view"
168
+ defaultMessage="View in Data Catalog"
169
+ />
170
+ </span>
171
+ </span>
172
+ </Link>
173
+ ) : null}
174
+ {structureId ? (
175
+ <Link
176
+ className="lineage-context-menu__action lineage-context-menu__action--link"
177
+ onClick={handleNavigate}
178
+ target="_blank"
179
+ rel="noopener noreferrer"
180
+ to={linkTo.STRUCTURE_RULES({ id: structureId })}
181
+ >
182
+ <span className="lineage-context-menu__action-icon">
183
+ <Icon name="check circle outline" />
184
+ </span>
185
+ <span className="lineage-context-menu__action-copy">
186
+ <span className="lineage-context-menu__action-title">
187
+ <FormattedMessage
188
+ id="lineage.context_menu.quality_rules"
189
+ defaultMessage="View Quality Rules"
190
+ />
191
+ </span>
192
+ </span>
193
+ </Link>
194
+ ) : null}
195
+ </div>
196
+ </div>
197
+ );
198
+ };
75
199
 
76
200
  LineageContextMenu.propTypes = {
77
201
  createGraph: PropTypes.func,
@@ -79,6 +203,9 @@ LineageContextMenu.propTypes = {
79
203
  id: PropTypes.string,
80
204
  levels: PropTypes.number,
81
205
  name: PropTypes.string,
206
+ onAction: PropTypes.func,
207
+ onTogglePin: PropTypes.func,
208
+ pinned: PropTypes.bool,
82
209
  resource: PropTypes.bool,
83
210
  structureId: PropTypes.number,
84
211
  };
@@ -1,7 +1,8 @@
1
1
  import _ from "lodash/fp";
2
2
  import PropTypes from "prop-types";
3
3
  import { connect } from "react-redux";
4
- import { Button, Popup, Icon } from "semantic-ui-react";
4
+ import { Button, Popup } from "semantic-ui-react";
5
+ import { Loader2, Download } from "lucide-react";
5
6
  import { useIntl } from "react-intl";
6
7
  import { downloadCsvGraph } from "../routines";
7
8
 
@@ -26,6 +27,7 @@ export const LineageDownloadButton = ({
26
27
  content,
27
28
  compact = false,
28
29
  labelPosition = "left",
30
+ tooltipPosition = "top right",
29
31
  size,
30
32
  }) => {
31
33
  const { formatMessage } = useIntl();
@@ -40,11 +42,12 @@ export const LineageDownloadButton = ({
40
42
  return (
41
43
  !graphLoading && (
42
44
  <Popup
43
- position="top right"
45
+ position={tooltipPosition}
44
46
  content={messagePopup}
45
47
  trigger={
46
48
  <span>
47
49
  <Button
50
+ aria-label={content || messagePopup}
48
51
  basic={basic}
49
52
  className={className}
50
53
  compact={compact}
@@ -60,10 +63,16 @@ export const LineageDownloadButton = ({
60
63
  secondary={!basic}
61
64
  size={size}
62
65
  >
63
- <Icon
64
- name={csvGraphDownloading ? "spinner" : "download"}
65
- loading={csvGraphDownloading}
66
- />
66
+ {csvGraphDownloading ? (
67
+ <Loader2
68
+ aria-hidden="true"
69
+ className="lineage-graph__dock-download-spinner"
70
+ size={20}
71
+ strokeWidth={2}
72
+ />
73
+ ) : (
74
+ <Download aria-hidden="true" size={20} strokeWidth={2} />
75
+ )}
67
76
  </Button>
68
77
  </span>
69
78
  }
@@ -83,6 +92,7 @@ LineageDownloadButton.propTypes = {
83
92
  graphLoading: PropTypes.bool,
84
93
  downloadCsvGraph: PropTypes.func,
85
94
  labelPosition: PropTypes.string,
95
+ tooltipPosition: PropTypes.string,
86
96
  size: PropTypes.string,
87
97
  };
88
98
 
@@ -1,6 +1,6 @@
1
- import React from "react";
1
+ import React, { useRef, useState } from "react";
2
2
  import PropTypes from "prop-types";
3
- import { useNavigate } from "react-router";
3
+ import { useNavigate, useParams } from "react-router";
4
4
  import { connect } from "react-redux";
5
5
  import { FormattedMessage, useIntl } from "react-intl";
6
6
  import { Icon, Popup } from "semantic-ui-react";
@@ -10,6 +10,7 @@ import {
10
10
  isCompletedStatus,
11
11
  isFailedStatus,
12
12
  lineageEventType,
13
+ graphIdentity,
13
14
  graphLabel,
14
15
  graphScope,
15
16
  graphDomain,
@@ -18,6 +19,7 @@ import {
18
19
  graphTypeIcon,
19
20
  eventErrorMessage,
20
21
  eventTimestamp,
22
+ groupLineageEvents,
21
23
  } from "./lineageEventHelpers";
22
24
 
23
25
  export const LineageEventsPanel = ({
@@ -27,7 +29,13 @@ export const LineageEventsPanel = ({
27
29
  }) => {
28
30
  const { formatMessage } = useIntl();
29
31
  const navigate = useNavigate();
30
- const sortedEvents = [...lineageEvents].sort(
32
+ const { id: activeGraphId } = useParams();
33
+ const [copiedId, setCopiedId] = useState(null);
34
+ const copiedTimer = useRef(null);
35
+ const dedupedEvents = groupLineageEvents(lineageEvents).flatMap((group) =>
36
+ Object.values(group.eventsByType),
37
+ );
38
+ const sortedEvents = [...dedupedEvents].sort(
31
39
  (a, b) => eventTimestamp(b) - eventTimestamp(a),
32
40
  );
33
41
 
@@ -59,6 +67,21 @@ export const LineageEventsPanel = ({
59
67
  const scope = graphScope(event.graph_data);
60
68
  const domain = graphDomain(event);
61
69
  const firstLevel = graphFirstLevel(event);
70
+ const title = graphLabel(event, index);
71
+ const identity = graphIdentity(event, index);
72
+
73
+ const handleCopyError = (e) => {
74
+ e.stopPropagation();
75
+ const text = errorMessage || "";
76
+ navigator.clipboard.writeText(text).then(() => {
77
+ setCopiedId(event.graph_id || event.id);
78
+ clearTimeout(copiedTimer.current);
79
+ copiedTimer.current = setTimeout(
80
+ () => setCopiedId(null),
81
+ 2000,
82
+ );
83
+ });
84
+ };
62
85
 
63
86
  const handleCardClick = () => {
64
87
  if (onGraphLoad) onGraphLoad();
@@ -75,7 +98,13 @@ export const LineageEventsPanel = ({
75
98
  return (
76
99
  <article
77
100
  className={`lineage-events-panel__item${
101
+ type ? ` lineage-events-panel__item--${type}` : ""
102
+ }${
78
103
  canLoadGraph ? " lineage-events-panel__item--clickable" : ""
104
+ }${
105
+ String(event.graph_id) === String(activeGraphId)
106
+ ? " lineage-events-panel__item--active"
107
+ : ""
79
108
  }`}
80
109
  key={event.graph_id || event.id || index}
81
110
  onClick={canLoadGraph ? handleCardClick : undefined}
@@ -91,7 +120,7 @@ export const LineageEventsPanel = ({
91
120
  <div className="lineage-events-panel__item-header">
92
121
  <div className="lineage-events-panel__item-title">
93
122
  <Icon name="code branch" />
94
- <span>{graphLabel(event, index)}</span>
123
+ <span title={title}>{title}</span>
95
124
  </div>
96
125
  <div
97
126
  className={`lineage-events-panel__card-status${
@@ -104,36 +133,56 @@ export const LineageEventsPanel = ({
104
133
  : ""
105
134
  }`}
106
135
  >
107
- {failed ? (
108
- <Popup
109
- inverted
110
- on="hover click"
111
- position="top center"
112
- trigger={
136
+ <Popup
137
+ on="hover"
138
+ role="tooltip"
139
+ aria-label={formatMessage({
140
+ id: "lineage.events.panel.graph.id",
141
+ })}
142
+ position="top center"
143
+ wide="very"
144
+ className="lineage-events-panel__status-popup"
145
+ trigger={
146
+ failed ? (
113
147
  <button
114
148
  aria-label={formatMessage({
115
149
  id: "lineage.events.panel.error",
116
150
  })}
117
151
  className="lineage-events-panel__card-status-trigger"
118
152
  type="button"
153
+ onClick={handleCopyError}
119
154
  >
120
155
  <Icon name="warning circle" />
121
- KO
156
+ {copiedId === (event.graph_id || event.id)
157
+ ? "Copiado ✓"
158
+ : "KO"}
122
159
  </button>
123
- }
124
- >
125
- {errorMessage || (
126
- <FormattedMessage id="lineage.events.panel.error.empty" />
127
- )}
128
- </Popup>
129
- ) : (
130
- <span className="lineage-events-panel__card-status-trigger">
131
- <Icon
132
- name={completed ? "check circle" : "circle notched"}
133
- />
134
- {completed ? "OK" : "..."}
160
+ ) : (
161
+ <span className="lineage-events-panel__card-status-trigger">
162
+ <Icon
163
+ name={
164
+ completed ? "check circle" : "circle notched"
165
+ }
166
+ />
167
+ {completed ? "OK" : "..."}
168
+ </span>
169
+ )
170
+ }
171
+ >
172
+ <span className="lineage-events-panel__status-popup-id">
173
+ {identity}
135
174
  </span>
136
- )}
175
+ {failed ? (
176
+ <>
177
+ <div className="lineage-events-panel__status-popup-divider" />
178
+ <div className="lineage-events-panel__status-popup-error">
179
+ {errorMessage || (
180
+ <FormattedMessage id="lineage.events.panel.error.empty" />
181
+ )}
182
+ </div>
183
+ </>
184
+ ) : null}
185
+ </Popup>
137
186
  </div>
138
187
  </div>
139
188
  {domain || firstLevel ? (
@@ -152,14 +201,18 @@ export const LineageEventsPanel = ({
152
201
  ) : null}
153
202
  </div>
154
203
  ) : null}
155
- {scope ? (
204
+ {scope && scope !== title ? (
156
205
  <p className="lineage-events-panel__scope">{scope}</p>
157
206
  ) : null}
158
207
  <div className="lineage-events-panel__row">
159
208
  <div className="lineage-events-panel__row-main">
160
209
  <div className="lineage-events-panel__type">
161
- <Icon name={graphTypeIcon(type)} />
162
- <FormattedMessage id={graphTypeMessageId(type)} />
210
+ {type ? (
211
+ <>
212
+ <Icon name={graphTypeIcon(type)} />
213
+ <FormattedMessage id={graphTypeMessageId(type)} />
214
+ </>
215
+ ) : null}
163
216
  </div>
164
217
  <div className="lineage-events-panel__date">
165
218
  {event.inserted_at ? (
@@ -10,6 +10,7 @@ import { columnDecorator } from "@truedat/core/services";
10
10
  import { linkTo } from "@truedat/core/routes";
11
11
  import { lineageEventColumns as columns } from "../selectors/getLineageEventColumns";
12
12
  import { fetchLineageEvents } from "../routines";
13
+ import { groupLineageEvents, eventTimestamp } from "./lineageEventHelpers";
13
14
 
14
15
  export const LineageEventsTable = ({
15
16
  fetchLineageEvents,
@@ -18,6 +19,9 @@ export const LineageEventsTable = ({
18
19
  }) => {
19
20
  const { formatMessage } = useIntl();
20
21
  const navigate = useNavigate();
22
+ const dedupedEvents = groupLineageEvents(lineageEvents)
23
+ .flatMap((group) => Object.values(group.eventsByType))
24
+ .sort((a, b) => eventTimestamp(b) - eventTimestamp(a));
21
25
 
22
26
  useEffect(() => {
23
27
  fetchLineageEvents();
@@ -25,7 +29,7 @@ export const LineageEventsTable = ({
25
29
 
26
30
  return (
27
31
  <>
28
- {!_.isEmpty(lineageEvents) && (
32
+ {!_.isEmpty(dedupedEvents) && (
29
33
  <Table sortable selectable>
30
34
  <Table.Header>
31
35
  <Table.Row>
@@ -44,7 +48,7 @@ export const LineageEventsTable = ({
44
48
  </Table.Row>
45
49
  </Table.Header>
46
50
  <Table.Body>
47
- {lineageEvents.map((event, i) => (
51
+ {dedupedEvents.map((event, i) => (
48
52
  <Table.Row
49
53
  key={i}
50
54
  onClick={
@@ -66,7 +70,7 @@ export const LineageEventsTable = ({
66
70
  </Table.Body>
67
71
  </Table>
68
72
  )}
69
- {_.isEmpty(lineageEvents) && !lineageEventsLoading && (
73
+ {_.isEmpty(dedupedEvents) && !lineageEventsLoading && (
70
74
  <Header as="h4">
71
75
  <Icon name="search" />
72
76
  <Header.Content>