@truedat/dd 4.33.3 → 4.33.8
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 +18 -0
- package/package.json +6 -6
- package/src/api.js +4 -0
- package/src/components/GraphLoader.js +21 -6
- package/src/components/LineageEvents.js +23 -0
- package/src/components/LineageEventsTable.js +94 -0
- package/src/components/LineageForm.js +2 -1
- package/src/components/LineageGraph.js +62 -11
- package/src/components/LineageRoutes.js +9 -7
- package/src/components/StructuresBulkUpdate.js +23 -9
- package/src/components/StructuresRoutes.js +7 -0
- package/src/components/__tests__/LineageEventsTable.spec.js +39 -0
- package/src/components/__tests__/StructuresBulkUpdate.spec.js +2 -0
- package/src/components/__tests__/__snapshots__/LineageEventsTable.spec.js.snap +90 -0
- package/src/components/__tests__/__snapshots__/LineageRoutes.spec.js.snap +4 -0
- package/src/messages/en.js +18 -0
- package/src/messages/es.js +19 -0
- package/src/reducers/__tests__/graphLoading.spec.js +2 -2
- package/src/reducers/__tests__/lineageEventsLoading.spec.js +26 -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 +162 -13
- package/src/sagas/createGraph.js +100 -7
- 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 +48 -0
- package/src/styles/lineageGraph.less +3 -0
- package/src/components/StructuresUpdateButton.js +0 -45
package/src/messages/en.js
CHANGED
|
@@ -103,12 +103,30 @@ 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":
|
|
121
|
+
"The task for this graph was already launched",
|
|
122
|
+
"lineage.graph.loading.polling": "Poll retry #{retry}",
|
|
123
|
+
"lineage.graph.status.already_calculated": "Already calculated",
|
|
124
|
+
"lineage.graph.status.already_started": "Already started",
|
|
125
|
+
"lineage.graph.status.completed": "Completed",
|
|
126
|
+
"lineage.graph.status.failed": "Failed",
|
|
127
|
+
"lineage.graph.status.just_started": "Just started",
|
|
128
|
+
"lineage.graph.status.started": "Started",
|
|
129
|
+
"lineage.graph.status.timed_out": "Timed out",
|
|
112
130
|
"lineage.group.placeholder": "Select Top Level Structure",
|
|
113
131
|
"lineage.resources.placeholder": "Select Child Structures",
|
|
114
132
|
"lineage.subgroup.placeholder": "Select Child Structure",
|
package/src/messages/es.js
CHANGED
|
@@ -105,12 +105,31 @@ 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":
|
|
122
|
+
"Tarea en segundo plano lanzada para grafo",
|
|
123
|
+
"lineage.graph.loading.background.existing":
|
|
124
|
+
"La tarea en segundo plano para este grafo ya fue lanzada",
|
|
125
|
+
"lineage.graph.loading.polling": "Sondeo #{retry}",
|
|
126
|
+
"lineage.graph.status.already_calculated": "Previamente calculado",
|
|
127
|
+
"lineage.graph.status.already_started": "Previamente iniciado",
|
|
128
|
+
"lineage.graph.status.completed": "Finalizado",
|
|
129
|
+
"lineage.graph.status.failed": "Fallido",
|
|
130
|
+
"lineage.graph.status.just_started": "Acaba de iniciarse",
|
|
131
|
+
"lineage.graph.status.started": "Iniciado",
|
|
132
|
+
"lineage.graph.status.timed_out": "Tiempo de espera máximo sobrepasado",
|
|
114
133
|
"lineage.group.placeholder": "Seleccionar Estructura Padre",
|
|
115
134
|
"lineage.resources.placeholder": "Seleccionar Estructuras Hijos",
|
|
116
135
|
"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,26 @@
|
|
|
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(
|
|
13
|
+
lineageEventsLoading(fooState, { type: fetchLineageEvents.FULFILL })
|
|
14
|
+
).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns true on fetchLineageEvents.REQUEST action", () => {
|
|
18
|
+
expect(
|
|
19
|
+
lineageEventsLoading(fooState, { type: fetchLineageEvents.REQUEST })
|
|
20
|
+
).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should ignore unhandled actions", () => {
|
|
24
|
+
expect(lineageEventsLoading(fooState, { type: "FOO" })).toBe(fooState);
|
|
25
|
+
});
|
|
26
|
+
});
|
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", () => {
|
|
@@ -16,7 +51,7 @@ describe("sagas: createGraphRequestSaga", () => {
|
|
|
16
51
|
.next()
|
|
17
52
|
.all([
|
|
18
53
|
takeLatest(createGraph.TRIGGER, createGraphSaga),
|
|
19
|
-
takeLatest(excludeNode.TRIGGER, createGraphSaga)
|
|
54
|
+
takeLatest(excludeNode.TRIGGER, createGraphSaga),
|
|
20
55
|
])
|
|
21
56
|
.finish()
|
|
22
57
|
.isDone();
|
|
@@ -33,20 +68,57 @@ 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
|
+
};
|
|
76
|
+
|
|
77
|
+
const graphAccepted = {
|
|
78
|
+
data: graphAcceptedData,
|
|
79
|
+
status: 202,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const graphCreated = {
|
|
83
|
+
data: graphData,
|
|
84
|
+
status: 201,
|
|
85
|
+
};
|
|
38
86
|
|
|
39
|
-
it("should put a success action when uuids are requested and a response is returned", () => {
|
|
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
|
+
expect(() => {
|
|
107
|
+
testSaga(createGraphSaga)
|
|
108
|
+
.next()
|
|
109
|
+
.select(selectLineageQuery)
|
|
110
|
+
.next(query)
|
|
111
|
+
.put(createGraph.request(query))
|
|
112
|
+
.next()
|
|
113
|
+
.call(apiJsonPost, API_GRAPHS, query, JSON_OPTS)
|
|
114
|
+
.next(graphAccepted)
|
|
115
|
+
.put(fetchGraph.request(graphAccepted))
|
|
116
|
+
.next()
|
|
117
|
+
.fork(polling, graphAccepted)
|
|
118
|
+
.next()
|
|
119
|
+
.take(cancelPoll.TRIGGER)
|
|
120
|
+
.next()
|
|
121
|
+
.cancel()
|
|
50
122
|
.next()
|
|
51
123
|
.put(createGraph.fulfill())
|
|
52
124
|
.next()
|
|
@@ -59,8 +131,8 @@ describe("sagas: createGraphSaga", () => {
|
|
|
59
131
|
const error = { message };
|
|
60
132
|
|
|
61
133
|
expect(() => {
|
|
62
|
-
testSaga(createGraphSaga
|
|
63
|
-
.next(
|
|
134
|
+
testSaga(createGraphSaga)
|
|
135
|
+
.next()
|
|
64
136
|
.select(selectLineageQuery)
|
|
65
137
|
.next(query)
|
|
66
138
|
.put(createGraph.request(query))
|
|
@@ -75,3 +147,80 @@ describe("sagas: createGraphSaga", () => {
|
|
|
75
147
|
}).not.toThrow();
|
|
76
148
|
});
|
|
77
149
|
});
|
|
150
|
+
|
|
151
|
+
describe("generator: polling", () => {
|
|
152
|
+
const graphAccepted = {
|
|
153
|
+
data: graphAcceptedData,
|
|
154
|
+
status: 202,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const graphPending = {
|
|
158
|
+
data: graphPendingData,
|
|
159
|
+
status: 202,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const graphCompleted = {
|
|
163
|
+
data: graphData,
|
|
164
|
+
status: 200,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
it("polling should keep requesting by graph hash until graph is received", () => {
|
|
168
|
+
expect(() => {
|
|
169
|
+
testSaga(polling, graphAccepted)
|
|
170
|
+
.next()
|
|
171
|
+
.delay(1000)
|
|
172
|
+
.next()
|
|
173
|
+
.call(
|
|
174
|
+
apiJson,
|
|
175
|
+
compile(API_GRAPH_HASH)({ hash: graphAccepted.data.graph_hash }),
|
|
176
|
+
JSON_OPTS
|
|
177
|
+
)
|
|
178
|
+
.next(graphPending)
|
|
179
|
+
.put(
|
|
180
|
+
fetchGraph.failure({
|
|
181
|
+
data: { graph_hash: graphAccepted.data.graph_hash, retry: 1 },
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
.next()
|
|
185
|
+
.cancelled()
|
|
186
|
+
.next(false)
|
|
187
|
+
.delay(1200)
|
|
188
|
+
.next()
|
|
189
|
+
.call(
|
|
190
|
+
apiJson,
|
|
191
|
+
compile(API_GRAPH_HASH)({ hash: graphAccepted.data.graph_hash }),
|
|
192
|
+
JSON_OPTS
|
|
193
|
+
)
|
|
194
|
+
.next(graphCompleted)
|
|
195
|
+
.cancelled()
|
|
196
|
+
.next(false)
|
|
197
|
+
.put(createGraph.success(graphCompleted))
|
|
198
|
+
.next()
|
|
199
|
+
.isDone();
|
|
200
|
+
}).not.toThrow();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("cancel should stop polling and return createGraph failure", () => {
|
|
204
|
+
expect(() => {
|
|
205
|
+
testSaga(polling, graphAccepted)
|
|
206
|
+
.next()
|
|
207
|
+
.delay(1000)
|
|
208
|
+
.next()
|
|
209
|
+
.call(
|
|
210
|
+
apiJson,
|
|
211
|
+
compile(API_GRAPH_HASH)({ hash: graphAccepted.data.graph_hash }),
|
|
212
|
+
JSON_OPTS
|
|
213
|
+
)
|
|
214
|
+
.next(graphPending)
|
|
215
|
+
.put(
|
|
216
|
+
fetchGraph.failure({
|
|
217
|
+
data: { graph_hash: graphAccepted.data.graph_hash, retry: 1 },
|
|
218
|
+
})
|
|
219
|
+
)
|
|
220
|
+
.next()
|
|
221
|
+
.cancelled()
|
|
222
|
+
.next(true)
|
|
223
|
+
.put(createGraph.failure(POLLING_CANCELLED));
|
|
224
|
+
}).not.toThrow();
|
|
225
|
+
});
|
|
226
|
+
});
|
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,9 +70,56 @@ 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 && !isCancelled
|
|
116
|
+
? yield put(createGraph.success(graphResp))
|
|
117
|
+
: yield put(createGraph.failure({}));
|
|
118
|
+
}
|
|
119
|
+
|
|
27
120
|
export function* createGraphRequestSaga() {
|
|
28
121
|
yield all([
|
|
29
122
|
takeLatest(createGraph.TRIGGER, createGraphSaga),
|
|
30
|
-
takeLatest(excludeNode.TRIGGER, createGraphSaga)
|
|
123
|
+
takeLatest(excludeNode.TRIGGER, createGraphSaga),
|
|
31
124
|
]);
|
|
32
125
|
}
|
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
|
+
}
|