@truedat/qx 7.13.8 → 7.14.0

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 (46) hide show
  1. package/package.json +3 -3
  2. package/src/components/common/ClauseViewer.js +183 -21
  3. package/src/components/common/expressions/Condition.js +13 -6
  4. package/src/components/dataViews/DataViewEditor.js +0 -2
  5. package/src/components/dataViews/DataViewSummary.js +73 -0
  6. package/src/components/dataViews/__tests__/AdvancedDataViewEditor.spec.js +4 -1
  7. package/src/components/dataViews/__tests__/DataViewEditor.spec.js +167 -132
  8. package/src/components/dataViews/__tests__/DataViewSummary.spec.js +820 -0
  9. package/src/components/dataViews/__tests__/DataViews.spec.js +57 -17
  10. package/src/components/dataViews/__tests__/SimpleDataViewEditor.spec.js +140 -141
  11. package/src/components/dataViews/__tests__/__snapshots__/AdvancedDataViewEditor.spec.js.snap +963 -759
  12. package/src/components/dataViews/__tests__/__snapshots__/DataViewSelect.spec.js.snap +17 -13
  13. package/src/components/dataViews/__tests__/__snapshots__/DataViewSummary.spec.js.snap +1786 -0
  14. package/src/components/dataViews/__tests__/__snapshots__/Queryable.spec.js.snap +18 -14
  15. package/src/components/dataViews/__tests__/__snapshots__/Queryables.spec.js.snap +18 -14
  16. package/src/components/dataViews/advancedForm/AdvancedDataViewEditor.js +59 -48
  17. package/src/components/dataViews/queryableProperties/Join.js +2 -1
  18. package/src/components/dataViews/queryableProperties/Select.js +22 -30
  19. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Join.spec.js.snap +1 -1
  20. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Select.spec.js.snap +37 -25
  21. package/src/components/dataViews/queryableSummaryHelpers.js +101 -0
  22. package/src/components/dataViews/simpleForm/SimpleDataViewEditor.js +9 -4
  23. package/src/components/dataViews/summary/From.js +45 -0
  24. package/src/components/dataViews/summary/GroupBy.js +82 -0
  25. package/src/components/dataViews/summary/Join.js +60 -0
  26. package/src/components/dataViews/summary/Select.js +31 -0
  27. package/src/components/dataViews/summary/Where.js +37 -0
  28. package/src/components/qualityControls/ControlPropertiesView.js +115 -63
  29. package/src/components/qualityControls/EditQualityControl.js +5 -3
  30. package/src/components/qualityControls/NewDraftQualityControl.js +8 -3
  31. package/src/components/qualityControls/NewQualityControl.js +5 -3
  32. package/src/components/qualityControls/QualityControlCrumbs.js +46 -5
  33. package/src/components/qualityControls/QualityControlRoutes.js +3 -1
  34. package/src/components/qualityControls/QualityControls.js +9 -18
  35. package/src/components/qualityControls/QualityControlsLabelResults.js +2 -2
  36. package/src/components/qualityControls/__tests__/__snapshots__/ControlPropertiesView.spec.js.snap +12 -9
  37. package/src/components/qualityControls/__tests__/__snapshots__/EditQualityControl.spec.js.snap +536 -493
  38. package/src/components/qualityControls/__tests__/__snapshots__/NewDraftQualityControl.spec.js.snap +510 -483
  39. package/src/components/qualityControls/__tests__/__snapshots__/NewQualityControl.spec.js.snap +261 -245
  40. package/src/components/qualityControls/__tests__/__snapshots__/QualityControl.spec.js.snap +11 -8
  41. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlCrumbs.spec.js.snap +1 -1
  42. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlHeader.spec.js.snap +1 -1
  43. package/src/components/qualityControls/__tests__/__snapshots__/QualityControls.spec.js.snap +87 -87
  44. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlsLabelResults.spec.js.snap +6 -2
  45. package/src/hooks/useDataViews.js +1 -1
  46. package/src/styles/Expression.less +25 -1
@@ -2,7 +2,10 @@ import React from "react";
2
2
  import { render, waitForLoad } from "@truedat/test/render";
3
3
  import userEvent from "@testing-library/user-event";
4
4
  import DataViews from "../DataViews";
5
- import { useDataViews, useDataViewDelete } from "@truedat/qx/hooks/useDataViews";
5
+ import {
6
+ useDataViews,
7
+ useDataViewDelete,
8
+ } from "@truedat/qx/hooks/useDataViews";
6
9
 
7
10
  const mockFormatMessage = jest.fn(({ id }) => id);
8
11
 
@@ -23,7 +26,6 @@ jest.mock("@truedat/qx/hooks/useDataViews", () => ({
23
26
  useDataViewDelete: jest.fn(),
24
27
  }));
25
28
 
26
-
27
29
  describe("<DataViews />", () => {
28
30
  beforeEach(() => {
29
31
  useDataViewDelete.mockReturnValue({
@@ -45,10 +47,14 @@ describe("<DataViews />", () => {
45
47
  const rendered = render(<DataViews />);
46
48
  await waitForLoad(rendered);
47
49
 
48
- expect(rendered.getByRole("heading", { name: /dataViews.header/i })).toBeInTheDocument();
50
+ expect(
51
+ rendered.getByRole("heading", { name: /dataViews.header/i })
52
+ ).toBeInTheDocument();
49
53
  expect(rendered.getByText(/dataViews.empty_list/i)).toBeInTheDocument();
50
54
 
51
- expect(rendered.getByRole("button", { name: /dataViews.action.new/i })).toBeInTheDocument();
55
+ expect(
56
+ rendered.getByRole("button", { name: /dataViews.action.new/i })
57
+ ).toBeInTheDocument();
52
58
  expect(rendered.container).toMatchSnapshot();
53
59
  });
54
60
 
@@ -56,8 +62,18 @@ describe("<DataViews />", () => {
56
62
  useDataViews.mockReturnValue({
57
63
  data: {
58
64
  data: [
59
- { id: 2, name: "Inventory", description: "Stock levels", mode: "advanced" },
60
- { id: 1, name: "Users Spain", description: "Active users in Spain", mode: "guided" },
65
+ {
66
+ id: 2,
67
+ name: "Inventory",
68
+ description: "Stock levels",
69
+ mode: "advanced",
70
+ },
71
+ {
72
+ id: 1,
73
+ name: "Users Spain",
74
+ description: "Active users in Spain",
75
+ mode: "guided",
76
+ },
61
77
  ],
62
78
  },
63
79
  loading: false,
@@ -70,16 +86,21 @@ describe("<DataViews />", () => {
70
86
  expect(rendered.getByText("Users Spain")).toBeInTheDocument();
71
87
  expect(rendered.getByText("Inventory")).toBeInTheDocument();
72
88
 
73
-
74
- const guidedIcon = rendered.container.querySelector(".dataViews\\.mode\\.icon\\.guided.icon");
75
- const advancedIcon = rendered.container.querySelector(".dataViews\\.mode\\.icon\\.advanced.icon");
89
+ const guidedIcon = rendered.container.querySelector(
90
+ ".dataViews\\.mode\\.icon\\.guided.icon"
91
+ );
92
+ const advancedIcon = rendered.container.querySelector(
93
+ ".dataViews\\.mode\\.icon\\.advanced.icon"
94
+ );
76
95
  expect(guidedIcon).toBeInTheDocument();
77
96
  expect(advancedIcon).toBeInTheDocument();
78
97
 
79
- const dataViewLinks = rendered.container.querySelectorAll('a[href^="/dataviews/"]');
98
+ const dataViewLinks = rendered.container.querySelectorAll(
99
+ 'a[href^="/dataviews/"]'
100
+ );
80
101
  expect(dataViewLinks.length).toBe(3);
81
-
82
- const hrefs = Array.from(dataViewLinks).map(a => a.getAttribute("href"));
102
+
103
+ const hrefs = Array.from(dataViewLinks).map((a) => a.getAttribute("href"));
83
104
  expect(hrefs).toContain("/dataviews/1");
84
105
  expect(hrefs).toContain("/dataviews/2");
85
106
  expect(rendered.container).toMatchSnapshot();
@@ -90,9 +111,24 @@ describe("<DataViews />", () => {
90
111
  useDataViews.mockReturnValue({
91
112
  data: {
92
113
  data: [
93
- { id: 1, name: "Revenue Report", description: "Monthly KPIs", mode: "advanced" },
94
- { id: 2, name: "Users", description: "Active users in Spain", mode: "guided" },
95
- { id: 3, name: "Inventory", description: "Stock and SKUs", mode: "advanced" },
114
+ {
115
+ id: 1,
116
+ name: "Revenue Report",
117
+ description: "Monthly KPIs",
118
+ mode: "advanced",
119
+ },
120
+ {
121
+ id: 2,
122
+ name: "Users",
123
+ description: "Active users in Spain",
124
+ mode: "guided",
125
+ },
126
+ {
127
+ id: 3,
128
+ name: "Inventory",
129
+ description: "Stock and SKUs",
130
+ mode: "advanced",
131
+ },
96
132
  ],
97
133
  },
98
134
  loading: false,
@@ -117,7 +153,9 @@ describe("<DataViews />", () => {
117
153
 
118
154
  it("shows untitled message when name is missing", async () => {
119
155
  useDataViews.mockReturnValue({
120
- data: { data: [{ id: 1, name: "", description: "desc", mode: "guided" }] },
156
+ data: {
157
+ data: [{ id: 1, name: "", description: "desc", mode: "guided" }],
158
+ },
121
159
  loading: false,
122
160
  });
123
161
 
@@ -135,7 +173,9 @@ describe("<DataViews />", () => {
135
173
 
136
174
  const rendered = render(<DataViews />);
137
175
 
138
- const newBtn = rendered.getByRole("button", { name: /dataViews.action.new/i });
176
+ const newBtn = rendered.getByRole("button", {
177
+ name: /dataViews.action.new/i,
178
+ });
139
179
  expect(newBtn).toBeInTheDocument();
140
180
  expect(newBtn.getAttribute("href")).toBe("/dataviews/new");
141
181
  });
@@ -6,8 +6,8 @@ import SimpleDataViewEditor from "../simpleForm/SimpleDataViewEditor";
6
6
  // Keep intl messages simple (just echo the id)
7
7
  const mockFormatMessage = jest.fn(({ id }) => id);
8
8
  jest.mock("react-intl", () => ({
9
- ...jest.requireActual("react-intl"),
10
- useIntl: () => ({ formatMessage: mockFormatMessage }),
9
+ ...jest.requireActual("react-intl"),
10
+ useIntl: () => ({ formatMessage: mockFormatMessage }),
11
11
  }));
12
12
 
13
13
  // We DO NOT mock child forms; they will render as-is.
@@ -15,150 +15,149 @@ jest.mock("react-intl", () => ({
15
15
 
16
16
  // Helpers
17
17
  const baseSelected = {
18
- id: undefined,
19
- name: "",
20
- description: "",
21
- mode: "guided",
22
- queryables: [{ alias: "a", type: "from", properties: { resource: 1 } }],
23
- select: { type: "select" },
18
+ id: undefined,
19
+ name: "",
20
+ description: "",
21
+ mode: "guided",
22
+ queryables: [{ alias: "a", type: "from", properties: { resource: 1 } }],
23
+ select: { type: "select" },
24
24
  };
25
25
  const baseContext = {
26
- dataViews: [],
27
- referenceDatasets: [],
28
- functions: [],
29
- currentDataViewId: undefined,
26
+ dataViews: [],
27
+ referenceDatasets: [],
28
+ functions: [],
29
+ currentDataViewId: undefined,
30
30
  };
31
31
 
32
32
  describe("<SimpleDataViewEditor /> (own rendering only, no child mocks)", () => {
33
- beforeEach(() => {
34
- jest.clearAllMocks();
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ });
36
+
37
+ it("returns null when selectedDataView is not provided", async () => {
38
+ const rendered = render(
39
+ <SimpleDataViewEditor
40
+ selectedDataView={undefined}
41
+ context={baseContext}
42
+ onSubmit={jest.fn()}
43
+ onCancel={jest.fn()}
44
+ isSubmitting={false}
45
+ mode="guided"
46
+ />
47
+ );
48
+ await waitForLoad(rendered);
49
+ expect(rendered.container).toBeEmptyDOMElement();
50
+ });
51
+
52
+ it("renders step headers and the forward action on initial step", async () => {
53
+ const rendered = render(
54
+ <SimpleDataViewEditor
55
+ selectedDataView={{ ...baseSelected, source_id: 10 }}
56
+ context={baseContext}
57
+ onSubmit={jest.fn()}
58
+ onCancel={jest.fn()}
59
+ isSubmitting={false}
60
+ mode="guided"
61
+ />
62
+ );
63
+ await waitForLoad(rendered);
64
+
65
+ // Step titles (they come from this component via formatMessage)
66
+ expect(
67
+ rendered.getByText("dataViews.form.step.information.title")
68
+ ).toBeInTheDocument();
69
+ expect(
70
+ rendered.getByText("dataViews.form.step.dataset.title")
71
+ ).toBeInTheDocument();
72
+ expect(
73
+ rendered.getByText("dataViews.form.step.aggregations.title")
74
+ ).toBeInTheDocument();
75
+ expect(
76
+ rendered.getByText("dataViews.form.step.select.title")
77
+ ).toBeInTheDocument();
78
+
79
+ // Initial actions: Cancel exists; Forward exists; Back should not (step 0)
80
+ expect(
81
+ screen.getByRole("button", { name: /dataViews\.action\.forward/i })
82
+ ).toBeInTheDocument();
83
+ expect(
84
+ screen.queryByRole("button", { name: /dataViews\.action\.back/i })
85
+ ).not.toBeInTheDocument();
86
+
87
+ // Save button only appears on last step → not present yet
88
+ expect(
89
+ screen.queryByRole("button", { name: /actions\.save/i })
90
+ ).not.toBeInTheDocument();
91
+ });
92
+
93
+ it("disables the forward action (and keeps back hidden) when isSubmitting is true on step 0", async () => {
94
+ const rendered = render(
95
+ <SimpleDataViewEditor
96
+ selectedDataView={{ ...baseSelected, source_id: 10 }}
97
+ context={baseContext}
98
+ onSubmit={jest.fn()}
99
+ onCancel={jest.fn()}
100
+ isSubmitting={true}
101
+ mode="guided"
102
+ />
103
+ );
104
+ await waitForLoad(rendered);
105
+
106
+ const forward = screen.getByRole("button", {
107
+ name: /dataViews\.action\.forward/i,
35
108
  });
36
-
37
- it("returns null when selectedDataView is not provided", async () => {
38
- const rendered = render(
39
- <SimpleDataViewEditor
40
- selectedDataView={undefined}
41
- context={baseContext}
42
- onSubmit={jest.fn()}
43
- onCancel={jest.fn()}
44
- isSubmitting={false}
45
- mode="guided"
46
- />
47
- );
48
- await waitForLoad(rendered);
49
- expect(rendered.container).toBeEmptyDOMElement();
50
- });
51
-
52
- it("renders step headers and the forward action on initial step", async () => {
53
- const rendered = render(
54
- <SimpleDataViewEditor
55
- selectedDataView={{ ...baseSelected, source_id: 10 }}
56
- context={baseContext}
57
- onSubmit={jest.fn()}
58
- onCancel={jest.fn()}
59
- isSubmitting={false}
60
- mode="guided"
61
- />
62
- );
63
- await waitForLoad(rendered);
64
-
65
- // Step titles (they come from this component via formatMessage)
66
- expect(
67
- rendered.getByText("dataViews.form.step.information.title")
68
- ).toBeInTheDocument();
69
- expect(
70
- rendered.getByText("dataViews.form.step.dataset.title")
71
- ).toBeInTheDocument();
72
- expect(
73
- rendered.getByText("dataViews.form.step.aggregations.title")
74
- ).toBeInTheDocument();
75
- expect(
76
- rendered.getByText("dataViews.form.step.select.title")
77
- ).toBeInTheDocument();
78
-
79
- // Initial actions: Cancel exists; Forward exists; Back should not (step 0)
80
- expect(
81
- screen.getByRole("button", { name: /dataViews\.action\.forward/i })
82
- ).toBeInTheDocument();
83
- expect(
84
- screen.queryByRole("button", { name: /dataViews\.action\.back/i })
85
- ).not.toBeInTheDocument();
86
-
87
- // Save button only appears on last step → not present yet
88
- expect(
89
- screen.queryByRole("button", { name: /actions\.save/i })
90
- ).not.toBeInTheDocument();
91
- });
92
-
93
-
94
- it("disables the forward action (and keeps back hidden) when isSubmitting is true on step 0", async () => {
95
- const rendered = render(
96
- <SimpleDataViewEditor
97
- selectedDataView={{ ...baseSelected, source_id: 10 }}
98
- context={baseContext}
99
- onSubmit={jest.fn()}
100
- onCancel={jest.fn()}
101
- isSubmitting={true}
102
- mode="guided"
103
- />
104
- );
105
- await waitForLoad(rendered);
106
-
107
- const forward = screen.getByRole("button", {
108
- name: /dataViews\.action\.forward/i,
109
- });
110
- expect(forward).toBeDisabled();
111
-
112
- expect(
113
- screen.queryByRole("button", { name: /dataViews\.action\.back/i })
114
- ).not.toBeInTheDocument();
115
- });
116
-
117
- it("renders Cancel and Forward buttons consistently on initial render", async () => {
118
- const rendered = render(
119
- <SimpleDataViewEditor
120
- selectedDataView={{ ...baseSelected, source_id: 123 }}
121
- context={baseContext}
122
- onSubmit={jest.fn()}
123
- onCancel={jest.fn()}
124
- isSubmitting={false}
125
- mode="guided"
126
- />
127
- );
128
- await waitForLoad(rendered);
129
-
130
- expect(rendered.getByText(/cancel/i)).toBeInTheDocument();
131
-
132
- const forward = rendered.getByRole("button", {
133
- name: /dataViews\.action\.forward/i,
134
- });
135
- expect(forward).toBeInTheDocument();
136
- });
137
-
138
- it("renders step descriptions (from this component) regardless of child internals", async () => {
139
- const rendered = render(
140
- <SimpleDataViewEditor
141
- selectedDataView={{ ...baseSelected, source_id: 10 }}
142
- context={baseContext}
143
- onSubmit={jest.fn()}
144
- onCancel={jest.fn()}
145
- isSubmitting={false}
146
- mode="guided"
147
- />
148
- );
149
- await waitForLoad(rendered);
150
-
151
- expect(
152
- rendered.getByText("dataViews.form.step.information.description")
153
- ).toBeInTheDocument();
154
- expect(
155
- rendered.getByText("dataViews.form.step.dataset.description")
156
- ).toBeInTheDocument();
157
- expect(
158
- rendered.getByText("dataViews.form.step.aggregations.description")
159
- ).toBeInTheDocument();
160
- expect(
161
- rendered.getByText("dataViews.form.step.select.description")
162
- ).toBeInTheDocument();
109
+ expect(forward).toBeDisabled();
110
+
111
+ expect(
112
+ screen.queryByRole("button", { name: /dataViews\.action\.back/i })
113
+ ).not.toBeInTheDocument();
114
+ });
115
+
116
+ it("renders Cancel and Forward buttons consistently on initial render", async () => {
117
+ const rendered = render(
118
+ <SimpleDataViewEditor
119
+ selectedDataView={{ ...baseSelected, source_id: 123 }}
120
+ context={baseContext}
121
+ onSubmit={jest.fn()}
122
+ onCancel={jest.fn()}
123
+ isSubmitting={false}
124
+ mode="guided"
125
+ />
126
+ );
127
+ await waitForLoad(rendered);
128
+
129
+ expect(rendered.getByText(/cancel/i)).toBeInTheDocument();
130
+
131
+ const forward = rendered.getByRole("button", {
132
+ name: /dataViews\.action\.forward/i,
163
133
  });
134
+ expect(forward).toBeInTheDocument();
135
+ });
136
+
137
+ it("renders step descriptions (from this component) regardless of child internals", async () => {
138
+ const rendered = render(
139
+ <SimpleDataViewEditor
140
+ selectedDataView={{ ...baseSelected, source_id: 10 }}
141
+ context={baseContext}
142
+ onSubmit={jest.fn()}
143
+ onCancel={jest.fn()}
144
+ isSubmitting={false}
145
+ mode="guided"
146
+ />
147
+ );
148
+ await waitForLoad(rendered);
149
+
150
+ expect(
151
+ rendered.getByText("dataViews.form.step.information.description")
152
+ ).toBeInTheDocument();
153
+ expect(
154
+ rendered.getByText("dataViews.form.step.dataset.description")
155
+ ).toBeInTheDocument();
156
+ expect(
157
+ rendered.getByText("dataViews.form.step.aggregations.description")
158
+ ).toBeInTheDocument();
159
+ expect(
160
+ rendered.getByText("dataViews.form.step.select.description")
161
+ ).toBeInTheDocument();
162
+ });
164
163
  });