@truedat/dd 4.33.3 → 4.33.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.33.4] 2021-11-26
4
+
5
+ ### Added
6
+
7
+ - [TD-4262] Lineage graph polling
8
+
3
9
  ## [4.33.3] 2021-11-25
4
10
 
5
11
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dd",
3
- "version": "4.33.3",
3
+ "version": "4.33.4",
4
4
  "description": "Truedat Web Data Dictionary",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.14.1",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "4.33.3",
37
+ "@truedat/test": "4.33.4",
38
38
  "babel-jest": "^27.0.6",
39
39
  "babel-plugin-dynamic-import-node": "^2.3.3",
40
40
  "babel-plugin-lodash": "^3.3.4",
@@ -84,9 +84,9 @@
84
84
  ]
85
85
  },
86
86
  "dependencies": {
87
- "@truedat/auth": "4.33.3",
88
- "@truedat/core": "4.33.3",
89
- "@truedat/df": "4.33.3",
87
+ "@truedat/auth": "4.33.4",
88
+ "@truedat/core": "4.33.4",
89
+ "@truedat/df": "4.33.4",
90
90
  "lodash": "^4.17.21",
91
91
  "path-to-regexp": "^1.7.0",
92
92
  "prop-types": "^15.7.2",
@@ -107,5 +107,5 @@
107
107
  "react-dom": ">= 16.8.6 < 17",
108
108
  "semantic-ui-react": ">= 0.88.2 < 2.1"
109
109
  },
110
- "gitHead": "4423c4077cf22e7d21d2679670287ed3d86ec8d7"
110
+ "gitHead": "08160df210692628d37599956bc78c587c019afc"
111
111
  }
package/src/api.js CHANGED
@@ -14,8 +14,10 @@ const API_GRANT_REQUESTS = "/api/grant_requests";
14
14
  const API_GRANTS_CSV = "/api/grants/csv";
15
15
  const API_GRANTS_SEARCH = "/api/grants/search";
16
16
  const API_GRAPH = "/api/graphs/:id";
17
+ const API_GRAPH_HASH = "/api/graphs/hash/:hash";
17
18
  const API_GRAPHS = "/api/graphs";
18
19
  const API_GRAPH_CSV = "/api/graphs/csv";
20
+ const API_LINEAGE_EVENTS = "/api/lineage_events";
19
21
  const API_MY_GRANT_FILTERS_SEARCH = "/api/grant_filters/search/mine";
20
22
  const API_MY_GRANTS_SEARCH = "/api/grants/search/mine";
21
23
  const API_NODE = "/api/nodes/:id";
@@ -58,9 +60,11 @@ export {
58
60
  API_GRANT_REQUESTS,
59
61
  API_GRANTS_SEARCH,
60
62
  API_GRAPH,
63
+ API_GRAPH_HASH,
61
64
  API_GRAPHS,
62
65
  API_GRAPH_CSV,
63
66
  API_GRANTS_CSV,
67
+ API_LINEAGE_EVENTS,
64
68
  API_MY_GRANTS_SEARCH,
65
69
  API_MY_GRANT_FILTERS_SEARCH,
66
70
  API_NODE,
@@ -2,27 +2,41 @@ import _ from "lodash/fp";
2
2
  import { useEffect } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
- import { useRouteMatch } from "react-router-dom";
5
+ import { useRouteMatch, useHistory } from "react-router-dom";
6
6
  import { GRAPH } from "@truedat/core/routes";
7
- import { fetchGraph, clearGraph } from "../routines";
7
+ import { fetchGraph, clearGraph, cancelPoll } from "../routines";
8
8
 
9
- export const GraphLoader = ({ clearGraph, fetchGraph, graph }) => {
9
+ export const GraphLoader = ({ clearGraph, fetchGraph, graph, cancelPoll }) => {
10
10
  const match = useRouteMatch(GRAPH);
11
11
  const id = _.path("params.id")(match);
12
+
13
+ const history = useHistory();
14
+
12
15
  useEffect(() => {
13
16
  return () => {
14
17
  clearGraph();
15
18
  };
16
19
  }, [clearGraph]);
20
+
17
21
  useEffect(() => {
18
22
  if (id) {
19
23
  if (_.isEmpty(graph)) {
20
24
  fetchGraph({ id });
21
- } else if (!_.propEq("id", id)(graph)) {
25
+ } else if (id != graph.id) {
22
26
  fetchGraph({ id });
23
27
  }
24
28
  }
25
- }, [graph, fetchGraph, id]);
29
+ }, [/*graph, *//*fetchGraph,*/ id]);
30
+
31
+ useEffect(() => {
32
+ return () => {
33
+ // && history.location.pathname === "any specific path")
34
+ if (history.action === "POP") {
35
+ cancelPoll();
36
+ }
37
+ };
38
+ }, [history]);
39
+
26
40
  return null;
27
41
  };
28
42
 
@@ -30,10 +44,12 @@ GraphLoader.propTypes = {
30
44
  clearGraph: PropTypes.func.isRequired,
31
45
  fetchGraph: PropTypes.func.isRequired,
32
46
  graph: PropTypes.object,
47
+ cancelPoll: PropTypes.func,
33
48
  };
34
49
 
35
50
  const mapStateToProps = ({ graph }) => ({ graph });
36
51
 
37
- export default connect(mapStateToProps, { clearGraph, fetchGraph })(
52
+ export default connect(mapStateToProps, { clearGraph, fetchGraph, cancelPoll })(
38
53
  GraphLoader
39
54
  );
55
+
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { Header, Icon, Segment, Dimmer, Loader } from "semantic-ui-react";
3
+ import { FormattedMessage } from "react-intl";
4
+ import LineageEventsTable from "./LineageEventsTable"
5
+
6
+ export const LineageEvents = () => (
7
+ <Segment>
8
+ <Header as="h2">
9
+ <Icon circular name="cogs" />
10
+ <Header.Content>
11
+ <FormattedMessage id="lineage.events.header" />
12
+ <Header.Subheader>
13
+ <FormattedMessage id="lineage.events.subheader" />
14
+ </Header.Subheader>
15
+ </Header.Content>
16
+ </Header>
17
+ <Segment attached="bottom">
18
+ <LineageEventsTable />
19
+ </Segment>
20
+ </Segment>
21
+ );
22
+
23
+ export default LineageEvents;
@@ -0,0 +1,91 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useEffect } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { FormattedMessage } from "react-intl";
5
+ import { Header, Icon, Table } from "semantic-ui-react";
6
+ import { useHistory, } from "react-router-dom";
7
+ import { connect } from "react-redux";
8
+ import { useIntl } from "react-intl";
9
+ import { columnDecorator } from "@truedat/core/services";
10
+ import { linkTo } from "@truedat/core/routes";
11
+ import { lineageEventColumns as columns } from "../selectors/getLineageEventColumns";
12
+ import { fetchLineageEvents } from "../routines";
13
+
14
+
15
+ export const LineageEventsTable = ({ fetchLineageEvents, lineageEvents, lineageEventsLoading }) => {
16
+ const { formatMessage } = useIntl();
17
+ const history = useHistory();
18
+
19
+ useEffect(() => {
20
+ fetchLineageEvents();
21
+ }, [fetchLineageEvents]);
22
+
23
+ return (
24
+ <>
25
+ {!_.isEmpty(lineageEvents) && (
26
+ <Table sortable selectable>
27
+ <Table.Header>
28
+ <Table.Row>
29
+ {columns.map((column, i) => {
30
+ return (
31
+ <Table.HeaderCell
32
+ key={i}
33
+ width={column.width}
34
+ content={formatMessage({
35
+ id: `lineage.events.props.${column.name}`,
36
+ defaultMessage: column.name,
37
+ })}
38
+ />
39
+ );
40
+ })}
41
+ </Table.Row>
42
+ </Table.Header>
43
+ <Table.Body>
44
+ {lineageEvents.map((event, i) => (
45
+ <Table.Row
46
+ key={i}
47
+ onClick={
48
+ event.graph_id
49
+ ? () => history.push(linkTo.GRAPH({ id: event?.graph_id }))
50
+ : null
51
+ }
52
+ >
53
+ {columns &&
54
+ columns.map((column, key) => (
55
+ <Table.Cell
56
+ key={key}
57
+ textAlign={column.textAlign}
58
+ content={columnDecorator(column)(event)}
59
+ />
60
+ ))}
61
+ </Table.Row>
62
+ ))}
63
+ </Table.Body>
64
+ </Table>
65
+ )}
66
+ {_.isEmpty(lineageEvents) && !lineageEventsLoading && (
67
+ <Header as="h4">
68
+ <Icon name="search" />
69
+ <Header.Content>
70
+ <FormattedMessage id="lineage.events.search.results.empty" />
71
+ </Header.Content>
72
+ </Header>
73
+ )}
74
+ </>
75
+ );
76
+ };
77
+
78
+ LineageEventsTable.propTypes = {
79
+ lineageEvents: PropTypes.array,
80
+ fetchLineageEvents: PropTypes.func,
81
+ lineageEventsLoading: PropTypes.bool,
82
+ };
83
+
84
+ const mapStateToProps = (state) => ({
85
+ lineageEvents: state.lineageEvents,
86
+ lineageEventsLoading: state.lineageEventsLoading,
87
+ });
88
+
89
+ export default connect(mapStateToProps, { fetchLineageEvents })(
90
+ LineageEventsTable
91
+ );
@@ -54,10 +54,11 @@ export const LineageForm = ({
54
54
  const [open, setOpen] = useState(true);
55
55
  const [selectLevels, setSelectLevels] = useState(levelsSelection);
56
56
  const [levels, setLevels] = useState(lineageLevels);
57
- const handleSubmit = (type) => () =>
57
+ const handleSubmit = (type) => () => {
58
58
  _.isNil(levels)
59
59
  ? createGraph({ ids: selectedResources, type })
60
60
  : createGraph({ ids: selectedResources, type, levels });
61
+ }
61
62
  const disabled = _.isEmpty(selectedResources);
62
63
  useEffect(() => {
63
64
  setOpen(!graphLoading && _.isEmpty(graph));
@@ -4,12 +4,19 @@ import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { Dimmer, Loader } from "semantic-ui-react";
6
6
  import svgPanZoom from "svg-pan-zoom";
7
- import { FormattedMessage } from "react-intl";
8
- import { clearNode, selectNode, hoverNode, initSvg } from "../routines";
7
+ import { FormattedMessage, useIntl } from "react-intl";
8
+ import {
9
+ clearNode,
10
+ selectNode,
11
+ hoverNode,
12
+ initSvg,
13
+ cancelPoll,
14
+ } from "../routines";
9
15
  import LineageLink from "./LineageLink";
10
16
  import LineageDefs from "./LineageDefs";
11
17
  import LineageGroup from "./LineageGroup";
12
18
  import LineageResource from "./LineageResource";
19
+ import "../styles/lineageGraph.less";
13
20
 
14
21
  const svgOptions = { minZoom: 0.2, maxZoom: 2.0, fit: false, center: false };
15
22
  const disablePanZoom = (svg) => {
@@ -50,11 +57,18 @@ export const LineageGraph = ({
50
57
  groups = [],
51
58
  resources = [],
52
59
  loading,
60
+ loadingInfo,
61
+ cancelPoll,
53
62
  }) => {
54
63
  const ref = useRef(null);
55
64
  const [state, dispatch] = useReducer(reducer, initialState);
56
65
  const { selectedNode, hover, svg } = state;
57
66
 
67
+ useEffect(() => {
68
+ // Anything in here is fired on component mount.
69
+ return () => cancelPoll();
70
+ }, []);
71
+
58
72
  useEffect(() => {
59
73
  if (!_.isEmpty(resources)) {
60
74
  const svg = svgPanZoom(ref.current, svgOptions);
@@ -67,6 +81,8 @@ export const LineageGraph = ({
67
81
  const onSelect = (g) => () => dispatch(selectNode.trigger(g));
68
82
  const onHover = (g) =>
69
83
  selectedNode ? undefined : () => dispatch(hoverNode.trigger(g));
84
+ const { formatMessage } = useIntl();
85
+
70
86
  return (
71
87
  <>
72
88
  <Dimmer.Dimmable dimmed={loading} className="dl">
@@ -74,8 +90,34 @@ export const LineageGraph = ({
74
90
  <Loader size="large">
75
91
  <FormattedMessage
76
92
  id="lineage.graph.loading"
77
- defaultMessage="Analyzing..."
93
+ defaultMessage={"Analyzing..."}
78
94
  />
95
+ {loadingInfo?.data?.graph_hash && loadingInfo?.data?.status && (
96
+ <p>
97
+ {formatMessage({
98
+ id:
99
+ loadingInfo?.data?.status === "already_started"
100
+ ? "lineage.graph.loading.background.existing"
101
+ : loadingInfo?.data?.status === "just_started" &&
102
+ "lineage.graph.loading.background.launched",
103
+ })}
104
+ </p>
105
+ )}
106
+ {loadingInfo?.data?.graph_hash && (
107
+ <p>
108
+ Graph hash:{" "}
109
+ <span className="graph-hash">
110
+ {loadingInfo?.data?.graph_hash}
111
+ </span>
112
+ </p>
113
+ )}
114
+ <p>
115
+ {loadingInfo?.data?.retry &&
116
+ formatMessage(
117
+ { id: "lineage.graph.loading.polling" },
118
+ { retry: loadingInfo?.data?.retry }
119
+ )}
120
+ </p>
79
121
  </Loader>
80
122
  </Dimmer>
81
123
  <svg
@@ -121,17 +163,23 @@ LineageGraph.propTypes = {
121
163
  paths: PropTypes.array,
122
164
  resources: PropTypes.array,
123
165
  height: PropTypes.string,
166
+ loading: PropTypes.bool,
167
+ loadingInfo: PropTypes.object,
168
+ cancelPoll: PropTypes.func,
124
169
  };
125
170
 
126
171
  const mapStateToProps = (
127
172
  { graph = {}, graphLoading, selectedNode },
128
173
  ownProps
129
- ) => ({
130
- paths: _.prop("paths")(_.propOr(graph, "graph")(ownProps)),
131
- groups: _.prop("groups")(_.propOr(graph, "graph")(ownProps)),
132
- resources: _.prop("resources")(_.propOr(graph, "graph")(ownProps)),
133
- loading: graphLoading,
134
- selectedNode,
135
- });
174
+ ) => {
175
+ return {
176
+ paths: _.prop("paths")(_.propOr(graph, "graph")(ownProps)),
177
+ groups: _.prop("groups")(_.propOr(graph, "graph")(ownProps)),
178
+ resources: _.prop("resources")(_.propOr(graph, "graph")(ownProps)),
179
+ loading: graphLoading !== false,
180
+ loadingInfo: graphLoading,
181
+ selectedNode,
182
+ };
183
+ };
136
184
 
137
- export default connect(mapStateToProps, { clearNode })(LineageGraph);
185
+ export default connect(mapStateToProps, { clearNode, cancelPoll })(LineageGraph);
@@ -2,7 +2,8 @@ import React from "react";
2
2
  import { Route } from "react-router-dom";
3
3
  import { Unauthorized } from "@truedat/core/components";
4
4
  import { useAuthorized } from "@truedat/core/hooks";
5
- import { GRAPH, GRAPHS, SAMPLE } from "@truedat/core/routes";
5
+ import { GRAPH, GRAPHS, LINEAGE_EVENTS, SAMPLE } from "@truedat/core/routes";
6
+ import LineageEvents from "./LineageEvents"
6
7
  import LineageGraph from "./LineageGraph";
7
8
  import LineageSample from "./LineageSample";
8
9
  import LineageForm from "./LineageForm";
@@ -13,16 +14,13 @@ const Graphs = () => (
13
14
  <>
14
15
  <LineageForm />
15
16
  <GraphRedirector />
16
- </>
17
- );
18
-
19
- const Graph = () => (
20
- <>
21
- <GraphLoader />
17
+ {/* Load LineageGraph here so that its <Loader> is already available when clicking the LinageForm */}
22
18
  <LineageGraph />
23
19
  </>
24
20
  );
25
21
 
22
+ const Graph = () => <GraphLoader />;
23
+
26
24
  export const LineageRoutes = () => {
27
25
  const authorized = useAuthorized("lineage");
28
26
  return (
@@ -35,6 +33,10 @@ export const LineageRoutes = () => {
35
33
  path={GRAPH}
36
34
  render={() => (authorized ? <Graph /> : <Unauthorized />)}
37
35
  />
36
+ <Route
37
+ path={LINEAGE_EVENTS}
38
+ render={() => (authorized ? <LineageEvents /> : <Unauthorized />)}
39
+ />
38
40
  <Route
39
41
  path={SAMPLE}
40
42
  render={() => (authorized ? <LineageSample /> : <Unauthorized />)}
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { LineageEventsTable } from "../LineageEventsTable";
4
+ import en from "../../messages/en";
5
+
6
+
7
+ describe("<LineageEventsTable />", () => {
8
+ const lineageEvents = [
9
+ {
10
+ graph_id: 1234,
11
+ graph_data: "graph_data",
12
+ status: "COMPLETED",
13
+ message: "message",
14
+ inserted_at: "2021-11-26T10:09:27.168999Z",
15
+ },
16
+ ];
17
+
18
+ const lineageEventsLoading = false;
19
+ const fetchLineageEvents = jest.fn();
20
+
21
+ it("matches the latest snapshot", () => {
22
+ const props = { fetchLineageEvents, lineageEvents, lineageEventsLoading };
23
+ const { container } = render(<LineageEventsTable {...props} />, {
24
+ messages: { en },
25
+ });
26
+ expect(container).toMatchSnapshot();
27
+ });
28
+
29
+ it("matches the latest snapshot (no jobs)", () => {
30
+ const props = {
31
+ fetchLineageEvents,
32
+ lineageEvents: [],
33
+ lineageEventsLoading,
34
+ };
35
+ const { container } = render(<LineageEventsTable {...props} />, {
36
+ messages: { en },
37
+ });
38
+ expect(container).toMatchSnapshot();
39
+ });
40
+ });
@@ -0,0 +1,88 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<LineageEventsTable /> matches the latest snapshot (no jobs) 1`] = `
4
+ <div>
5
+ <h4
6
+ class="ui header"
7
+ >
8
+ <i
9
+ aria-hidden="true"
10
+ class="search icon"
11
+ />
12
+ <div
13
+ class="content"
14
+ >
15
+ No graphs found
16
+ </div>
17
+ </h4>
18
+ </div>
19
+ `;
20
+
21
+ exports[`<LineageEventsTable /> matches the latest snapshot 1`] = `
22
+ <div>
23
+ <table
24
+ class="ui selectable sortable table"
25
+ >
26
+ <thead
27
+ class=""
28
+ >
29
+ <tr
30
+ class=""
31
+ >
32
+ <th
33
+ class="two wide"
34
+ >
35
+ Graph
36
+ </th>
37
+ <th
38
+ class="two wide"
39
+ >
40
+ Status
41
+ </th>
42
+ <th
43
+ class="two wide"
44
+ >
45
+ Message
46
+ </th>
47
+ <th
48
+ class="two wide"
49
+ >
50
+ Date
51
+ </th>
52
+ </tr>
53
+ </thead>
54
+ <tbody
55
+ class=""
56
+ >
57
+ <tr
58
+ class=""
59
+ >
60
+ <td
61
+ class=""
62
+ >
63
+ graph_data
64
+ </td>
65
+ <td
66
+ class=""
67
+ >
68
+ Completed
69
+ </td>
70
+ <td
71
+ class=""
72
+ >
73
+ message
74
+ </td>
75
+ <td
76
+ class="center aligned"
77
+ >
78
+ <time
79
+ datetime="1637921367168"
80
+ >
81
+ 2021-11-26 10:09
82
+ </time>
83
+ </td>
84
+ </tr>
85
+ </tbody>
86
+ </table>
87
+ </div>
88
+ `;
@@ -10,6 +10,10 @@ exports[`<LineageRoutes /> matches the latest snapshot 1`] = `
10
10
  path="/graphs/:id"
11
11
  render={[Function]}
12
12
  />
13
+ <Route
14
+ path="/lineage_events"
15
+ render={[Function]}
16
+ />
13
17
  <Route
14
18
  path="/sample"
15
19
  render={[Function]}
@@ -103,12 +103,27 @@ export default {
103
103
  "graph.header.target_class": "Target Type",
104
104
  "graph.header.target_external_id": "Target External Id",
105
105
  "graph.header.target_name": "Target Name",
106
+ "lineage.events.header": "My graphs",
107
+ "lineage.events.props.graph_data": "Graph",
108
+ "lineage.events.props.inserted_at": "Date",
109
+ "lineage.events.props.message": "Message",
110
+ "lineage.events.props.status": "Status",
111
+ "lineage.events.search.results.empty": "No graphs found",
112
+ "lineage.events.subheader": "Lineage graphs",
106
113
  "lineage.form.header": "Select Structures",
107
114
  "lineage.form.levels.all.label": "All",
108
115
  "lineage.form.levels.header": "Levels",
109
116
  "lineage.form.levels.select.input": "Select...",
110
117
  "lineage.form.levels.select.label": "Depth",
111
118
  "lineage.graph.loading": "Analyzing...",
119
+ "lineage.graph.loading.background.launched": "Background task launched",
120
+ "lineage.graph.loading.background.existing": "The task for this graph was already launched",
121
+ "lineage.graph.loading.polling": "Poll retry #{retry}",
122
+ "lineage.graph.status.already_calculated": "Already calculated",
123
+ "lineage.graph.status.already_started": "Already started",
124
+ "lineage.graph.status.just_started": "Just started",
125
+ "lineage.graph.status.completed": "Completed",
126
+ "lineage.graph.status.failed": "Failed",
112
127
  "lineage.group.placeholder": "Select Top Level Structure",
113
128
  "lineage.resources.placeholder": "Select Child Structures",
114
129
  "lineage.subgroup.placeholder": "Select Child Structure",
@@ -105,12 +105,27 @@ export default {
105
105
  "graph.header.target_class": "Tipo Destino",
106
106
  "graph.header.target_external_id": "Id Externo Destino",
107
107
  "graph.header.target_name": "Nombre Destino",
108
+ "lineage.events.header": "Mis grafos",
109
+ "lineage.events.props.graph_data": "Grafo",
110
+ "lineage.events.props.inserted_at": "Fecha",
111
+ "lineage.events.props.message": "Mensaje",
112
+ "lineage.events.props.status": "Estado",
113
+ "lineage.events.search.results.empty": "Ningún grafo encontrado",
114
+ "lineage.events.subheader": "Grafos de linaje",
108
115
  "lineage.form.header": "Seleccionar Estructuras",
109
116
  "lineage.form.levels.all.label": "Completo",
110
117
  "lineage.form.levels.header": "Niveles",
111
118
  "lineage.form.levels.select.input": "Seleccionar...",
112
119
  "lineage.form.levels.select.label": "Profundidad",
113
120
  "lineage.graph.loading": "Analizando...",
121
+ "lineage.graph.loading.background.launched": "Tarea en segundo plano lanzada para grafo",
122
+ "lineage.graph.loading.background.existing": "La tarea en segundo plano para este grafo ya fue lanzada",
123
+ "lineage.graph.loading.polling": "Sondeo #{retry}",
124
+ "lineage.graph.status.already_calculated": "Previamente calculado",
125
+ "lineage.graph.status.already_started": "Previamente iniciado",
126
+ "lineage.graph.status.just_started": "Iniciado",
127
+ "lineage.graph.status.completed": "Finalizado",
128
+ "lineage.graph.status.failed": "Fallido",
114
129
  "lineage.group.placeholder": "Seleccionar Estructura Padre",
115
130
  "lineage.resources.placeholder": "Seleccionar Estructuras Hijos",
116
131
  "lineage.subgroup.placeholder": "Seleccionar Estructura Hijo",
@@ -5,7 +5,7 @@ import {
5
5
  createStructureGraph,
6
6
  fetchGraph,
7
7
  fetchStructure,
8
- fetchStructureGraph
8
+ fetchStructureGraph,
9
9
  } from "../../routines";
10
10
  import { graphLoading } from "..";
11
11
 
@@ -17,7 +17,7 @@ describe("reducers: graphLoading", () => {
17
17
  });
18
18
 
19
19
  it("should be true after receiving the createGraph.REQUEST action", () => {
20
- expect(graphLoading(false, { type: createGraph.REQUEST })).toBe(true);
20
+ expect(graphLoading(false, { type: createGraph.REQUEST })).toBeTruthy();
21
21
  });
22
22
 
23
23
  it("should be true after receiving the createStructureGraph.REQUEST action", () => {
@@ -0,0 +1,24 @@
1
+ import { lineageEventsLoading } from "../lineageEventsLoading";
2
+ import { fetchLineageEvents } from "../../routines";
3
+
4
+ const fooState = { foo: "bar" };
5
+
6
+ describe("reducers: lineageEventsLoading", () => {
7
+ it("should provide the initial state", () => {
8
+ expect(lineageEventsLoading(undefined, {})).toBe(false);
9
+ });
10
+
11
+ it("returns to initial state on fetchLineageEvents.FULFILL action", () => {
12
+ expect(lineageEventsLoading(fooState, { type: fetchLineageEvents.FULFILL })).toBe(
13
+ false
14
+ );
15
+ });
16
+
17
+ it("returns true on fetchLineageEvents.REQUEST action", () => {
18
+ expect(lineageEventsLoading(fooState, { type: fetchLineageEvents.REQUEST })).toBe(true);
19
+ });
20
+
21
+ it("should ignore unhandled actions", () => {
22
+ expect(lineageEventsLoading(fooState, { type: "FOO" })).toBe(fooState);
23
+ });
24
+ });
@@ -3,7 +3,8 @@ import {
3
3
  clearGraph,
4
4
  clearStructure,
5
5
  fetchGraph,
6
- fetchStructure
6
+ fetchStructure,
7
+ createGraph
7
8
  } from "../routines";
8
9
 
9
10
  export const initialState = {};
@@ -18,6 +19,8 @@ export const graph = (state = initialState, { type, payload }) => {
18
19
  return initialState;
19
20
  case fetchGraph.SUCCESS:
20
21
  return _.prop("data")(payload);
22
+ case createGraph.SUCCESS:
23
+ return _.prop("data")(payload);
21
24
  default:
22
25
  return state;
23
26
  }
@@ -5,25 +5,29 @@ import {
5
5
  createStructureGraph,
6
6
  fetchGraph,
7
7
  fetchStructure,
8
- fetchStructureGraph
8
+ fetchStructureGraph,
9
9
  } from "../routines";
10
10
 
11
- export const graphLoading = (state = false, { type }) => {
11
+ export const graphLoading = (state = false, { type, payload }) => {
12
12
  switch (type) {
13
13
  case clearGraph.TRIGGER:
14
14
  return false;
15
15
  case clearStructure.TRIGGER:
16
16
  return false;
17
+ case createGraph.SUCCESS:
18
+ case createGraph.FAILURE:
17
19
  case createGraph.FULFILL:
18
20
  return false;
19
21
  case createGraph.REQUEST:
20
- return true;
22
+ return payload || true;
21
23
  case createStructureGraph.REQUEST:
22
24
  return true;
23
25
  case fetchGraph.FULFILL:
24
26
  return false;
25
27
  case fetchGraph.REQUEST:
26
- return true;
28
+ return payload || true;
29
+ case fetchGraph.FAILURE:
30
+ return payload;
27
31
  case fetchStructure.TRIGGER:
28
32
  return false;
29
33
  case fetchStructureGraph.FULFILL:
@@ -13,8 +13,7 @@ export const graphRedirect = (state = initialState, { type, payload }) => {
13
13
  case clearRedirect.TRIGGER:
14
14
  return initialState;
15
15
  case createGraph.SUCCESS:
16
- const { data } = payload;
17
- return linkTo.GRAPH(data);
16
+ return linkTo.GRAPH(payload?.data);
18
17
  default:
19
18
  return state;
20
19
  }
@@ -19,6 +19,8 @@ import { grantsPageSize } from "./grantsPageSize";
19
19
  import { graph } from "./graph";
20
20
  import { graphLoading } from "./graphLoading";
21
21
  import { graphRedirect } from "./graphRedirect";
22
+ import { lineageEvents } from "./lineageEvents";
23
+ import { lineageEventsLoading } from "./lineageEventsLoading";
22
24
  import { lineageQuery } from "./lineageQuery";
23
25
  import { linkedStructureTags } from "./linkedStructureTags";
24
26
  import { linkedStructureTagsLoading } from "./linkedStructureTagsLoading";
@@ -112,6 +114,8 @@ export {
112
114
  graph,
113
115
  graphLoading,
114
116
  graphRedirect,
117
+ lineageEvents,
118
+ lineageEventsLoading,
115
119
  lineageQuery,
116
120
  linkedStructureTags,
117
121
  linkedStructureTagsLoading,
@@ -0,0 +1,13 @@
1
+ import _ from "lodash/fp";
2
+ import { fetchLineageEvents } from "../routines";
3
+
4
+ export const initialState = [];
5
+
6
+ export const lineageEvents = (state = initialState, { type, payload }) => {
7
+ switch (type) {
8
+ case fetchLineageEvents.SUCCESS:
9
+ return _.prop("data")(payload);
10
+ default:
11
+ return state;
12
+ }
13
+ };
@@ -0,0 +1,14 @@
1
+ import { fetchLineageEvents } from "../routines";
2
+
3
+ const lineageEventsLoading = (state = false, { type }) => {
4
+ switch (type) {
5
+ case fetchLineageEvents.REQUEST:
6
+ return true;
7
+ case fetchLineageEvents.FULFILL:
8
+ return false;
9
+ default:
10
+ return state;
11
+ }
12
+ };
13
+
14
+ export { lineageEventsLoading };
package/src/routines.js CHANGED
@@ -13,6 +13,7 @@ export const applyStructureUserFilter = createRoutine(
13
13
  "APPLY_STRUCTURE_USER_FILTER"
14
14
  );
15
15
  export const bulkUpdateStructures = createRoutine("BULK_UPDATE_STRUCTURES");
16
+ export const cancelPoll = createRoutine("CANCEL_POLL");
16
17
  export const clearGrantFilters = createRoutine("CLEAR_GRANT_FILTERS");
17
18
  export const clearGrants = createRoutine("CLEAR_GRANTS");
18
19
  export const clearGrantsSort = createRoutine("CLEAR_GRANTS_SORT");
@@ -68,6 +69,7 @@ export const fetchGrant = createRoutine("FETCH_GRANT");
68
69
  export const fetchGrantFilters = createRoutine("FETCH_GRANT_FILTERS");
69
70
  export const fetchGrants = createRoutine("FETCH_GRANTS");
70
71
  export const fetchGraph = createRoutine("FETCH_GRAPH");
72
+ export const fetchLineageEvents = createRoutine("FETCH_LINEAGE_EVENTS");
71
73
  export const fetchLinksStructureTags = createRoutine(
72
74
  "FETCH_LINKS_STRUCTURE_TAGS"
73
75
  );
@@ -93,6 +95,7 @@ export const fetchUnitDomains = createRoutine("FETCH_UNIT_DOMAINS");
93
95
  export const filterStructures = createRoutine("FILTER_STRUCTURES");
94
96
  export const hoverNode = createRoutine("HOVER_NODE");
95
97
  export const initSvg = createRoutine("INIT_SVG");
98
+ export const loadLineageEvents = createRoutine("LOAD_LINEAGE_EVENTS");
96
99
  export const linkStructureTag = createRoutine("LINK_STRUCTURE_TAG");
97
100
  export const openGrantFilter = createRoutine("OPEN_GRANT_FILTER");
98
101
  export const openStructureFilter = createRoutine("OPEN_STRUCTURE_FILTER");
@@ -1,13 +1,48 @@
1
+ import { compile } from "path-to-regexp";
1
2
  import { takeLatest } from "redux-saga/effects";
2
3
  import { testSaga } from "redux-saga-test-plan";
3
- import { apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
4
+ import { apiJson, apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
4
5
  import {
5
6
  createGraphRequestSaga,
6
7
  createGraphSaga,
7
- selectLineageQuery
8
+ polling,
9
+ POLLING_CANCELLED,
10
+ selectLineageQuery,
8
11
  } from "../createGraph";
9
- import { createGraph, excludeNode } from "../../routines";
10
- import { API_GRAPHS } from "../../api";
12
+ import {
13
+ cancelPoll,
14
+ createGraph,
15
+ fetchGraph,
16
+ excludeNode,
17
+ } from "../../routines";
18
+ import { API_GRAPHS, API_GRAPH_HASH } from "../../api";
19
+
20
+ const graphAcceptedData = {
21
+ graph_hash: "CUYAfW8NAUhqDByfK1dSOBnKcv-rE2CjLtqoIKFMWoc=",
22
+ status: "just_started",
23
+ task_reference: "0.3951494048.1635778562.126161",
24
+ };
25
+
26
+ const graphPendingData = {
27
+ graph_data: '["BASILEAII_HIST_PROA_MOROSIDAD [ 2 ] [ 1 ].SOBREGIROCAPITAL"]',
28
+ graph_hash: "CUYAfW8NAUhqDByfK1dSOBnKcv-rE2CjLtqoIKFMWoc=",
29
+ inserted_at: "2021-11-25T16:03:29.145893",
30
+ status: "already_started",
31
+ task_reference: "0.3951494048.1635778562.126161",
32
+ };
33
+
34
+ const graphData = {
35
+ hash: "CUYAfW8NAUhqDByfK1dSOBnKcv-rE2CjLtqoIKFMWoc=",
36
+ id: 1692,
37
+ excludes: [],
38
+ groups: [],
39
+ ids: ["BASILEAII_HIST_PROA_MOROSIDAD [ 2 ] [ 1 ].SOBREGIROCAPITAL"],
40
+ opts: {
41
+ type: "lineage",
42
+ },
43
+ paths: [],
44
+ resources: [],
45
+ };
11
46
 
12
47
  describe("sagas: createGraphRequestSaga", () => {
13
48
  it("should invoke createGraphSaga on trigger", () => {
@@ -33,20 +68,58 @@ describe("sagas: createGraphRequestSaga", () => {
33
68
  });
34
69
 
35
70
  describe("sagas: createGraphSaga", () => {
36
- const query = { type: "lineage", ids: ["foo"], excludes: [] };
37
- const data = { foo: "bar" };
71
+ const query = {
72
+ ids: ["BASILEAII_HIST_PROA_MOROSIDAD [ 2 ] [ 1 ].SOBREGIROCAPITAL"],
73
+ type: "lineage",
74
+ exclude: [],
75
+ };
38
76
 
39
- it("should put a success action when uuids are requested and a response is returned", () => {
77
+ const graphAccepted = {
78
+ data: graphAcceptedData,
79
+ status: 202,
80
+ };
81
+
82
+ const graphCreated = {
83
+ data: graphData,
84
+ status: 201,
85
+ };
86
+
87
+ it("should put a success action when uuids are requested and a 201 (created) graph data response is returned", () => {
40
88
  expect(() => {
41
- testSaga(createGraphSaga, {})
89
+ testSaga(createGraphSaga)
42
90
  .next()
43
91
  .select(selectLineageQuery)
44
92
  .next(query)
45
93
  .put(createGraph.request(query))
46
94
  .next()
47
95
  .call(apiJsonPost, API_GRAPHS, query, JSON_OPTS)
48
- .next({ data })
49
- .put(createGraph.success(data))
96
+ .next(graphCreated)
97
+ .put(createGraph.success(graphCreated))
98
+ .next()
99
+ .put(createGraph.fulfill())
100
+ .next()
101
+ .isDone();
102
+ }).not.toThrow();
103
+ });
104
+
105
+ it("should fork a polling task when uuids are requested and a 202 (accepted) graph hash response is returned. Then, wait for task cancellation.", () => {
106
+
107
+ expect(() => {
108
+ testSaga(createGraphSaga)
109
+ .next()
110
+ .select(selectLineageQuery)
111
+ .next(query)
112
+ .put(createGraph.request(query))
113
+ .next()
114
+ .call(apiJsonPost, API_GRAPHS, query, JSON_OPTS)
115
+ .next(graphAccepted)
116
+ .put(fetchGraph.request(graphAccepted))
117
+ .next()
118
+ .fork(polling, graphAccepted)
119
+ .next()
120
+ .take(cancelPoll.TRIGGER)
121
+ .next()
122
+ .cancel()
50
123
  .next()
51
124
  .put(createGraph.fulfill())
52
125
  .next()
@@ -59,8 +132,8 @@ describe("sagas: createGraphSaga", () => {
59
132
  const error = { message };
60
133
 
61
134
  expect(() => {
62
- testSaga(createGraphSaga, {})
63
- .next(query)
135
+ testSaga(createGraphSaga)
136
+ .next()
64
137
  .select(selectLineageQuery)
65
138
  .next(query)
66
139
  .put(createGraph.request(query))
@@ -75,3 +148,82 @@ describe("sagas: createGraphSaga", () => {
75
148
  }).not.toThrow();
76
149
  });
77
150
  });
151
+
152
+ describe("generator: polling", () => {
153
+ const graphAccepted = {
154
+ data: graphAcceptedData,
155
+ status: 202,
156
+ };
157
+
158
+ const graphPending = {
159
+ data: graphPendingData,
160
+ status: 202,
161
+ };
162
+
163
+ const graphCompleted = {
164
+ data: graphData,
165
+ status: 200,
166
+ };
167
+
168
+ it("polling should keep requesting by graph hash until graph is received", () => {
169
+ expect(() => {
170
+ testSaga(polling, graphAccepted)
171
+ .next()
172
+ .delay(1000)
173
+ .next()
174
+ .call(
175
+ apiJson,
176
+ compile(API_GRAPH_HASH)({ hash: graphAccepted.data.graph_hash }),
177
+ JSON_OPTS
178
+ )
179
+ .next(graphPending)
180
+ .put(
181
+ fetchGraph.failure({
182
+ data: { graph_hash: graphAccepted.data.graph_hash, retry: 1 },
183
+ })
184
+ )
185
+ .next()
186
+ .cancelled()
187
+ .next(false)
188
+ .delay(1200)
189
+ .next()
190
+ .call(
191
+ apiJson,
192
+ compile(API_GRAPH_HASH)({ hash: graphAccepted.data.graph_hash }),
193
+ JSON_OPTS
194
+ )
195
+ .next(graphCompleted)
196
+ .cancelled()
197
+ .next(false)
198
+ .put(createGraph.success(graphCompleted))
199
+ .next()
200
+ .isDone();
201
+ }).not.toThrow();
202
+ });
203
+
204
+ it("cancel should stop polling and return createGraph failure", () => {
205
+ expect(() => {
206
+ testSaga(polling, graphAccepted)
207
+ .next()
208
+ .delay(1000)
209
+ .next()
210
+ .call(
211
+ apiJson,
212
+ compile(API_GRAPH_HASH)({ hash: graphAccepted.data.graph_hash }),
213
+ JSON_OPTS
214
+ )
215
+ .next(graphPending)
216
+ .put(
217
+ fetchGraph.failure({
218
+ data: { graph_hash: graphAccepted.data.graph_hash, retry: 1 },
219
+ })
220
+ )
221
+ .next()
222
+ .cancelled()
223
+ .next(true)
224
+ .put(createGraph.failure(POLLING_CANCELLED))
225
+ .next()
226
+ .isDone();
227
+ }).not.toThrow();
228
+ });
229
+ });
@@ -1,17 +1,63 @@
1
+ /* eslint-disable fp/no-mutation */
2
+ /* eslint-disable fp/no-let */
1
3
  import _ from "lodash/fp";
2
- import { all, call, put, select, takeLatest } from "redux-saga/effects";
3
- import { apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
4
- import { excludeNode, createGraph } from "../routines";
5
- import { API_GRAPHS } from "../api";
4
+ import {
5
+ all,
6
+ call,
7
+ cancel,
8
+ cancelled,
9
+ fork,
10
+ put,
11
+ select,
12
+ take,
13
+ takeLatest,
14
+ delay,
15
+ } from "redux-saga/effects";
16
+ import { apiJson, apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
17
+ import { compile } from "path-to-regexp";
18
+ import { excludeNode, cancelPoll, createGraph, fetchGraph } from "../routines";
19
+ import { API_GRAPHS, API_GRAPH_HASH } from "../api";
6
20
 
7
21
  export const selectLineageQuery = _.prop("lineageQuery");
8
22
 
23
+ export const POLLING_CANCELLED = "Polling cancelled!";
24
+
25
+ function* exponentialBackoff(base, power) {
26
+ while (true) {
27
+ yield Math.pow(base, power);
28
+ // eslint-disable-next-line fp/no-mutation, no-param-reassign
29
+ power++;
30
+ }
31
+ }
32
+
9
33
  export function* createGraphSaga() {
34
+ let graphAcceptedOrCreated;
10
35
  try {
11
36
  const body = yield select(selectLineageQuery);
12
37
  yield put(createGraph.request(body));
13
- const { data } = yield call(apiJsonPost, API_GRAPHS, body, JSON_OPTS);
14
- yield put(createGraph.success(data));
38
+
39
+ /* graph is either new (accepted) or "created" (actually previously stored
40
+ from an earlier accepted one) */
41
+ graphAcceptedOrCreated = yield call(
42
+ apiJsonPost,
43
+ API_GRAPHS,
44
+ body,
45
+ JSON_OPTS
46
+ );
47
+
48
+ if (graphAcceptedOrCreated.status == 202) {
49
+ yield put(fetchGraph.request(graphAcceptedOrCreated));
50
+ // starts the task in the background
51
+ const pollingTask = yield fork(polling, graphAcceptedOrCreated);
52
+
53
+ // wait for the user stop action
54
+ yield take(cancelPoll.TRIGGER);
55
+ // user clicked stop. cancel the background task
56
+ // this will cause the forked bgSync task to jump into its finally block
57
+ yield cancel(pollingTask);
58
+ } else if (graphAcceptedOrCreated.status == 201) {
59
+ yield put(createGraph.success(graphAcceptedOrCreated));
60
+ }
15
61
  } catch (error) {
16
62
  if (error.response) {
17
63
  const { status, data } = error.response;
@@ -24,6 +70,51 @@ export function* createGraphSaga() {
24
70
  }
25
71
  }
26
72
 
73
+ export function* polling(graphAccepted) {
74
+ const backoff = exponentialBackoff(1.2, 0);
75
+ let backoffTime,
76
+ retry = 0,
77
+ graphResp,
78
+ isCancelled = false;
79
+
80
+ do {
81
+ backoffTime = backoff.next().value * 1000;
82
+ yield delay(backoffTime);
83
+
84
+ try {
85
+ graphResp = yield call(
86
+ apiJson,
87
+ compile(API_GRAPH_HASH)({
88
+ hash: graphAccepted.data.graph_hash,
89
+ }),
90
+ JSON_OPTS
91
+ );
92
+ } catch (error) {
93
+ if (error.response) {
94
+ const { status, data } = error.response;
95
+ yield put(createGraph.failure({ status, data }));
96
+ } else {
97
+ yield put(createGraph.failure(error.message));
98
+ }
99
+ }
100
+
101
+ graphResp?.status == 202 &&
102
+ (yield put(
103
+ fetchGraph.failure({
104
+ data: {
105
+ graph_hash: graphAccepted.data.graph_hash,
106
+ retry: ++retry,
107
+ },
108
+ })
109
+ ));
110
+
111
+ isCancelled = yield cancelled();
112
+ isCancelled && (yield put(createGraph.failure(POLLING_CANCELLED)));
113
+ } while (!isCancelled && graphResp?.status == 202);
114
+
115
+ graphResp?.status == 200 && (yield put(createGraph.success(graphResp)));
116
+ }
117
+
27
118
  export function* createGraphRequestSaga() {
28
119
  yield all([
29
120
  takeLatest(createGraph.TRIGGER, createGraphSaga),
@@ -11,6 +11,7 @@ export function* fetchGraphSaga({ payload }) {
11
11
  const url = toApiPath(payload);
12
12
  yield put(fetchGraph.request(payload));
13
13
  const { data } = yield call(apiJson, url, JSON_OPTS);
14
+
14
15
  yield put(fetchGraph.success(data));
15
16
  } catch (error) {
16
17
  if (error.response) {
@@ -0,0 +1,26 @@
1
+ import { call, put, takeLatest } from "redux-saga/effects";
2
+ import { apiJson, JSON_OPTS } from "@truedat/core/services/api";
3
+ import { fetchLineageEvents } from "../routines";
4
+ import { API_LINEAGE_EVENTS } from "../api";
5
+
6
+ export function* fetchLineageEventsSaga() {
7
+ try {
8
+ const url = API_LINEAGE_EVENTS;
9
+ yield put(fetchLineageEvents.request());
10
+ const { data, headers } = yield call(apiJson, url, JSON_OPTS);
11
+ yield put(fetchLineageEvents.success({ data, headers }));
12
+ } catch (error) {
13
+ if (error.response) {
14
+ const { status, data } = error.response;
15
+ yield put(fetchLineageEvents.failure({ status, data }));
16
+ } else {
17
+ yield put(fetchLineageEvents.failure(error.message));
18
+ }
19
+ } finally {
20
+ yield put(fetchLineageEvents.fulfill());
21
+ }
22
+ }
23
+
24
+ export function* fetchLineageEventsRequestSaga() {
25
+ yield takeLatest(fetchLineageEvents.TRIGGER, fetchLineageEventsSaga);
26
+ }
@@ -1,4 +1,5 @@
1
1
  import { bulkUpdateStructuresRequestsSaga } from "./bulkUpdateStructures";
2
+ //import { cancelPollRequestSaga } from "./cancelPoll";
2
3
  import { createGrantRequestApprovalRequestSaga } from "./createGrantRequestApproval";
3
4
  import { createGraphRequestSaga } from "./createGraph";
4
5
  import { createProfileGroupRequestSaga } from "./createProfileGroup";
@@ -19,6 +20,7 @@ import { fetchGrantsRequestSaga } from "./fetchGrants";
19
20
  import { fetchGrantRequestRequestSaga } from "./fetchGrantRequest";
20
21
  import { fetchGrantRequestsRequestSaga } from "./fetchGrantRequests";
21
22
  import { fetchGraphRequestSaga } from "./fetchGraph";
23
+ import { fetchLineageEventsRequestSaga } from "./fetchLineageEvents";
22
24
  import { fetchLinksStructureTagsRequestSaga } from "./fetchLinksStructureTags";
23
25
  import { fetchNodesRequestSaga } from "./fetchNodes";
24
26
  import { fetchProfileExecutionRequestSaga } from "./fetchProfileExecution";
@@ -48,6 +50,7 @@ import { operateGrantRequestToCartRequestSaga } from "./operateGrantRequestCart"
48
50
 
49
51
  export {
50
52
  bulkUpdateStructuresRequestsSaga,
53
+ //cancelPollRequestSaga,
51
54
  createGrantRequestApprovalRequestSaga,
52
55
  createGraphRequestSaga,
53
56
  createProfileGroupRequestSaga,
@@ -68,6 +71,7 @@ export {
68
71
  fetchGrantRequestRequestSaga,
69
72
  fetchGrantRequestsRequestSaga,
70
73
  fetchGraphRequestSaga,
74
+ fetchLineageEventsRequestSaga,
71
75
  fetchLinksStructureTagsRequestSaga,
72
76
  fetchNodesRequestSaga,
73
77
  fetchProfileExecutionRequestSaga,
@@ -98,6 +102,7 @@ export {
98
102
 
99
103
  export default [
100
104
  bulkUpdateStructuresRequestsSaga(),
105
+ //cancelPollRequestSaga(),
101
106
  createGrantRequestApprovalRequestSaga(),
102
107
  createGraphRequestSaga(),
103
108
  createProfileGroupRequestSaga(),
@@ -118,6 +123,7 @@ export default [
118
123
  fetchGrantRequestRequestSaga(),
119
124
  fetchGrantRequestsRequestSaga(),
120
125
  fetchGraphRequestSaga(),
126
+ fetchLineageEventsRequestSaga(),
121
127
  fetchLinksStructureTagsRequestSaga(),
122
128
  fetchNodesRequestSaga(),
123
129
  fetchProfileExecutionRequestSaga(),
@@ -0,0 +1,52 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { FormattedMessage, useIntl } from "react-intl";
4
+ import Moment from "react-moment";
5
+
6
+ const dateDecorator = (date) =>
7
+ date && <Moment locale="es" date={date} format="YYYY-MM-DD HH:mm" />;
8
+
9
+ const statusDecorator = (status) =>
10
+ <FormattedMessage id={`lineage.graph.status.${_.lowerCase(status)}`} />
11
+
12
+ const customLineageEventColumns = null;
13
+ const defaultLineageEventColumns = [
14
+ {
15
+ name: "graph_data",
16
+ sort: { name: "graph_data.sort" },
17
+ width: 2
18
+ },
19
+ // {
20
+ // name: "graph_hash",
21
+ // sort: { name: "graph_hash.sort" },
22
+
23
+ // width: 2
24
+ // },
25
+ {
26
+ name: "status",
27
+ sort: { name: "status.sort" },
28
+ fieldDecorator: statusDecorator,
29
+ width: 2
30
+ },
31
+ // {
32
+ // name: "task_reference",
33
+ // sort: { name: "task_reference.sort" },
34
+ // fieldSelector: _.path("type"),
35
+ // width: 2
36
+ // },
37
+ {
38
+ name: "message",
39
+ width: 2
40
+ },
41
+ {
42
+ name: "inserted_at",
43
+ sort: { name: "inserted_at" },
44
+ fieldDecorator: dateDecorator,
45
+ textAlign: "center",
46
+ width: 2
47
+ }
48
+ ];
49
+
50
+
51
+ export const lineageEventColumns =
52
+ customLineageEventColumns || defaultLineageEventColumns;
@@ -0,0 +1,3 @@
1
+ .graph-hash {
2
+ font-family: monospace;
3
+ }