@truedat/qx 7.6.0 → 7.6.2

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 (35) hide show
  1. package/package.json +3 -3
  2. package/src/components/index.js +2 -1
  3. package/src/components/qualityControls/QualityControlEditor.js +2 -0
  4. package/src/components/qualityControls/QualityControlRoutes.js +13 -4
  5. package/src/components/qualityControls/QualityControlScores.js +141 -91
  6. package/src/components/qualityControls/ScoreCriteriaView.js +2 -2
  7. package/src/components/qualityControls/__tests__/NewQualityControl.spec.js +12 -4
  8. package/src/components/qualityControls/__tests__/QualityBadge.spec.js +3 -3
  9. package/src/components/qualityControls/__tests__/QualityControlEditor.spec.js +42 -14
  10. package/src/components/qualityControls/__tests__/QualityControlRoutes.spec.js +128 -0
  11. package/src/components/qualityControls/__tests__/QualityControlScores.spec.js +154 -48
  12. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlRoutes.spec.js.snap +27 -0
  13. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlScores.spec.js.snap +71 -0
  14. package/src/components/qualityControls/qualityByControlMode.js +6 -6
  15. package/src/components/qualityControls/scoreCriterias/Count.js +2 -2
  16. package/src/components/qualityControls/scoreCriterias/ErrorCount.js +2 -2
  17. package/src/components/qualityControls/scoreCriterias/__tests__/ErrorCount.spec.js +7 -7
  18. package/src/components/qualityControls/scoreCriterias/__tests__/__snapshots__/ErrorCount.spec.js.snap +3 -0
  19. package/src/components/scores/ScoreContext.js +64 -2
  20. package/src/components/scores/ScoreDetails.js +13 -2
  21. package/src/components/scores/ScoreGroupsTable.js +55 -50
  22. package/src/components/scores/ScorePagination.js +28 -0
  23. package/src/components/scores/__tests__/ScoreContext.spec.js +64 -0
  24. package/src/components/scores/__tests__/ScorePagination.spec.js +66 -0
  25. package/src/components/scores/__tests__/__snapshots__/ScoreDetails.spec.js.snap +13 -0
  26. package/src/components/scores/__tests__/__snapshots__/ScorePagination.spec.js.snap +220 -0
  27. package/src/components/scores/index.js +4 -2
  28. package/src/components/selectors/__tests__/__snapshots__/getMyScoreGroupsColumns.spec.js.snap +54 -0
  29. package/src/components/selectors/__tests__/__snapshots__/getQualityControlScoresColumns.spec.js.snap +34 -0
  30. package/src/components/selectors/__tests__/getMyScoreGroupsColumns.spec.js +59 -0
  31. package/src/components/selectors/__tests__/getQualityControlScoresColumns.spec.js +43 -0
  32. package/src/components/selectors/getMyScoreGroupsColumns.js +50 -0
  33. package/src/components/{qualityControls/qualityControlScoresColumns.js → selectors/getQualityControlScoresColumns.js} +19 -4
  34. package/src/components/selectors/index.js +2 -0
  35. package/src/hooks/useScores.js +6 -3
@@ -1,18 +1,24 @@
1
1
  import _ from "lodash/fp";
2
2
  import PropTypes from "prop-types";
3
+ import { connect } from "react-redux";
3
4
  import { FormattedMessage } from "react-intl";
4
- import { Table, Header, Icon, Label } from "semantic-ui-react";
5
+ import { Table, Header, Icon } from "semantic-ui-react";
5
6
  import { Loading } from "@truedat/core/components";
6
7
  import { columnDecorator } from "@truedat/core/services";
7
8
  import { useSearchContext } from "@truedat/core/search/SearchContext";
9
+ import { getMyScoreGroupsColumns } from "@truedat/qx/components/selectors/getMyScoreGroupsColumns";
8
10
  import Pagination from "@truedat/core/search/Pagination";
9
11
 
10
12
  import ScoreGroupLink from "./ScoreGroupLink";
11
13
 
12
14
  export const HeaderRow = ({ columns }) => (
13
- <Table.Row>{columns.map(({ name: id, textAlign }, i) => (
14
- <Table.HeaderCell key={i} textAlign={textAlign}><FormattedMessage id={id} defaultMessage={id} /></Table.HeaderCell>
15
- ))}</Table.Row>
15
+ <Table.Row>
16
+ {columns.map(({ name: id, textAlign }, i) => (
17
+ <Table.HeaderCell key={i} textAlign={textAlign}>
18
+ <FormattedMessage id={id} defaultMessage={id} />
19
+ </Table.HeaderCell>
20
+ ))}
21
+ </Table.Row>
16
22
  );
17
23
 
18
24
  HeaderRow.propTypes = {
@@ -20,66 +26,65 @@ HeaderRow.propTypes = {
20
26
  };
21
27
 
22
28
  export const ScoreGroupRow = ({ columns, ...props }) => (
23
- <Table.Row>{columns.map((col, i) => (
29
+ <Table.Row>
30
+ {columns.map((col, i) => (
24
31
  <Table.Cell
25
32
  key={i}
26
33
  textAlign={col.textAlign}
27
34
  content={columnDecorator(col)(props)}
28
35
  />
29
- ))}</Table.Row>
36
+ ))}
37
+ </Table.Row>
30
38
  );
31
39
 
32
40
  ScoreGroupRow.propTypes = {
33
41
  columns: PropTypes.arrayOf(PropTypes.object),
34
42
  };
35
43
 
36
- export default function ScoreGroupsTable() {
44
+ export function ScoreGroupsTable({ columns }) {
37
45
  const { searchData, loading } = useSearchContext();
38
46
  const scoreGroups = _.propOr([], "data")(searchData);
39
47
 
40
- const statusDecorator = (field, color) => {
41
- return field ? (
42
- <Label
43
- circular
44
- color={
45
- {
46
- PENDING: "yellow",
47
- QUEUED: "yellow",
48
- STARTED: "yellow",
49
- SUCCEEDED: "green",
50
- FAILED: "red",
51
- TIMEOUT: "red",
52
- }[color]
53
- }
54
- >{field}</Label>
55
- ) : null;
56
- };
48
+ return (
49
+ <>
50
+ {loading ? <Loading /> : null}
51
+ <>
52
+ {!_.isEmpty(scoreGroups) ? (
53
+ <>
54
+ <Table collapsing striped celled>
55
+ <Table.Header>
56
+ <HeaderRow columns={columns} />
57
+ </Table.Header>
58
+ <Table.Body>
59
+ {scoreGroups
60
+ ? scoreGroups.map((props, key) => (
61
+ <ScoreGroupRow key={key} columns={columns} {...props} />
62
+ ))
63
+ : null}
64
+ </Table.Body>
65
+ </Table>
66
+ <Pagination />
67
+ </>
68
+ ) : null}
69
+ {_.isEmpty(scoreGroups) ? (
70
+ <Header as="h4">
71
+ <Icon name="search" />
72
+ <Header.Content>
73
+ <FormattedMessage id="score_groups.search.results.empty" />
74
+ </Header.Content>
75
+ </Header>
76
+ ) : null}
77
+ </>
78
+ </>
79
+ );
80
+ }
57
81
 
58
- const statusColumns = _.map((status) => ({
59
- name: `score.status.${status}`,
60
- fieldSelector: _.pathOr(null, `status_summary.${status}`),
61
- fieldDecorator: (field) => statusDecorator(field, status),
62
- textAlign: "center",
63
- width: 2,
64
- }))(["PENDING", "QUEUED", "STARTED", "SUCCEEDED", "FAILED", "TIMEOUT"]);
82
+ ScoreGroupsTable.propTypes = {
83
+ columns: PropTypes.array,
84
+ };
65
85
 
66
- const columns = [
67
- {
68
- name: "score_groups.table.header.created",
69
- width: 4,
70
- fieldSelector: _.identity,
71
- fieldDecorator: ScoreGroupLink,
72
- },
73
- ...statusColumns,
74
- ];
86
+ const mapStateToProps = (state) => ({
87
+ columns: getMyScoreGroupsColumns(state),
88
+ });
75
89
 
76
- return (<>{loading ? <Loading /> : null}<>{!_.isEmpty(scoreGroups) ? (
77
- <><Table collapsing striped celled><Table.Header><HeaderRow columns={columns} /></Table.Header><Table.Body>{scoreGroups
78
- ? scoreGroups.map((props, key) => (
79
- <ScoreGroupRow key={key} columns={columns} {...props} />
80
- ))
81
- : null}</Table.Body></Table><Pagination /></>
82
- ) : null}{_.isEmpty(scoreGroups) ? (
83
- <Header as="h4"><Icon name="search" /><Header.Content><FormattedMessage id="score_groups.search.results.empty" /></Header.Content></Header>
84
- ) : null}</></>);
85
- }
90
+ export default connect(mapStateToProps)(ScoreGroupsTable);
@@ -0,0 +1,28 @@
1
+ import _ from "lodash/fp";
2
+ import { Pagination as SemanticPagination } from "semantic-ui-react";
3
+ import { useScoreContext } from "../scores/ScoreContext";
4
+
5
+ export const MAX_PAGES = 100;
6
+
7
+ export const ScorePagination = () => {
8
+ const { totalPages, currentPage, selectPage } = useScoreContext();
9
+
10
+ return totalPages > MAX_PAGES ? (
11
+ <SemanticPagination
12
+ activePage={currentPage}
13
+ boundaryRange={0}
14
+ lastItem={null}
15
+ totalPages={MAX_PAGES}
16
+ onPageChange={(_e, { activePage }) => selectPage(activePage)}
17
+ />
18
+ ) : totalPages ? (
19
+ <SemanticPagination
20
+ activePage={currentPage}
21
+ disabled={totalPages <= 1}
22
+ totalPages={totalPages}
23
+ onPageChange={(_e, { activePage }) => selectPage(activePage)}
24
+ />
25
+ ) : null;
26
+ };
27
+
28
+ export default ScorePagination;
@@ -0,0 +1,64 @@
1
+ import { waitFor } from "@testing-library/react";
2
+ import { render } from "@truedat/test/render";
3
+ import { ScoreContextProvider, useScoreContext } from "../ScoreContext";
4
+ import { score } from "./__fixtures__/scoreHelper";
5
+
6
+ const mockUseQualityControlScores = jest.fn();
7
+ const mockUseScoreDelete = jest.fn();
8
+
9
+ jest.mock("@truedat/qx/hooks/useScores", () => ({
10
+ useQualityControlScores: () => mockUseQualityControlScores(),
11
+ useScoreDelete: () => mockUseScoreDelete(),
12
+ }));
13
+
14
+ const TestComponent = () => {
15
+ const context = useScoreContext();
16
+ return <div data-testid="test-component">{JSON.stringify(context)}</div>;
17
+ };
18
+
19
+ describe("<ScoreContextProvider />", () => {
20
+ beforeEach(() => {
21
+ mockUseQualityControlScores.mockReturnValue({
22
+ trigger: jest.fn().mockResolvedValue({
23
+ data: {
24
+ data: {
25
+ scores: [score],
26
+ last_execution_result: {},
27
+ current_page: 1,
28
+ total_pages: 5,
29
+ page_size: 10,
30
+ },
31
+ },
32
+ }),
33
+
34
+ loading: false,
35
+ });
36
+
37
+ mockUseScoreDelete.mockReturnValue({
38
+ trigger: jest.fn(),
39
+ });
40
+ });
41
+
42
+ it("provides score context to children", async () => {
43
+ const rendered = render(
44
+ <ScoreContextProvider qualityControlId={1}>
45
+ <TestComponent />
46
+ </ScoreContextProvider>
47
+ );
48
+
49
+ await waitFor(() => {
50
+ const contextData = JSON.parse(
51
+ rendered.getByTestId("test-component").textContent
52
+ );
53
+ expect(contextData).toMatchObject({
54
+ scores: [score],
55
+ loadingQualityScores: false,
56
+ lastExecutionResult: {},
57
+ currentPage: 1,
58
+ totalPages: 5,
59
+ size: 10,
60
+ page: 1,
61
+ });
62
+ });
63
+ });
64
+ });
@@ -0,0 +1,66 @@
1
+ import { render, waitForLoad } from "@truedat/test/render";
2
+ import { useScoreContext } from "../../scores/ScoreContext";
3
+ import { ScorePagination, MAX_PAGES } from "../ScorePagination";
4
+
5
+ jest.mock("../../scores/ScoreContext", () => {
6
+ const originalModule = jest.requireActual("../../scores/ScoreContext");
7
+ return {
8
+ __esModule: true,
9
+ ...originalModule,
10
+ useScoreContext: jest.fn(),
11
+ };
12
+ });
13
+
14
+ describe("<ScorePagination />", () => {
15
+ it("matches the latest snapshot", async () => {
16
+ useScoreContext.mockReturnValue({
17
+ totalPages: 10,
18
+ currentPage: 1,
19
+ selectPage: jest.fn(),
20
+ });
21
+
22
+ const rendered = render(<ScorePagination />);
23
+ await waitForLoad(rendered);
24
+ expect(rendered.container).toMatchSnapshot();
25
+ });
26
+
27
+ it("matches the latest snapshot (totalPages > 100)", async () => {
28
+ useScoreContext.mockReturnValue({
29
+ totalPages: 200,
30
+ currentPage: 1,
31
+ selectPage: jest.fn(),
32
+ });
33
+
34
+ const rendered = render(<ScorePagination />);
35
+ await waitForLoad(rendered);
36
+ expect(rendered.container).toMatchSnapshot();
37
+ });
38
+
39
+ it("has no last item if totalPages exceeds page limit", async () => {
40
+ useScoreContext.mockReturnValue({
41
+ totalPages: MAX_PAGES + 1,
42
+ currentPage: 1,
43
+ selectPage: jest.fn(),
44
+ });
45
+
46
+ const rendered = render(<ScorePagination />);
47
+ await waitForLoad(rendered);
48
+ const pagination = rendered.container.querySelector(".ui.pagination");
49
+ expect(pagination.querySelector("[aria-label='Last item']")).toBeNull();
50
+ });
51
+
52
+ it("has a last item if totalPages is within the page limit", async () => {
53
+ useScoreContext.mockReturnValue({
54
+ totalPages: MAX_PAGES,
55
+ currentPage: 1,
56
+ selectPage: jest.fn(),
57
+ });
58
+
59
+ const rendered = render(<ScorePagination />);
60
+ await waitForLoad(rendered);
61
+ const pagination = rendered.container.querySelector(".ui.pagination");
62
+ expect(
63
+ pagination.querySelector("[aria-label='Last item']")
64
+ ).toHaveTextContent("»");
65
+ });
66
+ });
@@ -95,6 +95,19 @@ exports[`<ScoreDetails /> matches the latest snapshot 1`] = `
95
95
  Deviation
96
96
  </div>
97
97
  </div>
98
+ <div
99
+ class="item"
100
+ role="listitem"
101
+ >
102
+ <div
103
+ class="header"
104
+ >
105
+ scores.props.created_at
106
+ </div>
107
+ <div
108
+ class="description"
109
+ />
110
+ </div>
98
111
  <div
99
112
  class="item"
100
113
  role="listitem"
@@ -0,0 +1,220 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ScorePagination /> matches the latest snapshot (totalPages > 100) 1`] = `
4
+ <div>
5
+ <div
6
+ aria-label="Pagination Navigation"
7
+ class="ui pagination menu"
8
+ role="navigation"
9
+ >
10
+ <a
11
+ aria-current="false"
12
+ aria-disabled="false"
13
+ aria-label="First item"
14
+ class="item"
15
+ tabindex="0"
16
+ type="firstItem"
17
+ value="1"
18
+ >
19
+ «
20
+ </a>
21
+ <a
22
+ aria-current="false"
23
+ aria-disabled="false"
24
+ aria-label="Previous item"
25
+ class="item"
26
+ tabindex="0"
27
+ type="prevItem"
28
+ value="1"
29
+ >
30
+
31
+ </a>
32
+ <a
33
+ aria-current="true"
34
+ aria-disabled="false"
35
+ class="active item"
36
+ tabindex="0"
37
+ type="pageItem"
38
+ value="1"
39
+ >
40
+ 1
41
+ </a>
42
+ <a
43
+ aria-current="false"
44
+ aria-disabled="false"
45
+ class="item"
46
+ tabindex="0"
47
+ type="pageItem"
48
+ value="2"
49
+ >
50
+ 2
51
+ </a>
52
+ <a
53
+ aria-current="false"
54
+ aria-disabled="false"
55
+ class="item"
56
+ tabindex="0"
57
+ type="pageItem"
58
+ value="3"
59
+ >
60
+ 3
61
+ </a>
62
+ <a
63
+ aria-current="false"
64
+ aria-disabled="false"
65
+ class="item"
66
+ tabindex="0"
67
+ type="pageItem"
68
+ value="4"
69
+ >
70
+ 4
71
+ </a>
72
+ <a
73
+ aria-current="false"
74
+ aria-disabled="true"
75
+ class="item"
76
+ tabindex="-1"
77
+ type="ellipsisItem"
78
+ value="5"
79
+ >
80
+ ...
81
+ </a>
82
+ <a
83
+ aria-current="false"
84
+ aria-disabled="false"
85
+ aria-label="Next item"
86
+ class="item"
87
+ tabindex="0"
88
+ type="nextItem"
89
+ value="2"
90
+ >
91
+
92
+ </a>
93
+ </div>
94
+ </div>
95
+ `;
96
+
97
+ exports[`<ScorePagination /> matches the latest snapshot 1`] = `
98
+ <div>
99
+ <div
100
+ aria-label="Pagination Navigation"
101
+ class="ui pagination menu"
102
+ role="navigation"
103
+ >
104
+ <a
105
+ aria-current="false"
106
+ aria-disabled="false"
107
+ aria-label="First item"
108
+ class="item"
109
+ tabindex="0"
110
+ type="firstItem"
111
+ value="1"
112
+ >
113
+ «
114
+ </a>
115
+ <a
116
+ aria-current="false"
117
+ aria-disabled="false"
118
+ aria-label="Previous item"
119
+ class="item"
120
+ tabindex="0"
121
+ type="prevItem"
122
+ value="1"
123
+ >
124
+
125
+ </a>
126
+ <a
127
+ aria-current="true"
128
+ aria-disabled="false"
129
+ class="active item"
130
+ tabindex="0"
131
+ type="pageItem"
132
+ value="1"
133
+ >
134
+ 1
135
+ </a>
136
+ <a
137
+ aria-current="false"
138
+ aria-disabled="false"
139
+ class="item"
140
+ tabindex="0"
141
+ type="pageItem"
142
+ value="2"
143
+ >
144
+ 2
145
+ </a>
146
+ <a
147
+ aria-current="false"
148
+ aria-disabled="false"
149
+ class="item"
150
+ tabindex="0"
151
+ type="pageItem"
152
+ value="3"
153
+ >
154
+ 3
155
+ </a>
156
+ <a
157
+ aria-current="false"
158
+ aria-disabled="false"
159
+ class="item"
160
+ tabindex="0"
161
+ type="pageItem"
162
+ value="4"
163
+ >
164
+ 4
165
+ </a>
166
+ <a
167
+ aria-current="false"
168
+ aria-disabled="false"
169
+ class="item"
170
+ tabindex="0"
171
+ type="pageItem"
172
+ value="5"
173
+ >
174
+ 5
175
+ </a>
176
+ <a
177
+ aria-current="false"
178
+ aria-disabled="true"
179
+ class="item"
180
+ tabindex="-1"
181
+ type="ellipsisItem"
182
+ value="6"
183
+ >
184
+ ...
185
+ </a>
186
+ <a
187
+ aria-current="false"
188
+ aria-disabled="false"
189
+ class="item"
190
+ tabindex="0"
191
+ type="pageItem"
192
+ value="10"
193
+ >
194
+ 10
195
+ </a>
196
+ <a
197
+ aria-current="false"
198
+ aria-disabled="false"
199
+ aria-label="Next item"
200
+ class="item"
201
+ tabindex="0"
202
+ type="nextItem"
203
+ value="2"
204
+ >
205
+
206
+ </a>
207
+ <a
208
+ aria-current="false"
209
+ aria-disabled="false"
210
+ aria-label="Last item"
211
+ class="item"
212
+ tabindex="0"
213
+ type="lastItem"
214
+ value="10"
215
+ >
216
+ »
217
+ </a>
218
+ </div>
219
+ </div>
220
+ `;
@@ -1,15 +1,17 @@
1
+ import MyScoreGroups from "./MyScoreGroups";
1
2
  import Score from "./Score";
3
+ import ScoreContext from "./ScoreContext";
2
4
  import ScoreDetails from "./ScoreDetails";
3
5
  import ScoreEvents from "./ScoreEvents";
4
6
  import ScoreGroup from "./ScoreGroup";
5
7
  import ScoreGroupPopup from "./ScoreGroupPopup";
6
- import MyScoreGroups from "./MyScoreGroups";
7
8
 
8
9
  export {
10
+ MyScoreGroups,
9
11
  Score,
12
+ ScoreContext,
10
13
  ScoreDetails,
11
14
  ScoreEvents,
12
15
  ScoreGroup,
13
16
  ScoreGroupPopup,
14
- MyScoreGroups,
15
17
  };
@@ -0,0 +1,54 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`selectors: getMyScoreGroupsColumns should return default columns when no state is provided 1`] = `
4
+ [
5
+ {
6
+ "fieldDecorator": [Function],
7
+ "fieldSelector": [Function],
8
+ "name": "score_groups.table.header.created",
9
+ "width": 4,
10
+ },
11
+ {
12
+ "fieldDecorator": [Function],
13
+ "fieldSelector": [Function],
14
+ "name": "score.status.PENDING",
15
+ "textAlign": "center",
16
+ "width": 2,
17
+ },
18
+ {
19
+ "fieldDecorator": [Function],
20
+ "fieldSelector": [Function],
21
+ "name": "score.status.QUEUED",
22
+ "textAlign": "center",
23
+ "width": 2,
24
+ },
25
+ {
26
+ "fieldDecorator": [Function],
27
+ "fieldSelector": [Function],
28
+ "name": "score.status.STARTED",
29
+ "textAlign": "center",
30
+ "width": 2,
31
+ },
32
+ {
33
+ "fieldDecorator": [Function],
34
+ "fieldSelector": [Function],
35
+ "name": "score.status.SUCCEEDED",
36
+ "textAlign": "center",
37
+ "width": 2,
38
+ },
39
+ {
40
+ "fieldDecorator": [Function],
41
+ "fieldSelector": [Function],
42
+ "name": "score.status.FAILED",
43
+ "textAlign": "center",
44
+ "width": 2,
45
+ },
46
+ {
47
+ "fieldDecorator": [Function],
48
+ "fieldSelector": [Function],
49
+ "name": "score.status.TIMEOUT",
50
+ "textAlign": "center",
51
+ "width": 2,
52
+ },
53
+ ]
54
+ `;
@@ -0,0 +1,34 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`selectors: getQualityControlScoresColumns should return default columns when no state is provided 1`] = `
4
+ [
5
+ {
6
+ "fieldDecorator": [Function],
7
+ "fieldSelector": [Function],
8
+ "name": "execution_timestamp",
9
+ },
10
+ {
11
+ "fieldDecorator": [Function],
12
+ "name": "status",
13
+ },
14
+ {
15
+ "fieldDecorator": [Function],
16
+ "fieldSelector": [Function],
17
+ "name": "quality",
18
+ },
19
+ {
20
+ "fieldDecorator": [Function],
21
+ "fieldSelector": [Function],
22
+ "name": "control_mode",
23
+ },
24
+ {
25
+ "fieldDecorator": [Function],
26
+ "fieldSelector": [Function],
27
+ "name": "created_at",
28
+ },
29
+ {
30
+ "fieldDecorator": [Function],
31
+ "name": "quality_control_status",
32
+ },
33
+ ]
34
+ `;