@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 +6 -0
- package/package.json +6 -6
- package/src/api.js +4 -0
- package/src/components/GraphLoader.js +22 -6
- package/src/components/LineageEvents.js +23 -0
- package/src/components/LineageEventsTable.js +91 -0
- package/src/components/LineageForm.js +2 -1
- package/src/components/LineageGraph.js +59 -11
- package/src/components/LineageRoutes.js +9 -7
- package/src/components/__tests__/LineageEventsTable.spec.js +40 -0
- package/src/components/__tests__/__snapshots__/LineageEventsTable.spec.js.snap +88 -0
- package/src/components/__tests__/__snapshots__/LineageRoutes.spec.js.snap +4 -0
- package/src/messages/en.js +15 -0
- package/src/messages/es.js +15 -0
- package/src/reducers/__tests__/graphLoading.spec.js +2 -2
- package/src/reducers/__tests__/lineageEventsLoading.spec.js +24 -0
- package/src/reducers/graph.js +4 -1
- package/src/reducers/graphLoading.js +8 -4
- package/src/reducers/graphRedirect.js +1 -2
- package/src/reducers/index.js +4 -0
- package/src/reducers/lineageEvents.js +13 -0
- package/src/reducers/lineageEventsLoading.js +14 -0
- package/src/routines.js +3 -0
- package/src/sagas/__tests__/createGraph.spec.js +164 -12
- package/src/sagas/createGraph.js +97 -6
- package/src/sagas/fetchGraph.js +1 -0
- package/src/sagas/fetchLineageEvents.js +26 -0
- package/src/sagas/index.js +6 -0
- package/src/selectors/getLineageEventColumns.js +52 -0
- package/src/styles/lineageGraph.less +3 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dd",
|
|
3
|
-
"version": "4.33.
|
|
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.
|
|
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.
|
|
88
|
-
"@truedat/core": "4.33.
|
|
89
|
-
"@truedat/df": "4.33.
|
|
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": "
|
|
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 (
|
|
25
|
+
} else if (id != graph.id) {
|
|
22
26
|
fetchGraph({ id });
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
|
-
}, [graph, fetchGraph
|
|
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 {
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
`;
|
package/src/messages/en.js
CHANGED
|
@@ -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",
|
package/src/messages/es.js
CHANGED
|
@@ -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 })).
|
|
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
|
+
});
|
package/src/reducers/graph.js
CHANGED
|
@@ -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
|
-
|
|
17
|
-
return linkTo.GRAPH(data);
|
|
16
|
+
return linkTo.GRAPH(payload?.data);
|
|
18
17
|
default:
|
|
19
18
|
return state;
|
|
20
19
|
}
|
package/src/reducers/index.js
CHANGED
|
@@ -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
|
-
|
|
8
|
+
polling,
|
|
9
|
+
POLLING_CANCELLED,
|
|
10
|
+
selectLineageQuery,
|
|
8
11
|
} from "../createGraph";
|
|
9
|
-
import {
|
|
10
|
-
|
|
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 = {
|
|
37
|
-
|
|
71
|
+
const query = {
|
|
72
|
+
ids: ["BASILEAII_HIST_PROA_MOROSIDAD [ 2 ] [ 1 ].SOBREGIROCAPITAL"],
|
|
73
|
+
type: "lineage",
|
|
74
|
+
exclude: [],
|
|
75
|
+
};
|
|
38
76
|
|
|
39
|
-
|
|
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(
|
|
49
|
-
.put(createGraph.success(
|
|
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(
|
|
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
|
+
});
|
package/src/sagas/createGraph.js
CHANGED
|
@@ -1,17 +1,63 @@
|
|
|
1
|
+
/* eslint-disable fp/no-mutation */
|
|
2
|
+
/* eslint-disable fp/no-let */
|
|
1
3
|
import _ from "lodash/fp";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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),
|
package/src/sagas/fetchGraph.js
CHANGED
|
@@ -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
|
+
}
|
package/src/sagas/index.js
CHANGED
|
@@ -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;
|