Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter and sort functionality for Data Management table #750

Merged
merged 54 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
74fee34
First pass on functioning sort buttons for DataManagePage
stalgiag Aug 16, 2023
e7c702f
Functioning sort
stalgiag Aug 17, 2023
2a7f114
Filter functionality for DataManagement
stalgiag Aug 17, 2023
3b4a587
Refactor filter buttons on DataManagement to reduce complexity
stalgiag Aug 17, 2023
9a68926
Filter buttons as separate component
stalgiag Aug 17, 2023
3799020
DataManagement page, break sort out into dedicated hook
stalgiag Aug 17, 2023
0a807a5
DataManagement page, dedicated hook for filtering
stalgiag Aug 17, 2023
c960446
DataManagement, add constant for test plan version phases to simplify…
stalgiag Aug 17, 2023
26dd891
Add relevant dynamic aria attributes to DataManagement column sort el…
stalgiag Aug 21, 2023
96bb54c
Add relevant dynamic aria attributes to DataManagement filter buttons
stalgiag Aug 21, 2023
8e96601
unit tests for SortableTableHeader
stalgiag Aug 21, 2023
650bb8c
unit tests for FilterButtons
stalgiag Aug 21, 2023
79e7c61
unit tests for useDataManagementTableSorting hook
stalgiag Aug 21, 2023
79da38b
Add unit tests for useDataManagementTableFiltering hook
stalgiag Aug 21, 2023
d8b1583
Merge branch 'update-database-impl' into filtering-sort-data-management
stalgiag Aug 21, 2023
4f1511d
Filter buttons only show buttons that have have label, handle testPla…
stalgiag Aug 22, 2023
13249a3
Break out overall phase derivation logic into dedicated hooks, functi…
stalgiag Aug 22, 2023
1764d6b
Simplify useDerivedTestPlanOverallPhase
stalgiag Aug 22, 2023
4e5eb51
Additional unit tests for additional hooks and to test scenarios with…
stalgiag Aug 22, 2023
2459e53
Different UX click interaction sequence with SortableTableHeader
stalgiag Aug 23, 2023
d35256d
Correct interpretation of alphabetical ascending/descending
stalgiag Aug 23, 2023
e182a00
Rename DataManagement/hooks.js to filterSortHooks
stalgiag Aug 23, 2023
0e867ca
Move sorting and filtering enums to more specific locations based on use
stalgiag Aug 23, 2023
efcc3e9
Fix file locations in unit tests, DataManagement, FilterButtons, Sort…
stalgiag Aug 23, 2023
e3820a3
Smaller margin between buttons, FilterButtons
stalgiag Aug 23, 2023
8b49372
First pass on functioning sort buttons for DataManagePage
stalgiag Aug 16, 2023
7de6d95
Functioning sort
stalgiag Aug 17, 2023
05626b0
Filter functionality for DataManagement
stalgiag Aug 17, 2023
bd15703
Refactor filter buttons on DataManagement to reduce complexity
stalgiag Aug 17, 2023
ffd6696
Filter buttons as separate component
stalgiag Aug 17, 2023
030e92c
DataManagement page, break sort out into dedicated hook
stalgiag Aug 17, 2023
792f633
DataManagement page, dedicated hook for filtering
stalgiag Aug 17, 2023
fa8e283
DataManagement, add constant for test plan version phases to simplify…
stalgiag Aug 17, 2023
839ebcc
Add relevant dynamic aria attributes to DataManagement column sort el…
stalgiag Aug 21, 2023
a77b476
Add relevant dynamic aria attributes to DataManagement filter buttons
stalgiag Aug 21, 2023
0457ead
unit tests for SortableTableHeader
stalgiag Aug 21, 2023
f1368cb
unit tests for FilterButtons
stalgiag Aug 21, 2023
3ae538c
unit tests for useDataManagementTableSorting hook
stalgiag Aug 21, 2023
82f792a
Add unit tests for useDataManagementTableFiltering hook
stalgiag Aug 21, 2023
004cd06
Filter buttons only show buttons that have have label, handle testPla…
stalgiag Aug 22, 2023
093bde8
Break out overall phase derivation logic into dedicated hooks, functi…
stalgiag Aug 22, 2023
035177b
Simplify useDerivedTestPlanOverallPhase
stalgiag Aug 22, 2023
3b1d1d7
Additional unit tests for additional hooks and to test scenarios with…
stalgiag Aug 22, 2023
5c90be0
Different UX click interaction sequence with SortableTableHeader
stalgiag Aug 23, 2023
be97861
Correct interpretation of alphabetical ascending/descending
stalgiag Aug 23, 2023
e1351ba
Rename DataManagement/hooks.js to filterSortHooks
stalgiag Aug 23, 2023
3f0e638
Move sorting and filtering enums to more specific locations based on use
stalgiag Aug 23, 2023
d7666eb
Fix file locations in unit tests, DataManagement, FilterButtons, Sort…
stalgiag Aug 23, 2023
d5373d7
Smaller margin between buttons, FilterButtons
stalgiag Aug 23, 2023
32b320e
Cleanup after rebase
stalgiag Aug 28, 2023
5a8e246
Merge branch 'filtering-sort-data-management' of https://github.com/w…
stalgiag Aug 28, 2023
146b187
Render SortableTableHeader inside <table> for test to dismiss warning
stalgiag Aug 28, 2023
e7d6fd0
Add AriaLiveRegionProvider, Update DataManagement table to use it
stalgiag Aug 28, 2023
2541e7d
Allow SortableTableHeader component to work without AriaLiveRegionPro…
stalgiag Aug 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 5 additions & 23 deletions client/components/DataManagement/DataManagementRow/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import ReportStatusDot from '../../common/ReportStatusDot';
import UpdateTargetDateModal from '@components/common/UpdateTargetDateModal';
import VersionString from '../../common/VersionString';
import PhasePill from '../../common/PhasePill';
import { getVersionData } from '../utils';

const StatusCell = styled.div`
display: flex;
Expand Down Expand Up @@ -147,7 +148,8 @@ const DataManagementRow = ({
ats,
testPlan,
testPlanVersions,
setTestPlanVersions
setTestPlanVersions,
tableRowIndex
}) => {
const { triggerLoad, loadingMessage } = useTriggerLoad();
const {
Expand Down Expand Up @@ -206,27 +208,6 @@ const DataManagementRow = ({
);
}, [testPlanVersions]);

// Get the version information based on the latest or earliest date info from a group of
// TestPlanVersions
const getVersionData = (testPlanVersions, dateKey = 'updatedAt') => {
const earliestVersion = testPlanVersions.reduce((a, b) =>
new Date(a[dateKey]) < new Date(b[dateKey]) ? a : b
);
const earliestVersionDate = new Date(earliestVersion[dateKey]);

const latestVersion = testPlanVersions.reduce((a, b) =>
new Date(a[dateKey]) > new Date(b[dateKey]) ? a : b
);
const latestVersionDate = new Date(latestVersion[dateKey]);

return {
earliestVersion,
earliestVersionDate,
latestVersion,
latestVersionDate
};
};

const getUniqueAtObjects = testPlanReports => {
const uniqueAtObjects = {};
testPlanReports.forEach(testPlanReport => {
Expand Down Expand Up @@ -1009,7 +990,7 @@ const DataManagementRow = ({

return (
<LoadingStatus message={loadingMessage}>
<tr>
<tr aria-rowindex={tableRowIndex}>
<th>
<a href={`/data-management/${testPlan.directory}`}>
<b>{testPlan.title}</b>
Expand Down Expand Up @@ -1123,6 +1104,7 @@ DataManagementRow.propTypes = {
recommendedPhaseReachedAt: PropTypes.string
})
).isRequired,
tableRowIndex: PropTypes.number.isRequired,
setTestPlanVersions: PropTypes.func
};

Expand Down
232 changes: 232 additions & 0 deletions client/components/DataManagement/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import { useMemo, useState } from 'react';
alflennik marked this conversation as resolved.
Show resolved Hide resolved
import {
DATA_MANAGEMENT_TABLE_FILTER_OPTIONS,
DATA_MANAGEMENT_TABLE_SORT_OPTIONS,
TABLE_SORT_ORDERS,
TEST_PLAN_VERSION_PHASES
} from '../../utils/constants';
import { getVersionData } from './utils';

export const useTestPlanVersionsByPhase = testPlanVersions => {
const testPlanVersionsByPhase = useMemo(() => {
const initialPhases = Object.keys(TEST_PLAN_VERSION_PHASES).reduce(
(acc, key) => {
acc[TEST_PLAN_VERSION_PHASES[key]] = [];
return acc;
},
{}
);

return testPlanVersions.reduce((acc, testPlanVersion) => {
acc[testPlanVersion.phase].push(testPlanVersion);
return acc;
}, initialPhases);
}, [testPlanVersions]);

return { testPlanVersionsByPhase };
};

export const useDerivedOverallPhaseByTestPlanId = (
testPlans,
testPlanVersions
) => {
const { testPlanVersionsByPhase } =
useTestPlanVersionsByPhase(testPlanVersions);

const getVersionDataByDirectory = (directory, phaseKey, phaseTimeKey) => {
if (
testPlanVersionsByPhase[phaseKey].some(
testPlanVersion =>
testPlanVersion.testPlan.directory === directory
)
) {
const { earliestVersion, latestVersion } = getVersionData(
testPlanVersionsByPhase[phaseKey],
phaseTimeKey
);
return phaseTimeKey ? earliestVersion?.phase : latestVersion?.phase;
}
return undefined;
};

const derivedOverallPhaseByTestPlanId = useMemo(() => {
const derivedOverallPhaseByTestPlanId = {};
const phases = [
{
phase: TEST_PLAN_VERSION_PHASES.RECOMMENDED,
timeKey: 'recommendedPhaseReachedAt'
},
{
phase: TEST_PLAN_VERSION_PHASES.CANDIDATE,
timeKey: 'candidatePhaseReachedAt'
},
{
phase: TEST_PLAN_VERSION_PHASES.DRAFT,
timeKey: 'draftPhaseReachedAt'
},
{ phase: TEST_PLAN_VERSION_PHASES.RD }
];
for (const testPlan of testPlans) {
for (const { phase, timeKey } of phases) {
const derivedPhase = getVersionDataByDirectory(
testPlan.directory,
phase,
timeKey
);
if (derivedPhase) {
derivedOverallPhaseByTestPlanId[testPlan.id] = derivedPhase;
break;
}
}
}
return derivedOverallPhaseByTestPlanId;
}, [testPlans, testPlanVersions]);

return { derivedOverallPhaseByTestPlanId };
};

export const useTestPlansByPhase = (testPlans, testPlanVersions) => {
alflennik marked this conversation as resolved.
Show resolved Hide resolved
const { derivedOverallPhaseByTestPlanId } =
useDerivedOverallPhaseByTestPlanId(testPlans, testPlanVersions);

const testPlansByPhase = useMemo(() => {
const testPlansByPhase = {};
for (const key of Object.keys(TEST_PLAN_VERSION_PHASES)) {
testPlansByPhase[TEST_PLAN_VERSION_PHASES[key]] = [];
}
for (const testPlan of testPlans) {
testPlansByPhase[
derivedOverallPhaseByTestPlanId[testPlan.id]
]?.push(testPlan);
}
return testPlansByPhase;
}, [derivedOverallPhaseByTestPlanId]);

return { testPlansByPhase };
};

export const useDataManagementTableFiltering = (
testPlans,
testPlanVersions,
filter
) => {
const { testPlansByPhase } = useTestPlansByPhase(
testPlans,
testPlanVersions
);

const filteredTestPlans = useMemo(() => {
if (!filter || filter === DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL) {
return testPlans;
} else {
return testPlansByPhase[filter];
}
}, [filter, testPlansByPhase, testPlans]);

const filterLabels = {
[DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.ALL]: `All Plans (${testPlans.length})`
};

if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.RD].length > 0) {
filterLabels[
DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RD
] = `R&D Complete (${
testPlansByPhase[TEST_PLAN_VERSION_PHASES.RD].length
})`;
}

if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.DRAFT].length > 0) {
filterLabels[
DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.DRAFT
] = `In Draft Review (${
testPlansByPhase[TEST_PLAN_VERSION_PHASES.DRAFT].length
})`;
}

if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.CANDIDATE].length > 0) {
filterLabels[
DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.CANDIDATE
] = `In Candidate Review (${
testPlansByPhase[TEST_PLAN_VERSION_PHASES.CANDIDATE].length
})`;
}

if (testPlansByPhase[TEST_PLAN_VERSION_PHASES.RECOMMENDED].length > 0) {
filterLabels[
DATA_MANAGEMENT_TABLE_FILTER_OPTIONS.RECOMMENDED
] = `Recommended Plans (${
testPlansByPhase[TEST_PLAN_VERSION_PHASES.RECOMMENDED].length
})`;
}

return { filteredTestPlans, filterLabels };
};

export const useDataManagementTableSorting = (
testPlans,
testPlanVersions,
ats
) => {
const [activeSort, setActiveSort] = useState({
key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.PHASE,
direction: TABLE_SORT_ORDERS.ASC
});

const { derivedOverallPhaseByTestPlanId } =
useDerivedOverallPhaseByTestPlanId(testPlans, testPlanVersions);

const sortedTestPlans = useMemo(() => {
const phaseOrder = {
NOT_STARTED: -1,
RD: 0,
DRAFT: 1,
CANDIDATE: 2,
RECOMMENDED: 3
};
const directionMod =
activeSort.direction === TABLE_SORT_ORDERS.ASC ? -1 : 1;

const sortByName = (a, b, dir = directionMod) =>
dir * (a.title < b.title ? -1 : 1);

const sortByAts = (a, b) => {
const countA = ats.length; // Stubs based on current rendering in DataManagementRow
const countB = ats.length;
if (countA === countB) return sortByName(a, b);
return directionMod * (countA - countB);
};

const sortByPhase = (a, b) => {
const testPlanVersionOverallA =
derivedOverallPhaseByTestPlanId[a.id] ?? 'NOT_STARTED';
const testPlanVersionOverallB =
derivedOverallPhaseByTestPlanId[b.id] ?? 'NOT_STARTED';
if (testPlanVersionOverallA === testPlanVersionOverallB) {
return sortByName(a, b, directionMod * -1);
}
return (
directionMod *
(phaseOrder[testPlanVersionOverallA] -
phaseOrder[testPlanVersionOverallB])
);
};

const sortFunctions = {
NAME: sortByName,
ATS: sortByAts,
PHASE: sortByPhase
};

return testPlans.slice().sort(sortFunctions[activeSort.key]);
}, [activeSort, testPlans]);

const updateSort = ({ key, direction }) => {
setActiveSort({ key, direction });
};

return {
sortedTestPlans,
updateSort,
activeSort
};
};
Loading