@truedat/qx 6.12.3 → 6.12.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.
Files changed (23) hide show
  1. package/package.json +3 -3
  2. package/src/api.js +2 -0
  3. package/src/components/common/ResourceSelector.js +12 -2
  4. package/src/components/common/__tests__/ResourceSelector.spec.js +3 -2
  5. package/src/components/common/__tests__/__snapshots__/ResourceSelector.spec.js.snap +0 -78
  6. package/src/components/common/resourceSelectors/DataStructureSelector.js +2 -2
  7. package/src/components/common/resourceSelectors/DataViewSelector.js +7 -3
  8. package/src/components/common/resourceSelectors/__tests__/DataViewSelector.spec.js +12 -6
  9. package/src/components/dataViews/DataViewEditor.js +28 -4
  10. package/src/components/dataViews/__tests__/DataViewEditor.spec.js +18 -8
  11. package/src/components/dataViews/__tests__/__snapshots__/DataViewEditor.spec.js.snap +18 -39
  12. package/src/components/dataViews/__tests__/__snapshots__/Queryable.spec.js.snap +3 -107
  13. package/src/components/dataViews/__tests__/__snapshots__/Queryables.spec.js.snap +0 -78
  14. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/From.spec.js.snap +0 -26
  15. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Join.spec.js.snap +0 -26
  16. package/src/components/qualityControls/QualityControl.js +1 -1
  17. package/src/components/qualityControls/QualityControlActions.js +6 -0
  18. package/src/components/qualityControls/QualityControlEditor.js +45 -9
  19. package/src/components/qualityControls/QualityControlHeader.js +2 -2
  20. package/src/components/qualityControls/QualityControlQueryModal.js +83 -0
  21. package/src/components/qualityControls/QualityControlRoutes.js +0 -1
  22. package/src/hooks/useQualityControls.js +6 -0
  23. package/src/styles/Expression.less +12 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/qx",
3
- "version": "6.12.3",
3
+ "version": "6.12.4",
4
4
  "description": "Truedat Web Quality Experience package",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -84,7 +84,7 @@
84
84
  ]
85
85
  },
86
86
  "dependencies": {
87
- "@truedat/core": "6.12.3",
87
+ "@truedat/core": "6.12.4",
88
88
  "prop-types": "^15.8.1",
89
89
  "react-hook-form": "^7.45.4",
90
90
  "react-intl": "^5.20.10",
@@ -97,5 +97,5 @@
97
97
  "react-dom": ">= 16.8.6 < 17",
98
98
  "semantic-ui-react": ">= 2.0.3 < 2.2"
99
99
  },
100
- "gitHead": "2a55116634e90fc77f5317f980b7117e7dbdea31"
100
+ "gitHead": "0d67a012c6b0337f9c82db464137cf76062cbd81"
101
101
  }
package/src/api.js CHANGED
@@ -9,6 +9,7 @@ const API_QUALITY_CONTROL_PUBLISHED = "/api/quality_controls/:id/published";
9
9
  const API_QUALITY_CONTROL_DRAFT = "/api/quality_controls/:id/draft";
10
10
  const API_QUALITY_CONTROL_STATUS = "/api/quality_controls/:id/status";
11
11
  const API_QUALITY_CONTROL_DOMAINS = "/api/quality_controls/:id/domains";
12
+ const API_QUALITY_CONTROL_QUERIES = "/api/quality_controls/:id/queries";
12
13
  const API_QUALITY_CONTROL_SEARCH = "/api/quality_controls/search";
13
14
  const API_QUALITY_CONTROL_FILTERS = "/api/quality_controls/filters";
14
15
  const API_QUALITY_CONTROL_EXECUTION_GROUPS =
@@ -28,6 +29,7 @@ export {
28
29
  API_QUALITY_CONTROL_DRAFT,
29
30
  API_QUALITY_CONTROL_STATUS,
30
31
  API_QUALITY_CONTROL_DOMAINS,
32
+ API_QUALITY_CONTROL_QUERIES,
31
33
  API_QUALITY_CONTROL_SEARCH,
32
34
  API_QUALITY_CONTROL_FILTERS,
33
35
  API_QUALITY_CONTROL_EXECUTION_GROUPS,
@@ -13,15 +13,25 @@ import {
13
13
 
14
14
  export default function ResourceSelector({ required, labelId }) {
15
15
  const { formatMessage } = useIntl();
16
- const { field } = useContext(QxContext);
16
+ const { field, referenceDatasets, dataViews, sourceId } =
17
+ useContext(QxContext);
17
18
  const { control, setValue, watch } = useFormContext();
18
19
 
20
+ const sourceDataViews = _.filter({ source_id: parseInt(sourceId) })(
21
+ dataViews
22
+ );
23
+ const resourceTypes = [
24
+ "data_structure",
25
+ ...(!_.isEmpty(referenceDatasets) ? ["reference_dataset"] : []),
26
+ ...(!_.isEmpty(sourceDataViews) ? ["data_view"] : []),
27
+ ];
28
+
19
29
  const type = watch(`${field}.type`);
20
30
  const resourceTypeOptions = _.map((v) => ({
21
31
  key: v,
22
32
  value: v,
23
33
  text: formatMessage({ id: `queryables.resource.type.${v}` }),
24
- }))(["data_structure", "reference_dataset", "data_view"]);
34
+ }))(resourceTypes);
25
35
 
26
36
  const resourceSelectorForType = {
27
37
  data_view: DataViewSelector,
@@ -53,7 +53,6 @@ const renderOpts = {
53
53
  };
54
54
 
55
55
  const searchProps = {
56
- userFiltersType: "foo",
57
56
  useFilters: jest.fn(),
58
57
  useSearch: jest.fn(),
59
58
  loadingFilters: false,
@@ -80,7 +79,8 @@ describe("<ResourceSelector />", () => {
80
79
  const { container, getByRole } = render(
81
80
  <TestFormWrapper
82
81
  context={{
83
- dataViews: [{ name: "UserDataView", id: 1 }],
82
+ dataViews: [{ name: "UserDataView", id: 1, source_id: 10 }],
83
+ sourceId: 10,
84
84
  }}
85
85
  watcher={watcher}
86
86
  >
@@ -105,6 +105,7 @@ describe("<ResourceSelector />", () => {
105
105
  fields: [],
106
106
  id: 1,
107
107
  name: "UserDataView",
108
+ source_id: 10,
108
109
  },
109
110
  id: 1,
110
111
  type: "data_view",
@@ -42,32 +42,6 @@ exports[`<ResourceSelector /> matches the latest snapshot 1`] = `
42
42
  data_structure
43
43
  </span>
44
44
  </div>
45
- <div
46
- aria-checked="false"
47
- aria-selected="false"
48
- class="item"
49
- role="option"
50
- style="pointer-events: all;"
51
- >
52
- <span
53
- class="text"
54
- >
55
- reference_dataset
56
- </span>
57
- </div>
58
- <div
59
- aria-checked="false"
60
- aria-selected="false"
61
- class="item"
62
- role="option"
63
- style="pointer-events: all;"
64
- >
65
- <span
66
- class="text"
67
- >
68
- data_view
69
- </span>
70
- </div>
71
45
  </div>
72
46
  </div>
73
47
  </div>
@@ -116,32 +90,6 @@ exports[`<ResourceSelector /> select data_structure 1`] = `
116
90
  data_structure
117
91
  </span>
118
92
  </div>
119
- <div
120
- aria-checked="false"
121
- aria-selected="false"
122
- class="item"
123
- role="option"
124
- style="pointer-events: all;"
125
- >
126
- <span
127
- class="text"
128
- >
129
- reference_dataset
130
- </span>
131
- </div>
132
- <div
133
- aria-checked="false"
134
- aria-selected="false"
135
- class="item"
136
- role="option"
137
- style="pointer-events: all;"
138
- >
139
- <span
140
- class="text"
141
- >
142
- data_view
143
- </span>
144
- </div>
145
93
  </div>
146
94
  </div>
147
95
  </div>
@@ -292,19 +240,6 @@ exports[`<ResourceSelector /> select data_view 1`] = `
292
240
  data_structure
293
241
  </span>
294
242
  </div>
295
- <div
296
- aria-checked="false"
297
- aria-selected="false"
298
- class="item"
299
- role="option"
300
- style="pointer-events: all;"
301
- >
302
- <span
303
- class="text"
304
- >
305
- reference_dataset
306
- </span>
307
- </div>
308
243
  <div
309
244
  aria-checked="true"
310
245
  aria-selected="true"
@@ -423,19 +358,6 @@ exports[`<ResourceSelector /> select reference_dataset 1`] = `
423
358
  reference_dataset
424
359
  </span>
425
360
  </div>
426
- <div
427
- aria-checked="false"
428
- aria-selected="false"
429
- class="item"
430
- role="option"
431
- style="pointer-events: all;"
432
- >
433
- <span
434
- class="text"
435
- >
436
- data_view
437
- </span>
438
- </div>
439
361
  </div>
440
362
  </div>
441
363
  </div>
@@ -8,7 +8,7 @@ import StructureSelector from "@truedat/dd/components/StructureSelector";
8
8
 
9
9
  export default function DataStructureSelector({ onChange }) {
10
10
  const { formatMessage } = useIntl();
11
- const { field } = useContext(QxContext);
11
+ const { field, sourceId } = useContext(QxContext);
12
12
  const { watch } = useFormContext();
13
13
  const selectedStructure = watch(`${field}.embedded`);
14
14
 
@@ -54,7 +54,7 @@ export default function DataStructureSelector({ onChange }) {
54
54
  <StructureSelector
55
55
  withDataFields
56
56
  pageSize={20}
57
- defaultFilters={{ "class.raw": ["table"] }}
57
+ defaultFilters={{ "class.raw": ["table"], source_id: [sourceId] }}
58
58
  selectedStructure={selectedStructure}
59
59
  onSelect={handleSelect}
60
60
  />
@@ -8,7 +8,7 @@ import QxContext from "@truedat/qx/components/QxContext";
8
8
  export default function DataViewSelector({ onChange, onBlur, value }) {
9
9
  const { formatMessage } = useIntl();
10
10
  const [searchTerm, setSearchTerm] = useState("");
11
- const { dataViews, currentDataViewId } = useContext(QxContext);
11
+ const { dataViews, sourceId, currentDataViewId } = useContext(QxContext);
12
12
 
13
13
  const matchSorterFp = (items) =>
14
14
  matchSorter(items, searchTerm, {
@@ -16,6 +16,10 @@ export default function DataViewSelector({ onChange, onBlur, value }) {
16
16
  threshold: matchSorter.rankings.CONTAINS,
17
17
  });
18
18
 
19
+ const sourceDataViews = _.filter({ source_id: parseInt(sourceId) })(
20
+ dataViews
21
+ );
22
+
19
23
  const options = _.flow(
20
24
  _.reject(({ id }) => currentDataViewId && currentDataViewId == id),
21
25
  _.cond([
@@ -27,7 +31,7 @@ export default function DataViewSelector({ onChange, onBlur, value }) {
27
31
  value: id,
28
32
  text: name,
29
33
  }))
30
- )(dataViews);
34
+ )(sourceDataViews);
31
35
 
32
36
  const buildEmbedded = (id) =>
33
37
  _.flow(_.find({ id }), (dataView) => ({
@@ -41,7 +45,7 @@ export default function DataViewSelector({ onChange, onBlur, value }) {
41
45
  parent_name: dataView.name,
42
46
  }))
43
47
  )(dataView),
44
- }))(dataViews);
48
+ }))(sourceDataViews);
45
49
 
46
50
  const handleSearchChange = (_e, { searchQuery }) =>
47
51
  setSearchTerm(searchQuery);
@@ -19,7 +19,8 @@ describe("<DataViewSelector />", () => {
19
19
  const { container } = render(
20
20
  <TestFormWrapper
21
21
  context={{
22
- dataViews: [{ name: "UserDataView", id: 1 }],
22
+ sourceId: 10,
23
+ dataViews: [{ name: "UserDataView", id: 1, source_id: 10 }],
23
24
  }}
24
25
  >
25
26
  <DataViewSelector />
@@ -35,9 +36,10 @@ describe("<DataViewSelector />", () => {
35
36
  const { queryByText } = render(
36
37
  <TestFormWrapper
37
38
  context={{
39
+ sourceId: 10,
38
40
  dataViews: [
39
- { name: "UserDataView", id: 1 },
40
- { name: "SelectedDataView", id: 2 },
41
+ { name: "UserDataView", id: 1, source_id: 10 },
42
+ { name: "SelectedDataView", id: 2, source_id: 10 },
41
43
  ],
42
44
  currentDataViewId: 2,
43
45
  }}
@@ -56,11 +58,13 @@ describe("<DataViewSelector />", () => {
56
58
  const { getByRole } = render(
57
59
  <TestFormWrapper
58
60
  context={{
61
+ sourceId: 10,
59
62
  dataViews: [
60
- { name: "UserDataView", id: 1 },
63
+ { name: "UserDataView", id: 1, source_id: 10 },
61
64
  {
62
65
  name: "SelectedDataView",
63
66
  id: 2,
67
+ source_id: 10,
64
68
  select: {
65
69
  properties: {
66
70
  fields: [
@@ -95,6 +99,7 @@ describe("<DataViewSelector />", () => {
95
99
  },
96
100
  ],
97
101
  id: 2,
102
+ source_id: 10,
98
103
  name: "SelectedDataView",
99
104
  select: {
100
105
  properties: {
@@ -114,9 +119,10 @@ describe("<DataViewSelector />", () => {
114
119
  const { getByRole, queryByText } = render(
115
120
  <TestFormWrapper
116
121
  context={{
122
+ sourceId: 10,
117
123
  dataViews: [
118
- { name: "UserDataView", id: 1 },
119
- { name: "SelectedDataView", id: 2 },
124
+ { name: "UserDataView", id: 1, source_id: 10 },
125
+ { name: "SelectedDataView", id: 2, source_id: 10 },
120
126
  ],
121
127
  }}
122
128
  >
@@ -1,3 +1,4 @@
1
+ import _ from "lodash/fp";
1
2
  import React, { Fragment, useEffect } from "react";
2
3
  import PropTypes from "prop-types";
3
4
  import { useIntl } from "react-intl";
@@ -17,6 +18,10 @@ import QxContext from "@truedat/qx/components/QxContext";
17
18
  import DataViewSelect from "./DataViewSelect";
18
19
  import Queryables from "./Queryables";
19
20
 
21
+ const SourceSelector = React.lazy(() =>
22
+ import("@truedat/cx/sources/components/SourceSelector")
23
+ );
24
+
20
25
  export default function DataViewEditor({
21
26
  selectedDataView,
22
27
  context,
@@ -38,6 +43,8 @@ export default function DataViewEditor({
38
43
  setDirty(isDirty);
39
44
  }, [setDirty, isDirty]);
40
45
 
46
+ const isEditForm = !_.isNil(selectedDataView?.id);
47
+ const sourceId = watch("source_id");
41
48
  if (!selectedDataView) return null;
42
49
 
43
50
  return (
@@ -94,11 +101,28 @@ export default function DataViewEditor({
94
101
  </Grid.Row>
95
102
  </GridColumn>
96
103
  </Grid>
97
- <QxContext.Provider value={context}>
98
- <Queryables />
99
- <DataViewSelect />
100
- </QxContext.Provider>
104
+ <Controller
105
+ control={control}
106
+ name="source_id"
107
+ rules={{ required: true }}
108
+ render={({ field: { onChange, value } }) => (
109
+ <Form.Field required>
110
+ <label>{formatMessage({ id: "dataViews.form.source" })}</label>
101
111
 
112
+ <SourceSelector
113
+ disabled={isEditForm}
114
+ value={value + ""}
115
+ onChange={(_e, { value }) => onChange(value)}
116
+ />
117
+ </Form.Field>
118
+ )}
119
+ />
120
+ {!_.isNil(sourceId) ? (
121
+ <QxContext.Provider value={{ ...context, sourceId }}>
122
+ <Queryables />
123
+ <DataViewSelect />
124
+ </QxContext.Provider>
125
+ ) : null}
102
126
  <Divider hidden />
103
127
  <Container textAlign="right">
104
128
  <Button
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { Suspense } from "react";
2
2
  import { act } from "react-dom/test-utils";
3
3
  import userEvent from "@testing-library/user-event";
4
4
  import { render } from "@truedat/test/render";
@@ -48,13 +48,15 @@ const renderOpts = {
48
48
  "queryables.select.form.add_all_select_fields": "add_all_select_fields",
49
49
  "queryables.select.form.add_select_field": "add_select_field",
50
50
  "queryables.select.form.alias": "alias",
51
+ "source.search.placeholder": "placeholder",
52
+ "dataViews.form.source": "source",
51
53
  },
52
54
  },
53
55
  };
54
56
 
55
57
  const defaultProps = {
56
58
  selectedDataView: null,
57
- context: {},
59
+ context: { sourceId: 10 },
58
60
  onSubmit: jest.fn(),
59
61
  onCancel: jest.fn(),
60
62
  onDelete: jest.fn(),
@@ -78,6 +80,7 @@ describe("<DataViewEditor />", () => {
78
80
  ...defaultProps,
79
81
  selectedDataView: {
80
82
  id: 4,
83
+ source_id: 10,
81
84
  description: "Description",
82
85
  name: "StructureView",
83
86
  queryables: [
@@ -126,7 +129,12 @@ describe("<DataViewEditor />", () => {
126
129
  },
127
130
  },
128
131
  };
129
- const { container } = render(<DataViewEditor {...props} />, renderOpts);
132
+ const { container } = render(
133
+ <Suspense fallback={<p>Loading selector</p>}>
134
+ <DataViewEditor {...props} />
135
+ </Suspense>,
136
+ renderOpts
137
+ );
130
138
 
131
139
  await act(async () => {
132
140
  expect(container).toMatchSnapshot();
@@ -140,6 +148,7 @@ describe("<DataViewEditor />", () => {
140
148
  onSubmit,
141
149
  selectedDataView: {
142
150
  name: "",
151
+ source_id: 10,
143
152
  description: "",
144
153
  queryables: [
145
154
  {
@@ -159,6 +168,7 @@ describe("<DataViewEditor />", () => {
159
168
  dataViews: [
160
169
  {
161
170
  id: 4,
171
+ source_id: 10,
162
172
  description: "Description",
163
173
  name: "AnotherDataView",
164
174
  select: {
@@ -185,16 +195,14 @@ describe("<DataViewEditor />", () => {
185
195
  ],
186
196
  },
187
197
  };
188
- const { container, getAllByRole, getByRole } = render(
189
- <DataViewEditor {...props} />,
190
- renderOpts
191
- );
198
+ const { container, getAllByRole, getByRole, getByPlaceholderText, debug } =
199
+ render(<DataViewEditor {...props} />, renderOpts);
192
200
 
193
201
  // Insert name
194
202
  userEvent.type(getAllByRole("textbox")[0], "data_view_name");
195
203
 
196
204
  // Insert From information
197
- userEvent.type(getAllByRole("textbox")[1], "from_alias");
205
+ userEvent.type(getByPlaceholderText("alias"), "from_alias");
198
206
  userEvent.click(getByRole("option", { name: /data_view/i }));
199
207
  userEvent.click(getByRole("option", { name: /AnotherDataView/i }));
200
208
 
@@ -221,6 +229,7 @@ describe("<DataViewEditor />", () => {
221
229
 
222
230
  expect(onSubmit).toHaveBeenCalledWith({
223
231
  description: "",
232
+ source_id: 10,
224
233
  name: "data_view_name",
225
234
  queryables: [
226
235
  {
@@ -260,6 +269,7 @@ describe("<DataViewEditor />", () => {
260
269
  },
261
270
  type: "select",
262
271
  },
272
+ source_id: 10,
263
273
  },
264
274
  id: 4,
265
275
  type: "data_view",
@@ -60,6 +60,13 @@ exports[`<DataViewEditor /> handles user interaction 1`] = `
60
60
  </div>
61
61
  </div>
62
62
  </div>
63
+ <div
64
+ class="required field"
65
+ >
66
+ <label>
67
+ source
68
+ </label>
69
+ </div>
63
70
  <div
64
71
  class="ui list"
65
72
  role="list"
@@ -151,19 +158,6 @@ exports[`<DataViewEditor /> handles user interaction 1`] = `
151
158
  data_structure
152
159
  </span>
153
160
  </div>
154
- <div
155
- aria-checked="false"
156
- aria-selected="false"
157
- class="item"
158
- role="option"
159
- style="pointer-events: all;"
160
- >
161
- <span
162
- class="text"
163
- >
164
- reference_dataset
165
- </span>
166
- </div>
167
161
  <div
168
162
  aria-checked="true"
169
163
  aria-selected="true"
@@ -518,6 +512,7 @@ exports[`<DataViewEditor /> matches the latest snapshot with content 1`] = `
518
512
  <div>
519
513
  <form
520
514
  class="ui form"
515
+ style="display: none;"
521
516
  >
522
517
  <h3
523
518
  class="ui dividing header"
@@ -574,6 +569,13 @@ exports[`<DataViewEditor /> matches the latest snapshot with content 1`] = `
574
569
  </div>
575
570
  </div>
576
571
  </div>
572
+ <div
573
+ class="required field"
574
+ >
575
+ <label>
576
+ source
577
+ </label>
578
+ </div>
577
579
  <div
578
580
  class="ui list"
579
581
  role="list"
@@ -665,32 +667,6 @@ exports[`<DataViewEditor /> matches the latest snapshot with content 1`] = `
665
667
  data_structure
666
668
  </span>
667
669
  </div>
668
- <div
669
- aria-checked="false"
670
- aria-selected="false"
671
- class="item"
672
- role="option"
673
- style="pointer-events: all;"
674
- >
675
- <span
676
- class="text"
677
- >
678
- reference_dataset
679
- </span>
680
- </div>
681
- <div
682
- aria-checked="false"
683
- aria-selected="false"
684
- class="item"
685
- role="option"
686
- style="pointer-events: all;"
687
- >
688
- <span
689
- class="text"
690
- >
691
- data_view
692
- </span>
693
- </div>
694
670
  </div>
695
671
  </div>
696
672
  </div>
@@ -996,5 +972,8 @@ exports[`<DataViewEditor /> matches the latest snapshot with content 1`] = `
996
972
  </button>
997
973
  </div>
998
974
  </form>
975
+ <p>
976
+ Loading selector
977
+ </p>
999
978
  </div>
1000
979
  `;
@@ -85,32 +85,6 @@ exports[`<Queryable /> matches the latest snapshot with duplicated alias 1`] = `
85
85
  data_structure
86
86
  </span>
87
87
  </div>
88
- <div
89
- aria-checked="false"
90
- aria-selected="false"
91
- class="item"
92
- role="option"
93
- style="pointer-events: all;"
94
- >
95
- <span
96
- class="text"
97
- >
98
- reference_dataset
99
- </span>
100
- </div>
101
- <div
102
- aria-checked="false"
103
- aria-selected="false"
104
- class="item"
105
- role="option"
106
- style="pointer-events: all;"
107
- >
108
- <span
109
- class="text"
110
- >
111
- data_view
112
- </span>
113
- </div>
114
88
  </div>
115
89
  </div>
116
90
  </div>
@@ -185,7 +159,7 @@ exports[`<Queryable /> matches the latest snapshot with duplicated resource 1`]
185
159
  class="divider text"
186
160
  role="alert"
187
161
  >
188
- data_view
162
+ type
189
163
  </div>
190
164
  <i
191
165
  aria-hidden="true"
@@ -196,41 +170,15 @@ exports[`<Queryable /> matches the latest snapshot with duplicated resource 1`]
196
170
  >
197
171
  <div
198
172
  aria-checked="false"
199
- aria-selected="false"
200
- class="item"
201
- role="option"
202
- style="pointer-events: all;"
203
- >
204
- <span
205
- class="text"
206
- >
207
- data_structure
208
- </span>
209
- </div>
210
- <div
211
- aria-checked="false"
212
- aria-selected="false"
213
- class="item"
214
- role="option"
215
- style="pointer-events: all;"
216
- >
217
- <span
218
- class="text"
219
- >
220
- reference_dataset
221
- </span>
222
- </div>
223
- <div
224
- aria-checked="true"
225
173
  aria-selected="true"
226
- class="active selected item"
174
+ class="selected item"
227
175
  role="option"
228
176
  style="pointer-events: all;"
229
177
  >
230
178
  <span
231
179
  class="text"
232
180
  >
233
- data_view
181
+ data_structure
234
182
  </span>
235
183
  </div>
236
184
  </div>
@@ -365,32 +313,6 @@ exports[`<Queryable /> matches the latest snapshot with from queryable 1`] = `
365
313
  data_structure
366
314
  </span>
367
315
  </div>
368
- <div
369
- aria-checked="false"
370
- aria-selected="false"
371
- class="item"
372
- role="option"
373
- style="pointer-events: all;"
374
- >
375
- <span
376
- class="text"
377
- >
378
- reference_dataset
379
- </span>
380
- </div>
381
- <div
382
- aria-checked="false"
383
- aria-selected="false"
384
- class="item"
385
- role="option"
386
- style="pointer-events: all;"
387
- >
388
- <span
389
- class="text"
390
- >
391
- data_view
392
- </span>
393
- </div>
394
316
  </div>
395
317
  </div>
396
318
  </div>
@@ -584,32 +506,6 @@ exports[`<Queryable /> matches the latest snapshot with join queryable 1`] = `
584
506
  data_structure
585
507
  </span>
586
508
  </div>
587
- <div
588
- aria-checked="false"
589
- aria-selected="false"
590
- class="item"
591
- role="option"
592
- style="pointer-events: all;"
593
- >
594
- <span
595
- class="text"
596
- >
597
- reference_dataset
598
- </span>
599
- </div>
600
- <div
601
- aria-checked="false"
602
- aria-selected="false"
603
- class="item"
604
- role="option"
605
- style="pointer-events: all;"
606
- >
607
- <span
608
- class="text"
609
- >
610
- data_view
611
- </span>
612
- </div>
613
509
  </div>
614
510
  </div>
615
511
  </div>
@@ -93,32 +93,6 @@ exports[`<Queryables /> matches the latest snapshot with from queryable 1`] = `
93
93
  data_structure
94
94
  </span>
95
95
  </div>
96
- <div
97
- aria-checked="false"
98
- aria-selected="false"
99
- class="item"
100
- role="option"
101
- style="pointer-events: all;"
102
- >
103
- <span
104
- class="text"
105
- >
106
- reference_dataset
107
- </span>
108
- </div>
109
- <div
110
- aria-checked="false"
111
- aria-selected="false"
112
- class="item"
113
- role="option"
114
- style="pointer-events: all;"
115
- >
116
- <span
117
- class="text"
118
- >
119
- data_view
120
- </span>
121
- </div>
122
96
  </div>
123
97
  </div>
124
98
  </div>
@@ -390,32 +364,6 @@ exports[`<Queryables /> matches the latest snapshot with join queryable 1`] = `
390
364
  data_structure
391
365
  </span>
392
366
  </div>
393
- <div
394
- aria-checked="false"
395
- aria-selected="false"
396
- class="item"
397
- role="option"
398
- style="pointer-events: all;"
399
- >
400
- <span
401
- class="text"
402
- >
403
- reference_dataset
404
- </span>
405
- </div>
406
- <div
407
- aria-checked="false"
408
- aria-selected="false"
409
- class="item"
410
- role="option"
411
- style="pointer-events: all;"
412
- >
413
- <span
414
- class="text"
415
- >
416
- data_view
417
- </span>
418
- </div>
419
367
  </div>
420
368
  </div>
421
369
  </div>
@@ -842,32 +790,6 @@ exports[`<Queryables /> test delete queryable 1`] = `
842
790
  data_structure
843
791
  </span>
844
792
  </div>
845
- <div
846
- aria-checked="false"
847
- aria-selected="false"
848
- class="item"
849
- role="option"
850
- style="pointer-events: all;"
851
- >
852
- <span
853
- class="text"
854
- >
855
- reference_dataset
856
- </span>
857
- </div>
858
- <div
859
- aria-checked="false"
860
- aria-selected="false"
861
- class="item"
862
- role="option"
863
- style="pointer-events: all;"
864
- >
865
- <span
866
- class="text"
867
- >
868
- data_view
869
- </span>
870
- </div>
871
793
  </div>
872
794
  </div>
873
795
  </div>
@@ -42,32 +42,6 @@ exports[`<From /> matches the latest snapshot 1`] = `
42
42
  data_structure
43
43
  </span>
44
44
  </div>
45
- <div
46
- aria-checked="false"
47
- aria-selected="false"
48
- class="item"
49
- role="option"
50
- style="pointer-events: all;"
51
- >
52
- <span
53
- class="text"
54
- >
55
- reference_dataset
56
- </span>
57
- </div>
58
- <div
59
- aria-checked="false"
60
- aria-selected="false"
61
- class="item"
62
- role="option"
63
- style="pointer-events: all;"
64
- >
65
- <span
66
- class="text"
67
- >
68
- data_view
69
- </span>
70
- </div>
71
45
  </div>
72
46
  </div>
73
47
  </div>
@@ -42,32 +42,6 @@ exports[`<Join /> matches the latest snapshot 1`] = `
42
42
  data_structure
43
43
  </span>
44
44
  </div>
45
- <div
46
- aria-checked="false"
47
- aria-selected="false"
48
- class="item"
49
- role="option"
50
- style="pointer-events: all;"
51
- >
52
- <span
53
- class="text"
54
- >
55
- reference_dataset
56
- </span>
57
- </div>
58
- <div
59
- aria-checked="false"
60
- aria-selected="false"
61
- class="item"
62
- role="option"
63
- style="pointer-events: all;"
64
- >
65
- <span
66
- class="text"
67
- >
68
- data_view
69
- </span>
70
- </div>
71
45
  </div>
72
46
  </div>
73
47
  </div>
@@ -37,7 +37,7 @@ export default function QualityControl() {
37
37
  <DynamicFormViewer
38
38
  boxLayout
39
39
  template={qualityControl.df_type}
40
- content={qualityControl.dynamic_content}
40
+ content={qualityControl.df_content}
41
41
  />
42
42
  </>
43
43
  ) : null}
@@ -5,7 +5,9 @@ import { useIntl } from "react-intl";
5
5
  import { Link } from "react-router-dom";
6
6
  import { Button, Container } from "semantic-ui-react";
7
7
  import { linkTo } from "@truedat/core/routes";
8
+ import { useAuthorized } from "@truedat/core/hooks";
8
9
  import { useQualityControlUpdateStatus } from "../../hooks/useQualityControls";
10
+ import QualityControlQueryModal from "./QualityControlQueryModal";
9
11
 
10
12
  export default function QualityControlActions({
11
13
  actions,
@@ -16,11 +18,15 @@ export default function QualityControlActions({
16
18
  const { trigger, isMutating } = useQualityControlUpdateStatus(
17
19
  qualityControl.id
18
20
  );
21
+ const authorized = useAuthorized();
19
22
 
20
23
  const updateStatus = (action) => trigger({ action }).then(() => mutate());
21
24
 
22
25
  return (
23
26
  <Container textAlign="right">
27
+ {authorized ? (
28
+ <QualityControlQueryModal qualityControlId={qualityControl.id} />
29
+ ) : null}
24
30
  {_.map((action) =>
25
31
  action === "create_draft" ? (
26
32
  <Button
@@ -24,6 +24,9 @@ import ResultType from "./ResultType";
24
24
  const SelectableDynamicForm = React.lazy(() =>
25
25
  import("@truedat/df/components/SelectableDynamicForm")
26
26
  );
27
+ const SourceSelector = React.lazy(() =>
28
+ import("@truedat/cx/sources/components/SourceSelector")
29
+ );
27
30
 
28
31
  export default function QualityControlEditor({
29
32
  value,
@@ -82,10 +85,13 @@ export default function QualityControlEditor({
82
85
 
83
86
  const formData = watch();
84
87
 
88
+ console.log("formData", formData);
89
+
85
90
  const {
86
91
  resource,
87
92
  name,
88
- dynamic_content: dfContent,
93
+ source_id: sourceId,
94
+ df_content: dfContent,
89
95
  domain_ids: domainIds,
90
96
  } = formData;
91
97
 
@@ -196,17 +202,47 @@ export default function QualityControlEditor({
196
202
  )}
197
203
  />
198
204
 
199
- <QxContext.Provider value={{ ...context, field: "resource" }}>
200
- <ResourceSelector required labelId="quality_control.form.dataset" />
201
- </QxContext.Provider>
202
- {resource?.id ? (
203
- <div className="vertical-space">
205
+ <Controller
206
+ control={control}
207
+ name="source_id"
208
+ rules={{ required: true }}
209
+ render={({ field: { onChange, value } }) => (
210
+ <Form.Field required>
211
+ <label>{formatMessage({ id: "dataViews.form.source" })}</label>
212
+
213
+ <SourceSelector
214
+ disabled={isModification}
215
+ value={value + ""}
216
+ onChange={(_e, { value }) => onChange(value)}
217
+ />
218
+ </Form.Field>
219
+ )}
220
+ />
221
+ {!_.isNil(sourceId) ? (
222
+ <>
204
223
  <QxContext.Provider
205
- value={{ ...context, fields, field: "validation" }}
224
+ value={{ ...context, sourceId, field: "resource" }}
206
225
  >
207
- <Clauses labelId="quality_control.form.validation" />
226
+ <ResourceSelector
227
+ required
228
+ labelId="quality_control.form.dataset"
229
+ />
208
230
  </QxContext.Provider>
209
- </div>
231
+ {resource?.id ? (
232
+ <div className="vertical-space">
233
+ <QxContext.Provider
234
+ value={{
235
+ ...context,
236
+ sourceId,
237
+ fields,
238
+ field: "validation",
239
+ }}
240
+ >
241
+ <Clauses labelId="quality_control.form.validation" />
242
+ </QxContext.Provider>
243
+ </div>
244
+ ) : null}
245
+ </>
210
246
  ) : null}
211
247
 
212
248
  <Divider hidden />
@@ -12,7 +12,7 @@ import QualityControlCrumbs from "./QualityControlCrumbs";
12
12
  import QualityControlActions from "./QualityControlActions";
13
13
  import QualityControlTabs from "./QualityControlTabs";
14
14
 
15
- export default function QualityControl({ children }) {
15
+ export default function QualityControlHeader({ children }) {
16
16
  const { id } = useParams();
17
17
  const { data, loading, mutate } = useQualityControl(id);
18
18
 
@@ -59,6 +59,6 @@ export default function QualityControl({ children }) {
59
59
  );
60
60
  }
61
61
 
62
- QualityControl.propTypes = {
62
+ QualityControlHeader.propTypes = {
63
63
  children: PropTypes.node,
64
64
  };
@@ -0,0 +1,83 @@
1
+ import React, { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import {
4
+ Accordion,
5
+ AccordionTitle,
6
+ AccordionContent,
7
+ Icon,
8
+ TextArea,
9
+ Button,
10
+ Modal,
11
+ Divider,
12
+ } from "semantic-ui-react";
13
+ import { FormattedMessage } from "react-intl";
14
+ import { useQualityControlQueries } from "../../hooks/useQualityControls";
15
+
16
+ const JsonBox = ({ title, content }) => {
17
+ const [expanded, setExpanded] = useState(false);
18
+ const [copied, setCopied] = useState(false);
19
+ const contentString = JSON.stringify(content, null, 2);
20
+ return (
21
+ <>
22
+ <AccordionTitle active={expanded} onClick={() => setExpanded(!expanded)}>
23
+ <div className="json-box-title">
24
+ <span>
25
+ <Icon name="dropdown" />
26
+ {title}
27
+ </span>
28
+ <Button
29
+ icon={copied ? "check" : "copy"}
30
+ color={copied && "green"}
31
+ onClick={(e) => {
32
+ e.stopPropagation();
33
+ setCopied(true);
34
+ setTimeout(() => setCopied(false), 2000);
35
+ navigator.clipboard.writeText(contentString);
36
+ }}
37
+ />
38
+ </div>
39
+ </AccordionTitle>
40
+ <AccordionContent active={expanded}>
41
+ <TextArea
42
+ className="json-box"
43
+ fluid
44
+ editable={false}
45
+ rows={10}
46
+ size="small"
47
+ value={contentString}
48
+ />
49
+ </AccordionContent>
50
+ </>
51
+ );
52
+ };
53
+ const ModalContent = ({ id }) => {
54
+ const { data, loading } = useQualityControlQueries(id);
55
+ return loading ? (
56
+ <FormattedMessage id={`quality_control.queries.loading`} />
57
+ ) : (
58
+ <Accordion styled>
59
+ {data?.data?.queries.map((json, id) => (
60
+ <JsonBox key={id} title={`Query ${id}`} content={json} />
61
+ ))}
62
+ <JsonBox
63
+ title="Resources Lookup"
64
+ content={data?.data?.resources_lookup}
65
+ />
66
+ </Accordion>
67
+ );
68
+ };
69
+ export default function QualityControlQueryModal({ qualityControlId: id }) {
70
+ const [open, setOpen] = useState(open);
71
+ return (
72
+ <Modal
73
+ open={open}
74
+ onOpen={() => setOpen(true)}
75
+ onClose={() => setOpen(false)}
76
+ trigger={<Button icon="file code outline" />}
77
+ closeIcon
78
+ size={"small"}
79
+ content={<ModalContent id={id} />}
80
+ role="dialog"
81
+ />
82
+ );
83
+ }
@@ -40,7 +40,6 @@ export default function QxRoutes() {
40
40
  initialSortDirection: "descending",
41
41
  useSearch: useQualityControlsSearch,
42
42
  useFilters: useQualityControlsFilters,
43
- userFiltersType: "qx",
44
43
  };
45
44
 
46
45
  return (
@@ -17,6 +17,7 @@ import {
17
17
  API_QUALITY_CONTROL_DOMAINS,
18
18
  API_QUALITY_CONTROL_SEARCH,
19
19
  API_QUALITY_CONTROL_FILTERS,
20
+ API_QUALITY_CONTROL_QUERIES,
20
21
  } from "../api";
21
22
 
22
23
  export const useQualityControls = () => {
@@ -72,3 +73,8 @@ export const useQualityControlsFilters = () => {
72
73
  apiJsonPost(url, arg)
73
74
  );
74
75
  };
76
+ export const useQualityControlQueries = (id) => {
77
+ const url = compile(API_QUALITY_CONTROL_QUERIES)({ id });
78
+ const { data, error, mutate } = useSWR(url, apiJson);
79
+ return { data: data?.data, error, loading: !error && !data, mutate };
80
+ };
@@ -284,4 +284,16 @@ ul.function-tree {
284
284
  display: flex;
285
285
  justify-content: center;
286
286
  align-items: center;
287
+ }
288
+
289
+ .json-box {
290
+ font-family: monospace;
291
+ font-size: smaller;
292
+ width: 100%;
293
+ }
294
+
295
+ .json-box-title {
296
+ display: flex;
297
+ justify-content: space-between;
298
+ align-items: center;
287
299
  }