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

[TTAHUB-3402] Hook up QA widgets to BE #2394

Open
wants to merge 37 commits into
base: al-ttahub-3316-3317-overview-widgets
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0b05d01
start to hook up async calls for qa widgets
AdamAdHocTeam Oct 1, 2024
540d854
recipients with ohs standard fei goals async
AdamAdHocTeam Oct 1, 2024
c735cbe
Merge branch 'al-ttahub-3316-3317-overview-widgets' into al-ttahub-34…
AdamAdHocTeam Oct 8, 2024
14efd3f
updates to for ssdi
AdamAdHocTeam Oct 9, 2024
7b9c1d0
Connect delivery method graph
thewatermethod Oct 9, 2024
88cc0a2
Remove console.log
thewatermethod Oct 9, 2024
906c873
updates to widgets and tests for ssdi
AdamAdHocTeam Oct 9, 2024
2faddd0
Merge branch 'al-ttahub-3316-3317-overview-widgets' into al-ttahub-34…
AdamAdHocTeam Oct 9, 2024
9401ea1
hook up reciepents without tta
AdamAdHocTeam Oct 10, 2024
de607a5
Merge branch 'al-ttahub-3316-3317-overview-widgets' into al-ttahub-34…
AdamAdHocTeam Oct 10, 2024
8a1f13c
Add number of grants with FEI to widget
GarrettEHill Oct 10, 2024
988f57c
hook up fei page
AdamAdHocTeam Oct 10, 2024
a9d6168
adding creator and collaborators to the with_class_page
GarrettEHill Oct 11, 2024
ffcd8f2
add grant count to class widget data
GarrettEHill Oct 11, 2024
57bc831
hook up class
AdamAdHocTeam Oct 11, 2024
2123b4c
hook up sorting and clean up
AdamAdHocTeam Oct 13, 2024
b6c0880
fix for per page
AdamAdHocTeam Oct 13, 2024
66f3350
adding region id
GarrettEHill Oct 15, 2024
3435275
hook up links for recipients and goals
AdamAdHocTeam Oct 15, 2024
3db19d6
Merge branch 'al-ttahub-3402-connect-be-overview' of https://github.c…
AdamAdHocTeam Oct 15, 2024
9dd8268
Merge branch 'al-ttahub-3316-3317-overview-widgets' into al-ttahub-34…
AdamAdHocTeam Oct 15, 2024
cb8d503
fix links
AdamAdHocTeam Oct 15, 2024
6465d39
hook up filter applicable message
AdamAdHocTeam Oct 15, 2024
d06b2e1
make sure closed and merged goals are not included
GarrettEHill Oct 15, 2024
a5a5a39
Merge branch 'al-ttahub-3402-connect-be-overview' of https://github.c…
GarrettEHill Oct 15, 2024
cf23802
add tests fix sorting on class
AdamAdHocTeam Oct 15, 2024
c84f338
Merge branch 'al-ttahub-3402-connect-be-overview' of https://github.c…
AdamAdHocTeam Oct 15, 2024
3678598
hook up class to new export
AdamAdHocTeam Oct 15, 2024
ac26eea
resolve weird collision between sorting and exporting
AdamAdHocTeam Oct 15, 2024
d45be6f
Merge branch 'al-ttahub-3316-3317-overview-widgets' into al-ttahub-34…
AdamAdHocTeam Oct 16, 2024
fe6e2a9
fix bad merge of routes
AdamAdHocTeam Oct 17, 2024
8d4a330
handle divide by zero case
GarrettEHill Oct 17, 2024
faf7c44
code updates per Matt
AdamAdHocTeam Oct 17, 2024
3f0d96f
fix bugs and add more filters
GarrettEHill Oct 17, 2024
020ea4e
Merge branch 'al-ttahub-3402-connect-be-overview' of https://github.c…
GarrettEHill Oct 17, 2024
f2e6a87
add report id to dashboard async and handle for no data returned
AdamAdHocTeam Oct 17, 2024
4021036
add coverage for null checks
AdamAdHocTeam Oct 18, 2024
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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ parameters:
type: string
dev_git_branch: # change to feature branch to test deployment
description: "Name of github branch that will deploy to dev"
default: "al-ttahub-3316-3317-overview-widgets"
default: "al-ttahub-3402-connect-be-overview"
type: string
sandbox_git_branch: # change to feature branch to test deployment
default: "kw-fix-duplicate-programs"
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ import SessionForm from './pages/SessionForm';
import ViewTrainingReport from './pages/ViewTrainingReport';
import QADashboard from './pages/QADashboard';
import SomethingWentWrong from './components/SomethingWentWrong';
import RecipientsWithNoTta from './pages/QADashboard/RecipientsWithNoTta';
import RecipientsWithClassScoresAndGoals from './pages/QADashboard/RecipientsWithClassScoresAndGoals';
import RecipientsWithOhsStandardFeiGoal from './pages/QADashboard/RecipientsWithOhsStandardFeiGoal';

export default function Routes({
alert,
Expand Down Expand Up @@ -157,6 +160,33 @@ export default function Routes({
</AppWrapper>
)}
/>
<Route
exact
path="/dashboards/qa-dashboard/recipients-with-no-tta"
render={() => (
<AppWrapper authenticated logout={logout}>
<RecipientsWithNoTta />
</AppWrapper>
)}
/>
<Route
exact
path="/dashboards/qa-dashboard/recipients-with-ohs-standard-fei-goal"
render={() => (
<AppWrapper authenticated logout={logout}>
<RecipientsWithOhsStandardFeiGoal />
</AppWrapper>
)}
/>
<Route
exact
path="/dashboards/qa-dashboard/recipients-with-class-scores-and-goals"
render={() => (
<AppWrapper authenticated logout={logout}>
<RecipientsWithClassScoresAndGoals />
</AppWrapper>
)}
/>
<Route
exact
path="/training-reports/:status(not-started|in-progress|complete|suspended)"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function Menu({
<ul className="usa-list usa-list--unstyled" role="menu">
{menuItems.map((item) => (
<li key={item.label} role="menuitem">
<Button type="button" id={item.id || false} onClick={() => { updateShown(false); item.onClick(); }} unstyled className="smart-hub--menu-button smart-hub--button__no-margin">
<Button type="button" id={item.id || undefined} onClick={() => { updateShown(false); item.onClick(); }} unstyled className="smart-hub--menu-button smart-hub--button__no-margin">
<div className="padding-2 padding-right-3">
{item.label}
</div>
Expand Down
48 changes: 42 additions & 6 deletions frontend/src/fetchers/__tests__/ssdi.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import fetchMock from 'fetch-mock';
import join from 'url-join';
import { getSelfServiceData } from '../ssdi';
import { getSelfServiceData, containsFiltersThatAreNotApplicable } from '../ssdi';

const ssdiUrl = join('/', 'api', 'ssdi');
const ssdiUrl = join('/', 'api', 'ssdi', 'api', 'dashboards', 'qa');

beforeEach(() => {
fetchMock.restore();
Expand All @@ -11,7 +11,7 @@ beforeEach(() => {
describe('SSDI fetcher', () => {
it('should fetch data for qa-dashboard', async () => {
const mockData = [{ data: 'Expected' }];
const url = join(ssdiUrl, 'qa-dashboard', '?region.in[]=14');
const url = join(ssdiUrl, 'dashboard.sql', '?region.in[]=14');
fetchMock.getOnce(url, mockData);
const res = await getSelfServiceData('qa-dashboard', [{
id: '9ac8381c-2507-4b4a-a30c-6f1f87a00901',
Expand All @@ -24,10 +24,10 @@ describe('SSDI fetcher', () => {
});
it('recipients-with-ohs-standard-goal', async () => {
const mockData = [{ data: 'Expected' }];
const url = join(ssdiUrl, 'recipients-with-ohs-standard-goal', '?region.in[]=14');
const url = join(ssdiUrl, 'fei.sql', '?region.in[]=14');
fetchMock.getOnce(url, mockData);

const res = await getSelfServiceData('recipients-with-ohs-standard-goal', [
const res = await getSelfServiceData('recipients-with-ohs-standard-fei-goal', [
{
id: '9ac8381c-2507-4b4a-a30c-6f1f87a00901',
topic: 'region',
Expand All @@ -46,7 +46,7 @@ describe('SSDI fetcher', () => {
});
it('recipients-with-no-tta', async () => {
const mockData = [{ data: 'Expected' }];
const url = join(ssdiUrl, 'recipients-with-no-tta', '?region.in[]=14');
const url = join(ssdiUrl, 'no-tta.sql', '?region.in[]=14');
fetchMock.getOnce(url, mockData);

const res = await getSelfServiceData('recipients-with-no-tta', [
Expand Down Expand Up @@ -82,4 +82,40 @@ describe('SSDI fetcher', () => {
},
])).rejects.toThrow('Invalid filter name');
});

it('containsFiltersThatAreNotApplicable returns false if all filters are allowed', () => {
const filters = [
{
id: '9ac8381c-2507-4b4a-a30c-6f1f87a00901',
topic: 'region',
condition: 'is',
query: '14',
},
{
id: '9ac8381c-2507-4b4a-a30c-6f1f8723401',
topic: 'pickles',
condition: 'are',
query: 'spicy',
},
];
expect(containsFiltersThatAreNotApplicable('recipients-with-no-tta', filters)).toBe(true);
});

it('containsFiltersThatAreNotApplicable returns true if any filter is not allowed', () => {
const filters = [
{
id: '9ac8381c-2507-4b4a-a30c-6f1f87a00901',
topic: 'region',
condition: 'is',
query: '14',
},
{
id: '9ac8381c-2507-4b4a-a30c-6f1f8723401',
topic: 'stateCode',
condition: 'is',
query: 'ct',
},
];
expect(containsFiltersThatAreNotApplicable('recipients-with-class-scores-and-goals', filters)).toBe(false);
});
});
54 changes: 45 additions & 9 deletions frontend/src/fetchers/ssdi.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,58 @@ import { QA_DASHBOARD_FILTER_CONFIG } from '../pages/QADashboard/constants';

const ssdiUrl = join('/', 'api', 'ssdi');

const urlsForQuery = {
'recipients-with-no-tta': join(ssdiUrl, 'api', 'dashboards', 'qa', 'no-tta.sql'),
'recipients-with-ohs-standard-fei-goal': join(ssdiUrl, 'api', 'dashboards', 'qa', 'fei.sql'),
'recipients-with-class-scores-and-goals': join(ssdiUrl, 'api', 'dashboards', 'qa', 'class.sql'),
'qa-dashboard': join(ssdiUrl, 'api', 'dashboards', 'qa', 'dashboard.sql'),
};

const allowedTopicsForQuery = {
'recipients-with-no-tta': [
'recipient',
'grantNumber',
'programType',
'stateCode',
'region',
'startDate',
'endDate',
'dataSetSelection',
],
'recipients-with-ohs-standard-fei-goal': [
'recipient',
'grantNumber',
'programType',
'stateCode',
],
'recipients-with-ohs-standard-goal': [
'region',
'startDate',
'endDate',
'group',
'createDate',
'reason',
],
'recipients-with-class-scores-and-goals': [
'recipient',
'grantNumber',
'programType',
'stateCode',
'region',
'domainEmotionalSupport',
'domainClassroomOrganization',
'domainInstructionalSupport',
'createDate',
],
'qa-dashboard': [...QA_DASHBOARD_FILTER_CONFIG.map((filter) => filter.id), 'region'],
'qa-dashboard': [...QA_DASHBOARD_FILTER_CONFIG.map((filter) => filter),
'region',
'reportId',
],
};

export const containsFiltersThatAreNotApplicable = (filterName, filters) => {
if (!allowedTopicsForQuery[filterName]) {
throw new Error('Invalid filter name');
}

const config = allowedTopicsForQuery[filterName];
return filters.some((filter) => !config.includes(filter.topic));
};

export const getSelfServiceDataQueryString = (filterName, filters) => {
Expand All @@ -36,14 +72,14 @@ export const getSelfServiceDataQueryString = (filterName, filters) => {

const getSelfServiceUrl = (filterName, filters) => {
const queryString = getSelfServiceDataQueryString(filterName, filters);
const baseUrl = join(ssdiUrl, filterName);
const baseUrl = join(urlsForQuery[filterName]);
return `${baseUrl}?${queryString}`;
};

export const getSelfServiceData = async (filterName, filters) => {
export const getSelfServiceData = async (filterName, filters, dataSetSelection = []) => {
const url = getSelfServiceUrl(filterName, filters);

const response = await get(url);
const urlToUse = url + dataSetSelection.map((s) => `&dataSetSelection[]=${s}`).join('');
const response = await get(urlToUse);
if (!response.ok) {
throw new Error('Error fetching self service data');
}
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/hooks/useWidgetExport.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default function useWidgetExport(
checkboxes,
exportHeading,
exportName,
exportDataName = null, // Specify the data to export.
) {
const exportRows = useCallback((exportType) => {
let url = null;
Expand All @@ -33,7 +34,8 @@ export default function useWidgetExport(

// create a csv file of all the rows.
const csvRows = dataToExport.map((row) => {
const rowValues = row.data.map((d) => d.value);
const dataToUse = !row.data && exportDataName ? row[exportDataName] : row.data;
const rowValues = dataToUse.map((d) => d.value);
// If the heading has a comma, wrap it in quotes.
const rowHeadingToUse = row.heading.includes(',') ? `"${row.heading}"` : row.heading;
return `${rowHeadingToUse},${rowValues.join(',')}`;
Expand All @@ -59,7 +61,7 @@ export default function useWidgetExport(
} finally {
window.URL.revokeObjectURL(url);
}
}, [checkboxes, data, exportHeading, exportName, headers]);
}, [checkboxes, data, exportHeading, exportName, headers, exportDataName]);

return {
exportRows,
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/hooks/useWidgetPaging.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default function useWidgetPaging(
stringColumns = [],
dateColumns = [],
exportName,
exportDataName = null,
) {
const {
sortConfig,
Expand All @@ -50,6 +51,7 @@ export default function useWidgetPaging(
checkBoxes,
exportHeading,
exportName,
exportDataName,
);

const { activePage } = sortConfig;
Expand Down Expand Up @@ -82,8 +84,8 @@ export default function useWidgetPaging(
}
}, [loading, perPageNumber, setSortConfig, sortConfig]);

const sort = useCallback((sortBy) => {
requestSort(sortBy);
const sort = useCallback((sortBy, direction) => {
requestSort(sortBy, direction);
setOffset(0);
}, [requestSort]);

Expand All @@ -98,5 +100,6 @@ export default function useWidgetPaging(
requestSort: sort,
exportRows,
sortConfig,
setSortConfig,
};
}
21 changes: 15 additions & 6 deletions frontend/src/hooks/useWidgetSorting.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ export default function useWidgetSorting(
) {
const [sortConfig, setSortConfig] = useSessionSort(defaultSortConfig, localStorageKey);

const requestSort = useCallback((sortBy) => {
const requestSort = useCallback((sortBy, passedDirection = null) => {
// Get sort direction.
let direction = 'asc';
if (
// If we have a passed direction this means that we are sorting via a dropdown and not arrow.
if (passedDirection) {
// If the direction is passed, use it.
direction = passedDirection;
} else if (
sortConfig
&& sortConfig.sortBy === sortBy
&& sortConfig.direction === 'asc'
Expand All @@ -42,20 +46,23 @@ export default function useWidgetSorting(

// default is "value", otherwise use the key from the lookup
const sortingBy = sorts[sortBy] || 'value';

let valuesToSort;
switch (sortingBy) {
case 'string':
valuesToSort = dataToUse.map((t) => ({
...t,
sortBy: t.heading,
sortBy: !t.heading
? t[sortBy].toString().toLowerCase() // If we don't have heading data, use the value.
: t.heading.toString().toLowerCase(),
}));
break;
case 'date':
valuesToSort = dataToUse.map((t) => (
{
...t,
sortBy: new Date(t.data.find((tp) => (tp.sortKey || tp.title) === sortBy).value),
sortBy: !t.data
? new Date(t[sortBy]) // If we don't have data, use the value.
: new Date(t.data.find((tp) => (tp.sortKey || tp.title) === sortBy).value),
}));
break;
case 'key':
Expand All @@ -69,7 +76,9 @@ export default function useWidgetSorting(
valuesToSort = dataToUse.map((t) => (
{
...t,
sortBy: parseValue(t.data.find((tp) => (tp.sortKey || tp.title) === sortBy).value),
sortBy: !t.data
? parseValue(t[sortBy]) // If we don't have data, use the value.
: parseValue(t.data.find((tp) => (tp.sortKey || tp.title) === sortBy).value),
}));
break;
}
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/pages/QADashboard/Components/GoalCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import DataRow from '../../../components/DataRow';

function GoalCard({
goal,
recipientId,
regionId,
expanded,
}) {
return (
Expand All @@ -15,7 +17,7 @@ function GoalCard({
<DataRow
label="Goal number"
value={(
<Link to="recipient-tta-records/376/region/1/goals?id[]=83697">
<Link to={`/recipient-tta-records/${recipientId}/region/${regionId}/goals?id[]=${goal.id}`}>
{goal.goalNumber}
</Link>
)}
Expand Down Expand Up @@ -48,5 +50,7 @@ export const goalPropTypes = PropTypes.shape({
GoalCard.propTypes = {
goal: goalPropTypes.isRequired,
expanded: PropTypes.bool.isRequired,
recipientId: PropTypes.string.isRequired,
regionId: PropTypes.string.isRequired,
};
export default GoalCard;
Loading