Skip to content

Commit 20cd8d1

Browse files
authored
Merge pull request #1273 from w3c/releases
November 7, 2024 Production Release [v1.10.0] Includes changes recently included in the [releases branch](https://github.com/w3c/aria-at-app/tree/releases) through #1271. [Latest CHANGELOG.md update: v1.10.0](https://github.com/w3c/aria-at-app/blob/releases/CHANGELOG.md#1100-2024-11-07).
2 parents 8873bf0 + 53f6ced commit 20cd8d1

File tree

22 files changed

+677
-192
lines changed

22 files changed

+677
-192
lines changed

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## [1.10.0](https://github.com/w3c/aria-at-app/compare/v1.9.3...v1.10.0) (2024-11-07)
2+
3+
4+
### Features
5+
6+
* Issues table for Test Plan Review page ([#1269](https://github.com/w3c/aria-at-app/issues/1269)) ([cdd212d](https://github.com/w3c/aria-at-app/commit/cdd212d6a3cf9f1f673a5c445d8893fbdcc0520f))
7+
* Sortable issues table, improved issues table heading ([#1266](https://github.com/w3c/aria-at-app/issues/1266)) ([18fd21c](https://github.com/w3c/aria-at-app/commit/18fd21c1eda8adddca77b4f8edb62a9844e6ee11))
8+
9+
10+
### Bug Fixes
11+
12+
* Exclude unsupported assertion results from failure count in report headings; use separate "unsupported" metric ([#1265](https://github.com/w3c/aria-at-app/issues/1265)) ([9c54e2d](https://github.com/w3c/aria-at-app/commit/9c54e2d12e471c69f3bb5e79a11193df33c7fbe9))
13+
* sortable table th rendering bug ([#1272](https://github.com/w3c/aria-at-app/issues/1272)) ([77ba7cf](https://github.com/w3c/aria-at-app/commit/77ba7cf0cad94ff2a795f615cdc3fd7558d89713))
14+
115
### [1.9.3](https://github.com/w3c/aria-at-app/compare/v1.9.2...v1.9.3) (2024-10-28)
216

317

client/components/CandidateReview/CandidateTestPlanRun/index.jsx

+11-4
Original file line numberDiff line numberDiff line change
@@ -507,16 +507,23 @@ const CandidateTestPlanRun = () => {
507507
const testResult =
508508
testPlanReport.finalizedTestResults[currentTestIndex];
509509

510-
const { assertionsPassedCount, assertionsFailedCount } = getMetrics(
511-
{ testResult }
512-
);
510+
const {
511+
assertionsPassedCount,
512+
mustAssertionsFailedCount,
513+
shouldAssertionsFailedCount,
514+
mayAssertionsFailedCount
515+
} = getMetrics({ testResult });
516+
517+
const mustShouldAssertionsFailedCount =
518+
mustAssertionsFailedCount + shouldAssertionsFailedCount;
513519

514520
return (
515521
<>
516522
<h2 className="test-results-header">
517523
Test Results&nbsp;(
518524
{assertionsPassedCount} passed,&nbsp;
519-
{assertionsFailedCount} failed)
525+
{mustShouldAssertionsFailedCount} failed,&nbsp;
526+
{mayAssertionsFailedCount} unsupported)
520527
</h2>
521528
<TestPlanResultsTable
522529
key={`${testPlanReport.id} + ${testResult.id}`}

client/components/Reports/SummarizeTestPlanReport.jsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -272,17 +272,26 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => {
272272
'https://aria-at.netlify.app'
273273
);
274274

275-
const { assertionsPassedCount, assertionsFailedCount } = getMetrics({
275+
const {
276+
assertionsPassedCount,
277+
mustAssertionsFailedCount,
278+
shouldAssertionsFailedCount,
279+
mayAssertionsFailedCount
280+
} = getMetrics({
276281
testResult
277282
});
278283

284+
const mustShouldAssertionsFailedCount =
285+
mustAssertionsFailedCount + shouldAssertionsFailedCount;
286+
279287
return (
280288
<Fragment key={testResult.id}>
281289
<div className="test-result-heading">
282290
<h2 id={`result-${testResult.id}`} tabIndex="-1">
283291
Test {index + 1}: {test.title}&nbsp;(
284-
{assertionsPassedCount}
285-
&nbsp;passed, {assertionsFailedCount} failed)
292+
{assertionsPassedCount} passed,&nbsp;
293+
{mustShouldAssertionsFailedCount} failed,&nbsp;
294+
{mayAssertionsFailedCount} unsupported)
286295
<DisclaimerInfo phase={testPlanVersion.phase} />
287296
</h2>
288297
<div className="test-result-buttons">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import React, { useMemo, useState } from 'react';
2+
import { ThemeTable, ThemeTableUnavailable } from '../common/ThemeTable';
3+
import { dates } from 'shared';
4+
import { NoneText } from '../TestPlanVersionsPage';
5+
import SortableTableHeader, {
6+
TABLE_SORT_ORDERS
7+
} from '../common/SortableTableHeader';
8+
import FilterButtons from '../common/FilterButtons';
9+
import { IssuePropType } from '../common/proptypes';
10+
import PropTypes from 'prop-types';
11+
12+
const FILTER_OPTIONS = {
13+
OPEN: 'Open',
14+
CLOSED: 'Closed',
15+
ALL: 'All'
16+
};
17+
18+
const SORT_FIELDS = {
19+
AUTHOR: 'author',
20+
TITLE: 'title',
21+
STATUS: 'status',
22+
AT: 'at',
23+
CREATED_AT: 'createdAt',
24+
CLOSED_AT: 'closedAt'
25+
};
26+
27+
const SortableIssuesTable = ({ issues }) => {
28+
const [activeSort, setActiveSort] = useState(SORT_FIELDS.STATUS);
29+
const [sortOrder, setSortOrder] = useState(TABLE_SORT_ORDERS.ASC);
30+
const [activeFilter, setActiveFilter] = useState('OPEN');
31+
32+
const issueStats = useMemo(() => {
33+
const openIssues = issues.filter(issue => issue.isOpen).length;
34+
const closedIssues = issues.length - openIssues;
35+
return { openIssues, closedIssues };
36+
}, [issues]);
37+
38+
// Helper function to get sortable value from issue
39+
const getSortableValue = (issue, sortField) => {
40+
switch (sortField) {
41+
case SORT_FIELDS.AUTHOR:
42+
return issue.author;
43+
case SORT_FIELDS.TITLE:
44+
return issue.title;
45+
case SORT_FIELDS.AT:
46+
return issue.at?.name ?? '';
47+
case SORT_FIELDS.CREATED_AT:
48+
return new Date(issue.createdAt);
49+
case SORT_FIELDS.CLOSED_AT:
50+
return issue.closedAt ? new Date(issue.closedAt) : new Date(0);
51+
default:
52+
return '';
53+
}
54+
};
55+
56+
const compareByStatus = (a, b) => {
57+
if (a.isOpen !== b.isOpen) {
58+
if (sortOrder === TABLE_SORT_ORDERS.ASC) {
59+
return a.isOpen ? -1 : 1; // Open first for ascending
60+
}
61+
return a.isOpen ? 1 : -1; // Closed first for descending
62+
}
63+
// If status is the same, sort by date created (newest first)
64+
return new Date(b.createdAt) - new Date(a.createdAt);
65+
};
66+
67+
const compareValues = (aValue, bValue) => {
68+
return sortOrder === TABLE_SORT_ORDERS.ASC
69+
? aValue < bValue
70+
? -1
71+
: 1
72+
: aValue > bValue
73+
? -1
74+
: 1;
75+
};
76+
77+
const sortedAndFilteredIssues = useMemo(() => {
78+
// Filter issues
79+
const filtered =
80+
activeFilter === 'ALL'
81+
? issues
82+
: issues.filter(issue => issue.isOpen === (activeFilter === 'OPEN'));
83+
84+
// Sort issues
85+
return filtered.sort((a, b) => {
86+
// Special handling for status sorting
87+
if (activeSort === SORT_FIELDS.STATUS) {
88+
return compareByStatus(a, b);
89+
}
90+
91+
// Normal sorting for other fields
92+
const aValue = getSortableValue(a, activeSort);
93+
const bValue = getSortableValue(b, activeSort);
94+
return compareValues(aValue, bValue);
95+
});
96+
}, [issues, activeSort, sortOrder, activeFilter]);
97+
98+
const handleSort = column => newSortOrder => {
99+
setActiveSort(column);
100+
setSortOrder(newSortOrder);
101+
};
102+
103+
const renderTableHeader = () => (
104+
<thead>
105+
<tr>
106+
{[
107+
{ field: SORT_FIELDS.AUTHOR, title: 'Author' },
108+
{ field: SORT_FIELDS.TITLE, title: 'Issue' },
109+
{ field: SORT_FIELDS.STATUS, title: 'Status' },
110+
{ field: SORT_FIELDS.AT, title: 'Assistive Technology' },
111+
{ field: SORT_FIELDS.CREATED_AT, title: 'Created On' },
112+
{ field: SORT_FIELDS.CLOSED_AT, title: 'Closed On' }
113+
].map(({ field, title }) => (
114+
<SortableTableHeader
115+
key={field}
116+
title={title}
117+
active={activeSort === field}
118+
onSort={handleSort(field)}
119+
data-test={`sort-${field.toLowerCase()}`}
120+
/>
121+
))}
122+
</tr>
123+
</thead>
124+
);
125+
126+
const renderTableBody = () => (
127+
<tbody>
128+
{sortedAndFilteredIssues.map(issue => (
129+
<tr
130+
key={issue.link}
131+
data-test="issue-row"
132+
data-status={issue.isOpen ? 'open' : 'closed'}
133+
>
134+
<td>
135+
<a
136+
target="_blank"
137+
rel="noreferrer"
138+
href={`https://github.com/${issue.author}`}
139+
>
140+
{issue.author}
141+
</a>
142+
</td>
143+
<td>
144+
<a target="_blank" rel="noreferrer" href={issue.link}>
145+
{issue.title}
146+
</a>
147+
</td>
148+
<td data-test="issue-status">{issue.isOpen ? 'Open' : 'Closed'}</td>
149+
<td>{issue.at?.name ?? 'AT not specified'}</td>
150+
<td>{dates.convertDateToString(issue.createdAt, 'MMM D, YYYY')}</td>
151+
<td>
152+
{!issue.closedAt ? (
153+
<NoneText>N/A</NoneText>
154+
) : (
155+
dates.convertDateToString(issue.closedAt, 'MMM D, YYYY')
156+
)}
157+
</td>
158+
</tr>
159+
))}
160+
</tbody>
161+
);
162+
163+
return (
164+
<>
165+
<h2 id="github-issues">
166+
GitHub Issues ({issueStats.openIssues} open, {issueStats.closedIssues}
167+
&nbsp;closed)
168+
</h2>
169+
<FilterButtons
170+
filterLabel="Filter"
171+
filterAriaLabel="Filter GitHub issues"
172+
filterOptions={FILTER_OPTIONS}
173+
activeFilter={activeFilter}
174+
onFilterChange={setActiveFilter}
175+
/>
176+
{!sortedAndFilteredIssues.length ? (
177+
<ThemeTableUnavailable aria-labelledby="github-issues">
178+
No GitHub Issues
179+
</ThemeTableUnavailable>
180+
) : (
181+
<ThemeTable
182+
bordered
183+
aria-labelledby="github-issues"
184+
data-test="issues-table"
185+
>
186+
{renderTableHeader()}
187+
{renderTableBody()}
188+
</ThemeTable>
189+
)}
190+
</>
191+
);
192+
};
193+
194+
SortableIssuesTable.propTypes = {
195+
issues: PropTypes.arrayOf(IssuePropType).isRequired
196+
};
197+
198+
export default SortableIssuesTable;

0 commit comments

Comments
 (0)