Skip to content

Commit

Permalink
Merge pull request #1271 from w3c/development
Browse files Browse the repository at this point in the history
Create November 7, 2024 Release

Includes the following changes:
* #1265, to address #1248
* #1266, to address #1251 and #1252
* #1269, to address #1250
* #1272
  • Loading branch information
howard-e authored Nov 7, 2024
2 parents 475230a + 77ba7cf commit 098cda8
Show file tree
Hide file tree
Showing 20 changed files with 662 additions and 191 deletions.
15 changes: 11 additions & 4 deletions client/components/CandidateReview/CandidateTestPlanRun/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -507,16 +507,23 @@ const CandidateTestPlanRun = () => {
const testResult =
testPlanReport.finalizedTestResults[currentTestIndex];

const { assertionsPassedCount, assertionsFailedCount } = getMetrics(
{ testResult }
);
const {
assertionsPassedCount,
mustAssertionsFailedCount,
shouldAssertionsFailedCount,
mayAssertionsFailedCount
} = getMetrics({ testResult });

const mustShouldAssertionsFailedCount =
mustAssertionsFailedCount + shouldAssertionsFailedCount;

return (
<>
<h2 className="test-results-header">
Test Results&nbsp;(
{assertionsPassedCount} passed,&nbsp;
{assertionsFailedCount} failed)
{mustShouldAssertionsFailedCount} failed,&nbsp;
{mayAssertionsFailedCount} unsupported)
</h2>
<TestPlanResultsTable
key={`${testPlanReport.id} + ${testResult.id}`}
Expand Down
15 changes: 12 additions & 3 deletions client/components/Reports/SummarizeTestPlanReport.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,17 +272,26 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => {
'https://aria-at.netlify.app'
);

const { assertionsPassedCount, assertionsFailedCount } = getMetrics({
const {
assertionsPassedCount,
mustAssertionsFailedCount,
shouldAssertionsFailedCount,
mayAssertionsFailedCount
} = getMetrics({
testResult
});

const mustShouldAssertionsFailedCount =
mustAssertionsFailedCount + shouldAssertionsFailedCount;

return (
<Fragment key={testResult.id}>
<div className="test-result-heading">
<h2 id={`result-${testResult.id}`} tabIndex="-1">
Test {index + 1}: {test.title}&nbsp;(
{assertionsPassedCount}
&nbsp;passed, {assertionsFailedCount} failed)
{assertionsPassedCount} passed,&nbsp;
{mustShouldAssertionsFailedCount} failed,&nbsp;
{mayAssertionsFailedCount} unsupported)
<DisclaimerInfo phase={testPlanVersion.phase} />
</h2>
<div className="test-result-buttons">
Expand Down
198 changes: 198 additions & 0 deletions client/components/SortableIssuesTable/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import React, { useMemo, useState } from 'react';
import { ThemeTable, ThemeTableUnavailable } from '../common/ThemeTable';
import { dates } from 'shared';
import { NoneText } from '../TestPlanVersionsPage';
import SortableTableHeader, {
TABLE_SORT_ORDERS
} from '../common/SortableTableHeader';
import FilterButtons from '../common/FilterButtons';
import { IssuePropType } from '../common/proptypes';
import PropTypes from 'prop-types';

const FILTER_OPTIONS = {
OPEN: 'Open',
CLOSED: 'Closed',
ALL: 'All'
};

const SORT_FIELDS = {
AUTHOR: 'author',
TITLE: 'title',
STATUS: 'status',
AT: 'at',
CREATED_AT: 'createdAt',
CLOSED_AT: 'closedAt'
};

const SortableIssuesTable = ({ issues }) => {
const [activeSort, setActiveSort] = useState(SORT_FIELDS.STATUS);
const [sortOrder, setSortOrder] = useState(TABLE_SORT_ORDERS.ASC);
const [activeFilter, setActiveFilter] = useState('OPEN');

const issueStats = useMemo(() => {
const openIssues = issues.filter(issue => issue.isOpen).length;
const closedIssues = issues.length - openIssues;
return { openIssues, closedIssues };
}, [issues]);

// Helper function to get sortable value from issue
const getSortableValue = (issue, sortField) => {
switch (sortField) {
case SORT_FIELDS.AUTHOR:
return issue.author;
case SORT_FIELDS.TITLE:
return issue.title;
case SORT_FIELDS.AT:
return issue.at?.name ?? '';
case SORT_FIELDS.CREATED_AT:
return new Date(issue.createdAt);
case SORT_FIELDS.CLOSED_AT:
return issue.closedAt ? new Date(issue.closedAt) : new Date(0);
default:
return '';
}
};

const compareByStatus = (a, b) => {
if (a.isOpen !== b.isOpen) {
if (sortOrder === TABLE_SORT_ORDERS.ASC) {
return a.isOpen ? -1 : 1; // Open first for ascending
}
return a.isOpen ? 1 : -1; // Closed first for descending
}
// If status is the same, sort by date created (newest first)
return new Date(b.createdAt) - new Date(a.createdAt);
};

const compareValues = (aValue, bValue) => {
return sortOrder === TABLE_SORT_ORDERS.ASC
? aValue < bValue
? -1
: 1
: aValue > bValue
? -1
: 1;
};

const sortedAndFilteredIssues = useMemo(() => {
// Filter issues
const filtered =
activeFilter === 'ALL'
? issues
: issues.filter(issue => issue.isOpen === (activeFilter === 'OPEN'));

// Sort issues
return filtered.sort((a, b) => {
// Special handling for status sorting
if (activeSort === SORT_FIELDS.STATUS) {
return compareByStatus(a, b);
}

// Normal sorting for other fields
const aValue = getSortableValue(a, activeSort);
const bValue = getSortableValue(b, activeSort);
return compareValues(aValue, bValue);
});
}, [issues, activeSort, sortOrder, activeFilter]);

const handleSort = column => newSortOrder => {
setActiveSort(column);
setSortOrder(newSortOrder);
};

const renderTableHeader = () => (
<thead>
<tr>
{[
{ field: SORT_FIELDS.AUTHOR, title: 'Author' },
{ field: SORT_FIELDS.TITLE, title: 'Issue' },
{ field: SORT_FIELDS.STATUS, title: 'Status' },
{ field: SORT_FIELDS.AT, title: 'Assistive Technology' },
{ field: SORT_FIELDS.CREATED_AT, title: 'Created On' },
{ field: SORT_FIELDS.CLOSED_AT, title: 'Closed On' }
].map(({ field, title }) => (
<SortableTableHeader
key={field}
title={title}
active={activeSort === field}
onSort={handleSort(field)}
data-test={`sort-${field.toLowerCase()}`}
/>
))}
</tr>
</thead>
);

const renderTableBody = () => (
<tbody>
{sortedAndFilteredIssues.map(issue => (
<tr
key={issue.link}
data-test="issue-row"
data-status={issue.isOpen ? 'open' : 'closed'}
>
<td>
<a
target="_blank"
rel="noreferrer"
href={`https://github.com/${issue.author}`}
>
{issue.author}
</a>
</td>
<td>
<a target="_blank" rel="noreferrer" href={issue.link}>
{issue.title}
</a>
</td>
<td data-test="issue-status">{issue.isOpen ? 'Open' : 'Closed'}</td>
<td>{issue.at?.name ?? 'AT not specified'}</td>
<td>{dates.convertDateToString(issue.createdAt, 'MMM D, YYYY')}</td>
<td>
{!issue.closedAt ? (
<NoneText>N/A</NoneText>
) : (
dates.convertDateToString(issue.closedAt, 'MMM D, YYYY')
)}
</td>
</tr>
))}
</tbody>
);

return (
<>
<h2 id="github-issues">
GitHub Issues ({issueStats.openIssues} open, {issueStats.closedIssues}
&nbsp;closed)
</h2>
<FilterButtons
filterLabel="Filter"
filterAriaLabel="Filter GitHub issues"
filterOptions={FILTER_OPTIONS}
activeFilter={activeFilter}
onFilterChange={setActiveFilter}
/>
{!sortedAndFilteredIssues.length ? (
<ThemeTableUnavailable aria-labelledby="github-issues">
No GitHub Issues
</ThemeTableUnavailable>
) : (
<ThemeTable
bordered
aria-labelledby="github-issues"
data-test="issues-table"
>
{renderTableHeader()}
{renderTableBody()}
</ThemeTable>
)}
</>
);
};

SortableIssuesTable.propTypes = {
issues: PropTypes.arrayOf(IssuePropType).isRequired
};

export default SortableIssuesTable;
83 changes: 13 additions & 70 deletions client/components/TestPlanVersionsPage/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef } from 'react';
import React, { useMemo, useRef } from 'react';
import { useQuery } from '@apollo/client';
import { TEST_PLAN_VERSIONS_PAGE_QUERY } from './queries';
import PageStatus from '../common/PageStatus';
Expand All @@ -7,7 +7,6 @@ import { Helmet } from 'react-helmet';
import { Container } from 'react-bootstrap';
import {
ThemeTable,
ThemeTableUnavailable,
ThemeTableHeaderH3 as UnstyledThemeTableHeader
} from '../common/ThemeTable';
import VersionString from '../common/VersionString';
Expand All @@ -22,6 +21,7 @@ import {
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import DisclosureComponentUnstyled from '../common/DisclosureComponent';
import useForceUpdate from '../../hooks/useForceUpdate';
import SortableIssuesTable from '../SortableIssuesTable';

const DisclosureContainer = styled.div`
.timeline-for-version-table {
Expand All @@ -40,7 +40,7 @@ const DisclosureComponent = styled(DisclosureComponentUnstyled)`
}
`;

const NoneText = styled.span`
export const NoneText = styled.span`
font-style: italic;
color: #6a7989;
`;
Expand Down Expand Up @@ -88,6 +88,12 @@ const TestPlanVersionsPage = () => {
const expandedVersionSections = useRef();
const toggleVersionSections = useRef();

// GraphQL results are read only so they need to be cloned
// before passing to SortableIssuesTable
const issues = useMemo(() => {
return data ? [...data.testPlan.issues] : [];
}, [data]);

if (error) {
return (
<PageStatus
Expand Down Expand Up @@ -192,23 +198,12 @@ const TestPlanVersionsPage = () => {
return derivedPhaseDeprecatedDuring;
};

const testPlan = data.testPlan;
const { testPlan, ats } = data;

// GraphQL results are read only so they need to be cloned before sorting
const issues = [...testPlan.issues].sort((a, b) => {
const aCreatedAt = new Date(a.createdAt);
const bCreatedAt = new Date(b.createdAt);
return bCreatedAt - aCreatedAt;
const testPlanVersions = testPlan.testPlanVersions.slice().sort((a, b) => {
return new Date(b.updatedAt) - new Date(a.updatedAt);
});

const ats = data.ats;

const testPlanVersions = data.testPlan.testPlanVersions
.slice()
.sort((a, b) => {
return new Date(b.updatedAt) - new Date(a.updatedAt);
});

const timelineForAllVersions = [];

testPlanVersions.forEach(testPlanVersion => {
Expand Down Expand Up @@ -390,59 +385,7 @@ const TestPlanVersionsPage = () => {
<PageSpacer />
</>
)}
<ThemeTableHeader id="github-issues">GitHub Issues</ThemeTableHeader>
{!issues.length ? (
<ThemeTableUnavailable aria-labelledby="github-issues">
No GitHub Issues
</ThemeTableUnavailable>
) : (
<ThemeTable bordered responsive aria-labelledby="github-issues">
<thead>
<tr>
<th>Author</th>
<th>Issue</th>
<th>Status</th>
<th>AT</th>
<th>Created On</th>
<th>Closed On</th>
</tr>
</thead>
<tbody>
{issues.map(issue => {
return (
<tr key={issue.link}>
<td>
<a
target="_blank"
rel="noreferrer"
href={`https://github.com/${issue.author}`}
>
{issue.author}
</a>
</td>
<td>
<a target="_blank" rel="noreferrer" href={issue.link}>
{issue.title}
</a>
</td>
<td>{issue.isOpen ? 'Open' : 'Closed'}</td>
<td>{issue.at?.name ?? 'AT not specified'}</td>
<td>
{dates.convertDateToString(issue.createdAt, 'MMM D, YYYY')}
</td>
<td>
{!issue.closedAt ? (
<NoneText>N/A</NoneText>
) : (
dates.convertDateToString(issue.closedAt, 'MMM D, YYYY')
)}
</td>
</tr>
);
})}
</tbody>
</ThemeTable>
)}
<SortableIssuesTable issues={issues} />
<PageSpacer />
<ThemeTableHeader id="timeline-for-all-versions">
Timeline for All Versions
Expand Down
Loading

0 comments on commit 098cda8

Please sign in to comment.