@truedat/dq 4.56.2 → 4.56.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.56.3] 2022-11-24
4
+
5
+ ### Added
6
+
7
+ - [TD-5286] Implementation results pagination
8
+
3
9
  ## [4.56.2] 2022-11-24
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dq",
3
- "version": "4.56.2",
3
+ "version": "4.56.3",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.16.4",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "4.56.0",
37
+ "@truedat/test": "4.56.3",
38
38
  "babel-jest": "^28.1.0",
39
39
  "babel-plugin-dynamic-import-node": "^2.3.3",
40
40
  "babel-plugin-lodash": "^3.3.4",
@@ -93,8 +93,8 @@
93
93
  },
94
94
  "dependencies": {
95
95
  "@apollo/client": "^3.7.0",
96
- "@truedat/core": "4.56.2",
97
- "@truedat/df": "4.56.2",
96
+ "@truedat/core": "4.56.3",
97
+ "@truedat/df": "4.56.3",
98
98
  "graphql": "^15.5.3",
99
99
  "path-to-regexp": "^1.7.0",
100
100
  "prop-types": "^15.8.1",
@@ -114,5 +114,5 @@
114
114
  "react-dom": ">= 16.8.6 < 17",
115
115
  "semantic-ui-react": ">= 0.88.2 < 2.1"
116
116
  },
117
- "gitHead": "d76f2da5ed4892e557e4aa159d4e9ff009e0e853"
117
+ "gitHead": "7940a71d7e8ed3eb6c1fc088cf631e37e87fd093"
118
118
  }
@@ -15,16 +15,17 @@ export const IMPLEMENTATION_VERSIONS_QUERY = gql`
15
15
  }
16
16
  `;
17
17
 
18
- export const IMPLEMENTATION_RESULTS_QUERY = gql`
19
- query Implementation($id: ID!) {
18
+ export const IMPLEMENTATION_RESULTS_CONNECTION_QUERY = gql`
19
+ query Implementation(
20
+ $id: ID!
21
+ $after: Cursor
22
+ $before: Cursor
23
+ $last: Int
24
+ $first: Int
25
+ ) {
20
26
  implementation(id: $id) {
21
27
  id
22
- implementationKey
23
- lastQualityEvent {
24
- insertedAt
25
- message
26
- type
27
- }
28
+ version
28
29
  versions {
29
30
  id
30
31
  implementationKey
@@ -32,7 +33,20 @@ export const IMPLEMENTATION_RESULTS_QUERY = gql`
32
33
  status
33
34
  minimum
34
35
  goal
35
- results {
36
+ }
37
+ lastQualityEvent {
38
+ insertedAt
39
+ message
40
+ type
41
+ }
42
+ resultsConnection(
43
+ first: $first
44
+ last: $last
45
+ before: $before
46
+ after: $after
47
+ ) {
48
+ totalCount
49
+ page {
36
50
  id
37
51
  date
38
52
  details
@@ -43,8 +57,22 @@ export const IMPLEMENTATION_RESULTS_QUERY = gql`
43
57
  records
44
58
  result
45
59
  resultType
60
+ implementation {
61
+ id
62
+ version
63
+ }
64
+ __typename
65
+ }
66
+ pageInfo {
67
+ startCursor
68
+ endCursor
69
+ hasNextPage
70
+ hasPreviousPage
71
+ __typename
46
72
  }
73
+ __typename
47
74
  }
75
+ __typename
48
76
  }
49
77
  }
50
78
  `;
@@ -58,33 +58,36 @@ ExecutionRow.propTypes = {
58
58
  implementation: PropTypes.object,
59
59
  };
60
60
 
61
- export const ImplementationExecutions = ({ page }) => (
62
- <Table basic="very">
63
- <Table.Header>
64
- <Table.Row>
65
- <Table.HeaderCell width="1" textAlign="right">
66
- <FormattedMessage id="ruleImplementations.props.version" />
67
- </Table.HeaderCell>
68
- <Table.HeaderCell width="2">
69
- <FormattedMessage id="execution" />
70
- </Table.HeaderCell>
71
- <Table.HeaderCell width="2">
72
- <FormattedMessage id="execution.latestEvent.insertedAt" />
73
- </Table.HeaderCell>
74
- <Table.HeaderCell width="2">
75
- <FormattedMessage id="execution.latestEvent.type" />
76
- </Table.HeaderCell>
77
- <Table.HeaderCell>
78
- <FormattedMessage id="execution.latestEvent.message" />
79
- </Table.HeaderCell>
80
- </Table.Row>
81
- </Table.Header>
82
- <Table.Body>
83
- {page.map((props, key) => (
84
- <ExecutionRow key={key} {...props} />
85
- ))}
86
- </Table.Body>
87
- </Table>
61
+ export const ImplementationExecutions = (connection) => (
62
+ <>
63
+ <Table basic="very">
64
+ <Table.Header>
65
+ <Table.Row>
66
+ <Table.HeaderCell width="1" textAlign="right">
67
+ <FormattedMessage id="ruleImplementations.props.version" />
68
+ </Table.HeaderCell>
69
+ <Table.HeaderCell width="2">
70
+ <FormattedMessage id="execution" />
71
+ </Table.HeaderCell>
72
+ <Table.HeaderCell width="2">
73
+ <FormattedMessage id="execution.latestEvent.insertedAt" />
74
+ </Table.HeaderCell>
75
+ <Table.HeaderCell width="2">
76
+ <FormattedMessage id="execution.latestEvent.type" />
77
+ </Table.HeaderCell>
78
+ <Table.HeaderCell>
79
+ <FormattedMessage id="execution.latestEvent.message" />
80
+ </Table.HeaderCell>
81
+ </Table.Row>
82
+ </Table.Header>
83
+ <Table.Body>
84
+ {connection.page.map((props, key) => (
85
+ <ExecutionRow key={key} {...props} />
86
+ ))}
87
+ </Table.Body>
88
+ </Table>
89
+ <CursorPagination {...connection} />
90
+ </>
88
91
  );
89
92
 
90
93
  ImplementationExecutions.propTypes = {
@@ -111,9 +114,6 @@ export const ImplementationExecutionsLoader = () => {
111
114
  <Grid.Column>
112
115
  <ImplementationExecutionFilters />
113
116
  </Grid.Column>
114
- <Grid.Column textAlign="right">
115
- <CursorPagination {...connection} />
116
- </Grid.Column>
117
117
  </Grid.Row>
118
118
  </Grid>
119
119
  {loading ? <Loading /> : null}
@@ -4,29 +4,28 @@ import { useParams } from "react-router-dom";
4
4
  import { useQuery } from "@apollo/client";
5
5
  import PropTypes from "prop-types";
6
6
  import { useIntl } from "react-intl";
7
- import { Message, Divider } from "semantic-ui-react";
7
+ import { Segment, Message, Divider } from "semantic-ui-react";
8
8
  import { columnDecorator } from "@truedat/core/services";
9
9
  import { DateTime, Loading } from "@truedat/core/components";
10
- import { IMPLEMENTATION_RESULTS_QUERY } from "../api/queries";
10
+ import { CursorPagination } from "@truedat/core/components";
11
+ import { useLocation } from "react-router-dom";
12
+ import queryString from "query-string";
13
+ import { IMPLEMENTATION_RESULTS_CONNECTION_QUERY } from "../api/queries";
11
14
  import RuleResultsTable from "./RuleResultsTable";
12
15
 
16
+ export const PAGE_SIZE = 20;
17
+
13
18
  export const getCustomColumnsWithData = (ruleResults, columns) =>
14
19
  _.filter((column) =>
15
20
  _.any(_.flow(columnDecorator(column), _.negate(_.isEmpty)))(ruleResults)
16
21
  )(columns);
17
22
 
18
- export const RuleImplementationResults = ({ ruleImplementation }) => {
23
+ export const RuleImplementationResults = ({ implementation, results }) => {
19
24
  const { formatMessage, locale } = useIntl();
20
25
 
21
- if (_.isEmpty(ruleImplementation)) return null;
22
-
23
- const implementationVersions = _.propOr([], "versions")(ruleImplementation);
24
- const currentImplementationVersion = _.flow(
25
- _.filter((version) => version.id === ruleImplementation.id),
26
- _.first
27
- )(implementationVersions);
26
+ if (_.isEmpty(implementation)) return null;
28
27
 
29
- const { lastQualityEvent } = ruleImplementation;
28
+ const { lastQualityEvent } = implementation;
30
29
 
31
30
  return (
32
31
  <>
@@ -46,31 +45,45 @@ export const RuleImplementationResults = ({ ruleImplementation }) => {
46
45
  <p style={{ whiteSpace: "pre-wrap" }}>{lastQualityEvent.message}</p>
47
46
  </Message>
48
47
  ) : null}
49
- <RuleResultsTable
50
- currentImplementationVersion={currentImplementationVersion}
51
- implementationVersions={implementationVersions}
52
- />
48
+ <RuleResultsTable implementation={implementation} results={results} />
53
49
  </>
54
50
  );
55
51
  };
56
52
 
57
53
  RuleImplementationResults.propTypes = {
58
- ruleImplementation: PropTypes.object,
54
+ results: PropTypes.array,
55
+ implementation: PropTypes.object,
59
56
  };
60
57
 
61
58
  export const RuleImplementationResultsLoader = (props) => {
62
59
  const { implementation_id: id } = useParams();
63
- const { loading, error, data } = useQuery(IMPLEMENTATION_RESULTS_QUERY, {
64
- variables: { id },
65
- });
60
+
61
+ const { search } = useLocation();
62
+ const { before, after } = queryString.parse(search);
63
+ const variables = after
64
+ ? { id, after, before, first: PAGE_SIZE }
65
+ : { id, after, before, last: PAGE_SIZE };
66
+
67
+ const { loading, error, data } = useQuery(
68
+ IMPLEMENTATION_RESULTS_CONNECTION_QUERY,
69
+ {
70
+ variables,
71
+ }
72
+ );
66
73
  if (error) return null;
67
74
  if (loading) return <Loading />;
68
- const ruleImplementation = data?.implementation;
75
+ const implementation = data?.implementation;
76
+ const connection = data?.implementation?.resultsConnection;
77
+ const results = implementation?.resultsConnection?.page;
69
78
  return (
70
- <RuleImplementationResults
71
- ruleImplementation={ruleImplementation}
72
- {...props}
73
- />
79
+ <Segment attached="bottom">
80
+ <RuleImplementationResults
81
+ implementation={implementation}
82
+ results={results}
83
+ {...props}
84
+ />
85
+ <CursorPagination {...connection} />
86
+ </Segment>
74
87
  );
75
88
  };
76
89
 
@@ -79,7 +79,10 @@ export const RuleResultRow = ({
79
79
  <>
80
80
  <Icon
81
81
  name="circle"
82
- color={selectColor({ ...ruleImplementation, ...ruleResult })}
82
+ color={selectColor({
83
+ ...ruleImplementation,
84
+ ...ruleResult,
85
+ })}
83
86
  />
84
87
  {`${parseFloat(ruleResult.result)} %`}
85
88
  </>
@@ -21,22 +21,14 @@ export const getCustomColumnsWithData = (ruleResults, columns) =>
21
21
  _.any(_.flow(columnDecorator(column), _.negate(_.isEmpty)))(ruleResults)
22
22
  )(columns);
23
23
 
24
- export const RuleResultsTable = ({
25
- columns,
26
- implementationVersions,
27
- currentImplementationVersion,
28
- }) => {
24
+ export const RuleResultsTable = ({ columns, implementation, results }) => {
29
25
  const isAdmin = useAuthorized();
30
26
  const { formatMessage } = useIntl();
31
27
 
32
- const ruleResults = _.flow(
33
- _.map((iv) => _.pathOr([], "results")(iv)),
34
- _.flatten
35
- )(implementationVersions);
36
- const optionalColumns = getOptionalColumnsWithData(ruleResults);
37
- const customColumns = getCustomColumnsWithData(ruleResults, columns);
28
+ const optionalColumns = getOptionalColumnsWithData(results);
29
+ const customColumns = getCustomColumnsWithData(results, columns);
38
30
 
39
- return _.isEmpty(ruleResults) ? (
31
+ return _.isEmpty(results) ? (
40
32
  <Message
41
33
  style={{ marginTop: "14px" }}
42
34
  header={formatMessage({
@@ -77,28 +69,43 @@ export const RuleResultsTable = ({
77
69
  </Table.Row>
78
70
  </Table.Header>
79
71
  <Table.Body>
80
- {implementationVersions.map((version) =>
81
- _.pathOr(
82
- [],
83
- "results"
84
- )(version).map((result, key) => (
72
+ {_.flow(
73
+ _.groupBy("implementation.version"),
74
+ _.toPairs, // first element: version number; second element: version results.
75
+ // order by descending version number
76
+ _.orderBy(
77
+ (versionAndResultsTuple) => versionAndResultsTuple[0],
78
+ "desc"
79
+ ),
80
+ _.mapValues((versionAndResultsTuple) =>
81
+ versionAndResultsTuple[1].map(
82
+ (result, indexWithinVersionGroup) => ({
83
+ indexWithinVersionGroup,
84
+ ...result,
85
+ })
86
+ )
87
+ ),
88
+ _.reduce((acc, versionResults) => [...acc, ...versionResults], []),
89
+ _.map((result) => (
85
90
  <RuleResultRow
86
- key={key}
91
+ key={result.id}
87
92
  optionalColumns={optionalColumns}
88
93
  ruleResult={result}
89
94
  customColumns={customColumns}
90
- ruleImplementation={version}
91
- active={currentImplementationVersion.id === version.id}
95
+ ruleImplementation={_.find(
96
+ (version) => version?.id === result?.implementation?.id
97
+ )(implementation?.versions)}
98
+ active={implementation.version === result.implementation.version}
92
99
  tagLabel={
93
- key === 0
100
+ result.indexWithinVersionGroup === 0
94
101
  ? `${formatMessage({
95
102
  id: "ruleImplementations.props.version",
96
- })}: ${version.version}`
103
+ })}: ${result.implementation.version}`
97
104
  : null
98
105
  }
99
106
  />
100
107
  ))
101
- )}
108
+ )(results)}
102
109
  </Table.Body>
103
110
  </Table>
104
111
  );
@@ -106,8 +113,8 @@ export const RuleResultsTable = ({
106
113
 
107
114
  RuleResultsTable.propTypes = {
108
115
  columns: PropTypes.array,
109
- implementationVersions: PropTypes.array,
110
- currentImplementationVersion: PropTypes.object,
116
+ implementation: PropTypes.object,
117
+ results: PropTypes.array,
111
118
  };
112
119
 
113
120
  const mapStateToProps = (state) => ({
@@ -1,78 +1,31 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import { render } from "@truedat/test/render";
4
- import {
5
- RuleImplementationResults,
4
+ import RuleImplementationResults, {
6
5
  getCustomColumnsWithData,
6
+ PAGE_SIZE,
7
7
  } from "../RuleImplementationResults";
8
8
 
9
+ import { implementationResultsMock } from "./__fixtures__/implementationResultsMocks";
10
+
11
+ jest.mock("react-router-dom", () => ({
12
+ ...jest.requireActual("react-router-dom"),
13
+ useParams: () => ({ implementation_id: "2" }),
14
+ }));
15
+
9
16
  describe("<RuleImplementationResults />", () => {
10
- const ruleImplementation = {
11
- id: 1,
12
- implementation_key: "default_key",
13
- versions: [
14
- {
15
- implementation_key: "default_key",
16
- id: 1,
17
- goal: "20.0",
18
- minimum: "10.0",
19
- results: [
20
- {
21
- errors: 2,
22
- hasRemediation: false,
23
- hasSegments: true,
24
- id: "66577",
25
- records: 31,
26
- result: "93.54",
27
- resultType: "percentage",
28
- },
29
- {
30
- errors: 2,
31
- hasRemediation: false,
32
- hasSegments: true,
33
- id: "66578",
34
- records: 31,
35
- result: "93.54",
36
- resultType: "percentage",
37
- },
38
- ],
39
- },
40
- {
41
- implementation_key: "key_2",
42
- id: 2,
43
- goal: "20.0",
44
- minimum: "10.0",
45
- results: [
46
- {
47
- errors: 2,
48
- hasRemediation: false,
49
- hasSegments: true,
50
- id: "66579",
51
- records: 31,
52
- result: "93.54",
53
- resultType: "percentage",
54
- },
55
- {
56
- errors: 2,
57
- hasRemediation: false,
58
- hasSegments: true,
59
- id: "66580",
60
- records: 31,
61
- result: "93.54",
62
- resultType: "percentage",
63
- },
64
- ],
65
- },
66
- ],
17
+ const renderOpts = {
18
+ mocks: [implementationResultsMock({ id: "2", last: PAGE_SIZE })],
67
19
  };
20
+
68
21
  const ruleResultsColumns = [{ foo: "bar", name: "foo" }];
69
- const props = {
70
- ruleImplementation,
71
- ruleResultsColumns,
72
- };
73
22
 
74
- it("matches the latest snapshot", () => {
75
- const { container } = render(<RuleImplementationResults {...props} />);
23
+ it("matches the latest snapshot", async () => {
24
+ const { container, findByRole } = render(
25
+ <RuleImplementationResults />,
26
+ renderOpts
27
+ );
28
+ expect(await findByRole("table")).toBeInTheDocument();
76
29
  expect(container).toMatchSnapshot();
77
30
  });
78
31
  });
@@ -0,0 +1,137 @@
1
+ import { IMPLEMENTATION_RESULTS_CONNECTION_QUERY } from "../../../api/queries";
2
+
3
+ const versions = [
4
+ {
5
+ __typename: "Implementation",
6
+ minimum: 85,
7
+ goal: 95,
8
+ id: "2",
9
+ implementationKey: "implementation_2_key",
10
+ status: "published",
11
+ version: 2,
12
+ },
13
+ {
14
+ __typename: "Implementation",
15
+ minimum: 70,
16
+ goal: 80,
17
+ id: "1",
18
+ implementationKey: "implementation_1_key",
19
+ status: "versioned",
20
+ version: 1,
21
+ },
22
+ ];
23
+
24
+ const implementation = {
25
+ __typename: "Implementation",
26
+ id: "2",
27
+ lastQualityEvent: null,
28
+ versions,
29
+ resultsConnection: {
30
+ __typename: "ResultsConnection",
31
+ page: [
32
+ {
33
+ __typename: "ImplementationResult",
34
+ date: "2021-08-10T00:00:00Z",
35
+ details: {},
36
+ hasRemediation: true,
37
+ hasSegments: false,
38
+ id: "6515",
39
+ implementation: {
40
+ __typename: "Implementation",
41
+ id: "2",
42
+ version: 2,
43
+ },
44
+ params: {
45
+ cbloqfun: "VID-ALM",
46
+ cmesano: 202107,
47
+ },
48
+ records: 100,
49
+ errors: 20,
50
+ result: "80.00", // 80 < 85 Red
51
+ resultType: "percentage",
52
+ },
53
+ {
54
+ __typename: "ImplementationResult",
55
+ date: "2021-08-06T00:00:00Z",
56
+ details: {},
57
+ hasRemediation: false,
58
+ hasSegments: false,
59
+ id: "6514",
60
+ implementation: {
61
+ __typename: "Implementation",
62
+ id: "2",
63
+ version: 2,
64
+ },
65
+ params: {
66
+ cbloqfun: "VID-ALM",
67
+ cmesano: 202107,
68
+ },
69
+ records: 100,
70
+ errors: 10,
71
+ result: "90.00", // 85 < 90 < 95 Yellow
72
+ resultType: "percentage",
73
+ },
74
+ {
75
+ __typename: "ImplementationResult",
76
+ date: "2022-05-05T00:00:00Z",
77
+ details: {},
78
+ hasRemediation: false,
79
+ hasSegments: false,
80
+ id: "6488",
81
+ implementation: {
82
+ __typename: "Implementation",
83
+ id: "1",
84
+ version: 1,
85
+ },
86
+ params: {
87
+ cbloqfun: "VID-ALM",
88
+ cmesano: "202204",
89
+ },
90
+ records: 100,
91
+ errors: 40,
92
+ result: "60.00", // 60 < 70 Red
93
+ resultType: "percentage",
94
+ },
95
+ {
96
+ __typename: "ImplementationResult",
97
+ date: "2022-04-07T00:00:00Z",
98
+ details: {},
99
+ hasRemediation: false,
100
+ hasSegments: false,
101
+ id: "6388",
102
+ implementation: {
103
+ __typename: "Implementation",
104
+ id: "1",
105
+ version: 1,
106
+ },
107
+ params: {
108
+ cbloqfun: "VID-ALM",
109
+ cmesano: "202203",
110
+ },
111
+ records: 100,
112
+ errors: 5,
113
+ result: "95.00", // 80 < 95 Green
114
+ resultType: "percentage",
115
+ },
116
+ ],
117
+ pageInfo: {
118
+ __typename: "PageInfo",
119
+ endCursor: null,
120
+ hasNextPage: false,
121
+ hasPreviousPage: true,
122
+ startCursor: null,
123
+ },
124
+ totalCount: 4,
125
+ },
126
+ version: 2,
127
+ };
128
+
129
+ export const implementationResultsMock = (variables) => ({
130
+ request: {
131
+ query: IMPLEMENTATION_RESULTS_CONNECTION_QUERY,
132
+ variables,
133
+ },
134
+ result: {
135
+ data: { implementation },
136
+ },
137
+ });
@@ -46,34 +46,6 @@ exports[`<ImplementationExecutions /> matches snapshot with executions table 1`]
46
46
  </a>
47
47
  </div>
48
48
  </div>
49
- <div
50
- class="right aligned column"
51
- >
52
- <div
53
- class="ui pagination menu"
54
- role="navigation"
55
- >
56
- <div
57
- aria-label="Latest"
58
- class="disabled item"
59
- >
60
- «
61
- </div>
62
- <div
63
- aria-label="Later"
64
- class="disabled item"
65
- >
66
-
67
- </div>
68
- <a
69
- aria-label="Earlier"
70
- class="link item"
71
- href="/?before=FOO"
72
- >
73
-
74
- </a>
75
- </div>
76
- </div>
77
49
  </div>
78
50
  </div>
79
51
  <table
@@ -189,6 +161,30 @@ exports[`<ImplementationExecutions /> matches snapshot with executions table 1`]
189
161
  </tr>
190
162
  </tbody>
191
163
  </table>
164
+ <div
165
+ class="ui pagination menu"
166
+ role="navigation"
167
+ >
168
+ <div
169
+ aria-label="Latest"
170
+ class="disabled item"
171
+ >
172
+ «
173
+ </div>
174
+ <div
175
+ aria-label="Later"
176
+ class="disabled item"
177
+ >
178
+
179
+ </div>
180
+ <a
181
+ aria-label="Earlier"
182
+ class="link item"
183
+ href="/?before=FOO"
184
+ >
185
+
186
+ </a>
187
+ </div>
192
188
  </div>
193
189
  </div>
194
190
  `;
@@ -2,212 +2,251 @@
2
2
 
3
3
  exports[`<RuleImplementationResults /> matches the latest snapshot 1`] = `
4
4
  <div>
5
- <table
6
- class="ui table implementation-results small"
5
+ <div
6
+ class="ui bottom attached segment"
7
7
  >
8
- <thead
9
- class=""
8
+ <table
9
+ class="ui table implementation-results small"
10
10
  >
11
- <tr
11
+ <thead
12
12
  class=""
13
13
  >
14
- <th
14
+ <tr
15
15
  class=""
16
16
  >
17
- Date
18
- </th>
19
- <th
20
- class=""
21
- >
22
- Quality
23
- </th>
24
- <th
25
- class=""
26
- >
27
- Records
28
- </th>
29
- <th
30
- class=""
31
- >
32
- Errors
33
- </th>
34
- <th
35
- class=""
36
- />
37
- </tr>
38
- </thead>
39
- <tbody
40
- class=""
41
- >
42
- <tr
17
+ <th
18
+ class=""
19
+ >
20
+ Date
21
+ </th>
22
+ <th
23
+ class=""
24
+ >
25
+ Quality
26
+ </th>
27
+ <th
28
+ class=""
29
+ >
30
+ Records
31
+ </th>
32
+ <th
33
+ class=""
34
+ >
35
+ Errors
36
+ </th>
37
+ <th
38
+ class=""
39
+ />
40
+ </tr>
41
+ </thead>
42
+ <tbody
43
43
  class=""
44
44
  >
45
- <td
45
+ <tr
46
46
  class=""
47
47
  >
48
- <a
49
- class="ui active ribbon label"
50
- href="/implementations/1"
51
- >
52
- Version: undefined
53
- </a>
54
- </td>
55
- </tr>
56
- <tr
57
- class="warning"
58
- >
59
- <td
60
- class=""
61
- >
62
- <a
63
- href="/implementations/1/results/66577"
48
+ <td
49
+ class=""
50
+ >
51
+ <a
52
+ class="ui active ribbon label"
53
+ href="/implementations/2"
54
+ >
55
+ Version: 2
56
+ </a>
57
+ </td>
58
+ </tr>
59
+ <tr
60
+ class="warning"
61
+ >
62
+ <td
63
+ class=""
64
+ >
65
+ <a
66
+ href="/implementations/2/results/6515"
67
+ >
68
+ <time
69
+ class="rule-result-date"
70
+ datetime="1628553600000"
71
+ >
72
+ 2021-08-10 00:00
73
+ </time>
74
+ <i
75
+ aria-hidden="true"
76
+ class="blue rain small circular inverted icon"
77
+ />
78
+ </a>
79
+ </td>
80
+ <td
81
+ class=""
64
82
  >
65
83
  <i
66
84
  aria-hidden="true"
67
- class="blue grid layout small circular inverted icon"
85
+ class="red circle icon"
68
86
  />
69
- </a>
70
- </td>
71
- <td
72
- class=""
73
- >
74
- <i
75
- aria-hidden="true"
76
- class="green circle icon"
77
- />
78
- 93.54 %
79
- </td>
80
- <td
81
- class=""
82
- >
83
- 31
84
- </td>
85
- <td
86
- class=""
87
- >
88
- 2
89
- </td>
90
- </tr>
91
- <tr
92
- class="warning"
93
- >
94
- <td
95
- class=""
96
- >
97
- <a
98
- href="/implementations/1/results/66578"
87
+ 80 %
88
+ </td>
89
+ <td
90
+ class=""
91
+ >
92
+ 100
93
+ </td>
94
+ <td
95
+ class=""
96
+ >
97
+ 20
98
+ </td>
99
+ </tr>
100
+ <tr
101
+ class="warning"
102
+ >
103
+ <td
104
+ class=""
105
+ >
106
+ <a
107
+ href="/implementations/2/results/6514"
108
+ >
109
+ <time
110
+ class="rule-result-date"
111
+ datetime="1628208000000"
112
+ >
113
+ 2021-08-06 00:00
114
+ </time>
115
+ </a>
116
+ </td>
117
+ <td
118
+ class=""
99
119
  >
100
120
  <i
101
121
  aria-hidden="true"
102
- class="blue grid layout small circular inverted icon"
122
+ class="yellow circle icon"
103
123
  />
104
- </a>
105
- </td>
106
- <td
107
- class=""
108
- >
109
- <i
110
- aria-hidden="true"
111
- class="green circle icon"
112
- />
113
- 93.54 %
114
- </td>
115
- <td
116
- class=""
117
- >
118
- 31
119
- </td>
120
- <td
121
- class=""
122
- >
123
- 2
124
- </td>
125
- </tr>
126
- <tr
127
- class=""
128
- >
129
- <td
130
- class=""
131
- >
132
- <a
133
- class="ui ribbon label"
134
- href="/implementations/2"
135
- >
136
- Version: undefined
137
- </a>
138
- </td>
139
- </tr>
140
- <tr
141
- class=""
142
- >
143
- <td
124
+ 90 %
125
+ </td>
126
+ <td
127
+ class=""
128
+ >
129
+ 100
130
+ </td>
131
+ <td
132
+ class=""
133
+ >
134
+ 10
135
+ </td>
136
+ </tr>
137
+ <tr
144
138
  class=""
145
139
  >
146
- <a
147
- href="/implementations/2/results/66579"
140
+ <td
141
+ class=""
142
+ >
143
+ <a
144
+ class="ui ribbon label"
145
+ href="/implementations/1"
146
+ >
147
+ Version: 1
148
+ </a>
149
+ </td>
150
+ </tr>
151
+ <tr
152
+ class=""
153
+ >
154
+ <td
155
+ class=""
156
+ >
157
+ <a
158
+ href="/implementations/1/results/6488"
159
+ >
160
+ <time
161
+ class="rule-result-date"
162
+ datetime="1651708800000"
163
+ >
164
+ 2022-05-05 00:00
165
+ </time>
166
+ </a>
167
+ </td>
168
+ <td
169
+ class=""
148
170
  >
149
171
  <i
150
172
  aria-hidden="true"
151
- class="blue grid layout small circular inverted icon"
173
+ class="red circle icon"
152
174
  />
153
- </a>
154
- </td>
155
- <td
156
- class=""
157
- >
158
- <i
159
- aria-hidden="true"
160
- class="green circle icon"
161
- />
162
- 93.54 %
163
- </td>
164
- <td
165
- class=""
166
- >
167
- 31
168
- </td>
169
- <td
170
- class=""
171
- >
172
- 2
173
- </td>
174
- </tr>
175
- <tr
176
- class=""
177
- >
178
- <td
175
+ 60 %
176
+ </td>
177
+ <td
178
+ class=""
179
+ >
180
+ 100
181
+ </td>
182
+ <td
183
+ class=""
184
+ >
185
+ 40
186
+ </td>
187
+ </tr>
188
+ <tr
179
189
  class=""
180
190
  >
181
- <a
182
- href="/implementations/2/results/66580"
191
+ <td
192
+ class=""
193
+ >
194
+ <a
195
+ href="/implementations/1/results/6388"
196
+ >
197
+ <time
198
+ class="rule-result-date"
199
+ datetime="1649289600000"
200
+ >
201
+ 2022-04-07 00:00
202
+ </time>
203
+ </a>
204
+ </td>
205
+ <td
206
+ class=""
183
207
  >
184
208
  <i
185
209
  aria-hidden="true"
186
- class="blue grid layout small circular inverted icon"
210
+ class="green circle icon"
187
211
  />
188
- </a>
189
- </td>
190
- <td
191
- class=""
192
- >
193
- <i
194
- aria-hidden="true"
195
- class="green circle icon"
196
- />
197
- 93.54 %
198
- </td>
199
- <td
200
- class=""
201
- >
202
- 31
203
- </td>
204
- <td
205
- class=""
206
- >
207
- 2
208
- </td>
209
- </tr>
210
- </tbody>
211
- </table>
212
+ 95 %
213
+ </td>
214
+ <td
215
+ class=""
216
+ >
217
+ 100
218
+ </td>
219
+ <td
220
+ class=""
221
+ >
222
+ 5
223
+ </td>
224
+ </tr>
225
+ </tbody>
226
+ </table>
227
+ <div
228
+ class="ui pagination menu"
229
+ role="navigation"
230
+ >
231
+ <div
232
+ aria-label="Latest"
233
+ class="disabled item"
234
+ >
235
+ «
236
+ </div>
237
+ <div
238
+ aria-label="Later"
239
+ class="disabled item"
240
+ >
241
+
242
+ </div>
243
+ <div
244
+ aria-label="Earlier"
245
+ class="item"
246
+ >
247
+
248
+ </div>
249
+ </div>
250
+ </div>
212
251
  </div>
213
252
  `;