@truedat/dd 8.10.2 → 8.10.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "8.10.2",
3
+ "version": "8.10.4",
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.10.2",
57
+ "@truedat/test": "8.10.4",
58
58
  "identity-obj-proxy": "^3.0.0",
59
59
  "jest": "^29.7.0",
60
60
  "redux-saga-test-plan": "^4.0.6"
@@ -89,5 +89,5 @@
89
89
  "svg-pan-zoom": "^3.6.2",
90
90
  "swr": "^2.3.3"
91
91
  },
92
- "gitHead": "40436c0d8d650f31421d9695035f2d54e5b1c28c"
92
+ "gitHead": "fe4fda03b2b73d26f644219d806dcc45767a3c7a"
93
93
  }
@@ -0,0 +1,109 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useParams } from "react-router";
4
+ import { Segment } from "semantic-ui-react";
5
+ import LineageHeader from "./LineageHeader";
6
+ import LineageSelectionPanel from "./LineageSelectionPanel";
7
+ import GraphRedirector from "./GraphRedirector";
8
+ import LineageGraph from "./LineageGraph";
9
+
10
+ export const Graphs = () => {
11
+ const { id } = useParams();
12
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(Boolean(id));
13
+ const [sidebarHintActive, setSidebarHintActive] = useState(Boolean(id));
14
+ const [sidebarHintOn, setSidebarHintOn] = useState(Boolean(id));
15
+ const [sidebarHintToken, setSidebarHintToken] = useState(0);
16
+ const sidebarHintTimeoutRef = useRef();
17
+ const sidebarHintIntervalRef = useRef();
18
+ const clearSidebarHint = useCallback(() => {
19
+ if (sidebarHintTimeoutRef.current) {
20
+ clearTimeout(sidebarHintTimeoutRef.current);
21
+ sidebarHintTimeoutRef.current = undefined;
22
+ }
23
+ if (sidebarHintIntervalRef.current) {
24
+ clearInterval(sidebarHintIntervalRef.current);
25
+ sidebarHintIntervalRef.current = undefined;
26
+ }
27
+ setSidebarHintActive(false);
28
+ setSidebarHintOn(false);
29
+ }, []);
30
+ const showSidebarHint = useCallback(() => {
31
+ if (sidebarHintTimeoutRef.current)
32
+ clearTimeout(sidebarHintTimeoutRef.current);
33
+ if (sidebarHintIntervalRef.current)
34
+ clearInterval(sidebarHintIntervalRef.current);
35
+
36
+ setSidebarHintToken((token) => token + 1);
37
+ setSidebarHintActive(true);
38
+ setSidebarHintOn(true);
39
+ const pulseCountRef = { current: 0 };
40
+ sidebarHintIntervalRef.current = setInterval(() => {
41
+ pulseCountRef.current += 1;
42
+ setSidebarHintOn((active) => !active);
43
+
44
+ if (pulseCountRef.current >= 3) {
45
+ clearInterval(sidebarHintIntervalRef.current);
46
+ sidebarHintIntervalRef.current = undefined;
47
+ }
48
+ }, 300);
49
+ sidebarHintTimeoutRef.current = setTimeout(() => {
50
+ setSidebarHintActive(false);
51
+ setSidebarHintOn(false);
52
+ sidebarHintTimeoutRef.current = undefined;
53
+ if (sidebarHintIntervalRef.current) {
54
+ clearInterval(sidebarHintIntervalRef.current);
55
+ sidebarHintIntervalRef.current = undefined;
56
+ }
57
+ }, 1200);
58
+ }, []);
59
+ const collapseSidebarWithHint = useCallback(() => {
60
+ setSidebarCollapsed(true);
61
+ showSidebarHint();
62
+ }, [showSidebarHint]);
63
+ const handleToggleSidebar = useCallback(() => {
64
+ clearSidebarHint();
65
+ setSidebarCollapsed((collapsed) => !collapsed);
66
+ }, [clearSidebarHint]);
67
+
68
+ useEffect(() => {
69
+ if (id) collapseSidebarWithHint();
70
+ }, [collapseSidebarWithHint, id]);
71
+
72
+ useEffect(
73
+ () => () => {
74
+ if (sidebarHintTimeoutRef.current) {
75
+ clearTimeout(sidebarHintTimeoutRef.current);
76
+ }
77
+ if (sidebarHintIntervalRef.current) {
78
+ clearInterval(sidebarHintIntervalRef.current);
79
+ }
80
+ },
81
+ [],
82
+ );
83
+
84
+ return (
85
+ <Segment className="lineage-view">
86
+ <LineageHeader />
87
+ <div
88
+ className={`lineage-view__workspace${
89
+ sidebarCollapsed ? " lineage-view__workspace--sidebar-collapsed" : ""
90
+ }`}
91
+ >
92
+ <LineageSelectionPanel
93
+ collapsed={sidebarCollapsed}
94
+ onGraphLoad={collapseSidebarWithHint}
95
+ onToggle={handleToggleSidebar}
96
+ sidebarHintActive={sidebarHintActive}
97
+ sidebarHintOn={sidebarHintOn}
98
+ sidebarHintToken={sidebarHintToken}
99
+ />
100
+ <main className="lineage-view__main">
101
+ <GraphRedirector />
102
+ <LineageGraph />
103
+ </main>
104
+ </div>
105
+ </Segment>
106
+ );
107
+ };
108
+
109
+ export default Graphs;
@@ -0,0 +1,8 @@
1
+ const LineageDomainSelectorSkeleton = () => (
2
+ <div className="lineage-domain-selector-skeleton" aria-hidden="true">
3
+ <span className="lineage-domain-selector-skeleton__label" />
4
+ <span className="lineage-domain-selector-skeleton__icon" />
5
+ </div>
6
+ );
7
+
8
+ export default LineageDomainSelectorSkeleton;
@@ -16,17 +16,25 @@ const staticLabels = [
16
16
  ];
17
17
 
18
18
  export const LineageDownloadButton = ({
19
+ basic = false,
20
+ className,
19
21
  graph,
20
22
  csvGraphDownloading,
21
23
  graphLoading,
22
24
  downloadCsvGraph,
25
+ disabled = false,
26
+ content,
27
+ compact = false,
28
+ labelPosition = "left",
29
+ size,
23
30
  }) => {
24
31
  const { formatMessage } = useIntl();
25
32
  const headerLabels = _.flow(
26
33
  _.map((l) => [l, formatMessage({ id: `graph.header.${l}` })]),
27
34
  _.fromPairs,
28
35
  )(staticLabels);
29
- const messagePopup = _.negate(_.isEmpty)(graph)
36
+ const downloadDisabled = disabled || _.isEmpty(graph);
37
+ const messagePopup = !downloadDisabled
30
38
  ? formatMessage({ id: "graph.actions.download.tooltip" })
31
39
  : formatMessage({ id: "graph.actions.download.popup" });
32
40
  return (
@@ -37,13 +45,21 @@ export const LineageDownloadButton = ({
37
45
  trigger={
38
46
  <span>
39
47
  <Button
40
- secondary
48
+ basic={basic}
49
+ className={className}
50
+ compact={compact}
51
+ content={content}
41
52
  icon="download"
42
- onClick={() =>
43
- downloadCsvGraph({ headerLabels, id: _.prop("id")(graph) })
44
- }
53
+ labelPosition={content ? labelPosition : undefined}
54
+ onClick={() => {
55
+ if (downloadDisabled) return;
56
+
57
+ downloadCsvGraph({ headerLabels, id: _.prop("id")(graph) });
58
+ }}
45
59
  loading={csvGraphDownloading}
46
- disabled={_.isEmpty(graph)}
60
+ disabled={downloadDisabled}
61
+ secondary={!basic}
62
+ size={size}
47
63
  />
48
64
  </span>
49
65
  }
@@ -53,10 +69,17 @@ export const LineageDownloadButton = ({
53
69
  };
54
70
 
55
71
  LineageDownloadButton.propTypes = {
72
+ basic: PropTypes.bool,
73
+ className: PropTypes.string,
74
+ compact: PropTypes.bool,
75
+ content: PropTypes.node,
76
+ disabled: PropTypes.bool,
56
77
  graph: PropTypes.object,
57
78
  csvGraphDownloading: PropTypes.bool,
58
79
  graphLoading: PropTypes.bool,
59
80
  downloadCsvGraph: PropTypes.func,
81
+ labelPosition: PropTypes.string,
82
+ size: PropTypes.string,
60
83
  };
61
84
 
62
85
  const mapStateToProps = (state, ownProps) => ({
@@ -0,0 +1,206 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useNavigate } from "react-router";
4
+ import { connect } from "react-redux";
5
+ import { FormattedMessage, useIntl } from "react-intl";
6
+ import { Icon, Popup } from "semantic-ui-react";
7
+ import Moment from "react-moment";
8
+ import { linkTo } from "@truedat/core/routes";
9
+ import {
10
+ isCompletedStatus,
11
+ isFailedStatus,
12
+ lineageEventType,
13
+ graphLabel,
14
+ graphScope,
15
+ graphDomain,
16
+ graphFirstLevel,
17
+ graphTypeMessageId,
18
+ graphTypeIcon,
19
+ eventErrorMessage,
20
+ eventTimestamp,
21
+ } from "./lineageEventHelpers";
22
+
23
+ export const LineageEventsPanel = ({
24
+ lineageEvents = [],
25
+ lineageEventsLoading,
26
+ onGraphLoad,
27
+ }) => {
28
+ const { formatMessage } = useIntl();
29
+ const navigate = useNavigate();
30
+ const sortedEvents = [...lineageEvents].sort(
31
+ (a, b) => eventTimestamp(b) - eventTimestamp(a),
32
+ );
33
+
34
+ return (
35
+ <div className="lineage-events-panel">
36
+ <div className="lineage-events-panel__header">
37
+ <span>
38
+ <FormattedMessage id="lineage.events.panel.header" />
39
+ </span>
40
+ <Icon name="clock outline" />
41
+ </div>
42
+ <div className="lineage-events-panel__scrollable">
43
+ {lineageEventsLoading ? (
44
+ <div className="lineage-events-panel__empty">
45
+ <Icon loading name="circle notched" />
46
+ <span>
47
+ <FormattedMessage id="lineage.events.panel.loading" />
48
+ </span>
49
+ </div>
50
+ ) : null}
51
+ {!lineageEventsLoading && sortedEvents.length ? (
52
+ <div className="lineage-events-panel__list">
53
+ {sortedEvents.map((event, index) => {
54
+ const completed = isCompletedStatus(event.status);
55
+ const failed = isFailedStatus(event.status);
56
+ const canLoadGraph = completed && event.graph_id;
57
+ const type = lineageEventType(event);
58
+ const errorMessage = eventErrorMessage(event);
59
+ const scope = graphScope(event.graph_data);
60
+ const domain = graphDomain(event);
61
+ const firstLevel = graphFirstLevel(event);
62
+
63
+ const handleCardClick = () => {
64
+ if (onGraphLoad) onGraphLoad();
65
+ navigate(linkTo.GRAPH({ id: event.graph_id }));
66
+ };
67
+
68
+ const handleCardKeyDown = (e) => {
69
+ if (e.key === "Enter" || e.key === " ") {
70
+ e.preventDefault();
71
+ handleCardClick();
72
+ }
73
+ };
74
+
75
+ return (
76
+ <article
77
+ className={`lineage-events-panel__item${
78
+ canLoadGraph ? " lineage-events-panel__item--clickable" : ""
79
+ }`}
80
+ key={event.graph_id || event.id || index}
81
+ onClick={canLoadGraph ? handleCardClick : undefined}
82
+ onKeyDown={canLoadGraph ? handleCardKeyDown : undefined}
83
+ role={canLoadGraph ? "button" : undefined}
84
+ aria-label={
85
+ canLoadGraph
86
+ ? formatMessage({ id: "lineage.events.panel.load" })
87
+ : undefined
88
+ }
89
+ tabIndex={canLoadGraph ? 0 : undefined}
90
+ >
91
+ <div className="lineage-events-panel__item-header">
92
+ <div className="lineage-events-panel__item-title">
93
+ <Icon name="code branch" />
94
+ <span>{graphLabel(event, index)}</span>
95
+ </div>
96
+ <div
97
+ className={`lineage-events-panel__card-status${
98
+ completed
99
+ ? " lineage-events-panel__card-status--completed"
100
+ : ""
101
+ }${
102
+ failed
103
+ ? " lineage-events-panel__card-status--failed"
104
+ : ""
105
+ }`}
106
+ >
107
+ {failed ? (
108
+ <Popup
109
+ inverted
110
+ on="hover click"
111
+ position="top center"
112
+ trigger={
113
+ <button
114
+ aria-label={formatMessage({
115
+ id: "lineage.events.panel.error",
116
+ })}
117
+ className="lineage-events-panel__card-status-trigger"
118
+ type="button"
119
+ >
120
+ <Icon name="warning circle" />
121
+ KO
122
+ </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" : "..."}
135
+ </span>
136
+ )}
137
+ </div>
138
+ </div>
139
+ {domain || firstLevel ? (
140
+ <div className="lineage-events-panel__meta">
141
+ {domain ? (
142
+ <span>
143
+ <FormattedMessage id="lineage.events.panel.domain" />:{" "}
144
+ {domain}
145
+ </span>
146
+ ) : null}
147
+ {firstLevel ? (
148
+ <span>
149
+ <FormattedMessage id="lineage.events.panel.first_level" />
150
+ : {firstLevel}
151
+ </span>
152
+ ) : null}
153
+ </div>
154
+ ) : null}
155
+ {scope ? (
156
+ <p className="lineage-events-panel__scope">{scope}</p>
157
+ ) : null}
158
+ <div className="lineage-events-panel__row">
159
+ <div className="lineage-events-panel__row-main">
160
+ <div className="lineage-events-panel__type">
161
+ <Icon name={graphTypeIcon(type)} />
162
+ <FormattedMessage id={graphTypeMessageId(type)} />
163
+ </div>
164
+ <div className="lineage-events-panel__date">
165
+ {event.inserted_at ? (
166
+ <Moment
167
+ locale="es"
168
+ date={event.inserted_at}
169
+ format="YYYY-MM-DD HH:mm"
170
+ />
171
+ ) : (
172
+ "-"
173
+ )}
174
+ </div>
175
+ </div>
176
+ </div>
177
+ </article>
178
+ );
179
+ })}
180
+ </div>
181
+ ) : null}
182
+ {!lineageEventsLoading && !sortedEvents.length ? (
183
+ <div className="lineage-events-panel__empty">
184
+ <Icon name="clock outline" />
185
+ <span>
186
+ <FormattedMessage id="lineage.events.search.results.empty" />
187
+ </span>
188
+ </div>
189
+ ) : null}
190
+ </div>
191
+ </div>
192
+ );
193
+ };
194
+
195
+ LineageEventsPanel.propTypes = {
196
+ lineageEvents: PropTypes.array,
197
+ lineageEventsLoading: PropTypes.bool,
198
+ onGraphLoad: PropTypes.func,
199
+ };
200
+
201
+ const mapStateToProps = (state) => ({
202
+ lineageEvents: state.lineageEvents,
203
+ lineageEventsLoading: state.lineageEventsLoading,
204
+ });
205
+
206
+ export default connect(mapStateToProps)(LineageEventsPanel);