@truedat/qx 7.3.0 → 7.3.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 (31) hide show
  1. package/package.json +4 -4
  2. package/src/api.js +4 -0
  3. package/src/components/__tests__/__fixtures__/helper.js +1 -0
  4. package/src/components/qualityControls/QualityControlActions.js +6 -1
  5. package/src/components/qualityControls/QualityControlEditor.js +1 -1
  6. package/src/components/qualityControls/QualityControls.js +4 -2
  7. package/src/components/qualityControls/QualityControlsPagination.js +16 -0
  8. package/src/components/qualityControls/QualityControlsTable.js +1 -0
  9. package/src/components/qualityControls/__tests__/QualityControlActions.spec.js +10 -2
  10. package/src/components/qualityControls/__tests__/QualityControlHeader.spec.js +11 -1
  11. package/src/components/qualityControls/__tests__/QualityControls.spec.js +1 -2
  12. package/src/components/qualityControls/__tests__/QualityControlsPagination.spec.js +44 -0
  13. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlHeader.spec.js.snap +46 -1
  14. package/src/components/qualityControls/__tests__/__snapshots__/QualityControls.spec.js.snap +1 -1
  15. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlsPagination.spec.js.snap +96 -0
  16. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlsTable.spec.js.snap +1 -1
  17. package/src/components/scores/MyScoreGroups.js +36 -4
  18. package/src/components/scores/ScoreGroupBreadcrumbs.js +2 -2
  19. package/src/components/scores/ScoreGroupForm.js +1 -1
  20. package/src/components/scores/ScoreGroupsTable.js +60 -55
  21. package/src/components/scores/ScoreRoutes.js +0 -1
  22. package/src/components/scores/__tests__/MyScoreGroups.spec.js +50 -10
  23. package/src/components/scores/__tests__/ScoreGroupForm.spec.js +1 -1
  24. package/src/components/scores/__tests__/ScoreGroupPopup.spec.js +1 -1
  25. package/src/components/scores/__tests__/ScoreGroupsTable.spec.js +26 -24
  26. package/src/components/scores/__tests__/__snapshots__/MyScoreGroups.spec.js.snap +134 -0
  27. package/src/components/scores/__tests__/__snapshots__/ScoreGroup.spec.js.snap +1 -1
  28. package/src/components/scores/__tests__/__snapshots__/ScoreGroupBreadcrumbs.spec.js.snap +1 -1
  29. package/src/components/scores/__tests__/__snapshots__/ScoreGroupsTable.spec.js.snap +22 -0
  30. package/src/hooks/useScoreGroups.js +18 -1
  31. package/src/hooks/useScores.js +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/qx",
3
- "version": "7.3.0",
3
+ "version": "7.3.2",
4
4
  "description": "Truedat Web Quality Experience package",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -35,7 +35,7 @@
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/react-hooks": "^8.0.1",
37
37
  "@testing-library/user-event": "^13.2.1",
38
- "@truedat/test": "7.3.0",
38
+ "@truedat/test": "7.3.2",
39
39
  "babel-jest": "^28.1.0",
40
40
  "babel-plugin-dynamic-import-node": "^2.3.3",
41
41
  "babel-plugin-lodash": "^3.3.4",
@@ -90,7 +90,7 @@
90
90
  ]
91
91
  },
92
92
  "dependencies": {
93
- "@truedat/core": "7.3.0",
93
+ "@truedat/core": "7.3.2",
94
94
  "prop-types": "^15.8.1",
95
95
  "react-hook-form": "^7.45.4",
96
96
  "react-intl": "^5.20.10",
@@ -106,5 +106,5 @@
106
106
  "overrides": {
107
107
  "jsdom": "26.0.0"
108
108
  },
109
- "gitHead": "cbf85fe17b535953689418b85658cc804d095dfb"
109
+ "gitHead": "3a5d8fc5160ffabc1ef86251883070331045f738"
110
110
  }
package/src/api.js CHANGED
@@ -12,6 +12,8 @@ const API_QUALITY_CONTROL_SEARCH = "/api/quality_controls/search";
12
12
  const API_QUALITY_CONTROL_STATUS = "/api/quality_controls/:id/status";
13
13
  const API_QUALITY_CONTROL_VERSIONS = "/api/quality_controls/:id/versions";
14
14
  const API_QUALITY_CONTROLS = "/api/quality_controls";
15
+ const API_SCORE_GROUPS_SEARCH = "/api/score_groups/search";
16
+ const API_SCORE_GROUPS_FILTERS = "/api/score_groups/filters";
15
17
  const API_SCORE_GROUPS = "/api/score_groups";
16
18
  const API_SCORE_GROUP = "/api/score_groups/:id";
17
19
  const API_SCORE = "/api/scores/:id";
@@ -43,4 +45,6 @@ export {
43
45
  API_QUALITY_CONTROL_EXECUTION_GROUP,
44
46
  API_QUALITY_CONTROL_EXECUTION_GROUPS,
45
47
  API_QUALITY_CONTROL_SCORES,
48
+ API_SCORE_GROUPS_SEARCH,
49
+ API_SCORE_GROUPS_FILTERS,
46
50
  };
@@ -161,6 +161,7 @@ export const messages = {
161
161
  "score.status.QUEUED": "Queued",
162
162
  "score.status.SUCCEEDED": "Succeeded",
163
163
  "score.status.STARTED": "Started",
164
+ "score.status.TIMEOUT": "Timeout",
164
165
  "sidemenu.executions": "My Executions",
165
166
  "score_groups.header": "My score Groups",
166
167
  "score_groups.table.header.created": "Created",
@@ -70,8 +70,12 @@ export default function QualityControlActions() {
70
70
  quality_control: { active: !qualityControl.active },
71
71
  }).then(() => mutate());
72
72
 
73
+ const canExecute = _.includes("execute")(actions);
74
+
73
75
  const availableActions = _.flow(
74
- _.reject(_.includes(["delete_score"])),
76
+ _.reject((action) =>
77
+ _.includes(action, ["delete_score", "update_main", "execute"])
78
+ ),
75
79
  _.map((action) => {
76
80
  const actionIcon = {
77
81
  create_draft: "edit",
@@ -142,6 +146,7 @@ export default function QualityControlActions() {
142
146
  ) : null}
143
147
 
144
148
  {qualityControl.active &&
149
+ canExecute &&
145
150
  !_.includes(qualityControl.status)(["deprecated", "versioned"]) ? (
146
151
  <ScoreGroupPopup
147
152
  loading={groupCreating}
@@ -24,7 +24,7 @@ const SourceSelector = React.lazy(() =>
24
24
  import("@truedat/cx/sources/components/SourceSelector")
25
25
  );
26
26
 
27
- export default function QualityControlEditor({
27
+ export default function QualityControlEditor({
28
28
  value,
29
29
  onPublish,
30
30
  onSave,
@@ -19,6 +19,7 @@ import { useSearchContext } from "@truedat/core/search/SearchContext";
19
19
  import SearchWidget from "@truedat/core/search/SearchWidget";
20
20
  import { useScoreGroupCreate } from "@truedat/qx/hooks/useScoreGroups";
21
21
  import { ScoreGroupPopup } from "../scores";
22
+ import QualityControlsPagination from "./QualityControlsPagination";
22
23
  import QualityControlsTable from "./QualityControlsTable";
23
24
  import QualityControlsLabelResults from "./QualityControlsLabelResults";
24
25
 
@@ -27,7 +28,7 @@ export const QualityControls = () => {
27
28
  const history = useHistory();
28
29
 
29
30
  const { data } = useAuthorizedAction({
30
- action: "createQualityControls",
31
+ action: "writeQualityControls",
31
32
  });
32
33
  const canCreate = _.prop("has_any_domain")(data);
33
34
 
@@ -122,7 +123,7 @@ export const QualityControls = () => {
122
123
  const body = _.isEmpty(selectedQualityControls)
123
124
  ? {
124
125
  score_group,
125
- search: { query, must: searchMust },
126
+ search: { ...(!_.isEmpty(query) && { query }), must: searchMust },
126
127
  }
127
128
  : {
128
129
  score_group,
@@ -205,6 +206,7 @@ export const QualityControls = () => {
205
206
  executeQualityControlOn={executeQualityControlOn}
206
207
  isRowChecked={isRowChecked}
207
208
  />
209
+ <QualityControlsPagination />
208
210
  </Dimmer.Dimmable>
209
211
  </Segment>
210
212
  );
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import { Pagination } from "@truedat/core/components";
3
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
4
+
5
+ export default function QualityControlsPagination() {
6
+ const { count, size, page, selectPage } = useSearchContext();
7
+ const totalPages = Math.ceil(count / size);
8
+
9
+ return (
10
+ <Pagination
11
+ totalPages={totalPages}
12
+ activePage={page}
13
+ selectPage={selectPage}
14
+ />
15
+ );
16
+ }
@@ -27,6 +27,7 @@ DateDecorator.propTypes = {
27
27
  export const columns = [
28
28
  {
29
29
  name: "name",
30
+ sort: { name: "name.raw" },
30
31
  fieldSelector: _.pick(["id", "name"]),
31
32
  fieldDecorator: ({ id, name }) => (
32
33
  <Link to={linkTo.QUALITY_CONTROL({ id })}>{name}</Link>
@@ -53,7 +53,15 @@ jest.mock("@truedat/qx/hooks/useScoreGroups", () => {
53
53
  };
54
54
  });
55
55
 
56
- const actions = ["deprecate", "create_draft", "toggle_active"];
56
+ const actions = [
57
+ "deprecate",
58
+ "create_draft",
59
+ "toggle_active",
60
+ "delete_score",
61
+ "update_main",
62
+ "execute",
63
+ ];
64
+
57
65
  const mutate = jest.fn();
58
66
  const qualityControl = qualityControlData();
59
67
 
@@ -62,7 +70,7 @@ const context = {
62
70
  qualityControl,
63
71
  mutate,
64
72
  };
65
- const variables = { scope: "qe" };
73
+ const variables = { scope: "qxe" };
66
74
  const renderOpts = {
67
75
  mocks: [singleTemplateMock(variables)],
68
76
  messages,
@@ -17,7 +17,17 @@ jest.mock("@truedat/qx/hooks/useQualityControls", () => {
17
17
 
18
18
  return {
19
19
  useQualityControl: jest.fn(() => ({
20
- data: { data: qualityControlData() },
20
+ data: {
21
+ data: qualityControlData(),
22
+ _actions: [
23
+ "deprecate",
24
+ "create_draft",
25
+ "toggle_active",
26
+ "delete_score",
27
+ "update_main",
28
+ "execute",
29
+ ],
30
+ },
21
31
  loading: false,
22
32
  mutate: jest.fn(),
23
33
  })),
@@ -23,7 +23,7 @@ jest.mock("@truedat/qx/hooks/useScoreGroups", () => ({
23
23
  }));
24
24
 
25
25
  const renderOpts = {
26
- mocks: [singleTemplateMock({ scope: "qe", domainIds: undefined })],
26
+ mocks: [singleTemplateMock({ scope: "qxe", domainIds: undefined })],
27
27
  messages,
28
28
  fallback: "loading",
29
29
  };
@@ -148,7 +148,6 @@ describe("<QualityControls />", () => {
148
148
  },
149
149
  search: {
150
150
  must: "",
151
- query: "",
152
151
  },
153
152
  });
154
153
  });
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { SearchContextProvider } from "@truedat/core/search/SearchContext";
4
+ import QualityControlsPagination from "../QualityControlsPagination";
5
+
6
+ const useFilters = () => ({
7
+ trigger: () => ({
8
+ then: (callback) =>
9
+ callback({
10
+ data: { data: {} },
11
+ }),
12
+ }),
13
+ });
14
+
15
+ const useSearch = () => ({
16
+ trigger: () => ({
17
+ then: (callback) =>
18
+ callback({
19
+ data: { data: [] },
20
+ headers: { "x-total-count": 22 },
21
+ }),
22
+ }),
23
+ });
24
+
25
+ const searchProps = {
26
+ useSearch: useSearch,
27
+ useFilters: useFilters,
28
+ count: 20,
29
+ size: 20,
30
+ page: 1,
31
+ selectPage: jest.fn(),
32
+ };
33
+
34
+ describe("<QualityControlsPagination />", () => {
35
+ it("matches the latest snapshot", async () => {
36
+ const { container, findByText } = render(
37
+ <SearchContextProvider {...searchProps}>
38
+ <QualityControlsPagination />
39
+ </SearchContextProvider>
40
+ );
41
+ await findByText(/1/);
42
+ expect(container).toMatchSnapshot();
43
+ });
44
+ });
@@ -90,7 +90,52 @@ exports[`<QualityControlHeader /> matches the latest snapshot with control quali
90
90
  />
91
91
  <div
92
92
  class="left menu transition"
93
- />
93
+ >
94
+ <div
95
+ aria-checked="false"
96
+ aria-selected="true"
97
+ class="selected item"
98
+ role="option"
99
+ style="pointer-events: all;"
100
+ >
101
+ <i
102
+ aria-hidden="true"
103
+ class="folder open outline icon"
104
+ />
105
+ <span
106
+ class="text"
107
+ >
108
+ Deprecate
109
+ </span>
110
+ </div>
111
+ <a
112
+ aria-checked="false"
113
+ aria-selected="false"
114
+ class="item"
115
+ href="/qualityControls/8/new_draft"
116
+ role="option"
117
+ style="pointer-events: all;"
118
+ >
119
+ <i
120
+ aria-hidden="true"
121
+ class="edit icon"
122
+ />
123
+ <span
124
+ class="text"
125
+ >
126
+ Create Draft
127
+ </span>
128
+ </a>
129
+ <button
130
+ class="ui button"
131
+ >
132
+ <i
133
+ aria-hidden="true"
134
+ class="pause icon"
135
+ />
136
+ Disable
137
+ </button>
138
+ </div>
94
139
  </div>
95
140
  </div>
96
141
  </div>
@@ -145,7 +145,7 @@ exports[`<QualityControls /> matches the latest snapshot 1`] = `
145
145
  class=""
146
146
  >
147
147
  <th
148
- class="disabled"
148
+ class=""
149
149
  >
150
150
  Name
151
151
  </th>
@@ -0,0 +1,96 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<QualityControlsPagination /> matches the latest snapshot 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="false"
75
+ aria-label="Next item"
76
+ class="item"
77
+ tabindex="0"
78
+ type="nextItem"
79
+ value="2"
80
+ >
81
+
82
+ </a>
83
+ <a
84
+ aria-current="false"
85
+ aria-disabled="false"
86
+ aria-label="Last item"
87
+ class="item"
88
+ tabindex="0"
89
+ type="lastItem"
90
+ value="4"
91
+ >
92
+ »
93
+ </a>
94
+ </div>
95
+ </div>
96
+ `;
@@ -12,7 +12,7 @@ exports[`<QualityControlsTable /> matches the latest snapshot 1`] = `
12
12
  class=""
13
13
  >
14
14
  <th
15
- class="disabled"
15
+ class=""
16
16
  >
17
17
  Name
18
18
  </th>
@@ -1,11 +1,17 @@
1
- import _ from "lodash/fp";
2
1
  import React from "react";
2
+ import PropTypes from "prop-types";
3
3
  import { FormattedMessage } from "react-intl";
4
- import { Header, Icon, Grid, Segment } from "semantic-ui-react";
5
-
4
+ import { Header, Icon, Grid, Segment, Divider } from "semantic-ui-react";
5
+ import { SearchContextProvider } from "@truedat/core/search/SearchContext";
6
+ import SearchWidget from "@truedat/core/search/SearchWidget";
7
+ import {
8
+ useScoreGroupsSearch,
9
+ useScoreGroupsFilters,
10
+ } from "@truedat/qx/hooks/useScoreGroups";
11
+ import { useAuthorized } from "@truedat/core/hooks";
6
12
  import ScoreGroupsTable from "./ScoreGroupsTable";
7
13
 
8
- export default function MyScoreGroups() {
14
+ export function MyScoreGroupsContent({ isAdmin }) {
9
15
  return (
10
16
  <Segment>
11
17
  <Grid>
@@ -18,7 +24,33 @@ export default function MyScoreGroups() {
18
24
  </Header>
19
25
  </Grid.Column>
20
26
  </Grid>
27
+ <Divider hidden />
28
+ {isAdmin ? <SearchWidget /> : null}
21
29
  <ScoreGroupsTable />
22
30
  </Segment>
23
31
  );
24
32
  }
33
+
34
+ MyScoreGroupsContent.propTypes = {
35
+ isAdmin: PropTypes.bool,
36
+ };
37
+
38
+ export default function MyScoreGroups() {
39
+ const isAdmin = useAuthorized();
40
+ const enrichSearchPayload = isAdmin ? {} : { created_by: "me" };
41
+
42
+ const searchProps = {
43
+ initialSortColumn: "inserted_at",
44
+ initialSortDirection: "ascending",
45
+ useSearch: useScoreGroupsSearch,
46
+ useFilters: useScoreGroupsFilters,
47
+ pageSize: 20,
48
+ enrichSearchPayload,
49
+ };
50
+
51
+ return (
52
+ <SearchContextProvider {...searchProps}>
53
+ <MyScoreGroupsContent isAdmin={isAdmin} />
54
+ </SearchContextProvider>
55
+ );
56
+ }
@@ -4,12 +4,12 @@ import { Breadcrumb } from "semantic-ui-react";
4
4
  import { Link } from "react-router-dom";
5
5
  import { FormattedMessage } from "react-intl";
6
6
  import { DateTime } from "@truedat/core/components";
7
- import { SCORE_GROUPS } from "@truedat/core/routes";
7
+ import { MY_SCORE_GROUPS } from "@truedat/core/routes";
8
8
 
9
9
  export default function ScoreGroupBreadcrumbs({ timestamp }) {
10
10
  return (
11
11
  <Breadcrumb>
12
- <Breadcrumb.Section as={Link} to={SCORE_GROUPS}>
12
+ <Breadcrumb.Section as={Link} to={MY_SCORE_GROUPS}>
13
13
  <FormattedMessage id="sidemenu.executions" />
14
14
  </Breadcrumb.Section>
15
15
  <Breadcrumb.Divider icon="right angle" />
@@ -40,7 +40,7 @@ export default function ScoreGroupForm({ count, onSubmit, onCancel }) {
40
40
  />
41
41
  <Form loading={templatesLoading}>
42
42
  <TemplateSelector
43
- scope="qe"
43
+ scope="qxe"
44
44
  selectedValue={template?.id}
45
45
  onChange={handleTemplateSelected}
46
46
  onLoad={handleTemplatesLoaded}
@@ -3,8 +3,10 @@ import React from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { FormattedMessage } from "react-intl";
5
5
  import { Table, Header, Icon, Label } from "semantic-ui-react";
6
+ import { Loading } from "@truedat/core/components";
6
7
  import { columnDecorator } from "@truedat/core/services";
7
- import { useMyScoreGroupsIndex } from "../../hooks/useScoreGroups";
8
+ import { useSearchContext } from "@truedat/core/search/SearchContext";
9
+ import Pagination from "@truedat/core/search/Pagination";
8
10
 
9
11
  import ScoreGroupLink from "./ScoreGroupLink";
10
12
 
@@ -39,41 +41,36 @@ ScoreGroupRow.propTypes = {
39
41
  };
40
42
 
41
43
  export default function ScoreGroupsTable() {
42
- const { data } = useMyScoreGroupsIndex();
43
- const scoreGroups = data?.data;
44
+ const { searchData, loading } = useSearchContext();
45
+ const scoreGroups = _.propOr([], "data")(searchData);
44
46
 
45
- const presentStatuses = _.flow(
46
- _.flatMap(_.flow(_.prop("status_summary"), _.keys)),
47
- _.uniq
48
- )(scoreGroups);
47
+ const statusDecorator = (field, color) => {
48
+ return field ? (
49
+ <Label
50
+ circular
51
+ color={
52
+ {
53
+ PENDING: "yellow",
54
+ QUEUED: "yellow",
55
+ STARTED: "yellow",
56
+ SUCCEEDED: "green",
57
+ FAILED: "red",
58
+ TIMEOUT: "red",
59
+ }[color]
60
+ }
61
+ >
62
+ {field}
63
+ </Label>
64
+ ) : null;
65
+ };
49
66
 
50
- const statusColumns = _.flow(
51
- _.filter((status) => _.includes(status)(presentStatuses)),
52
- _.map((status) => ({
53
- name: `score.status.${status}`,
54
- fieldSelector: `status_summary.${status}`,
55
- fieldDecorator: (count) =>
56
- count ? (
57
- <Label
58
- circular
59
- color={
60
- {
61
- PENDING: "yellow",
62
- QUEUED: "yellow",
63
- STARTED: "yellow",
64
- SUCCEEDED: "green",
65
- FAILED: "red",
66
- TIMEOUT: "red",
67
- }[status]
68
- }
69
- >
70
- {count}
71
- </Label>
72
- ) : null,
73
- textAlign: "center",
74
- width: 2,
75
- }))
76
- )(["PENDING", "QUEUED", "STARTED", "SUCCEEDED", "FAILED", "TIMEOUT"]);
67
+ const statusColumns = _.map((status) => ({
68
+ name: `score.status.${status}`,
69
+ fieldSelector: _.pathOr(null, `status_summary.${status}`),
70
+ fieldDecorator: (field) => statusDecorator(field, status),
71
+ textAlign: "center",
72
+ width: 2,
73
+ }))(["PENDING", "QUEUED", "STARTED", "SUCCEEDED", "FAILED", "TIMEOUT"]);
77
74
 
78
75
  const columns = [
79
76
  {
@@ -87,27 +84,35 @@ export default function ScoreGroupsTable() {
87
84
 
88
85
  return (
89
86
  <>
90
- {!_.isEmpty(scoreGroups) && (
91
- <Table collapsing striped celled>
92
- <Table.Header>
93
- <HeaderRow columns={columns} />
94
- </Table.Header>
95
- <Table.Body>
96
- {scoreGroups &&
97
- scoreGroups.map((props, key) => (
98
- <ScoreGroupRow key={key} columns={columns} {...props} />
99
- ))}
100
- </Table.Body>
101
- </Table>
102
- )}
103
- {_.isEmpty(scoreGroups) && (
104
- <Header as="h4">
105
- <Icon name="search" />
106
- <Header.Content>
107
- <FormattedMessage id="score_groups.search.results.empty" />
108
- </Header.Content>
109
- </Header>
110
- )}
87
+ {loading ? <Loading /> : null}
88
+
89
+ <>
90
+ {!_.isEmpty(scoreGroups) ? (
91
+ <>
92
+ <Table collapsing striped celled>
93
+ <Table.Header>
94
+ <HeaderRow columns={columns} />
95
+ </Table.Header>
96
+ <Table.Body>
97
+ {scoreGroups
98
+ ? scoreGroups.map((props, key) => (
99
+ <ScoreGroupRow key={key} columns={columns} {...props} />
100
+ ))
101
+ : null}
102
+ </Table.Body>
103
+ </Table>
104
+ <Pagination />
105
+ </>
106
+ ) : null}
107
+ {_.isEmpty(scoreGroups) ? (
108
+ <Header as="h4">
109
+ <Icon name="search" />
110
+ <Header.Content>
111
+ <FormattedMessage id="score_groups.search.results.empty" />
112
+ </Header.Content>
113
+ </Header>
114
+ ) : null}
115
+ </>
111
116
  </>
112
117
  );
113
118
  }
@@ -2,7 +2,6 @@ import React from "react";
2
2
  import { Route, Switch } from "react-router-dom";
3
3
  import {
4
4
  MY_SCORE_GROUPS,
5
- SCORE_GROUPS,
6
5
  SCORE_GROUP,
7
6
  SCORE,
8
7
  SCORE_EVENTS,
@@ -1,31 +1,71 @@
1
1
  import React from "react";
2
2
  import { render } from "@truedat/test/render";
3
+ import { waitFor } from "@testing-library/react";
4
+ import { useAuthorized } from "@truedat/core/hooks";
3
5
  import { messages } from "@truedat/qx/components/__tests__/__fixtures__/helper";
4
6
  import MyScoreGroups from "../MyScoreGroups";
5
7
 
6
8
  const renderOpts = { messages };
7
9
 
10
+ jest.mock("@truedat/core/hooks", () => ({
11
+ useAuthorized: jest.fn(() => true),
12
+ }));
13
+
8
14
  jest.mock("@truedat/qx/hooks/useScoreGroups", () => {
15
+ const originalModule = jest.requireActual("@truedat/qx/hooks/useScoreGroups");
9
16
  const { scoreGroupData } = jest.requireActual(
10
17
  "@truedat/qx/components/scores/__tests__/__fixtures__/scoreHelper"
11
18
  );
19
+ const data = {
20
+ data: [
21
+ scoreGroupData({ status_summary: { PENDING: 1, QUEUED: 2 } }),
22
+ scoreGroupData({ status_summary: { SUCCEEDED: 1, FAILED: 3 } }),
23
+ ],
24
+ };
12
25
 
13
26
  return {
14
- useMyScoreGroupsIndex: jest.fn(() => ({
15
- data: {
16
- data: [
17
- scoreGroupData({ status_summary: { PENDING: 1, QUEUED: 2 } }),
18
- scoreGroupData({ status_summary: { SUCCEEDED: 1, FAILED: 3 } }),
19
- ],
20
- },
21
- loading: false,
22
- })),
27
+ __esModule: true,
28
+ ...originalModule,
29
+ useScoreGroupsFilters: () => ({
30
+ trigger: () => ({
31
+ then: (callback) =>
32
+ callback({
33
+ data: {},
34
+ }),
35
+ }),
36
+ }),
37
+ useScoreGroupsSearch: () => ({
38
+ trigger: () => ({
39
+ then: (callback) =>
40
+ callback({
41
+ data,
42
+ headers: { "x-total-count": 2 },
43
+ }),
44
+ }),
45
+ }),
23
46
  };
24
47
  });
25
48
 
26
49
  describe("<MyScoreGroups />", () => {
27
- it("matches the latest snapshot", () => {
50
+ it("matches the latest snapshot", async () => {
28
51
  const { container } = render(<MyScoreGroups />, renderOpts);
52
+
53
+ await waitFor(() => {
54
+ expect(container.querySelector(".loading")).toBeNull();
55
+ });
56
+
29
57
  expect(container).toMatchSnapshot();
30
58
  });
59
+
60
+ it("non_admin user do not show searchwidget", async () => {
61
+ useAuthorized.mockImplementationOnce(() => false);
62
+
63
+ const { container, queryByText } = render(<MyScoreGroups />, renderOpts);
64
+
65
+ await waitFor(() => {
66
+ expect(container.querySelector(".loading")).toBeNull();
67
+ });
68
+
69
+ expect(queryByText("Filters")).not.toBeInTheDocument();
70
+ });
31
71
  });
@@ -9,7 +9,7 @@ import ScoreGroupForm from "../ScoreGroupForm";
9
9
  const renderOpts = {
10
10
  mocks: [
11
11
  singleTemplateMock({
12
- scope: "qe",
12
+ scope: "qxe",
13
13
  domainIds: undefined,
14
14
  }),
15
15
  ],
@@ -7,7 +7,7 @@ import { messages } from "@truedat/qx/components/__tests__/__fixtures__/helper";
7
7
  import ScoreGroupPopup from "../ScoreGroupPopup";
8
8
 
9
9
  const renderOpts = {
10
- mocks: [singleTemplateMock({ scope: "qe" })],
10
+ mocks: [singleTemplateMock({ scope: "qxe" })],
11
11
  messages,
12
12
  fallback: "lazy",
13
13
  };
@@ -1,40 +1,42 @@
1
1
  import React from "react";
2
2
  import { render } from "@truedat/test/render";
3
3
  import { messages } from "@truedat/qx/components/__tests__/__fixtures__/helper";
4
+ import SearchContextWrapper from "@truedat/core/components/common/SearchContextWrapper";
5
+ import { scoreGroupData } from "@truedat/qx/components/scores/__tests__/__fixtures__/scoreHelper";
4
6
  import ScoreGroupsTable from "../ScoreGroupsTable";
5
- import { useMyScoreGroupsIndex } from "../../../hooks/useScoreGroups";
6
7
 
7
- jest.mock("@truedat/qx/hooks/useScoreGroups", () => {
8
- const { scoreGroupData } = jest.requireActual(
9
- "@truedat/qx/components/scores/__tests__/__fixtures__/scoreHelper"
10
- );
11
-
12
- return {
13
- useMyScoreGroupsIndex: jest.fn(() => ({
14
- data: {
15
- data: [
16
- scoreGroupData({ status_summary: { PENDING: 1, QUEUED: 2 } }),
17
- scoreGroupData({ status_summary: { SUCCEEDED: 1, FAILED: 3 } }),
18
- ],
19
- },
20
- loading: false,
21
- })),
22
- };
23
- });
8
+ const searchProps = {
9
+ searchData: {
10
+ data: [
11
+ scoreGroupData({ status_summary: { PENDING: 1, QUEUED: 2 } }),
12
+ scoreGroupData({ status_summary: { SUCCEEDED: 1, FAILED: 3 } }),
13
+ ],
14
+ },
15
+ loading: false,
16
+ };
24
17
 
25
18
  const renderOpts = { messages };
26
19
 
27
20
  describe("<ScoreGroupsTable />", () => {
28
21
  it("matches the latest snapshot", () => {
29
- const { container } = render(<ScoreGroupsTable />, renderOpts);
22
+ const { container } = render(
23
+ <SearchContextWrapper props={searchProps}>
24
+ <ScoreGroupsTable />
25
+ </SearchContextWrapper>,
26
+ renderOpts
27
+ );
28
+
30
29
  expect(container).toMatchSnapshot();
31
30
  });
31
+
32
32
  it("matches the latest snapshot for empty list", () => {
33
- useMyScoreGroupsIndex.mockImplementation(() => ({
34
- data: { data: [] },
35
- loading: false,
36
- }));
37
- const { container } = render(<ScoreGroupsTable />, renderOpts);
33
+ const newSearchProps = { ...searchProps, searchData: { data: [] } };
34
+ const { container } = render(
35
+ <SearchContextWrapper props={newSearchProps}>
36
+ <ScoreGroupsTable />
37
+ </SearchContextWrapper>,
38
+ renderOpts
39
+ );
38
40
  expect(container).toMatchSnapshot();
39
41
  });
40
42
  });
@@ -26,6 +26,58 @@ exports[`<MyScoreGroups /> matches the latest snapshot 1`] = `
26
26
  </h2>
27
27
  </div>
28
28
  </div>
29
+ <div
30
+ class="ui hidden divider"
31
+ />
32
+ <div
33
+ class="ui action left icon input"
34
+ >
35
+ <i
36
+ aria-hidden="true"
37
+ class="search link icon"
38
+ />
39
+ <input
40
+ placeholder="Search..."
41
+ type="text"
42
+ value=""
43
+ />
44
+ <div
45
+ aria-busy="false"
46
+ aria-disabled="false"
47
+ aria-expanded="false"
48
+ class="ui button floating labeled scrolling dropdown icon"
49
+ role="listbox"
50
+ tabindex="0"
51
+ >
52
+ <div
53
+ aria-atomic="true"
54
+ aria-live="polite"
55
+ class="divider text"
56
+ role="alert"
57
+ >
58
+ Filters
59
+ </div>
60
+ <i
61
+ aria-hidden="true"
62
+ class="filter icon"
63
+ />
64
+ <div
65
+ class="menu transition"
66
+ >
67
+ <div
68
+ class="item"
69
+ role="option"
70
+ >
71
+ <em>
72
+ (reset filters)
73
+ </em>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ <div
79
+ class="selectedFilters"
80
+ />
29
81
  <table
30
82
  class="ui celled collapsing striped table"
31
83
  >
@@ -50,6 +102,11 @@ exports[`<MyScoreGroups /> matches the latest snapshot 1`] = `
50
102
  >
51
103
  Queued
52
104
  </th>
105
+ <th
106
+ class="center aligned"
107
+ >
108
+ Started
109
+ </th>
53
110
  <th
54
111
  class="center aligned"
55
112
  >
@@ -60,6 +117,11 @@ exports[`<MyScoreGroups /> matches the latest snapshot 1`] = `
60
117
  >
61
118
  Failed
62
119
  </th>
120
+ <th
121
+ class="center aligned"
122
+ >
123
+ Timeout
124
+ </th>
63
125
  </tr>
64
126
  </thead>
65
127
  <tbody
@@ -105,6 +167,12 @@ exports[`<MyScoreGroups /> matches the latest snapshot 1`] = `
105
167
  <td
106
168
  class="center aligned"
107
169
  />
170
+ <td
171
+ class="center aligned"
172
+ />
173
+ <td
174
+ class="center aligned"
175
+ />
108
176
  </tr>
109
177
  <tr
110
178
  class=""
@@ -128,6 +196,9 @@ exports[`<MyScoreGroups /> matches the latest snapshot 1`] = `
128
196
  <td
129
197
  class="center aligned"
130
198
  />
199
+ <td
200
+ class="center aligned"
201
+ />
131
202
  <td
132
203
  class="center aligned"
133
204
  >
@@ -146,9 +217,72 @@ exports[`<MyScoreGroups /> matches the latest snapshot 1`] = `
146
217
  3
147
218
  </div>
148
219
  </td>
220
+ <td
221
+ class="center aligned"
222
+ />
149
223
  </tr>
150
224
  </tbody>
151
225
  </table>
226
+ <div
227
+ aria-label="Pagination Navigation"
228
+ class="ui pagination menu"
229
+ role="navigation"
230
+ >
231
+ <a
232
+ aria-current="false"
233
+ aria-disabled="true"
234
+ aria-label="First item"
235
+ class="disabled item"
236
+ tabindex="-1"
237
+ type="firstItem"
238
+ value="1"
239
+ >
240
+ «
241
+ </a>
242
+ <a
243
+ aria-current="false"
244
+ aria-disabled="true"
245
+ aria-label="Previous item"
246
+ class="disabled item"
247
+ tabindex="-1"
248
+ type="prevItem"
249
+ value="1"
250
+ >
251
+
252
+ </a>
253
+ <a
254
+ aria-current="true"
255
+ aria-disabled="true"
256
+ class="active disabled item"
257
+ tabindex="-1"
258
+ type="pageItem"
259
+ value="1"
260
+ >
261
+ 1
262
+ </a>
263
+ <a
264
+ aria-current="false"
265
+ aria-disabled="true"
266
+ aria-label="Next item"
267
+ class="disabled item"
268
+ tabindex="-1"
269
+ type="nextItem"
270
+ value="1"
271
+ >
272
+
273
+ </a>
274
+ <a
275
+ aria-current="false"
276
+ aria-disabled="true"
277
+ aria-label="Last item"
278
+ class="disabled item"
279
+ tabindex="-1"
280
+ type="lastItem"
281
+ value="1"
282
+ >
283
+ »
284
+ </a>
285
+ </div>
152
286
  </div>
153
287
  </div>
154
288
  `;
@@ -8,7 +8,7 @@ exports[`<ScoreGroup /> matches the latest snapshot 1`] = `
8
8
  >
9
9
  <a
10
10
  class="section"
11
- href="/scoreGroups"
11
+ href="/myScoreGroups"
12
12
  >
13
13
  My Executions
14
14
  </a>
@@ -7,7 +7,7 @@ exports[`<ScoreGroupBreadcrumbs /> matches the latest snapshot 1`] = `
7
7
  >
8
8
  <a
9
9
  class="section"
10
- href="/scoreGroups"
10
+ href="/myScoreGroups"
11
11
  >
12
12
  My Executions
13
13
  </a>
@@ -26,6 +26,11 @@ exports[`<ScoreGroupsTable /> matches the latest snapshot 1`] = `
26
26
  >
27
27
  Queued
28
28
  </th>
29
+ <th
30
+ class="center aligned"
31
+ >
32
+ Started
33
+ </th>
29
34
  <th
30
35
  class="center aligned"
31
36
  >
@@ -36,6 +41,11 @@ exports[`<ScoreGroupsTable /> matches the latest snapshot 1`] = `
36
41
  >
37
42
  Failed
38
43
  </th>
44
+ <th
45
+ class="center aligned"
46
+ >
47
+ Timeout
48
+ </th>
39
49
  </tr>
40
50
  </thead>
41
51
  <tbody
@@ -81,6 +91,12 @@ exports[`<ScoreGroupsTable /> matches the latest snapshot 1`] = `
81
91
  <td
82
92
  class="center aligned"
83
93
  />
94
+ <td
95
+ class="center aligned"
96
+ />
97
+ <td
98
+ class="center aligned"
99
+ />
84
100
  </tr>
85
101
  <tr
86
102
  class=""
@@ -104,6 +120,9 @@ exports[`<ScoreGroupsTable /> matches the latest snapshot 1`] = `
104
120
  <td
105
121
  class="center aligned"
106
122
  />
123
+ <td
124
+ class="center aligned"
125
+ />
107
126
  <td
108
127
  class="center aligned"
109
128
  >
@@ -122,6 +141,9 @@ exports[`<ScoreGroupsTable /> matches the latest snapshot 1`] = `
122
141
  3
123
142
  </div>
124
143
  </td>
144
+ <td
145
+ class="center aligned"
146
+ />
125
147
  </tr>
126
148
  </tbody>
127
149
  </table>
@@ -2,7 +2,12 @@ import { compile } from "path-to-regexp";
2
2
  import useSWR from "swr";
3
3
  import useSWRMutations from "swr/mutation";
4
4
  import { apiJson, apiJsonPost } from "@truedat/core/services/api";
5
- import { API_SCORE_GROUPS, API_SCORE_GROUP } from "../api";
5
+ import {
6
+ API_SCORE_GROUPS,
7
+ API_SCORE_GROUP,
8
+ API_SCORE_GROUPS_SEARCH,
9
+ API_SCORE_GROUPS_FILTERS,
10
+ } from "../api";
6
11
 
7
12
  export const useScoreGroupCreate = () => {
8
13
  return useSWRMutations(API_SCORE_GROUPS, (url, { arg }) =>
@@ -21,3 +26,15 @@ export const useMyScoreGroupsIndex = () => {
21
26
  const { data, error, mutate } = useSWR(url, apiJson);
22
27
  return { data: data?.data, error, loading: !error && !data, mutate };
23
28
  };
29
+
30
+ export const useScoreGroupsSearch = () => {
31
+ return useSWRMutations(API_SCORE_GROUPS_SEARCH, (url, { arg }) =>
32
+ apiJsonPost(url, arg)
33
+ );
34
+ };
35
+
36
+ export const useScoreGroupsFilters = () => {
37
+ return useSWRMutations(API_SCORE_GROUPS_FILTERS, (url, { arg }) =>
38
+ apiJsonPost(url, arg)
39
+ );
40
+ };
@@ -15,6 +15,7 @@ export const useScoreShow = (id) => {
15
15
  const { data, error, mutate } = useSWR(url, apiJson);
16
16
  return { data: data?.data?.data, error, loading: !error && !data, mutate };
17
17
  };
18
+
18
19
  export const useScoreDelete = () => {
19
20
  return useSWRMutations(API_SCORE, (_url, { arg }) =>
20
21
  apiJsonDelete(compile(API_SCORE)(arg))