@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.
Files changed (34) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +6 -6
  3. package/src/api.js +4 -0
  4. package/src/components/GraphLoader.js +21 -6
  5. package/src/components/LineageEvents.js +23 -0
  6. package/src/components/LineageEventsTable.js +94 -0
  7. package/src/components/LineageForm.js +2 -1
  8. package/src/components/LineageGraph.js +62 -11
  9. package/src/components/LineageRoutes.js +9 -7
  10. package/src/components/StructuresBulkUpdate.js +23 -9
  11. package/src/components/StructuresRoutes.js +7 -0
  12. package/src/components/__tests__/LineageEventsTable.spec.js +39 -0
  13. package/src/components/__tests__/StructuresBulkUpdate.spec.js +2 -0
  14. package/src/components/__tests__/__snapshots__/LineageEventsTable.spec.js.snap +90 -0
  15. package/src/components/__tests__/__snapshots__/LineageRoutes.spec.js.snap +4 -0
  16. package/src/messages/en.js +18 -0
  17. package/src/messages/es.js +19 -0
  18. package/src/reducers/__tests__/graphLoading.spec.js +2 -2
  19. package/src/reducers/__tests__/lineageEventsLoading.spec.js +26 -0
  20. package/src/reducers/graph.js +4 -1
  21. package/src/reducers/graphLoading.js +8 -4
  22. package/src/reducers/graphRedirect.js +1 -2
  23. package/src/reducers/index.js +4 -0
  24. package/src/reducers/lineageEvents.js +13 -0
  25. package/src/reducers/lineageEventsLoading.js +14 -0
  26. package/src/routines.js +3 -0
  27. package/src/sagas/__tests__/createGraph.spec.js +162 -13
  28. package/src/sagas/createGraph.js +100 -7
  29. package/src/sagas/fetchGraph.js +1 -0
  30. package/src/sagas/fetchLineageEvents.js +26 -0
  31. package/src/sagas/index.js +6 -0
  32. package/src/selectors/getLineageEventColumns.js +48 -0
  33. package/src/styles/lineageGraph.less +3 -0
  34. package/src/components/StructuresUpdateButton.js +0 -45
@@ -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,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",
@@ -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 })).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,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
+ });
@@ -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", () => {
@@ -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 = { 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
+ };
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({ 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
+ 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(query)
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
+ });
@@ -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,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
  }
@@ -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
+ }