@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
@@ -7,31 +7,57 @@ import {
7
7
  useQualityControlScores,
8
8
  useScoreDelete,
9
9
  } from "@truedat/qx/hooks/useScores";
10
- import QualityControlScores from "../QualityControlScores";
11
10
  import { qualityControlData } from "./__fixtures__/qualityControlHelper";
11
+ import { scoreData } from "@truedat/qx/components/scores/__tests__/__fixtures__/scoreHelper";
12
+ import { ScoreContextProvider } from "@truedat/qx/components/scores/ScoreContext";
13
+ import QualityControlScores from "../QualityControlScores";
12
14
 
13
15
  jest.mock("react-router", () => ({
14
16
  ...jest.requireActual("react-router"),
15
- useParams: () => ({ id: "8", version: "1" }),
17
+ useParams: () => ({ id: "8" }),
16
18
  }));
17
19
 
18
20
  jest.mock("@truedat/qx/hooks/useScores", () => {
19
21
  const { scoreData } = jest.requireActual(
20
22
  "@truedat/qx/components/scores/__tests__/__fixtures__/scoreHelper"
21
23
  );
22
- const data = [scoreData(), scoreData({ id: 16, status: "QUEUED" })];
23
- const mutate = jest.fn();
24
+
25
+ const quality_control_scores = {
26
+ last_execution_result: {
27
+ id: 15,
28
+ status: "SUCCEEDED",
29
+ details: {
30
+ foo: "bar",
31
+ },
32
+ execution_timestamp: "2024-11-19T16:34:21.438113Z",
33
+ group_id: 36,
34
+ quality_control_version_id: 1,
35
+ latest_event_message: "foo_message",
36
+ },
37
+ total_count: 2,
38
+ scores: [scoreData(), scoreData({ id: 16, status: "QUEUED" })],
39
+ total_pages: 1,
40
+ current_page: 1,
41
+ };
42
+
24
43
  return {
25
44
  useQualityControlScores: jest.fn(() => ({
26
- data: { data: data },
27
- loading: false,
28
- mutate,
45
+ trigger: () => ({
46
+ then: (callback) =>
47
+ callback({
48
+ data: { data: quality_control_scores },
49
+ loading: false,
50
+ mutate: jest.fn(),
51
+ }),
52
+ }),
29
53
  })),
54
+
30
55
  useScoreDelete: jest.fn(() => ({
31
56
  trigger: () => ({
32
57
  then: (callback) =>
33
58
  callback({
34
59
  data: {},
60
+ loading: false,
35
61
  }),
36
62
  }),
37
63
  })),
@@ -39,20 +65,21 @@ jest.mock("@truedat/qx/hooks/useScores", () => {
39
65
  });
40
66
 
41
67
  const renderOpts = { messages };
42
- const mutate = jest.fn();
43
68
 
44
69
  const context = {
45
70
  qualityControl: qualityControlData(),
46
71
  loading: false,
47
72
  actions: ["delete_score"],
48
- mutate,
73
+ scores: [],
49
74
  };
50
75
 
51
76
  describe("<QualityControlScores />", () => {
52
77
  it("matches the latest snapshot", async () => {
53
78
  const rendered = render(
54
79
  <TestFormWrapper context={context}>
55
- <QualityControlScores />
80
+ <ScoreContextProvider qualityControlId={8}>
81
+ <QualityControlScores />
82
+ </ScoreContextProvider>
56
83
  </TestFormWrapper>,
57
84
  renderOpts
58
85
  );
@@ -61,44 +88,72 @@ describe("<QualityControlScores />", () => {
61
88
  });
62
89
 
63
90
  it("shows a message when there are no scores", async () => {
64
- useQualityControlScores.withImplementation(
65
- () => ({
66
- data: { data: [] },
67
- loading: false,
68
- mutate,
91
+ useQualityControlScores.mockImplementation(() => ({
92
+ trigger: () => ({
93
+ then: (callback) =>
94
+ callback({
95
+ data: { data: [] },
96
+ loading: false,
97
+ mutate: jest.fn(),
98
+ }),
69
99
  }),
70
- async () => {
71
- const context = {
72
- qualityControl: qualityControlData(),
73
- loading: false,
74
- actions: ["delete_score"],
75
- mutate,
76
- };
77
-
78
- const rendered = render(
79
- <TestFormWrapper context={context}>
80
- <QualityControlScores />
81
- </TestFormWrapper>,
82
- renderOpts
83
- );
84
- await waitForLoad(rendered);
85
-
86
- expect(rendered.getByText(/no scores found/i)).toBeInTheDocument();
87
- }
100
+ }));
101
+
102
+ const rendered = render(
103
+ <TestFormWrapper context={context}>
104
+ <ScoreContextProvider qualityControlId={8}>
105
+ <QualityControlScores />
106
+ </ScoreContextProvider>
107
+ </TestFormWrapper>,
108
+ renderOpts
88
109
  );
110
+
111
+ await waitForLoad(rendered);
112
+
113
+ expect(rendered.getByText(/no scores found/i)).toBeInTheDocument();
89
114
  });
90
115
 
91
116
  it("does not show delete button when user does not have delete_score action", async () => {
117
+ const quality_control_scores = {
118
+ last_execution_result: {
119
+ id: 15,
120
+ status: "SUCCEEDED",
121
+ details: {
122
+ foo: "bar",
123
+ },
124
+ execution_timestamp: "2024-11-19T16:34:21.438113Z",
125
+ group_id: 36,
126
+ quality_control_version_id: 1,
127
+ latest_event_message: "foo_message",
128
+ },
129
+ total_count: 2,
130
+ scores: [scoreData(), scoreData({ id: 16, status: "QUEUED" })],
131
+ total_pages: 1,
132
+ current_page: 1,
133
+ };
134
+
135
+ useQualityControlScores.mockImplementation(() => ({
136
+ trigger: () => ({
137
+ then: (callback) =>
138
+ callback({
139
+ data: { data: quality_control_scores },
140
+ loading: false,
141
+ mutate: jest.fn(),
142
+ }),
143
+ }),
144
+ }));
145
+
92
146
  const context = {
93
147
  qualityControl: qualityControlData(),
94
148
  loading: false,
95
149
  actions: [],
96
- mutate,
97
150
  };
98
151
 
99
152
  const rendered = render(
100
153
  <TestFormWrapper context={context}>
101
- <QualityControlScores />
154
+ <ScoreContextProvider qualityControlId={8}>
155
+ <QualityControlScores />
156
+ </ScoreContextProvider>
102
157
  </TestFormWrapper>,
103
158
  renderOpts
104
159
  );
@@ -112,35 +167,86 @@ describe("<QualityControlScores />", () => {
112
167
  });
113
168
 
114
169
  it("deletes a score when the delete button is clicked", async () => {
115
- const trigger = jest.fn(() =>
116
- Promise.resolve({
117
- data: {},
118
- })
119
- );
170
+ const triggerDeleteScore = jest.fn(() => Promise.resolve({ data: {} }));
120
171
 
121
172
  useScoreDelete.mockImplementation(() => ({
122
- trigger,
173
+ trigger: triggerDeleteScore,
123
174
  }));
124
-
125
175
  const user = userEvent.setup({ delay: null });
126
176
 
127
177
  const rendered = render(
128
178
  <TestFormWrapper context={context}>
129
- <QualityControlScores />
179
+ <ScoreContextProvider qualityControlId={8}>
180
+ <QualityControlScores />
181
+ </ScoreContextProvider>
130
182
  </TestFormWrapper>,
131
183
  renderOpts
132
184
  );
185
+
133
186
  await waitForLoad(rendered);
134
187
 
135
- const deleteButtons = rendered.container.querySelectorAll(
136
- ".ui.red.basic.icon.button"
188
+ const boton = rendered.container.querySelector(
189
+ ".ui.red.mini.basic.icon.button"
137
190
  );
191
+ expect(boton).toBeInTheDocument();
138
192
 
139
- await user.click(deleteButtons[0]);
193
+ await user.click(boton);
140
194
  await user.click(rendered.getByText(/confirm_yes/i));
141
-
142
195
  await waitFor(() => {
143
- expect(trigger).toHaveBeenCalled();
196
+ expect(triggerDeleteScore).toHaveBeenCalledWith({
197
+ id: "15",
198
+ });
144
199
  });
145
200
  });
201
+
202
+ it("shows a message when the last execution result is failed", async () => {
203
+ const quality_control_scores = {
204
+ last_execution_result: {
205
+ id: 15,
206
+ status: "FAILED",
207
+ details: {
208
+ foo: "bar",
209
+ },
210
+ execution_timestamp: "2024-11-19T16:34:21.438113Z",
211
+ group_id: 36,
212
+ quality_control_version_id: 1,
213
+ latest_event_message: "foo_message",
214
+ },
215
+ total_count: 2,
216
+ scores: [scoreData({ status: "FAILED" })],
217
+ total_pages: 1,
218
+ current_page: 1,
219
+ };
220
+
221
+ useQualityControlScores.mockImplementation(() => ({
222
+ trigger: () => ({
223
+ then: (callback) =>
224
+ callback({
225
+ data: { data: quality_control_scores },
226
+ loading: false,
227
+ mutate: jest.fn(),
228
+ }),
229
+ }),
230
+ }));
231
+
232
+ const context = {
233
+ qualityControl: qualityControlData(),
234
+ loading: false,
235
+ actions: [],
236
+ };
237
+
238
+ const rendered = render(
239
+ <TestFormWrapper context={context}>
240
+ <ScoreContextProvider qualityControlId={8}>
241
+ <QualityControlScores />
242
+ </ScoreContextProvider>
243
+ </TestFormWrapper>,
244
+ renderOpts
245
+ );
246
+
247
+ await waitForLoad(rendered);
248
+
249
+ expect(rendered.getByText(/quality.error/i)).toBeInTheDocument();
250
+ expect(rendered.getByText(/foo_message/i)).toBeInTheDocument();
251
+ });
146
252
  });
@@ -0,0 +1,27 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<QualityControlRoutes /> renders correctly with default route 1`] = `
4
+ <div>
5
+ <div>
6
+ QualityControls
7
+ </div>
8
+ </div>
9
+ `;
10
+
11
+ exports[`<QualityControlRoutes /> renders correctly with deprecated route 1`] = `<div />`;
12
+
13
+ exports[`<QualityControlRoutes /> renders correctly with drafts route 1`] = `<div />`;
14
+
15
+ exports[`<QualityControlRoutes /> renders correctly with edit route 1`] = `<div />`;
16
+
17
+ exports[`<QualityControlRoutes /> renders correctly with history route 1`] = `<div />`;
18
+
19
+ exports[`<QualityControlRoutes /> renders correctly with new draft route 1`] = `<div />`;
20
+
21
+ exports[`<QualityControlRoutes /> renders correctly with new route 1`] = `<div />`;
22
+
23
+ exports[`<QualityControlRoutes /> renders correctly with quality control route 1`] = `<div />`;
24
+
25
+ exports[`<QualityControlRoutes /> renders correctly with scores route 1`] = `<div />`;
26
+
27
+ exports[`<QualityControlRoutes /> renders unauthorized component when not authorized 1`] = `<div />`;
@@ -31,6 +31,11 @@ exports[`<QualityControlScores /> matches the latest snapshot 1`] = `
31
31
  >
32
32
  Control Mode
33
33
  </th>
34
+ <th
35
+ class=""
36
+ >
37
+ scores.props.created_at
38
+ </th>
34
39
  <th
35
40
  class=""
36
41
  >
@@ -95,6 +100,9 @@ exports[`<QualityControlScores /> matches the latest snapshot 1`] = `
95
100
  >
96
101
  Percentage
97
102
  </td>
103
+ <td
104
+ class=""
105
+ />
98
106
  <td
99
107
  class=""
100
108
  >
@@ -149,6 +157,9 @@ exports[`<QualityControlScores /> matches the latest snapshot 1`] = `
149
157
  >
150
158
  Percentage
151
159
  </td>
160
+ <td
161
+ class=""
162
+ />
152
163
  <td
153
164
  class=""
154
165
  >
@@ -160,5 +171,65 @@ exports[`<QualityControlScores /> matches the latest snapshot 1`] = `
160
171
  </tr>
161
172
  </tbody>
162
173
  </table>
174
+ <div
175
+ aria-label="Pagination Navigation"
176
+ class="ui pagination menu"
177
+ role="navigation"
178
+ >
179
+ <a
180
+ aria-current="false"
181
+ aria-disabled="true"
182
+ aria-label="First item"
183
+ class="disabled item"
184
+ tabindex="-1"
185
+ type="firstItem"
186
+ value="1"
187
+ >
188
+ «
189
+ </a>
190
+ <a
191
+ aria-current="false"
192
+ aria-disabled="true"
193
+ aria-label="Previous item"
194
+ class="disabled item"
195
+ tabindex="-1"
196
+ type="prevItem"
197
+ value="1"
198
+ >
199
+
200
+ </a>
201
+ <a
202
+ aria-current="true"
203
+ aria-disabled="true"
204
+ class="active disabled item"
205
+ tabindex="-1"
206
+ type="pageItem"
207
+ value="1"
208
+ >
209
+ 1
210
+ </a>
211
+ <a
212
+ aria-current="false"
213
+ aria-disabled="true"
214
+ aria-label="Next item"
215
+ class="disabled item"
216
+ tabindex="-1"
217
+ type="nextItem"
218
+ value="1"
219
+ >
220
+
221
+ </a>
222
+ <a
223
+ aria-current="false"
224
+ aria-disabled="true"
225
+ aria-label="Last item"
226
+ class="disabled item"
227
+ tabindex="-1"
228
+ type="lastItem"
229
+ value="1"
230
+ >
231
+ »
232
+ </a>
233
+ </div>
163
234
  </div>
164
235
  `;
@@ -74,20 +74,20 @@ export default function qualityByControlMode(score) {
74
74
  };
75
75
  case "error_count":
76
76
  const errorCount =
77
- scoreContent.total_count == 0 ? null : scoreContent.validation_count;
77
+ scoreContent.total_count == 0
78
+ ? null
79
+ : (scoreContent.validation_count / scoreContent.total_count) * 100;
78
80
 
79
81
  const errorCountResult =
80
- scoreContent.total_count == 0
81
- ? noResultsMessage
82
- : scoreContent.validation_count;
82
+ scoreContent.total_count == 0 ? noResultsMessage : `${errorCount}%`;
83
83
 
84
84
  return {
85
85
  color:
86
86
  scoreContent.total_count == 0
87
87
  ? "grey"
88
- : errorCount > scoreCriteria.goal
88
+ : errorCount < scoreCriteria.goal
89
89
  ? "green"
90
- : errorCount > scoreCriteria.maximum
90
+ : errorCount < scoreCriteria.maximum
91
91
  ? "yellow"
92
92
  : "red",
93
93
  label1: "quality_control.score_criteria.error_count.goal",
@@ -1,4 +1,4 @@
1
- import React, { useContext } from "react";
1
+ import React, { use } from "react";
2
2
  import { useIntl } from "react-intl";
3
3
  import { Controller, useFormContext } from "react-hook-form";
4
4
  import { Form } from "semantic-ui-react";
@@ -8,7 +8,7 @@ import { numberRules } from "@truedat/core/services/formRules";
8
8
 
9
9
  export default function Count() {
10
10
  const { formatMessage } = useIntl();
11
- const { field } = useContext(QxContext);
11
+ const { field } = use(QxContext);
12
12
  const { control, watch } = useFormContext();
13
13
 
14
14
  const maximumField = `${field}.maximum`;
@@ -10,7 +10,6 @@ export default function ErrorCount() {
10
10
  const { formatMessage } = useIntl();
11
11
  const { field } = use(QxContext);
12
12
  const { control, watch } = useFormContext();
13
-
14
13
  const maximumField = `${field}.maximum`;
15
14
  const goalField = `${field}.goal`;
16
15
  const maximum = watch(maximumField);
@@ -68,7 +67,8 @@ export default function ErrorCount() {
68
67
  label={formatMessage({
69
68
  id: "quality_control.score_criteria.error_count.maximum",
70
69
  })}
71
- error={!!error}
70
+ required
71
+ error={error?.message}
72
72
  >
73
73
  <Form.Input
74
74
  autoComplete="off"
@@ -34,7 +34,7 @@ describe("<ErrorCount />", () => {
34
34
 
35
35
  await user.type(goalInput, "10");
36
36
  await user.type(thresholdInput, "20");
37
- rendered.debug();
37
+
38
38
  await waitFor(() =>
39
39
  expect(
40
40
  rendered.queryByText(/must be greater than y/i)
@@ -42,7 +42,7 @@ describe("<ErrorCount />", () => {
42
42
  );
43
43
  });
44
44
 
45
- it.skip("Show alert if thresholdInput is less than goal", async () => {
45
+ it("Show alert if thresholdInput is less than goal", async () => {
46
46
  const rendered = render(
47
47
  <TestFormWrapper>
48
48
  <ErrorCount />
@@ -60,12 +60,12 @@ describe("<ErrorCount />", () => {
60
60
 
61
61
  await user.type(goalInput, "10");
62
62
  await user.type(thresholdInput, "1");
63
- await waitForLoad(rendered);
64
63
 
65
- await waitFor(() =>
64
+ await waitForLoad(rendered);
65
+ await waitFor(() => {
66
66
  expect(
67
- rendered.queryByText(/must be greater than 10/i)
68
- ).toBeInTheDocument()
69
- );
67
+ rendered.queryByText("form.validation.must_be_greater_than_or_equal")
68
+ ).toBeInTheDocument();
69
+ });
70
70
  });
71
71
  });
@@ -35,6 +35,9 @@ exports[`<ErrorCount /> matches the latest snapshot 1`] = `
35
35
  class="field-label"
36
36
  >
37
37
  quality_control.score_criteria.error_count.maximum
38
+ <span>
39
+ *
40
+ </span>
38
41
  </label>
39
42
  <div
40
43
  class="field"
@@ -1,3 +1,65 @@
1
- import { createContext } from "react";
1
+ import _ from "lodash/fp";
2
+ import { createContext, useState, useEffect, use } from "react";
3
+ import {
4
+ useQualityControlScores,
5
+ useScoreDelete,
6
+ } from "@truedat/qx/hooks/useScores";
2
7
 
3
- export default createContext();
8
+ const ScoreContext = createContext();
9
+
10
+ export const ScoreContextProvider = (props) => {
11
+ const qualityControlId = _.prop("qualityControlId")(props);
12
+ const children = _.prop("children")(props);
13
+ const [page, setPage] = useState(1);
14
+ const [currentPage, setCurrentPage] = useState(1);
15
+ const [scores, setScores] = useState();
16
+ const [lastExecutionResult, setLastExecutionResult] = useState();
17
+ const [count, setCount] = useState();
18
+ const [size, setSize] = useState();
19
+ const [totalPages, setTotalPages] = useState();
20
+ const { trigger: triggerSearchScores, loading: loadingQualityScores } =
21
+ useQualityControlScores(qualityControlId);
22
+
23
+ const { trigger: deleteScore } = useScoreDelete();
24
+
25
+ const handleSearch = () => {
26
+ triggerSearchScores({
27
+ quality_control_id: qualityControlId,
28
+ page: page,
29
+ }).then(({ data }) => {
30
+ setScores(data?.data?.scores);
31
+ setLastExecutionResult(data?.data?.last_execution_result);
32
+ setCurrentPage(data?.data?.current_page);
33
+ setCount(data?.data?.total_pages);
34
+ setSize(data?.data?.page_size);
35
+ setTotalPages(data?.data?.total_pages);
36
+ });
37
+ };
38
+
39
+ const handleDelete = (id) => {
40
+ deleteScore(id);
41
+ page == 1 ? handleSearch() : setPage(1);
42
+ };
43
+
44
+ useEffect(() => {
45
+ handleSearch();
46
+ }, [page]);
47
+
48
+ const context = {
49
+ scores,
50
+ loadingQualityScores,
51
+ lastExecutionResult,
52
+ count,
53
+ size,
54
+ totalPages,
55
+ page,
56
+ currentPage,
57
+ selectPage: setPage,
58
+ handleDelete,
59
+ };
60
+
61
+ return <ScoreContext value={context}>{children}</ScoreContext>;
62
+ };
63
+
64
+ export const useScoreContext = () => use(ScoreContext);
65
+ export default ScoreContext;
@@ -1,12 +1,14 @@
1
1
  import _ from "lodash/fp";
2
2
  import { use } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { connect } from "react-redux";
3
5
  import { FormattedMessage } from "react-intl";
4
6
  import { Header, Icon, List, Segment } from "semantic-ui-react";
5
7
  import { columnDecorator } from "@truedat/core/services";
6
- import columns from "../qualityControls/qualityControlScoresColumns";
8
+ import { getQualityControlScoresColumns } from "@truedat/qx/components/selectors/getQualityControlScoresColumns";
7
9
  import ScoreContext from "./ScoreContext";
8
10
 
9
- export default function ScoreDetails() {
11
+ export function ScoreDetails({ columns }) {
10
12
  const { score } = use(ScoreContext);
11
13
  const details = _.prop("details")(score);
12
14
 
@@ -65,3 +67,12 @@ export default function ScoreDetails() {
65
67
  </>
66
68
  );
67
69
  }
70
+ ScoreDetails.propTypes = {
71
+ columns: PropTypes.array,
72
+ };
73
+
74
+ const mapStateToProps = (state) => ({
75
+ columns: getQualityControlScoresColumns(state),
76
+ });
77
+
78
+ export default connect(mapStateToProps)(ScoreDetails);