From c1b329c156be799535a57c7d3a9dfaef8353488e Mon Sep 17 00:00:00 2001
From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com>
Date: Wed, 18 Oct 2023 16:22:29 -0500
Subject: [PATCH] Add collapsible areas for Test Plan Versions History page
(#794)
* Fix bug that would allow testPlanVersion to be updated to RECOMMENDED before the associated reports were all marked as final (but the tests were 100% done in the Test Queue)
* Refine raise an issue behavior (#753)
* Refine raise an issue behavior
* Address feedback
* Fixed squished dot icon
* Address last feedback
* Hide closed issues on datamgmt page
* Fix for test results not being automatically saved when navigating through Test Run
* Filter and sort functionality for Data Management table (#750)
* First pass on functioning sort buttons for DataManagePage
* Functioning sort
* Filter functionality for DataManagement
* Refactor filter buttons on DataManagement to reduce complexity
* Filter buttons as separate component
* DataManagement page, break sort out into dedicated hook
* DataManagement page, dedicated hook for filtering
* DataManagement, add constant for test plan version phases to simplify hooks code
* Add relevant dynamic aria attributes to DataManagement column sort elements
* Add relevant dynamic aria attributes to DataManagement filter buttons
* unit tests for SortableTableHeader
* unit tests for FilterButtons
* unit tests for useDataManagementTableSorting hook
* Add unit tests for useDataManagementTableFiltering hook
* Filter buttons only show buttons that have have label, handle testPlans with multiple versions, ...
useDataManagementTableFiltering don't generate label for buttons that have no associated plans
* Break out overall phase derivation logic into dedicated hooks, functional filter and sort
* Simplify useDerivedTestPlanOverallPhase
* Additional unit tests for additional hooks and to test scenarios with multiple test plan versions for a single test plan
hook rename
* Different UX click interaction sequence with SortableTableHeader
* Correct interpretation of alphabetical ascending/descending
* Rename DataManagement/hooks.js to filterSortHooks
* Move sorting and filtering enums to more specific locations based on use
* Fix file locations in unit tests, DataManagement, FilterButtons, SortableTableHeader
* Smaller margin between buttons, FilterButtons
* First pass on functioning sort buttons for DataManagePage
* Functioning sort
* Filter functionality for DataManagement
* Refactor filter buttons on DataManagement to reduce complexity
* Filter buttons as separate component
* DataManagement page, break sort out into dedicated hook
* DataManagement page, dedicated hook for filtering
* DataManagement, add constant for test plan version phases to simplify hooks code
* Add relevant dynamic aria attributes to DataManagement column sort elements
* Add relevant dynamic aria attributes to DataManagement filter buttons
* unit tests for SortableTableHeader
* unit tests for FilterButtons
* unit tests for useDataManagementTableSorting hook
* Add unit tests for useDataManagementTableFiltering hook
* Filter buttons only show buttons that have have label, handle testPlans with multiple versions, ...
useDataManagementTableFiltering don't generate label for buttons that have no associated plans
* Break out overall phase derivation logic into dedicated hooks, functional filter and sort
* Simplify useDerivedTestPlanOverallPhase
* Additional unit tests for additional hooks and to test scenarios with multiple test plan versions for a single test plan
hook rename
* Different UX click interaction sequence with SortableTableHeader
* Correct interpretation of alphabetical ascending/descending
* Rename DataManagement/hooks.js to filterSortHooks
* Move sorting and filtering enums to more specific locations based on use
* Fix file locations in unit tests, DataManagement, FilterButtons, SortableTableHeader
* Smaller margin between buttons, FilterButtons
* Cleanup after rebase
* Render SortableTableHeader inside
for test to dismiss warning
* Add AriaLiveRegionProvider, Update DataManagement table to use it
* Allow SortableTableHeader component to work without AriaLiveRegionProvider
* Explicitly support 'DEPRECATED' phase for `TestPlanVersion.phase` (#749)
* Start to support for sunset phase
* Explicitly use 'DEPRECATED' phase for TestPlanVersion.phase
* Add checks to prevent test plan versions in R&D or Deprecated from being shown in data management dropdown
* Remove duplicate updateTestPlanVersion call
* Reuse phase variable
* Add copy for deprecated reports with DisclaimerInfo component
* Exclude 'DEPRECATED' testPlanVersions on DataManagement page query
* Ensure only reports marked as final are displayed on /embed/reports/
* Fix test
* Update failing test
* Adjust semantic structure on Data Management Page (#752)
* Adjust cell items for data management row to use list-related roles; update aria-labels
* Address PR feedback
* Remove width:max-content
* Address PR feedback
* Formatting
* Address feedback
* Adjust BasicModal to support AtAndBrowserDetailsModal closing
* Stop assign menu dropdown from creating unintended bottom space with the parent container
* Formatting
* Close #755
* Close #754
* Remove superfluous header from TestPlanReportStatusDialog (#766)
* Revise required reports conditions (#764)
* Revise required reports approach
* Update tests
* Revert dev.env
* Update Version History Page (#767)
* Apply correct sort order for Timeline for All Versions section
* Update headings used on Versions page
* Add aria-labelledby's for tables
* Add for applicable spaces so the text is properly announced by NVDA
* Add migration to add missing deprecatedAt dates and to also properly set the candidatePhaseReachedAt dates to more practical dates after the migrations (if candidatePhaseReachedAt < draftPhaseReachedAt, set it candidatePhaseReachedAt to draftPhaseReachedAt + 1day)
* Test Plan Versions Page: Use standard testPlanVersions descending sort and show all phases being included in Version Summary
* Switch issues search support for checking against hidden body content instead
* Fix TestPlanReportStatusDialog using d (day of the week) and y (era)
* Explicit check for older date when deprecating existing RD test plan versions during import
* Update hidden message content in github issue
* Update query for anon user on TestRun page
* Keep overall status pill from drifting to center of cell
* Fix R&D only TestPlanVersion's table not being shown
* Use aria-label for heading to prevent space being announced
* Update date format used in aria-label
* Make version history a drop down
* comment out unused variable
* Fix responsiveness
* Remove comments and logs
* Changes after merge
* Fix icon color
* Fix padding for timeline heading
* Fix console errors and delete comments
* Fix conflicts that caused error
* Implement required changes from review
---------
Co-authored-by: Howard Edwards
Co-authored-by: Alexander Flenniken
Co-authored-by: Stalgia Grigg
---
.../DataManagementRow/index.jsx | 12 +-
.../components/TestPlanVersionsPage/index.jsx | 436 ++++++++++++------
.../common/DisclosureComponent/index.jsx | 29 +-
.../components/common/VersionString/index.js | 6 +-
4 files changed, 318 insertions(+), 165 deletions(-)
diff --git a/client/components/DataManagement/DataManagementRow/index.jsx b/client/components/DataManagement/DataManagementRow/index.jsx
index df2b31a43..0a98a0311 100644
--- a/client/components/DataManagement/DataManagementRow/index.jsx
+++ b/client/components/DataManagement/DataManagementRow/index.jsx
@@ -754,21 +754,15 @@ const DataManagementRow = ({
// If a version of the plan is not in the candidate phase and there is a recommended
// version, show string "Review of VERSION_STRING completed DATE"
if (otherTestPlanVersions.length) {
- const {
- latestVersion: otherLatestVersion,
- latestVersionDate: otherLatestVersionDate
- } = getVersionData(otherTestPlanVersions);
+ const { latestVersion: otherLatestVersion } =
+ getVersionData(otherTestPlanVersions);
const completionDate =
otherLatestVersion.recommendedPhaseReachedAt;
return (
-
+
{otherLatestVersion.versionString}
diff --git a/client/components/TestPlanVersionsPage/index.jsx b/client/components/TestPlanVersionsPage/index.jsx
index d074a20b4..7a43c26fb 100644
--- a/client/components/TestPlanVersionsPage/index.jsx
+++ b/client/components/TestPlanVersionsPage/index.jsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useRef } from 'react';
import { useQuery } from '@apollo/client';
import { TEST_PLAN_VERSIONS_PAGE_QUERY } from './queries';
import PageStatus from '../common/PageStatus';
@@ -20,13 +20,93 @@ import {
faCodeCommit
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import DisclosureComponentUnstyled from '../common/DisclosureComponent';
-const H2 = styled.h2`
- font-size: 1.25em;
- padding-top: 3rem;
- padding-bottom: 15px;
- border-bottom: solid 1px #d2d5d9;
- margin-bottom: 2rem !important;
+const DisclosureContainer = styled.div`
+ // Following directives are related to the ManageTestQueue component
+ > span {
+ display: block;
+ margin-bottom: 1rem;
+ }
+
+ // Add Test Plan to Test Queue button
+ > button {
+ display: flex;
+ padding: 0.5rem 1rem;
+ margin-top: 1rem;
+ margin-left: auto;
+ margin-right: 0;
+ }
+
+ .disclosure-row-manage-ats {
+ display: grid;
+ grid-auto-flow: column;
+ grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
+ grid-gap: 1rem;
+
+ .ats-container {
+ grid-column: 1 / span 2;
+ }
+
+ .at-versions-container {
+ display: flex;
+ flex-direction: column;
+ grid-column: 3 / span 3;
+ }
+
+ .disclosure-buttons-row {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ > button {
+ margin: 0;
+ padding: 0;
+ color: #275caa;
+ border: none;
+ background-color: transparent;
+
+ &:nth-of-type(2) {
+ margin-left: auto;
+ }
+
+ // remove button
+ &:nth-of-type(3) {
+ margin-left: 1rem;
+ color: #ce1b4c;
+ }
+ }
+ }
+ }
+
+ .disclosure-row-test-plans {
+ display: grid;
+ grid-auto-flow: column;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ grid-gap: 1rem;
+ }
+
+ .disclosure-form-label {
+ font-weight: bold;
+ font-size: 1rem;
+ }
+
+ .timeline-for-version-table {
+ padding: 0.5rem 1rem;
+ }
+`;
+
+const DisclosureComponent = styled(DisclosureComponentUnstyled)`
+ & h2 {
+ padding: 0;
+ margin: 0;
+ font-size: 1.25em;
+
+ button {
+ font-size: unset;
+ font-weight: unset;
+ }
+ }
`;
const NoneText = styled.span`
@@ -65,6 +145,13 @@ const ThemeTableHeader = styled(UnstyledThemeTableHeader)`
margin: 0 !important;
`;
+// https://stackoverflow.com/a/53215514
+const useForceUpdate = () => {
+ const [, updateState] = React.useState();
+ const forceUpdate = React.useCallback(() => updateState({}), []);
+ return forceUpdate;
+};
+
const TestPlanVersionsPage = () => {
const { testPlanDirectory } = useParams();
@@ -72,6 +159,10 @@ const TestPlanVersionsPage = () => {
variables: { testPlanDirectory },
fetchPolicy: 'cache-and-network'
});
+ const forceUpdate = useForceUpdate();
+
+ const expandedVersionSections = useRef();
+ const toggleVersionSections = useRef();
if (error) {
return (
@@ -258,6 +349,20 @@ const TestPlanVersionsPage = () => {
return new Date(dateA) - new Date(dateB);
});
+ if (!expandedVersionSections.current) {
+ expandedVersionSections.current = [];
+ toggleVersionSections.current = [];
+
+ for (let i = 0; i < testPlanVersions.length; i += 1) {
+ expandedVersionSections.current[i] = false;
+ toggleVersionSections.current[i] = () => {
+ expandedVersionSections.current[i] =
+ !expandedVersionSections.current[i];
+ forceUpdate();
+ };
+ }
+ }
+
return (
@@ -389,7 +494,6 @@ const TestPlanVersionsPage = () => {
>
)}
-
GitHub Issues
@@ -458,7 +562,6 @@ const TestPlanVersionsPage = () => {
)}
-
Timeline for All Versions
@@ -500,23 +603,13 @@ const TestPlanVersionsPage = () => {
})}
-
- {testPlanVersions.map(testPlanVersion => {
- // Gets the derived phase even if deprecated by checking
- // the known dates on the testPlanVersion object
- const derivedDeprecatedAtPhase =
- deriveDeprecatedDuringPhase(testPlanVersion);
-
- const hasFinalReports =
- (derivedDeprecatedAtPhase === 'CANDIDATE' ||
- derivedDeprecatedAtPhase === 'RECOMMENDED') &&
- !!testPlanVersion.testPlanReports.filter(
- report => report.isFinal
- ).length;
-
- return (
-
-
+
{
+ return (
+ {
)} on ${getEventDate(testPlanVersion)}`}
>
{
{testPlanVersion.phase}
- on {getEventDate(testPlanVersion)}
-
-
-
-
-
- Commit {testPlanVersion.gitSha.substr(0, 7)}
- : {testPlanVersion.gitMessage}
-
-
-
-
+ );
+ })}
+ disclosureContainerView={testPlanVersions.map(
+ testPlanVersion => {
+ // Gets the derived phase even if deprecated by checking
+ // the known dates on the testPlanVersion object
+ const derivedDeprecatedAtPhase =
+ deriveDeprecatedDuringPhase(testPlanVersion);
+
+ const hasFinalReports =
+ (derivedDeprecatedAtPhase === 'CANDIDATE' ||
+ derivedDeprecatedAtPhase === 'RECOMMENDED') &&
+ !!testPlanVersion.testPlanReports.filter(
+ report => report.isFinal
+ ).length;
+
+ return (
+
+
-
- View tests in{' '}
- {testPlanVersion.versionString}
-
-
- {!hasFinalReports ? null : (
-
-
-
- View reports generated from{' '}
- {testPlanVersion.versionString}
-
-
- )}
-
-
- Covered AT
-
-
- {ats.map(at => (
- - {at.name}
- ))}
-
-
-
-
- Timeline for {testPlanVersion.versionString}
-
-
-
-
- Date |
- Event |
-
-
-
- {(() => {
- let events = [
- ['RD', testPlanVersion.updatedAt],
- [
- 'DRAFT',
- testPlanVersion.draftPhaseReachedAt
- ],
- [
- 'CANDIDATE',
- testPlanVersion.candidatePhaseReachedAt
- ],
- [
- 'RECOMMENDED',
- testPlanVersion.recommendedPhaseReachedAt
- ],
- [
- 'DEPRECATED',
- testPlanVersion.deprecatedAt
- ]
- ]
- .filter(event => event[1])
- .sort((a, b) => {
- const dateSort =
- new Date(a[1]) - new Date(b[1]);
- if (dateSort === 0) return 1; // maintain order above
- return dateSort;
- });
-
- return events.map(([phase, date]) => (
-
-
- {convertDateToString(
- date,
- 'MMM D, YYYY'
+
+
+
+
+ Commit{' '}
+ {testPlanVersion.gitSha.substr(
+ 0,
+ 7
)}
- |
- {getEventBody(phase)} |
-
- ));
- })()}
-
-
-
- );
- })}
+ : {testPlanVersion.gitMessage}
+
+
+
+
+
+ View tests in{' '}
+ {testPlanVersion.versionString}
+
+
+ {!hasFinalReports ? null : (
+
+
+
+ View reports generated from{' '}
+ {
+ testPlanVersion.versionString
+ }
+
+
+ )}
+
+
+ Covered AT
+
+
+ {ats.map(at => (
+ -
+ {at.name}
+
+ ))}
+
+
+
+
+ Timeline for{' '}
+ {testPlanVersion.versionString}
+
+
+
+
+ Date |
+ Event |
+
+
+
+ {(() => {
+ let events = [
+ [
+ 'RD',
+ testPlanVersion.updatedAt
+ ],
+ [
+ 'DRAFT',
+ testPlanVersion.draftPhaseReachedAt
+ ],
+ [
+ 'CANDIDATE',
+ testPlanVersion.candidatePhaseReachedAt
+ ],
+ [
+ 'RECOMMENDED',
+ testPlanVersion.recommendedPhaseReachedAt
+ ],
+ [
+ 'DEPRECATED',
+ testPlanVersion.deprecatedAt
+ ]
+ ]
+ .filter(event => event[1])
+ .sort((a, b) => {
+ const dateSort =
+ new Date(a[1]) -
+ new Date(b[1]);
+ if (dateSort === 0)
+ return 1; // maintain order above
+ return dateSort;
+ });
+
+ return events.map(
+ ([phase, date]) => (
+
+
+ {convertDateToString(
+ date,
+ 'MMM D, YYYY'
+ )}
+ |
+
+ {getEventBody(
+ phase
+ )}
+ |
+
+ )
+ );
+ })()}
+
+
+
+
+ );
+ }
+ )}
+ onClick={toggleVersionSections.current}
+ expanded={expandedVersionSections.current}
+ stacked={true}
+ headingLevel="2"
+ />
);
};
diff --git a/client/components/common/DisclosureComponent/index.jsx b/client/components/common/DisclosureComponent/index.jsx
index ca8acd32c..4fcee9a3e 100644
--- a/client/components/common/DisclosureComponent/index.jsx
+++ b/client/components/common/DisclosureComponent/index.jsx
@@ -22,11 +22,10 @@ const DisclosureParent = styled.div`
${({ stacked }) =>
stacked &&
`
- h1:not(:first-of-type) button {
- border-top: 1px solid #d3d5da;
- }
-
- h3:not(:first-of-type) button {
+ h1:not(:first-of-type) button,
+ h2:not(:first-of-type) button,
+ h3:not(:first-of-type) button,
+ h4:not(:first-of-type) button {
border-top: 1px solid #d3d5da;
}`}
`;
@@ -58,7 +57,7 @@ const DisclosureButton = styled.button`
cursor: pointer;
}
- svg {
+ .disclosure-icon {
position: absolute;
margin: 0;
top: 50%;
@@ -88,7 +87,8 @@ const DisclosureComponent = ({
onClick = null,
expanded = false,
stacked = false,
- headingLevel = '3'
+ headingLevel = '3',
+ className = null
}) => {
const [isExpanded, setIsExpanded] = useState(expanded);
const Tag = `h${headingLevel}`;
@@ -96,7 +96,7 @@ const DisclosureComponent = ({
return (
<>
{stacked ? (
-
+
{title.map((_, index) => {
const buttonTitle = title[index];
const buttonExpanded = expanded[index];
@@ -105,7 +105,7 @@ const DisclosureComponent = ({
disclosureContainerView[index];
return (
-
+
{buttonTitle}
) : (
-
+
{title}
@@ -173,7 +175,9 @@ DisclosureComponent.propTypes = {
componentId: PropTypes.string,
title: PropTypes.oneOfType([
PropTypes.string,
- PropTypes.arrayOf(PropTypes.string)
+ PropTypes.node,
+ PropTypes.arrayOf(PropTypes.string),
+ PropTypes.arrayOf(PropTypes.node)
]),
disclosureContainerView: PropTypes.oneOfType([
PropTypes.node,
@@ -188,7 +192,8 @@ DisclosureComponent.propTypes = {
PropTypes.arrayOf(PropTypes.bool)
]),
stacked: PropTypes.bool,
- headingLevel: PropTypes.string
+ headingLevel: PropTypes.string,
+ className: PropTypes.string
};
export default DisclosureComponent;
diff --git a/client/components/common/VersionString/index.js b/client/components/common/VersionString/index.js
index ea08146c5..1e19fe372 100644
--- a/client/components/common/VersionString/index.js
+++ b/client/components/common/VersionString/index.js
@@ -51,7 +51,11 @@ const VersionString = ({
}) => {
const body = (
-
+
{versionString}
);