From db5456c0ce5c3c1359cf43e586220ce4312c3bef Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Thu, 20 Jun 2024 11:28:33 -0400 Subject: [PATCH 01/26] disable scrape --- src/models/hooks/resource.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/models/hooks/resource.js b/src/models/hooks/resource.js index 12b2cf4d4e..b89bc77528 100644 --- a/src/models/hooks/resource.js +++ b/src/models/hooks/resource.js @@ -26,6 +26,8 @@ const afterUpdate = async (sequelize, instance, options) => { }; const afterCreate = async (sequelize, instance, options) => { + // TTAHUB-3102: Temporarily disable the resource scrape job (06/20/2024). + /* if (!instance.title) { // This is to resolve a recursive reference issue: // Service: /services/resourceQueue Imports: /lib/resource @@ -36,6 +38,7 @@ const afterCreate = async (sequelize, instance, options) => { const { addGetResourceMetadataToQueue } = require('../../services/resourceQueue'); addGetResourceMetadataToQueue(instance.id, instance.url); } + */ }; export { From 4ab28e3984e72e16a50bc88bf74fd5314185e287 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Thu, 20 Jun 2024 11:44:44 -0400 Subject: [PATCH 02/26] comment out test for now --- src/models/hooks/resource.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/models/hooks/resource.test.js b/src/models/hooks/resource.test.js index 6f57981c92..49cd111160 100644 --- a/src/models/hooks/resource.test.js +++ b/src/models/hooks/resource.test.js @@ -1,3 +1,5 @@ +/* eslint-disable jest/no-commented-out-tests */ +/* import { sequelize, Resource, @@ -41,3 +43,4 @@ describe('resource hooks', () => { }); }); }); +*/ From 419c4c3c1e23d170cc4cc02441a42d383797b449 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Thu, 20 Jun 2024 13:54:05 -0400 Subject: [PATCH 03/26] remove test --- src/models/hooks/resource.test.js | 46 ------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 src/models/hooks/resource.test.js diff --git a/src/models/hooks/resource.test.js b/src/models/hooks/resource.test.js deleted file mode 100644 index 49cd111160..0000000000 --- a/src/models/hooks/resource.test.js +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint-disable jest/no-commented-out-tests */ -/* -import { - sequelize, - Resource, -} from '..'; -import { addGetResourceMetadataToQueue } from '../../services/resourceQueue'; - -jest.mock('bull'); - -// Mock addGetResourceMetadataToQueue. -jest.mock('../../services/resourceQueue', () => ({ - addGetResourceMetadataToQueue: jest.fn(), -})); - -describe('resource hooks', () => { - afterAll(async () => { - await sequelize.close(); - }); - - describe('afterCreate', () => { - let newResource; - - afterEach(async () => { - // reset mocks. - addGetResourceMetadataToQueue.mockClear(); - }); - afterAll(async () => { - await Resource.destroy({ - where: { id: [newResource.id] }, - force: true, - }); - }); - - it('calls addGetResourceMetadataToQueue when title is missing on create', async () => { - newResource = await Resource.create({ - url: 'https://www.resource-with-title.com', - title: null, - description: 'resource-with-title', - individualHooks: true, - }); - expect(addGetResourceMetadataToQueue).toHaveBeenCalledWith(newResource.id, 'https://www.resource-with-title.com'); - }); - }); -}); -*/ From 9bfef849849837aa78289aa25f3591443b4cefc8 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Mon, 1 Jul 2024 10:52:53 -0400 Subject: [PATCH 04/26] add something went wrong page WIP --- frontend/src/App.js | 70 ++++++++++++------- frontend/src/HideSiteNavContext.js | 6 ++ frontend/src/colors.scss | 3 +- frontend/src/components/SomethingWentWrong.js | 55 +++++++++++++++ .../src/components/SomethingWentWrong.scss | 22 ++++++ frontend/src/pages/ActivityReport/index.js | 7 ++ 6 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 frontend/src/HideSiteNavContext.js create mode 100644 frontend/src/components/SomethingWentWrong.js create mode 100644 frontend/src/components/SomethingWentWrong.scss diff --git a/frontend/src/App.js b/frontend/src/App.js index 1c94bedc19..93a6463bb6 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -10,6 +10,7 @@ import { HTTPError } from './fetchers'; import { getSiteAlerts } from './fetchers/siteAlerts'; import FeatureFlag from './components/FeatureFlag'; import UserContext from './UserContext'; +import HideSiteNavContext from './HideSiteNavContext'; import SiteNav from './components/SiteNav'; import Header from './components/Header'; @@ -58,6 +59,7 @@ import SessionForm from './pages/SessionForm'; import ViewTrainingReport from './pages/ViewTrainingReport'; import useGaUserData from './hooks/useGaUserData'; import QADashboard from './pages/QADashboard'; +import SomethingWentWrong from './components/SomethingWentWrong'; const WHATSNEW_NOTIFICATIONS_KEY = 'whatsnew-read-notifications'; @@ -76,6 +78,7 @@ function App() { const [notifications, setNotifications] = useState({ whatsNew: '' }); const [areThereUnreadNotifications, setAreThereUnreadNotifications] = useState(false); + const [hideSiteNav, setHideSiteNav] = useState(false); useGaUserData(user); @@ -438,6 +441,14 @@ function App() { )} /> + ( + + + + )} + /> ( @@ -456,9 +467,13 @@ function App() { - - - {authenticated && ( + + + + {authenticated && !hideSiteNav && ( <> Skip to main content @@ -475,30 +490,31 @@ function App() { /> - )} - - - -
- {!authenticated && (authError === 403 - ? - : ( - - - - ) - )} - {authenticated && renderAuthenticatedRoutes()} - - - - - + )} + + + +
+ {!authenticated && (authError === 403 + ? + : ( + + + + ) + )} + {authenticated && renderAuthenticatedRoutes()} + + + + + + ); diff --git a/frontend/src/HideSiteNavContext.js b/frontend/src/HideSiteNavContext.js new file mode 100644 index 0000000000..23f77b7eb6 --- /dev/null +++ b/frontend/src/HideSiteNavContext.js @@ -0,0 +1,6 @@ +import React from 'react'; + +const HideSiteNavContext = React.createContext({ +}); + +export default HideSiteNavContext; diff --git a/frontend/src/colors.scss b/frontend/src/colors.scss index 80fc0e2643..fac7c72316 100644 --- a/frontend/src/colors.scss +++ b/frontend/src/colors.scss @@ -39,4 +39,5 @@ $error-dark: #b50909; $blue-vivid-focus: #2491FF; $text-ink: #1b1b1b; $text-link: #46789B; -$text-visited: #8C39DB; \ No newline at end of file +$text-visited: #8C39DB; +$response-code: #71767A; \ No newline at end of file diff --git a/frontend/src/components/SomethingWentWrong.js b/frontend/src/components/SomethingWentWrong.js new file mode 100644 index 0000000000..e9706d0a0b --- /dev/null +++ b/frontend/src/components/SomethingWentWrong.js @@ -0,0 +1,55 @@ +/* eslint-disable max-len */ +import React, { useContext } from 'react'; +import ReactRouterPropTypes from 'react-router-prop-types'; +import HideSiteNavContext from '../HideSiteNavContext'; +import './SomethingWentWrong.scss'; + +function SomethingWentWrong({ match }) { + const { setHideSiteNav } = useContext(HideSiteNavContext); + + const { responseCode } = match.params; + + setHideSiteNav(true); + return ( + <> + {responseCode && ( +
+

403 error - forbidden

+

Restricted access.

+
+

+ Sorry, but it looks like you're trying to access a restricted area. Here's what you can do: +

    +
  • + Double-check permissions: + {' '} + Ensure you have the proper clearance to access this page +
    + Contact support and ask them to check your permissions. +
    +
  • +
  • + Login again: + {' '} + Try logging in again. Maybe that's the missing key. +
  • +
  • + Explore elsewhere: + {' '} + Return to the main area and explore other permitted sections. +
  • +
+ If you believe this is an error or need further assistance, get in touch with support. +

+
+
+ )} + + ); +} + +SomethingWentWrong.propTypes = { + match: ReactRouterPropTypes.match.isRequired, +}; + +export default SomethingWentWrong; diff --git a/frontend/src/components/SomethingWentWrong.scss b/frontend/src/components/SomethingWentWrong.scss new file mode 100644 index 0000000000..5f0e7691ce --- /dev/null +++ b/frontend/src/components/SomethingWentWrong.scss @@ -0,0 +1,22 @@ +@use '../colors.scss' as *; + +.smart-hub--something-went-wrong h3 { + color: $response-code; + font-size: 1.5rem; + font-weight: 700; +} + +.smart-hub--something-went-wrong h1 { + font-size: 2.5rem; + font-weight: 700; +} + +.smart-hub--something-went-wrong p { + color: $base-dark; + font-size: 1.25rem; + font-weight: 400; +} + +.smart-hub--something-went-wrong li { + margin-bottom: .5rem; +} \ No newline at end of file diff --git a/frontend/src/pages/ActivityReport/index.js b/frontend/src/pages/ActivityReport/index.js index e8902bc549..010e225743 100644 --- a/frontend/src/pages/ActivityReport/index.js +++ b/frontend/src/pages/ActivityReport/index.js @@ -45,6 +45,7 @@ import { import useLocalStorage, { setConnectionActiveWithError } from '../../hooks/useLocalStorage'; import NetworkContext, { isOnlineMode } from '../../NetworkContext'; import UserContext from '../../UserContext'; +import { HTTPError } from '../../fetchers'; const defaultValues = { ECLKCResourcesUsed: [], @@ -383,6 +384,12 @@ function ActivityReport({ ); const errorMsg = !connection ? networkErrorMessage : <>Unable to load activity report; + + if (e instanceof HTTPError && [403, 404].includes(e.status)) { + // Redirect to the SomethingWentWrong component pass the response code as a param. + history.push(`/something-went-wrong/${e.status}`); + } + updateError(errorMsg); // If the error was caused by an invalid region, we need a way to communicate that to the // component so we can redirect the user. We can do this by updating the form data From 5813197cac67053f68c2ce6b9bb2c1c138068e31 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Mon, 1 Jul 2024 16:23:26 -0400 Subject: [PATCH 05/26] updates and test --- frontend/src/App.js | 4 +- frontend/src/components/SomethingWentWrong.js | 99 +++++++++++++++++-- .../__tests__/SomethingWentWrong.js | 59 +++++++++++ 3 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/__tests__/SomethingWentWrong.js diff --git a/frontend/src/App.js b/frontend/src/App.js index 93a6463bb6..756fb6d319 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -2,7 +2,9 @@ import React, { useState, useEffect, useMemo } from 'react'; import '@trussworks/react-uswds/lib/uswds.css'; import '@trussworks/react-uswds/lib/index.css'; -import { BrowserRouter, Route, Switch } from 'react-router-dom'; +import { + BrowserRouter, Route, Switch, +} from 'react-router-dom'; import { Helmet } from 'react-helmet'; import { fetchUser, fetchLogout } from './fetchers/Auth'; diff --git a/frontend/src/components/SomethingWentWrong.js b/frontend/src/components/SomethingWentWrong.js index e9706d0a0b..9f8450e094 100644 --- a/frontend/src/components/SomethingWentWrong.js +++ b/frontend/src/components/SomethingWentWrong.js @@ -1,18 +1,24 @@ /* eslint-disable max-len */ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; +import { Link } from '@trussworks/react-uswds'; +import { Link as ReactLink } from 'react-router-dom'; import ReactRouterPropTypes from 'react-router-prop-types'; import HideSiteNavContext from '../HideSiteNavContext'; import './SomethingWentWrong.scss'; function SomethingWentWrong({ match }) { + const { responseCode } = match.params; const { setHideSiteNav } = useContext(HideSiteNavContext); - const { responseCode } = match.params; + const supportLink = 'https://app.smartsheetgov.com/b/form/f0b4725683f04f349a939bd2e3f5425a'; + const getSupportLink = () => ( + support + ); - setHideSiteNav(true); - return ( - <> - {responseCode && ( + const determineMessage = () => { + // 403 Forbidden. + if (responseCode === '403') { + return (

403 error - forbidden

Restricted access.

@@ -25,7 +31,11 @@ function SomethingWentWrong({ match }) { {' '} Ensure you have the proper clearance to access this page
- Contact support and ask them to check your permissions. + Contact + {' '} + {getSupportLink()} + {' '} + and ask them to check your permissions.
  • @@ -39,11 +49,82 @@ function SomethingWentWrong({ match }) { Return to the main area and explore other permitted sections.
  • - If you believe this is an error or need further assistance, get in touch with support. + If you believe this is an error or need further assistance, get in touch with + {' '} + {getSupportLink()} + .

    - )} + ); + } + + // 404 Not found. + if (responseCode === '404') { + return ( +
    +

    404 error

    +

    Page not found

    +
    +

    + Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do: +

      +
    • + Go back to + {' '} + home +
    • +
    • + Contact + {' '} + {getSupportLink()} + {' '} + for help +
    • +
    + Thanks for your understanding and patience! +

    +
    +
    + ); + } + + // 500 Internal server error (display for everything else). + return ( +
    +

    Something went wrong.

    +
    +

    + Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do: +

      +
    • + Go back to + {' '} + home +
    • +
    • + Contact + {' '} + {getSupportLink()} + {' '} + for help +
    • +
    + Thanks for your understanding and patience! +

    +
    +
    + ); + }; + + useEffect(() => { + setHideSiteNav(true); + return () => setHideSiteNav(false); + }, [setHideSiteNav]); + + return ( + <> + {determineMessage(responseCode)} ); } diff --git a/frontend/src/components/__tests__/SomethingWentWrong.js b/frontend/src/components/__tests__/SomethingWentWrong.js new file mode 100644 index 0000000000..57ed30345c --- /dev/null +++ b/frontend/src/components/__tests__/SomethingWentWrong.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { createMemoryHistory } from 'history'; +import { Router } from 'react-router'; +import SomethingWentWrong from '../SomethingWentWrong'; +import HideSiteNavContext from '../../HideSiteNavContext'; + +const history = createMemoryHistory(); + +const renderSomethingWentWrong = ( + responseCode, + hideSiteNav = false, + setHideSiteNav = jest.fn, +) => render( + + + + + , + , +); + +describe('SomethingWentWrong component', () => { + // Write a test to pass the response code 403 to the component. + it('renders a 403 error message', async () => { + renderSomethingWentWrong('403'); + + expect(screen.getByText('403 error - forbidden')).toBeInTheDocument(); + expect(screen.getByText('Restricted access.')).toBeInTheDocument(); + expect(screen.getByText(/Sorry, but it looks like you're trying to access a restricted area./i)).toBeInTheDocument(); + expect(screen.getByText(/Double-check permissions:/i)).toBeInTheDocument(); + expect(screen.getByText(/Ensure you have the proper clearance to access this page/i)).toBeInTheDocument(); + expect(screen.getByText(/Login again:/i)).toBeInTheDocument(); + expect(screen.getByText(/Try logging in again. Maybe that's the missing key./i)).toBeInTheDocument(); + expect(screen.getByText(/Explore elsewhere:/i)).toBeInTheDocument(); + expect(screen.getByText(/Return to the main area and explore other permitted sections./i)).toBeInTheDocument(); + expect(screen.getByText(/If you believe this is an error or need further/i)).toBeInTheDocument(); + }); + + // Write a test to pass the response code 404 to the component. + it('renders a 404 error message', async () => { + renderSomethingWentWrong('404'); + + expect(screen.getByText('404 error')).toBeInTheDocument(); + expect(screen.getByText('Page not found')).toBeInTheDocument(); + expect(screen.getByText(/Well, this is awkward. It seems like the page/i)).toBeInTheDocument(); + expect(screen.getByText(/home/i)).toBeInTheDocument(); + expect(screen.getByText(/support/i)).toBeInTheDocument(); + expect(screen.getByText(/thanks for your understanding and patience/i)).toBeInTheDocument(); + }); + + // Write a test to pass an unknown response code to the component. + it('renders a generic error message', async () => { + renderSomethingWentWrong('500'); + expect(screen.getByText('Something went wrong.')).toBeInTheDocument(); + expect(screen.getByText(/Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do:/i)).toBeInTheDocument(); + expect(screen.getByText(/Thanks for your understanding and patience!/i)).toBeInTheDocument(); + }); +}); From c0f85699f9578920ec7bf9c54d01e02dfc1a0bd2 Mon Sep 17 00:00:00 2001 From: Nathan Powell Date: Tue, 2 Jul 2024 07:38:49 -0700 Subject: [PATCH 06/26] Remove r10 NC ARs plus associated goals, objs not used elsewhere --- ...240702000000-remove_national_center_ars.js | 526 ++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 src/migrations/20240702000000-remove_national_center_ars.js diff --git a/src/migrations/20240702000000-remove_national_center_ars.js b/src/migrations/20240702000000-remove_national_center_ars.js new file mode 100644 index 0000000000..caea78ba64 --- /dev/null +++ b/src/migrations/20240702000000-remove_national_center_ars.js @@ -0,0 +1,526 @@ +const { + prepMigration, +} = require('../lib/migration'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface) { + await queryInterface.sequelize.transaction(async (transaction) => { + const sessionSig = __filename; + await prepMigration(queryInterface, transaction, sessionSig); + + await queryInterface.sequelize.query(` + --------------------------------------------------- + -- NOTE: + -- Files and Resources are most properly managed by + -- maintenance jobs, so this and similar migrations + -- won't delete them directly. Deleting the link + -- records will give the maintenance job the info + -- it needs to perform its housekeeping. + --------------------------------------------------- + -------- Deleting unwanted ARs -------- + -- Create the AR deletion list + -- Remove AR link records: ------------- + -- ActivityRecipients + -- ActivityReportApprovers + -- ActivityReportCollaborators + -- ActivityReportFiles (no need to remove Files) + -- ActivityReportResources (no need to remove Resources) + + -- Create the NextSteps deletion list + -- Remove NextSteps link records: ------------- + -- NextStepResources + -- remove NextSteps ------------- + + -- Create the ARO deletion list + -- Remove ARO link records: ------------- + -- ActivityReportObjectiveFiles + -- ActivityReportObjectiveResources + -- ActivityReportObjectiveTopics + -- remove AROs ------------------- + + -- Create the orphaned Objective deletion list + -- remove Objectives ------------- + + -- Create the ARG deletion list + -- Remove ARG link records: ------------- + -- ActivityReportGoalFieldResponses + -- ActivityReportGoalResources + -- remove ARGs ------------------- + + -- Create the orphaned Goal deletions list + -- ( check if isFromSmartsheetTtaPlan, isRttapa) + -- Remove Goal link records: ------------- + -- EventReportPilotGoals + -- GoalFieldResponses + -- GoalResources + -- remove Goals ------------------ + + -- Create the orphaned ObjectiveTemplate deletion list + -- Create the orphaned GoalTemplate deletion list + -- Remove GoalTemplate link records: ------------- + -- GoalTemplateObjectiveTemplates + -- Remove ObjectiveTemplates -------- + -- Remove GoalTemplates ------------- + + -- Remove ARs ----------------------- + + ------------------------------------------------------------------------------------------------------------------- + -------- Deleting unwanted ARs -------- + -- Create the AR deletion list + DROP TABLE IF EXISTS ars_to_delete; + CREATE TEMP TABLE ars_to_delete + AS + SELECT id arid + FROM "ActivityReports" + WHERE id IN (24998, 24645, 24297, 24122, 27517, 30829, 29864, 6442, 23057, 23718, 25205, 25792, 25577, 25573, 26478, 26210, 27117, 26918, 28451, 28117, 27669, 29542, 29101, 29024, 30137, 29762, 31201) + AND "regionId" = 10 + ; + + -- Remove AR link records: ------------- + DROP TABLE IF EXISTS deleted_activityrecipients; + CREATE TEMP TABLE deleted_activityrecipients AS + WITH deletes AS ( + DELETE FROM "ActivityRecipients" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id + ) + SELECT id FROM deletes + ; + CREATE TEMP TABLE deleted_activityreportapprovers AS + WITH deletes AS ( + DELETE FROM "ActivityReportApprovers" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id + ) + SELECT id FROM deletes + ; + CREATE TEMP TABLE deleted_activityreportcollaborators AS + WITH deletes AS ( + DELETE FROM "ActivityReportCollaborators" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id + ) + SELECT id FROM deletes + ; + CREATE TEMP TABLE deleted_activityreportfiles AS + WITH deletes AS ( + DELETE FROM "ActivityReportFiles" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id, + "fileId" fid + ) + SELECT id, fid FROM deletes + ; + CREATE TEMP TABLE deleted_activityreportresources AS + WITH deletes AS ( + DELETE FROM "ActivityReportResources" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + + + + -- Create the NextSteps deletion list + DROP TABLE IF EXISTS nextsteps_to_delete; + CREATE TEMP TABLE nextsteps_to_delete + AS + SELECT + id nsid + FROM "NextSteps" + JOIN ars_to_delete + ON "activityReportId" = arid + ; + -- Remove NextSteps link records: ------------- + CREATE TEMP TABLE deleted_nextstepresources AS + WITH deletes AS ( + DELETE FROM "NextStepResources" + USING nextsteps_to_delete + WHERE "nextStepId" = nsid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + -- remove NextSteps ------------- + CREATE TEMP TABLE deleted_nextsteps AS + WITH deletes AS ( + DELETE FROM "NextSteps" + USING nextsteps_to_delete + WHERE id = nsid + RETURNING + id + ) + SELECT id FROM deletes + ; + + + -- Create the ARO deletion list + DROP TABLE IF EXISTS aros_to_delete; + CREATE TEMP TABLE aros_to_delete + AS + SELECT + id aroid, + "objectiveId" oid + FROM "ActivityReportObjectives" + JOIN ars_to_delete + ON "activityReportId" = arid + ; + -- Remove ARO link records: ------------- + CREATE TEMP TABLE deleted_activityreportobjectivefiles AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectiveFiles" + USING aros_to_delete + WHERE "activityReportObjectiveId" = aroid + RETURNING + id, + "fileId" fid + ) + SELECT id, fid FROM deletes + ; + CREATE TEMP TABLE deleted_activityreportobjectiveresources AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectiveResources" + USING aros_to_delete + WHERE "activityReportObjectiveId" = aroid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + CREATE TEMP TABLE deleted_activityreportobjectivetopics AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectiveTopics" + USING aros_to_delete + WHERE "activityReportObjectiveId" = aroid + RETURNING + id + ) + SELECT id FROM deletes + ; + -- remove AROs ------------------- + CREATE TEMP TABLE deleted_aros AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectives" + USING aros_to_delete + WHERE id = aroid + RETURNING + id, + "objectiveId" oid + ) + SELECT id, oid FROM deletes + ; + + -- Create the orphaned Objective deletion list + CREATE TEMP TABLE objectives_to_delete + AS + SELECT DISTINCT oid + FROM deleted_aros + EXCEPT + SELECT DISTINCT "objectiveId" + FROM "ActivityReportObjectives" + ; + + -- remove Objectives ------------------- + CREATE TEMP TABLE deleted_objectives AS + WITH deletes AS ( + DELETE FROM "Objectives" + USING objectives_to_delete + WHERE id = oid + RETURNING + id, + "goalId" gid, + "objectiveTemplateId" otid + ) + SELECT id, gid, otid FROM deletes + ; + + -- Create the ARG deletion list + DROP TABLE IF EXISTS args_to_delete; + CREATE TEMP TABLE args_to_delete + AS + SELECT DISTINCT + id argid, + "goalId" gid + FROM "ActivityReportGoals" + JOIN ars_to_delete + ON "activityReportId" = arid + ; + -- Remove ARG link records: ------------- + CREATE TEMP TABLE deleted_activityreportgoalfieldresponses AS + WITH deletes AS ( + DELETE FROM "ActivityReportGoalFieldResponses" + USING args_to_delete + WHERE "activityReportGoalId" = argid + RETURNING + id + ) + SELECT id FROM deletes + ; + CREATE TEMP TABLE deleted_activityreportgoalresources AS + WITH deletes AS ( + DELETE FROM "ActivityReportGoalResources" + USING args_to_delete + WHERE "activityReportGoalId" = argid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + -- remove ARGs ------------------- + CREATE TEMP TABLE deleted_args AS + WITH deletes AS ( + DELETE FROM "ActivityReportGoals" + USING args_to_delete + WHERE id = argid + RETURNING + id, + "goalId" gid + ) + SELECT id, gid FROM deletes + ; + + -- Create the orphaned Goal deletions list + CREATE TEMP TABLE goals_to_delete + AS + SELECT DISTINCT gid + FROM deleted_args dargs + JOIN "Goals" g + ON gid = g.id + WHERE (g."isRttapa" IS NULL OR g."isRttapa" != 'Yes') + AND g."isFromSmartsheetTtaPlan" != TRUE + AND g."createdVia" != 'merge' + EXCEPT + SELECT gid + FROM ( + SELECT DISTINCT "goalId" gid + FROM "ActivityReportGoals" + UNION + SELECT DISTINCT "goalId" + FROM "Objectives" + UNION + SELECT DISTINCT "goalId" + FROM "EventReportPilotGoals" + ) keepers + ; + -- Remove Goal link records: ------------- + CREATE TEMP TABLE deleted_goalcollaborators AS + WITH deletes AS ( + DELETE FROM "GoalCollaborators" + USING goals_to_delete + WHERE "goalId" = gid + RETURNING + id + ) + SELECT id FROM deletes + ; + CREATE TEMP TABLE deleted_goalfieldresponses AS + WITH deletes AS ( + DELETE FROM "GoalFieldResponses" + USING goals_to_delete + WHERE "goalId" = gid + RETURNING + id + ) + SELECT id FROM deletes + ; + CREATE TEMP TABLE deleted_goalresources AS + WITH deletes AS ( + DELETE FROM "GoalResources" + USING goals_to_delete + WHERE "goalId" = gid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + CREATE TEMP TABLE deleted_goalstatuschanges AS + WITH deletes AS ( + DELETE FROM "GoalStatusChanges" + USING goals_to_delete + WHERE "goalId" = gid + RETURNING + id, + "goalId" gid + ) + SELECT id, gid FROM deletes + ; + -- remove Goals ------------------- + CREATE TEMP TABLE deleted_goals AS + WITH deletes AS ( + DELETE FROM "Goals" + USING goals_to_delete + WHERE id = gid + RETURNING + id, + "goalTemplateId" gtid + ) + SELECT id, gtid FROM deletes + ; + + -- Create the orphaned ObjectiveTemplate deletion list + CREATE TEMP TABLE ots_to_delete + AS + SELECT DISTINCT otid + FROM deleted_objectives + EXCEPT + SELECT DISTINCT "objectiveTemplateId" + FROM "Objectives" + ; + + -- Create the orphaned GoalTemplate deletion list + CREATE TEMP TABLE gts_to_delete + AS + SELECT DISTINCT gtid + FROM deleted_goals + EXCEPT + SELECT DISTINCT "goalTemplateId" + FROM "Goals" + ; + -- Remove GoalTemplate link records: ------------- + CREATE TEMP TABLE deleted_goaltemplateobjectivetemplates AS + WITH unified_deletes AS ( + SELECT DISTINCT id gtotid + FROM "GoalTemplateObjectiveTemplates" + JOIN ots_to_delete + ON otid = "objectiveTemplateId" + UNION + SELECT DISTINCT id gtotid + FROM "GoalTemplateObjectiveTemplates" + JOIN gts_to_delete + ON gtid = "goalTemplateId" + ), + deletes AS ( + DELETE FROM "GoalTemplateObjectiveTemplates" + USING unified_deletes + WHERE id = gtotid + RETURNING + id + ) + SELECT id FROM deletes + ; + -- Remove ObjectiveTemplates -------- + CREATE TEMP TABLE deleted_objectivetemplates AS + WITH deletes AS ( + DELETE FROM "ObjectiveTemplates" + USING ots_to_delete + WHERE id = otid + RETURNING + id + ) + SELECT id FROM deletes + ; + -- Remove GoalTemplates ------------- + CREATE TEMP TABLE deleted_goaltemplates AS + WITH deletes AS ( + DELETE FROM "GoalTemplates" + USING gts_to_delete + WHERE id = gtid + RETURNING + id + ) + SELECT id FROM deletes + ; + + -- Remove ARs ------------- + CREATE TEMP TABLE deleted_ars AS + WITH deletes AS ( + DELETE FROM "ActivityReports" + USING ars_to_delete + WHERE id = arid + RETURNING + id + ) + SELECT id FROM deletes + ; + + + -- Stats ---------------------------- + SELECT 1,'ars_to_delete', count(*) FROM ars_to_delete + UNION + SELECT 2,'deleted_activityreportapprovers', count(*) FROM deleted_activityreportapprovers + UNION + SELECT 3,'deleted_activityreportcollaborators', count(*) FROM deleted_activityreportcollaborators + UNION + SELECT 4,'deleted_activityreportfiles', count(*) FROM deleted_activityreportfiles + UNION + SELECT 5,'deleted_activityreportresources', count(*) FROM deleted_activityreportresources + UNION + SELECT 6,'nextsteps_to_delete', count(*) FROM nextsteps_to_delete + UNION + SELECT 7,'deleted_nextstepresources', count(*) FROM deleted_nextstepresources + UNION + SELECT 8,'deleted_nextsteps', count(*) FROM deleted_nextsteps + UNION + SELECT 9,'aros_to_delete', count(*) FROM aros_to_delete + UNION + SELECT 10,'deleted_activityreportobjectivefiles', count(*) FROM deleted_activityreportobjectivefiles + UNION + SELECT 11,'deleted_activityreportobjectiveresources', count(*) FROM deleted_activityreportobjectiveresources + UNION + SELECT 12,'deleted_activityreportobjectivetopics', count(*) FROM deleted_activityreportobjectivetopics + UNION + SELECT 13,'deleted_aros', count(*) FROM deleted_aros + UNION + SELECT 14,'objectives_to_delete', count(*) FROM objectives_to_delete + UNION + SELECT 15,'deleted_objectives', count(*) FROM deleted_objectives + UNION + SELECT 16,'args_to_delete', count(*) FROM args_to_delete + UNION + SELECT 17,'deleted_activityreportgoalfieldresponses', count(*) FROM deleted_activityreportgoalfieldresponses + UNION + SELECT 18,'deleted_activityreportgoalresources', count(*) FROM deleted_activityreportgoalresources + UNION + SELECT 19,'deleted_args', count(*) FROM deleted_args + UNION + SELECT 20,'goals_to_delete', count(*) FROM goals_to_delete + UNION + SELECT 21,'deleted_goalcollaborators', count(*) FROM deleted_goalcollaborators + UNION + SELECT 22,'deleted_goalfieldresponses', count(*) FROM deleted_goalfieldresponses + UNION + SELECT 23,'deleted_goalresources', count(*) FROM deleted_goalresources + UNION + SELECT 24,'deleted_goalstatuschanges', count(*) FROM deleted_goalstatuschanges + UNION + SELECT 25,'deleted_goals', count(*) FROM deleted_goals + UNION + SELECT 26,'ots_to_delete', count(*) FROM ots_to_delete + UNION + SELECT 27,'gts_to_delete', count(*) FROM gts_to_delete + UNION + SELECT 28,'deleted_goaltemplateobjectivetemplates', count(*) FROM deleted_goaltemplateobjectivetemplates + UNION + SELECT 29,'deleted_objectivetemplates', count(*) FROM deleted_objectivetemplates + UNION + SELECT 30,'deleted_goaltemplates', count(*) FROM deleted_goaltemplates + UNION + SELECT 31,'deleted_ars', count(*) FROM deleted_ars + ORDER BY 1 + ; + + `, { transaction }); + }); + }, + + down: async () => { + }, +}; From 38c79e34637f46549a3d655004eefdcb11afa454 Mon Sep 17 00:00:00 2001 From: Nathan Powell Date: Tue, 2 Jul 2024 07:53:24 -0700 Subject: [PATCH 07/26] add drop table if exists for ci --- ...240702000000-remove_national_center_ars.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/migrations/20240702000000-remove_national_center_ars.js b/src/migrations/20240702000000-remove_national_center_ars.js index caea78ba64..dae0f8f6e2 100644 --- a/src/migrations/20240702000000-remove_national_center_ars.js +++ b/src/migrations/20240702000000-remove_national_center_ars.js @@ -89,6 +89,7 @@ module.exports = { ) SELECT id FROM deletes ; + DROP TABLE IF EXISTS deleted_activityreportapprovers; CREATE TEMP TABLE deleted_activityreportapprovers AS WITH deletes AS ( DELETE FROM "ActivityReportApprovers" @@ -99,6 +100,7 @@ module.exports = { ) SELECT id FROM deletes ; + DROP TABLE IF EXISTS deleted_activityreportcollaborators; CREATE TEMP TABLE deleted_activityreportcollaborators AS WITH deletes AS ( DELETE FROM "ActivityReportCollaborators" @@ -109,6 +111,7 @@ module.exports = { ) SELECT id FROM deletes ; + DROP TABLE IF EXISTS deleted_activityreportfiles; CREATE TEMP TABLE deleted_activityreportfiles AS WITH deletes AS ( DELETE FROM "ActivityReportFiles" @@ -120,6 +123,7 @@ module.exports = { ) SELECT id, fid FROM deletes ; + DROP TABLE IF EXISTS deleted_activityreportresources; CREATE TEMP TABLE deleted_activityreportresources AS WITH deletes AS ( DELETE FROM "ActivityReportResources" @@ -145,6 +149,7 @@ module.exports = { ON "activityReportId" = arid ; -- Remove NextSteps link records: ------------- + DROP TABLE IF EXISTS deleted_nextstepresources; CREATE TEMP TABLE deleted_nextstepresources AS WITH deletes AS ( DELETE FROM "NextStepResources" @@ -157,6 +162,7 @@ module.exports = { SELECT id, resourceid FROM deletes ; -- remove NextSteps ------------- + DROP TABLE IF EXISTS deleted_nextsteps; CREATE TEMP TABLE deleted_nextsteps AS WITH deletes AS ( DELETE FROM "NextSteps" @@ -181,6 +187,7 @@ module.exports = { ON "activityReportId" = arid ; -- Remove ARO link records: ------------- + DROP TABLE IF EXISTS deleted_activityreportobjectivefiles; CREATE TEMP TABLE deleted_activityreportobjectivefiles AS WITH deletes AS ( DELETE FROM "ActivityReportObjectiveFiles" @@ -192,6 +199,7 @@ module.exports = { ) SELECT id, fid FROM deletes ; + DROP TABLE IF EXISTS deleted_activityreportobjectiveresources; CREATE TEMP TABLE deleted_activityreportobjectiveresources AS WITH deletes AS ( DELETE FROM "ActivityReportObjectiveResources" @@ -203,6 +211,7 @@ module.exports = { ) SELECT id, resourceid FROM deletes ; + DROP TABLE IF EXISTS deleted_activityreportobjectivetopics; CREATE TEMP TABLE deleted_activityreportobjectivetopics AS WITH deletes AS ( DELETE FROM "ActivityReportObjectiveTopics" @@ -214,6 +223,7 @@ module.exports = { SELECT id FROM deletes ; -- remove AROs ------------------- + DROP TABLE IF EXISTS deleted_aros; CREATE TEMP TABLE deleted_aros AS WITH deletes AS ( DELETE FROM "ActivityReportObjectives" @@ -227,6 +237,7 @@ module.exports = { ; -- Create the orphaned Objective deletion list + DROP TABLE IF EXISTS objectives_to_delete; CREATE TEMP TABLE objectives_to_delete AS SELECT DISTINCT oid @@ -237,6 +248,7 @@ module.exports = { ; -- remove Objectives ------------------- + DROP TABLE IF EXISTS deleted_objectives; CREATE TEMP TABLE deleted_objectives AS WITH deletes AS ( DELETE FROM "Objectives" @@ -262,6 +274,7 @@ module.exports = { ON "activityReportId" = arid ; -- Remove ARG link records: ------------- + DROP TABLE IF EXISTS deleted_activityreportgoalfieldresponses; CREATE TEMP TABLE deleted_activityreportgoalfieldresponses AS WITH deletes AS ( DELETE FROM "ActivityReportGoalFieldResponses" @@ -272,6 +285,7 @@ module.exports = { ) SELECT id FROM deletes ; + DROP TABLE IF EXISTS deleted_activityreportgoalresources; CREATE TEMP TABLE deleted_activityreportgoalresources AS WITH deletes AS ( DELETE FROM "ActivityReportGoalResources" @@ -284,6 +298,7 @@ module.exports = { SELECT id, resourceid FROM deletes ; -- remove ARGs ------------------- + DROP TABLE IF EXISTS deleted_args; CREATE TEMP TABLE deleted_args AS WITH deletes AS ( DELETE FROM "ActivityReportGoals" @@ -297,6 +312,7 @@ module.exports = { ; -- Create the orphaned Goal deletions list + DROP TABLE IF EXISTS goals_to_delete; CREATE TEMP TABLE goals_to_delete AS SELECT DISTINCT gid @@ -320,6 +336,7 @@ module.exports = { ) keepers ; -- Remove Goal link records: ------------- + DROP TABLE IF EXISTS deleted_goalcollaborators; CREATE TEMP TABLE deleted_goalcollaborators AS WITH deletes AS ( DELETE FROM "GoalCollaborators" @@ -330,6 +347,7 @@ module.exports = { ) SELECT id FROM deletes ; + DROP TABLE IF EXISTS deleted_goalfieldresponses; CREATE TEMP TABLE deleted_goalfieldresponses AS WITH deletes AS ( DELETE FROM "GoalFieldResponses" @@ -340,6 +358,7 @@ module.exports = { ) SELECT id FROM deletes ; + DROP TABLE IF EXISTS deleted_goalresources; CREATE TEMP TABLE deleted_goalresources AS WITH deletes AS ( DELETE FROM "GoalResources" @@ -351,6 +370,7 @@ module.exports = { ) SELECT id, resourceid FROM deletes ; + DROP TABLE IF EXISTS deleted_goalstatuschanges; CREATE TEMP TABLE deleted_goalstatuschanges AS WITH deletes AS ( DELETE FROM "GoalStatusChanges" @@ -363,6 +383,7 @@ module.exports = { SELECT id, gid FROM deletes ; -- remove Goals ------------------- + DROP TABLE IF EXISTS deleted_goals; CREATE TEMP TABLE deleted_goals AS WITH deletes AS ( DELETE FROM "Goals" @@ -376,6 +397,7 @@ module.exports = { ; -- Create the orphaned ObjectiveTemplate deletion list + DROP TABLE IF EXISTS ots_to_delete; CREATE TEMP TABLE ots_to_delete AS SELECT DISTINCT otid @@ -386,6 +408,7 @@ module.exports = { ; -- Create the orphaned GoalTemplate deletion list + DROP TABLE IF EXISTS gts_to_delete; CREATE TEMP TABLE gts_to_delete AS SELECT DISTINCT gtid @@ -395,6 +418,7 @@ module.exports = { FROM "Goals" ; -- Remove GoalTemplate link records: ------------- + DROP TABLE IF EXISTS deleted_goaltemplateobjectivetemplates; CREATE TEMP TABLE deleted_goaltemplateobjectivetemplates AS WITH unified_deletes AS ( SELECT DISTINCT id gtotid @@ -417,6 +441,7 @@ module.exports = { SELECT id FROM deletes ; -- Remove ObjectiveTemplates -------- + DROP TABLE IF EXISTS deleted_objectivetemplates; CREATE TEMP TABLE deleted_objectivetemplates AS WITH deletes AS ( DELETE FROM "ObjectiveTemplates" @@ -428,6 +453,7 @@ module.exports = { SELECT id FROM deletes ; -- Remove GoalTemplates ------------- + DROP TABLE IF EXISTS deleted_goaltemplates; CREATE TEMP TABLE deleted_goaltemplates AS WITH deletes AS ( DELETE FROM "GoalTemplates" @@ -440,6 +466,7 @@ module.exports = { ; -- Remove ARs ------------- + DROP TABLE IF EXISTS deleted_ars; CREATE TEMP TABLE deleted_ars AS WITH deletes AS ( DELETE FROM "ActivityReports" From f136d48f5adf9981ee9fdb37d13ed7190a029be5 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 2 Jul 2024 11:54:11 -0400 Subject: [PATCH 08/26] changes per Jon and Matt --- frontend/src/App.js | 26 +- frontend/src/HideSiteNavContext.js | 6 - frontend/src/SomethingWentWrongContext.js | 6 + frontend/src/components/SomethingWentWrong.js | 264 ++++++++++-------- .../__tests__/SomethingWentWrong.js | 36 ++- frontend/src/pages/ActivityReport/index.js | 2 + 6 files changed, 195 insertions(+), 145 deletions(-) delete mode 100644 frontend/src/HideSiteNavContext.js create mode 100644 frontend/src/SomethingWentWrongContext.js diff --git a/frontend/src/App.js b/frontend/src/App.js index 756fb6d319..adc57c50c0 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -12,7 +12,7 @@ import { HTTPError } from './fetchers'; import { getSiteAlerts } from './fetchers/siteAlerts'; import FeatureFlag from './components/FeatureFlag'; import UserContext from './UserContext'; -import HideSiteNavContext from './HideSiteNavContext'; +import SomethingWentWrongContext from './SomethingWentWrongContext'; import SiteNav from './components/SiteNav'; import Header from './components/Header'; @@ -80,7 +80,7 @@ function App() { const [notifications, setNotifications] = useState({ whatsNew: '' }); const [areThereUnreadNotifications, setAreThereUnreadNotifications] = useState(false); - const [hideSiteNav, setHideSiteNav] = useState(false); + const [errorResponseCode, setErrorResponseCode] = useState(null); useGaUserData(user); @@ -443,14 +443,6 @@ function App() { )} /> - ( - - - - )} - /> ( @@ -469,13 +461,13 @@ function App() { - - {authenticated && !hideSiteNav && ( + {authenticated && !errorResponseCode && ( <>
    Skip to main content @@ -510,13 +502,19 @@ function App() { ) )} + {authenticated && errorResponseCode + && ( + + + + )} {authenticated && renderAuthenticatedRoutes()} - + ); diff --git a/frontend/src/HideSiteNavContext.js b/frontend/src/HideSiteNavContext.js deleted file mode 100644 index 23f77b7eb6..0000000000 --- a/frontend/src/HideSiteNavContext.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -const HideSiteNavContext = React.createContext({ -}); - -export default HideSiteNavContext; diff --git a/frontend/src/SomethingWentWrongContext.js b/frontend/src/SomethingWentWrongContext.js new file mode 100644 index 0000000000..f00723564f --- /dev/null +++ b/frontend/src/SomethingWentWrongContext.js @@ -0,0 +1,6 @@ +import React from 'react'; + +const SomethingWentWrong = React.createContext({ +}); + +export default SomethingWentWrong; diff --git a/frontend/src/components/SomethingWentWrong.js b/frontend/src/components/SomethingWentWrong.js index 9f8450e094..f8b41fa9ed 100644 --- a/frontend/src/components/SomethingWentWrong.js +++ b/frontend/src/components/SomethingWentWrong.js @@ -1,136 +1,176 @@ -/* eslint-disable max-len */ -import React, { useContext, useEffect } from 'react'; -import { Link } from '@trussworks/react-uswds'; -import { Link as ReactLink } from 'react-router-dom'; -import ReactRouterPropTypes from 'react-router-prop-types'; -import HideSiteNavContext from '../HideSiteNavContext'; +import React, { useContext } from 'react'; +import { Link, Button } from '@trussworks/react-uswds'; +import { Link as ReactLink, useHistory } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import SomethingWentWrongContext from '../SomethingWentWrongContext'; import './SomethingWentWrong.scss'; -function SomethingWentWrong({ match }) { - const { responseCode } = match.params; - const { setHideSiteNav } = useContext(HideSiteNavContext); +/* eslint-disable max-len */ + +function SomethingWentWrong({ errorResponseCode }) { + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); + const history = useHistory(); const supportLink = 'https://app.smartsheetgov.com/b/form/f0b4725683f04f349a939bd2e3f5425a'; const getSupportLink = () => ( support ); - const determineMessage = () => { - // 403 Forbidden. - if (responseCode === '403') { - return ( -
    -

    403 error - forbidden

    -

    Restricted access.

    -
    -

    - Sorry, but it looks like you're trying to access a restricted area. Here's what you can do: -

      -
    • - Double-check permissions: - {' '} - Ensure you have the proper clearance to access this page -
      - Contact - {' '} - {getSupportLink()} - {' '} - and ask them to check your permissions. -
      -
    • -
    • - Login again: - {' '} - Try logging in again. Maybe that's the missing key. -
    • -
    • - Explore elsewhere: - {' '} - Return to the main area and explore other permitted sections. -
    • -
    - If you believe this is an error or need further assistance, get in touch with - {' '} - {getSupportLink()} - . -

    -
    -
    - ); - } - - // 404 Not found. - if (responseCode === '404') { - return ( -
    -

    404 error

    -

    Page not found

    -
    -

    - Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do: -

      -
    • - Go back to - {' '} - home -
    • -
    • - Contact - {' '} - {getSupportLink()} - {' '} - for help -
    • -
    - Thanks for your understanding and patience! -

    -
    -
    - ); - } + const onHomeClick = () => { + setErrorResponseCode(null); + history.push('/'); + }; - // 500 Internal server error (display for everything else). - return ( -
    -

    Something went wrong.

    -
    -

    - Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do: -

      -
    • - Go back to + const responseCodeMessages = [ + { + code: 401, + message: '401 error - unauthorized', + title: 'Unauthorized access', + body: ( +

      + Sorry, but it looks like you're trying to access a restricted area. Here's what you can do: +

        +
      • + Double-check permissions: + {' '} + Ensure you have the proper clearance to access this page +
        + Contact + {' '} + {getSupportLink()} {' '} - home -
      • -
      • + and ask them to check your permissions. +
    + +
  • + Login again: + {' '} + Try logging in again. Maybe that's the missing key. +
  • +
  • + Explore elsewhere: + {' '} + Return to the main area and explore other permitted sections. +
  • + + If you believe this is an error or need further assistance, get in touch with + {' '} + {getSupportLink()} + . +

    + ), + }, + { + code: 403, + message: '403 error - forbidden', + title: 'Restricted access', + body: ( +

    + Sorry, but it looks like you're trying to access a restricted area. Here's what you can do: +

      +
    • + Double-check permissions: + {' '} + Ensure you have the proper clearance to access this page +
      Contact {' '} {getSupportLink()} {' '} - for help -
    • -
    - Thanks for your understanding and patience! -

    -
    - - ); - }; + and ask them to check your permissions. + + +
  • + Login again: + {' '} + Try logging in again. Maybe that's the missing key. +
  • +
  • + Explore elsewhere: + {' '} + Return to the main area and explore other permitted sections. +
  • + + If you believe this is an error or need further assistance, get in touch with + {' '} + {getSupportLink()} + . +

    + ), + }, + { + code: 404, + message: '404 error', + title: 'Page not found', + body: ( +

    + Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do: +

      +
    • + Go back to + {' '} + home +
    • +
    • + Contact + {' '} + {getSupportLink()} + {' '} + for help +
    • +
    + Thanks for your understanding and patience! +

    + ), + }, + { + code: 500, + message: null, + title: 'Something went wrong', + body: ( +

    + Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do: +

      +
    • + Go back to + {' '} + +
    • +
    • + Contact + {' '} + {getSupportLink()} + {' '} + for help +
    • +
    + Thanks for your understanding and patience! +

    + ), + }, + ]; - useEffect(() => { - setHideSiteNav(true); - return () => setHideSiteNav(false); - }, [setHideSiteNav]); + const messageToDisplay = responseCodeMessages.find((msg) => msg.code === errorResponseCode) || responseCodeMessages.find((msg) => msg.code === 500); return ( - <> - {determineMessage(responseCode)} - +
    + { + messageToDisplay.message && ( +

    {messageToDisplay.message}

    + ) + } +

    {messageToDisplay.title}

    +
    + { + messageToDisplay.body + } +
    +
    ); } SomethingWentWrong.propTypes = { - match: ReactRouterPropTypes.match.isRequired, + errorResponseCode: PropTypes.number.isRequired, }; export default SomethingWentWrong; diff --git a/frontend/src/components/__tests__/SomethingWentWrong.js b/frontend/src/components/__tests__/SomethingWentWrong.js index 57ed30345c..3902eaf977 100644 --- a/frontend/src/components/__tests__/SomethingWentWrong.js +++ b/frontend/src/components/__tests__/SomethingWentWrong.js @@ -3,30 +3,40 @@ import { render, screen } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router'; import SomethingWentWrong from '../SomethingWentWrong'; -import HideSiteNavContext from '../../HideSiteNavContext'; const history = createMemoryHistory(); const renderSomethingWentWrong = ( - responseCode, - hideSiteNav = false, - setHideSiteNav = jest.fn, + responseCode = 500, ) => render( - - - - , + , ); describe('SomethingWentWrong component', () => { + // Write a test to pass the response code 401 to the component. + it('renders a 401 error message', async () => { + renderSomethingWentWrong(401); + + expect(screen.getByText('401 error - unauthorized')).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /unauthorized access/i })).toBeInTheDocument(); + expect(screen.getByText(/Sorry, but it looks like you're trying to access a restricted area./i)).toBeInTheDocument(); + expect(screen.getByText(/Double-check permissions:/i)).toBeInTheDocument(); + expect(screen.getByText(/Ensure you have the proper clearance to access this page/i)).toBeInTheDocument(); + expect(screen.getByText(/Login again:/i)).toBeInTheDocument(); + expect(screen.getByText(/Try logging in again. Maybe that's the missing key./i)).toBeInTheDocument(); + expect(screen.getByText(/Explore elsewhere:/i)).toBeInTheDocument(); + expect(screen.getByText(/Return to the main area and explore other permitted sections./i)).toBeInTheDocument(); + expect(screen.getByText(/If you believe this is an error or need further/i)).toBeInTheDocument(); + }); + // Write a test to pass the response code 403 to the component. it('renders a 403 error message', async () => { - renderSomethingWentWrong('403'); + renderSomethingWentWrong(403); expect(screen.getByText('403 error - forbidden')).toBeInTheDocument(); - expect(screen.getByText('Restricted access.')).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /restricted access/i })).toBeInTheDocument(); expect(screen.getByText(/Sorry, but it looks like you're trying to access a restricted area./i)).toBeInTheDocument(); expect(screen.getByText(/Double-check permissions:/i)).toBeInTheDocument(); expect(screen.getByText(/Ensure you have the proper clearance to access this page/i)).toBeInTheDocument(); @@ -39,7 +49,7 @@ describe('SomethingWentWrong component', () => { // Write a test to pass the response code 404 to the component. it('renders a 404 error message', async () => { - renderSomethingWentWrong('404'); + renderSomethingWentWrong(404); expect(screen.getByText('404 error')).toBeInTheDocument(); expect(screen.getByText('Page not found')).toBeInTheDocument(); @@ -51,8 +61,8 @@ describe('SomethingWentWrong component', () => { // Write a test to pass an unknown response code to the component. it('renders a generic error message', async () => { - renderSomethingWentWrong('500'); - expect(screen.getByText('Something went wrong.')).toBeInTheDocument(); + renderSomethingWentWrong(); + expect(screen.getByRole('heading', { name: /something went wrong/i })).toBeInTheDocument(); expect(screen.getByText(/Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do:/i)).toBeInTheDocument(); expect(screen.getByText(/Thanks for your understanding and patience!/i)).toBeInTheDocument(); }); diff --git a/frontend/src/pages/ActivityReport/index.js b/frontend/src/pages/ActivityReport/index.js index 010e225743..bd93b23531 100644 --- a/frontend/src/pages/ActivityReport/index.js +++ b/frontend/src/pages/ActivityReport/index.js @@ -46,6 +46,7 @@ import useLocalStorage, { setConnectionActiveWithError } from '../../hooks/useLo import NetworkContext, { isOnlineMode } from '../../NetworkContext'; import UserContext from '../../UserContext'; import { HTTPError } from '../../fetchers'; +// import SomethingWentWrongContext from '../../SomethingWentWrongContext'; const defaultValues = { ECLKCResourcesUsed: [], @@ -203,6 +204,7 @@ function ActivityReport({ const [creatorNameWithRole, updateCreatorRoleWithName] = useState(''); const reportId = useRef(); const { user } = useContext(UserContext); + // const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const { socket, From f7505c4bc7c82a40580c323d5983dce9e094477c Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 2 Jul 2024 13:43:29 -0400 Subject: [PATCH 09/26] cleanup and fixes --- frontend/src/App.js | 2 +- frontend/src/components/SomethingWentWrong.js | 4 ++-- frontend/src/pages/ActivityReport/index.js | 18 ++++++++---------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/frontend/src/App.js b/frontend/src/App.js index adc57c50c0..efbf21c097 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -508,7 +508,7 @@ function App() { )} - {authenticated && renderAuthenticatedRoutes()} + {authenticated && !errorResponseCode && renderAuthenticatedRoutes()} diff --git a/frontend/src/components/SomethingWentWrong.js b/frontend/src/components/SomethingWentWrong.js index f8b41fa9ed..472550f233 100644 --- a/frontend/src/components/SomethingWentWrong.js +++ b/frontend/src/components/SomethingWentWrong.js @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; import { Link, Button } from '@trussworks/react-uswds'; -import { Link as ReactLink, useHistory } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import PropTypes from 'prop-types'; import SomethingWentWrongContext from '../SomethingWentWrongContext'; import './SomethingWentWrong.scss'; @@ -109,7 +109,7 @@ function SomethingWentWrong({ errorResponseCode }) {
  • Go back to {' '} - home +
  • Contact diff --git a/frontend/src/pages/ActivityReport/index.js b/frontend/src/pages/ActivityReport/index.js index bd93b23531..6619a4e10d 100644 --- a/frontend/src/pages/ActivityReport/index.js +++ b/frontend/src/pages/ActivityReport/index.js @@ -45,8 +45,7 @@ import { import useLocalStorage, { setConnectionActiveWithError } from '../../hooks/useLocalStorage'; import NetworkContext, { isOnlineMode } from '../../NetworkContext'; import UserContext from '../../UserContext'; -import { HTTPError } from '../../fetchers'; -// import SomethingWentWrongContext from '../../SomethingWentWrongContext'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; const defaultValues = { ECLKCResourcesUsed: [], @@ -204,7 +203,7 @@ function ActivityReport({ const [creatorNameWithRole, updateCreatorRoleWithName] = useState(''); const reportId = useRef(); const { user } = useContext(UserContext); - // const { setErrorResponseCode } = useContext(SomethingWentWrongContext); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const { socket, @@ -259,7 +258,12 @@ function ActivityReport({ reportId.current = activityReportId; if (activityReportId !== 'new') { - const fetchedReport = await getReport(activityReportId); + let fetchedReport; + try { + fetchedReport = await getReport(activityReportId); + } catch (e) { + setErrorResponseCode(e.status); + } report = convertReportToFormData(fetchedReport); } else { report = { @@ -386,12 +390,6 @@ function ActivityReport({ ); const errorMsg = !connection ? networkErrorMessage : <>Unable to load activity report; - - if (e instanceof HTTPError && [403, 404].includes(e.status)) { - // Redirect to the SomethingWentWrong component pass the response code as a param. - history.push(`/something-went-wrong/${e.status}`); - } - updateError(errorMsg); // If the error was caused by an invalid region, we need a way to communicate that to the // component so we can redirect the user. We can do this by updating the form data From bc3b97cd58743d33aae4ac9e451ed2f016398ea9 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 2 Jul 2024 15:11:55 -0400 Subject: [PATCH 10/26] updated on reports with tests --- .../pages/ActivityReport/__tests__/index.js | 9 ++++ frontend/src/pages/ActivityReport/index.js | 1 + .../src/pages/ActivityReport/testHelpers.js | 37 +++++++------ .../ApprovedActivityReport/__tests__/index.js | 38 ++++++++----- .../src/pages/ApprovedActivityReport/index.js | 54 +++---------------- 5 files changed, 63 insertions(+), 76 deletions(-) diff --git a/frontend/src/pages/ActivityReport/__tests__/index.js b/frontend/src/pages/ActivityReport/__tests__/index.js index 16f09e1912..017b0ca7f4 100644 --- a/frontend/src/pages/ActivityReport/__tests__/index.js +++ b/frontend/src/pages/ActivityReport/__tests__/index.js @@ -135,6 +135,15 @@ describe('ActivityReport', () => { }); }); + describe('something went wrong context', () => { + it('ensure we call set the response code on error', async () => { + fetchMock.get('/api/activity-reports/1', 500); + const setErrorResponseCode = jest.fn(); + renderActivityReport('1', 'activity-summary', null, 1, setErrorResponseCode); + await waitFor(() => expect(setErrorResponseCode).toHaveBeenCalledWith(500)); + }); + }); + describe('groups', () => { it('recipients correctly update for groups', async () => { const groupRecipients = { diff --git a/frontend/src/pages/ActivityReport/index.js b/frontend/src/pages/ActivityReport/index.js index 6619a4e10d..41bded708a 100644 --- a/frontend/src/pages/ActivityReport/index.js +++ b/frontend/src/pages/ActivityReport/index.js @@ -262,6 +262,7 @@ function ActivityReport({ try { fetchedReport = await getReport(activityReportId); } catch (e) { + // If error retrieving the report show the "something went wrong" page. setErrorResponseCode(e.status); } report = convertReportToFormData(fetchedReport); diff --git a/frontend/src/pages/ActivityReport/testHelpers.js b/frontend/src/pages/ActivityReport/testHelpers.js index 13c8b2a889..60808ea50e 100644 --- a/frontend/src/pages/ActivityReport/testHelpers.js +++ b/frontend/src/pages/ActivityReport/testHelpers.js @@ -11,6 +11,7 @@ import moment from 'moment'; import ActivityReport from './index'; import UserContext from '../../UserContext'; import AppLoadingContext from '../../AppLoadingContext'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; export const history = createMemoryHistory(); @@ -66,33 +67,37 @@ export const ReportComponent = ({ currentPage = 'activity-summary', showLastUpdatedTime = null, userId = 1, + setErrorResponseCode = jest.fn(), }) => ( - - - - - + + + + + + + ); -export const renderActivityReport = (id, currentPage = 'activity-summary', showLastUpdatedTime = null, userId = 1) => { +export const renderActivityReport = (id, currentPage = 'activity-summary', showLastUpdatedTime = null, userId = 1, setErrorResponseCode = jest.fn()) => { render( , ); }; diff --git a/frontend/src/pages/ApprovedActivityReport/__tests__/index.js b/frontend/src/pages/ApprovedActivityReport/__tests__/index.js index 1bb5e8e6ce..1150f212c8 100644 --- a/frontend/src/pages/ApprovedActivityReport/__tests__/index.js +++ b/frontend/src/pages/ApprovedActivityReport/__tests__/index.js @@ -10,6 +10,7 @@ import { } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import fetchMock from 'fetch-mock'; +import SomethingWentWrongContext from '../../../SomethingWentWrongContext'; import ApprovedActivityReport from '../index'; @@ -104,7 +105,7 @@ describe('Activity report print and share view', () => { ], }; - function renderApprovedActivityReport(id, passedUser = user) { + function renderApprovedActivityReport(id, passedUser = user, setErrorResponseCode = jest.fn()) { const match = { path: '', url: '', @@ -113,7 +114,11 @@ describe('Activity report print and share view', () => { }, }; - render(); + render( + + + , + ); } afterEach(() => fetchMock.restore()); @@ -224,18 +229,22 @@ describe('Activity report print and share view', () => { }); it('handles authorization errors', async () => { - act(() => renderApprovedActivityReport(5007)); - - await waitFor(() => { - expect(screen.getByText(/sorry, you are not allowed to view this report/i)).toBeInTheDocument(); + const setErrorResponseCode = jest.fn(); + act(async () => { + renderApprovedActivityReport(5007, user, setErrorResponseCode); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalledWith(401); + }); }); }); it('handles data errors', async () => { - act(() => renderApprovedActivityReport(5002)); - - await waitFor(() => { - expect(screen.getByText(/sorry, something went wrong\./i)).toBeInTheDocument(); + const setErrorResponseCode = jest.fn(); + act(async () => { + renderApprovedActivityReport(5002, user, setErrorResponseCode); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalled(); + }); }); }); @@ -314,9 +323,12 @@ describe('Activity report print and share view', () => { }); it('handles a malformed url', async () => { - act(() => renderApprovedActivityReport('butter-lover')); - await waitFor(() => { - expect(screen.getByText(/sorry, something went wrong\./i)).toBeInTheDocument(); + const setErrorResponseCode = jest.fn(); + act(async () => { + renderApprovedActivityReport('butter-lover', user, setErrorResponseCode); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalledWith(404); + }); }); }); diff --git a/frontend/src/pages/ApprovedActivityReport/index.js b/frontend/src/pages/ApprovedActivityReport/index.js index 4028d78c25..c595242aaf 100644 --- a/frontend/src/pages/ApprovedActivityReport/index.js +++ b/frontend/src/pages/ApprovedActivityReport/index.js @@ -1,4 +1,6 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React, { + useEffect, useState, useRef, useContext, +} from 'react'; import PropTypes from 'prop-types'; import ReactRouterPropTypes from 'react-router-prop-types'; import { Redirect } from 'react-router-dom'; @@ -16,10 +18,10 @@ import './index.scss'; import ApprovedReportV1 from './components/ApprovedReportV1'; import ApprovedReportV2 from './components/ApprovedReportV2'; import ApprovedReportSpecialButtons from '../../components/ApprovedReportSpecialButtons'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; export default function ApprovedActivityReport({ match, user }) { - const [notAuthorized, setNotAuthorized] = useState(false); - const [somethingWentWrong, setSomethingWentWrong] = useState(false); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const [justUnlocked, updatedJustUnlocked] = useState(false); @@ -75,7 +77,6 @@ export default function ApprovedActivityReport({ match, user }) { useEffect(() => { if (!parseInt(match.params.activityReportId, 10)) { - setSomethingWentWrong(true); return; } @@ -85,54 +86,13 @@ export default function ApprovedActivityReport({ match, user }) { // review and submit table setReport(data); } catch (err) { - if (err && err.status && (err.status >= 400 && err.status < 500)) { - setNotAuthorized(true); - return; - } - - // eslint-disable-next-line no-console - console.log(err); - setSomethingWentWrong(true); + setErrorResponseCode(err.status); } } fetchReport(); - }, [match.params.activityReportId, user]); - - if (notAuthorized) { - return ( - <> - - Not Authorized To View Activity Report - -
    -
    -

    Unauthorized

    -

    - Sorry, you are not allowed to view this report -

    -
    -
    - - ); - } + }, [match.params.activityReportId, user, setErrorResponseCode]); - if (somethingWentWrong) { - return ( - <> - - Error Displaying Activity Report - -
    -
    -

    - Sorry, something went wrong. -

    -
    -
    - - ); - } const { id: reportId, displayId, From c64be633f5d6bdf17e396a4c813ada0a1669e4bd Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 2 Jul 2024 15:18:53 -0400 Subject: [PATCH 11/26] make colors... --- colorsjschecksum | 2 +- frontend/src/colors.scss | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/colorsjschecksum b/colorsjschecksum index d9761a45bb..ecf579b7f2 100644 --- a/colorsjschecksum +++ b/colorsjschecksum @@ -1 +1 @@ -69962956f5817a804f1a491395986eb3c612a6feb4d76315f58c5b273cdf25a7 \ No newline at end of file +9d1caff32aaaea3109f14ce9fa740f7ef9c6ea6d55097a74abef1da773110c41 \ No newline at end of file diff --git a/frontend/src/colors.scss b/frontend/src/colors.scss index fac7c72316..80fc0e2643 100644 --- a/frontend/src/colors.scss +++ b/frontend/src/colors.scss @@ -39,5 +39,4 @@ $error-dark: #b50909; $blue-vivid-focus: #2491FF; $text-ink: #1b1b1b; $text-link: #46789B; -$text-visited: #8C39DB; -$response-code: #71767A; \ No newline at end of file +$text-visited: #8C39DB; \ No newline at end of file From 32c83781c5c5037150c1fe1bb93e228eab2c732a Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 2 Jul 2024 15:26:07 -0400 Subject: [PATCH 12/26] add color --- colorsjschecksum | 2 +- colorsscsschecksum | 2 +- frontend/src/colors.js | 1 + frontend/src/colors.scss | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/colorsjschecksum b/colorsjschecksum index ecf579b7f2..ff45aef792 100644 --- a/colorsjschecksum +++ b/colorsjschecksum @@ -1 +1 @@ -9d1caff32aaaea3109f14ce9fa740f7ef9c6ea6d55097a74abef1da773110c41 \ No newline at end of file +ebc7ad94231a4b6f7263c55eb114b48c1a9bcd927c6e829e71d24cf4184efc19 \ No newline at end of file diff --git a/colorsscsschecksum b/colorsscsschecksum index dd4f157735..5aa17e9d35 100644 --- a/colorsscsschecksum +++ b/colorsscsschecksum @@ -1 +1 @@ -45fe702aa79405030c90def8c4690064d0be4ca316fc297e6b1dbe1117ae95ff \ No newline at end of file +36906d289c12231e597eeb56423d83f37593429766f6d496f2db5f508c7d6ee8 \ No newline at end of file diff --git a/frontend/src/colors.js b/frontend/src/colors.js index 6d74aebfba..2196c2bd01 100644 --- a/frontend/src/colors.js +++ b/frontend/src/colors.js @@ -41,6 +41,7 @@ const colors = { textInk: '#1b1b1b', textLink: '#46789B', textVisited: '#8C39DB', + responseCode: '#71767A', }; module.exports = colors; diff --git a/frontend/src/colors.scss b/frontend/src/colors.scss index 80fc0e2643..fac7c72316 100644 --- a/frontend/src/colors.scss +++ b/frontend/src/colors.scss @@ -39,4 +39,5 @@ $error-dark: #b50909; $blue-vivid-focus: #2491FF; $text-ink: #1b1b1b; $text-link: #46789B; -$text-visited: #8C39DB; \ No newline at end of file +$text-visited: #8C39DB; +$response-code: #71767A; \ No newline at end of file From 686772bbbe2fbeba551c6f40aaa5795b4c811de3 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 2 Jul 2024 16:50:38 -0400 Subject: [PATCH 13/26] add to events --- frontend/src/components/SomethingWentWrong.js | 5 +++ .../src/pages/SessionForm/__tests__/index.js | 37 ++++++++++-------- frontend/src/pages/SessionForm/index.js | 6 ++- .../TrainingReportForm/__tests__/index.js | 39 +++++++++++-------- .../src/pages/TrainingReportForm/index.js | 11 +++++- src/services/currentUser.js | 2 + 6 files changed, 63 insertions(+), 37 deletions(-) diff --git a/frontend/src/components/SomethingWentWrong.js b/frontend/src/components/SomethingWentWrong.js index 472550f233..d40c06c7e0 100644 --- a/frontend/src/components/SomethingWentWrong.js +++ b/frontend/src/components/SomethingWentWrong.js @@ -3,14 +3,19 @@ import { Link, Button } from '@trussworks/react-uswds'; import { useHistory } from 'react-router-dom'; import PropTypes from 'prop-types'; import SomethingWentWrongContext from '../SomethingWentWrongContext'; +import AppLoadingContext from '../AppLoadingContext'; import './SomethingWentWrong.scss'; /* eslint-disable max-len */ function SomethingWentWrong({ errorResponseCode }) { const { setErrorResponseCode } = useContext(SomethingWentWrongContext); + const { setIsAppLoading, isAppLoading } = useContext(AppLoadingContext); const history = useHistory(); + // Make sure if something was loading when an error occurred, we stop the loading spinner. + if (isAppLoading) setIsAppLoading(false); + const supportLink = 'https://app.smartsheetgov.com/b/form/f0b4725683f04f349a939bd2e3f5425a'; const getSupportLink = () => ( support diff --git a/frontend/src/pages/SessionForm/__tests__/index.js b/frontend/src/pages/SessionForm/__tests__/index.js index 3100c15f04..257053deba 100644 --- a/frontend/src/pages/SessionForm/__tests__/index.js +++ b/frontend/src/pages/SessionForm/__tests__/index.js @@ -13,22 +13,30 @@ import UserContext from '../../../UserContext'; import AppLoadingContext from '../../../AppLoadingContext'; import { COMPLETE, IN_PROGRESS } from '../../../components/Navigator/constants'; import { mockRSSData } from '../../../testHelpers'; +import SomethingWentWrongContext from '../../../SomethingWentWrongContext'; describe('SessionReportForm', () => { const sessionsUrl = join('/', 'api', 'session-reports'); const history = createMemoryHistory(); - const renderSessionForm = (trainingReportId, currentPage, sessionId) => render( + const renderSessionForm = ( + trainingReportId, + currentPage, + sessionId, + setErrorResponseCode = jest.fn, + ) => render( - - - + + + + + , ); @@ -96,21 +104,18 @@ describe('SessionReportForm', () => { expect(screen.getByText(/Training report - Session/i)).toBeInTheDocument(); }); - it('handles an error fetching a session', async () => { + it('sets response error', async () => { const url = join(sessionsUrl, 'id', '1'); fetchMock.get( url, 500, ); - + const setErrorResponseCode = jest.fn(); act(() => { - renderSessionForm('1', 'session-summary', '1'); + renderSessionForm('1', 'session-summary', '1', setErrorResponseCode); }); - await waitFor(() => expect(fetchMock.called(url)).toBe(true)); - - expect(screen.getByText(/Training report - Session/i)).toBeInTheDocument(); - expect(screen.getByText(/Error fetching session/i)).toBeInTheDocument(); + expect(setErrorResponseCode).toHaveBeenCalledWith(500); }); it('saves draft', async () => { diff --git a/frontend/src/pages/SessionForm/index.js b/frontend/src/pages/SessionForm/index.js index 52e322f395..ed0853765d 100644 --- a/frontend/src/pages/SessionForm/index.js +++ b/frontend/src/pages/SessionForm/index.js @@ -21,6 +21,7 @@ import Navigator from '../../components/Navigator'; import BackLink from '../../components/BackLink'; import pages from './pages'; import AppLoadingContext from '../../AppLoadingContext'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; // websocket publish location interval const INTERVAL_DELAY = 10000; // TEN SECONDS @@ -96,6 +97,7 @@ export default function SessionForm({ match }) { const { user } = useContext(UserContext); const { setIsAppLoading } = useContext(AppLoadingContext); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const { socket, @@ -158,14 +160,14 @@ export default function SessionForm({ match }) { resetFormData(hookForm.reset, session); reportId.current = session.id; } catch (e) { - setError('Error fetching session'); + setErrorResponseCode(e.status); } finally { setReportFetched(true); setDatePickerKey(`f${Date.now().toString()}`); } } fetchSession(); - }, [currentPage, hookForm.reset, reportFetched, sessionId]); + }, [currentPage, hookForm.reset, reportFetched, sessionId, setErrorResponseCode]); // hook to update the page state in the sidebar useHookFormPageState(hookForm, pages, currentPage); diff --git a/frontend/src/pages/TrainingReportForm/__tests__/index.js b/frontend/src/pages/TrainingReportForm/__tests__/index.js index 6e21bc51bd..b5638f4d53 100644 --- a/frontend/src/pages/TrainingReportForm/__tests__/index.js +++ b/frontend/src/pages/TrainingReportForm/__tests__/index.js @@ -11,21 +11,27 @@ import TrainingReportForm from '../index'; import UserContext from '../../../UserContext'; import AppLoadingContext from '../../../AppLoadingContext'; import { COMPLETE } from '../../../components/Navigator/constants'; +import SomethingWentWrong from '../../../SomethingWentWrongContext'; describe('TrainingReportForm', () => { const history = createMemoryHistory(); const sessionsUrl = '/api/session-reports/eventId/1234'; - const renderTrainingReportForm = (trainingReportId, currentPage) => render( + const renderTrainingReportForm = ( + trainingReportId, currentPage, + setErrorResponseCode = jest.fn, + ) => render( - + + + , @@ -65,6 +71,15 @@ describe('TrainingReportForm', () => { expect(screen.getByText(/Training report - Event/i)).toBeInTheDocument(); }); + it('calls setErrorResponseCode when an error occurs', async () => { + fetchMock.get('/api/events/id/1', 500); + const setErrorResponseCode = jest.fn(); + act(() => { + renderTrainingReportForm('1', 'event-summary', setErrorResponseCode); + }); + await waitFor(() => expect(setErrorResponseCode).toHaveBeenCalledWith(500)); + }); + it('redirects to event summary', async () => { fetchMock.get('/api/events/id/1', { id: 1, @@ -145,16 +160,6 @@ describe('TrainingReportForm', () => { expect(fetchMock.called('/api/events/id/123')).toBe(true); }); - it('displays error when event report fails to load', async () => { - fetchMock.get('/api/events/id/123', 500); - act(() => { - renderTrainingReportForm('123', 'event-summary'); - }); - - expect(fetchMock.called('/api/events/id/123')).toBe(true); - expect(await screen.findByText(/error fetching training report/i)).toBeInTheDocument(); - }); - it('displays "no training report id provided" error', async () => { fetchMock.get('/api/events/id/123', { regionId: '1', diff --git a/frontend/src/pages/TrainingReportForm/index.js b/frontend/src/pages/TrainingReportForm/index.js index 763415daa2..9299bdf2df 100644 --- a/frontend/src/pages/TrainingReportForm/index.js +++ b/frontend/src/pages/TrainingReportForm/index.js @@ -25,6 +25,7 @@ import BackLink from '../../components/BackLink'; import pages from './pages'; import AppLoadingContext from '../../AppLoadingContext'; import useHookFormPageState from '../../hooks/useHookFormPageState'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; // websocket publish location interval const INTERVAL_DELAY = 10000; // TEN SECONDS @@ -125,6 +126,7 @@ export default function TrainingReportForm({ match }) { const { user } = useContext(UserContext); const { setIsAppLoading, isAppLoading } = useContext(AppLoadingContext); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const { socket, @@ -179,14 +181,19 @@ export default function TrainingReportForm({ match }) { resetFormData(hookForm.reset, event); reportId.current = trainingReportId; } catch (e) { - setError('Error fetching training report'); + setErrorResponseCode(e.status); } finally { setReportFetched(true); setDatePickerKey(Date.now().toString()); } } fetchReport(); - }, [currentPage, hookForm.reset, isAppLoading, reportFetched, trainingReportId]); + }, [currentPage, + hookForm.reset, + isAppLoading, + reportFetched, + trainingReportId, + setErrorResponseCode]); useEffect(() => { // set error if no training report id diff --git a/src/services/currentUser.js b/src/services/currentUser.js index 5a2d0177ab..dfbd1cc0f9 100644 --- a/src/services/currentUser.js +++ b/src/services/currentUser.js @@ -26,6 +26,7 @@ import { validateUserAuthForAdmin } from './accessValidation'; */ export async function currentUserId(req, res) { function idFromSessionOrLocals() { + /* if (req.session && req.session.userId) { httpContext.set('impersonationUserId', Number(req.session.userId)); return Number(req.session.userId); @@ -34,6 +35,7 @@ export async function currentUserId(req, res) { httpContext.set('impersonationUserId', Number(res.locals.userId)); return Number(res.locals.userId); } + */ // bypass authorization, used for cucumber UAT and axe accessibility testing if (process.env.NODE_ENV !== 'production' && process.env.BYPASS_AUTH === 'true') { From be1b0ef058799c3d78f6e81a7f728ce680b30dff Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Wed, 3 Jul 2024 12:10:54 -0400 Subject: [PATCH 14/26] add to goals with test --- .../components/GoalForm/__tests__/index.js | 46 ++++++++++++------- frontend/src/components/GoalForm/index.js | 14 ++++-- .../ApprovedActivityReport/__tests__/index.js | 16 +++---- .../pages/RecipientRecord/__tests__/index.js | 21 +++++---- src/services/currentUser.js | 2 - 5 files changed, 61 insertions(+), 38 deletions(-) diff --git a/frontend/src/components/GoalForm/__tests__/index.js b/frontend/src/components/GoalForm/__tests__/index.js index 00e7d7cd55..06808c15c7 100644 --- a/frontend/src/components/GoalForm/__tests__/index.js +++ b/frontend/src/components/GoalForm/__tests__/index.js @@ -6,6 +6,7 @@ import { screen, within, waitFor, + act, } from '@testing-library/react'; import { SCOPE_IDS } from '@ttahub/common'; import selectEvent from 'react-select-event'; @@ -18,6 +19,7 @@ import UserContext from '../../../UserContext'; import { OBJECTIVE_ERROR_MESSAGES } from '../constants'; import { BEFORE_OBJECTIVES_CREATE_GOAL, BEFORE_OBJECTIVES_SELECT_RECIPIENTS } from '../Form'; import AppLoadingContext from '../../../AppLoadingContext'; +import SomethingWentWrongContext from '../../../SomethingWentWrongContext'; const [objectiveTitleError] = OBJECTIVE_ERROR_MESSAGES; @@ -101,31 +103,33 @@ describe('create goal', () => { }], }]; - function renderForm(recipient = defaultRecipient, goalId = 'new') { + function renderForm(recipient = defaultRecipient, goalId = 'new', setErrorResponseCode = jest.fn()) { const history = createMemoryHistory(); render(( - - + + - - - + > + + + + )); } @@ -368,6 +372,16 @@ describe('create goal', () => { expect(alert.textContent).toBe('There was an error saving your goal'); }); + it('correctly calls the setErrorResponseCode function when there is an error', async () => { + const setErrorResponseCode = jest.fn(); + fetchMock.restore(); + fetchMock.get('/api/recipient/1/goals?goalIds=', 500); + await act(async () => { + renderForm(defaultRecipient, '48743', setErrorResponseCode); + await waitFor(() => expect(setErrorResponseCode).toHaveBeenCalledWith(500)); + }); + }); + it('removes goals', async () => { fetchMock.post('/api/goals', postResponse); diff --git a/frontend/src/components/GoalForm/index.js b/frontend/src/components/GoalForm/index.js index 5c5d010496..3f89df0267 100644 --- a/frontend/src/components/GoalForm/index.js +++ b/frontend/src/components/GoalForm/index.js @@ -36,6 +36,7 @@ import AppLoadingContext from '../../AppLoadingContext'; import useUrlParamState from '../../hooks/useUrlParamState'; import UserContext from '../../UserContext'; import VanillaModal from '../VanillaModal'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; const [objectiveTextError] = OBJECTIVE_ERROR_MESSAGES; @@ -112,6 +113,7 @@ export default function GoalForm({ const { isAppLoading, setIsAppLoading, setAppLoadingText } = useContext(AppLoadingContext); const { user } = useContext(UserContext); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const canView = useMemo(() => user.permissions.filter( (permission) => permission.regionId === parseInt(regionId, DECIMAL_BASE), @@ -134,9 +136,14 @@ export default function GoalForm({ async function fetchGoal() { setFetchAttempted(true); // as to only fetch once try { - const [goal] = await goalsByIdAndRecipient( - ids, recipient.id.toString(), - ); + let goal = null; + try { + [goal] = await goalsByIdAndRecipient( + ids, recipient.id.toString(), + ); + } catch (err) { + setErrorResponseCode(err.status); + } const selectedGoalGrants = goal.grants ? goal.grants : [goal.grant]; @@ -200,6 +207,7 @@ export default function GoalForm({ ids, setAppLoadingText, setIsAppLoading, + setErrorResponseCode, ]); const setObjectiveError = (objectiveIndex, errorText) => { diff --git a/frontend/src/pages/ApprovedActivityReport/__tests__/index.js b/frontend/src/pages/ApprovedActivityReport/__tests__/index.js index 1150f212c8..387db41d50 100644 --- a/frontend/src/pages/ApprovedActivityReport/__tests__/index.js +++ b/frontend/src/pages/ApprovedActivityReport/__tests__/index.js @@ -209,7 +209,7 @@ describe('Activity report print and share view', () => { version: null, }); - fetchMock.get('/api/activity-reports/5007', 401); + fetchMock.get('/api/activity-reports/5007', { status: 401 }); }); it('renders an activity report in clean view', async () => { @@ -315,13 +315,6 @@ describe('Activity report print and share view', () => { }); }); - it('renders a version 2 report with goals', async () => { - act(() => renderApprovedActivityReport(5005)); - await waitFor(() => { - expect(screen.getByText(report.author.fullName)).toBeInTheDocument(); - }); - }); - it('handles a malformed url', async () => { const setErrorResponseCode = jest.fn(); act(async () => { @@ -350,4 +343,11 @@ describe('Activity report print and share view', () => { global.localStorage = oldLocalStorage; }); + + it('renders a version 2 report with goals', async () => { + act(() => renderApprovedActivityReport(5005)); + await waitFor(() => { + expect(screen.getByText(report.author.fullName)).toBeInTheDocument(); + }); + }); }); diff --git a/frontend/src/pages/RecipientRecord/__tests__/index.js b/frontend/src/pages/RecipientRecord/__tests__/index.js index 46fe31fe71..b27808c553 100644 --- a/frontend/src/pages/RecipientRecord/__tests__/index.js +++ b/frontend/src/pages/RecipientRecord/__tests__/index.js @@ -10,6 +10,7 @@ import { createMemoryHistory } from 'history'; import RecipientRecord, { PageWithHeading } from '../index'; import { formatDateRange } from '../../../utils'; import UserContext from '../../../UserContext'; +import SomethingWentWrongContext from '../../../SomethingWentWrongContext'; import AppLoadingContext from '../../../AppLoadingContext'; import { GrantDataProvider } from '../pages/GrantDataContext'; @@ -101,20 +102,22 @@ describe('recipient record page', () => { render( - - - + + + - - - - + > + + + + + , ); } @@ -248,7 +251,7 @@ describe('recipient record page', () => { fetchMock.get('/api/goals/12389/recipient/45', mockGoal); fetchMock.get('/api/topic', []); memoryHistory.push('/recipient-tta-records/45/region/1/goals/12389'); - act(() => renderRecipientRecord()); + await act(() => renderRecipientRecord()); await waitFor(() => expect(screen.queryByText(/loading.../)).toBeNull()); await screen.findByText(/TTA Goals for the Mighty Recipient/i); }); diff --git a/src/services/currentUser.js b/src/services/currentUser.js index dfbd1cc0f9..5a2d0177ab 100644 --- a/src/services/currentUser.js +++ b/src/services/currentUser.js @@ -26,7 +26,6 @@ import { validateUserAuthForAdmin } from './accessValidation'; */ export async function currentUserId(req, res) { function idFromSessionOrLocals() { - /* if (req.session && req.session.userId) { httpContext.set('impersonationUserId', Number(req.session.userId)); return Number(req.session.userId); @@ -35,7 +34,6 @@ export async function currentUserId(req, res) { httpContext.set('impersonationUserId', Number(res.locals.userId)); return Number(res.locals.userId); } - */ // bypass authorization, used for cucumber UAT and axe accessibility testing if (process.env.NODE_ENV !== 'production' && process.env.BYPASS_AUTH === 'true') { From 12315848fe34da2a1806e1b0ee0f2e1bcad352ab Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Wed, 3 Jul 2024 13:08:13 -0400 Subject: [PATCH 15/26] cleanup fe tests --- .../ApprovedActivityReport/__tests__/index.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/ApprovedActivityReport/__tests__/index.js b/frontend/src/pages/ApprovedActivityReport/__tests__/index.js index 387db41d50..4f3cadcd63 100644 --- a/frontend/src/pages/ApprovedActivityReport/__tests__/index.js +++ b/frontend/src/pages/ApprovedActivityReport/__tests__/index.js @@ -152,7 +152,7 @@ describe('Activity report print and share view', () => { }], requester: 'chud', }); - fetchMock.get('/api/activity-reports/5002', 500); + fetchMock.get('/api/activity-reports/5002', { status: 500 }); fetchMock.get('/api/activity-reports/5003', { ...report, @@ -230,21 +230,21 @@ describe('Activity report print and share view', () => { it('handles authorization errors', async () => { const setErrorResponseCode = jest.fn(); - act(async () => { - renderApprovedActivityReport(5007, user, setErrorResponseCode); - await waitFor(() => { - expect(setErrorResponseCode).toHaveBeenCalledWith(401); - }); + act(() => renderApprovedActivityReport(5007, user, setErrorResponseCode)); + + await waitFor(() => { + expect(fetchMock.called('/api/activity-reports/5007')).toBeTruthy(); + expect(setErrorResponseCode).toHaveBeenCalledWith(401); }); }); it('handles data errors', async () => { const setErrorResponseCode = jest.fn(); - act(async () => { - renderApprovedActivityReport(5002, user, setErrorResponseCode); - await waitFor(() => { - expect(setErrorResponseCode).toHaveBeenCalled(); - }); + act(() => renderApprovedActivityReport(5002, user, setErrorResponseCode)); + + await waitFor(() => { + expect(fetchMock.called('/api/activity-reports/5002')).toBeTruthy(); + expect(setErrorResponseCode).toHaveBeenCalledWith(500); }); }); From 2bad8354618764b1c8501eeb7c8f93b09cbe410a Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 3 Jul 2024 13:32:23 -0400 Subject: [PATCH 16/26] Update hash --- colorsjschecksum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorsjschecksum b/colorsjschecksum index ff45aef792..15e52b0835 100644 --- a/colorsjschecksum +++ b/colorsjschecksum @@ -1 +1 @@ -ebc7ad94231a4b6f7263c55eb114b48c1a9bcd927c6e829e71d24cf4184efc19 \ No newline at end of file +5dfc9c0a9f719af9ac72655dee86b64b9122ee7cda857e6ac68ae4a3dc76ade6 \ No newline at end of file From 1e67b3e44a6e73807c6455b6afead10be17ab968 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Jul 2024 00:57:55 +0000 Subject: [PATCH 17/26] Bump certifi from 2023.7.22 to 2024.7.4 in /similarity_api/src Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.7.22 to 2024.7.4. - [Commits](https://github.com/certifi/python-certifi/compare/2023.07.22...2024.07.04) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- similarity_api/src/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/similarity_api/src/requirements.txt b/similarity_api/src/requirements.txt index ec0b0315f2..df4b224127 100644 --- a/similarity_api/src/requirements.txt +++ b/similarity_api/src/requirements.txt @@ -2,7 +2,7 @@ annotated-types==0.5.0 blinker==1.6.2 blis==0.7.10 catalogue==2.0.9 -certifi==2023.7.22 +certifi==2024.7.4 charset-normalizer==3.2.0 click==8.1.6 confection==0.1.1 From 16f28b1fd02b646b4e65dd155c78b2118f99c7f8 Mon Sep 17 00:00:00 2001 From: Nathan Powell Date: Mon, 8 Jul 2024 08:15:30 -0700 Subject: [PATCH 18/26] correct onAR type values and add missing link table handling --- ...240702000000-remove_national_center_ars.js | 364 ++++++++++++++++++ 1 file changed, 364 insertions(+) diff --git a/src/migrations/20240702000000-remove_national_center_ars.js b/src/migrations/20240702000000-remove_national_center_ars.js index dae0f8f6e2..c4dbc9863e 100644 --- a/src/migrations/20240702000000-remove_national_center_ars.js +++ b/src/migrations/20240702000000-remove_national_center_ars.js @@ -37,9 +37,12 @@ module.exports = { -- ActivityReportObjectiveFiles -- ActivityReportObjectiveResources -- ActivityReportObjectiveTopics + -- ActivityReportObjectiveCourses -- remove AROs ------------------- -- Create the orphaned Objective deletion list + -- Remove Objective link records: ------------- + -- Delete ObjectiveCollaborators -- remove Objectives ------------- -- Create the ARG deletion list @@ -65,6 +68,11 @@ module.exports = { -- Remove ARs ----------------------- + -- Test query + + -- Correct the onApprovedAR and onAR values for the goals + -- and objectives that were not deleted + ------------------------------------------------------------------------------------------------------------------- -------- Deleting unwanted ARs -------- -- Create the AR deletion list @@ -222,6 +230,17 @@ module.exports = { ) SELECT id FROM deletes ; + DROP TABLE IF EXISTS deleted_activityreportobjectivecourses; + CREATE TEMP TABLE deleted_activityreportobjectivecourses AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectiveCourses" + USING aros_to_delete + WHERE "activityReportObjectiveId" = aroid + RETURNING + id + ) + SELECT id FROM deletes + ; -- remove AROs ------------------- DROP TABLE IF EXISTS deleted_aros; CREATE TEMP TABLE deleted_aros AS @@ -246,6 +265,19 @@ module.exports = { SELECT DISTINCT "objectiveId" FROM "ActivityReportObjectives" ; + -- Remove Objective link records: ------------- + -- Delete ObjectiveCollaborators + DROP TABLE IF EXISTS deleted_objectivecollaborators; + CREATE TEMP TABLE deleted_objectivecollaborators AS + WITH deletes AS ( + DELETE FROM "ObjectiveCollaborators" + USING objectives_to_delete + WHERE "objectiveId" = oid + RETURNING + id + ) + SELECT id FROM deletes + ; -- remove Objectives ------------------- DROP TABLE IF EXISTS deleted_objectives; @@ -504,10 +536,14 @@ module.exports = { UNION SELECT 12,'deleted_activityreportobjectivetopics', count(*) FROM deleted_activityreportobjectivetopics UNION + SELECT 12,'deleted_activityreportobjectivecourses', count(*) FROM deleted_activityreportobjectivetopics + UNION SELECT 13,'deleted_aros', count(*) FROM deleted_aros UNION SELECT 14,'objectives_to_delete', count(*) FROM objectives_to_delete UNION + SELECT 14,'deleted_objectivecollaborators', count(*) FROM objectives_to_delete + UNION SELECT 15,'deleted_objectives', count(*) FROM deleted_objectives UNION SELECT 16,'args_to_delete', count(*) FROM args_to_delete @@ -544,6 +580,334 @@ module.exports = { ORDER BY 1 ; + -- Reset the onApprovedAR and onAR values for the goals and objectives that + -- were not deleted + -- 1. Calculate correct onApprovedAR values for objectives + DROP TABLE IF EXISTS objectives_on_ars; + CREATE TEMP TABLE objectives_on_ars + AS + WITH objectivelist AS ( + SELECT DISTINCT oid FROM aros_to_delete + EXCEPT + SELECT id FROM deleted_objectives + ) + SELECT + o.id oid, + BOOL_OR(ar.id IS NOT NULL AND ar."calculatedStatus" = 'approved') on_approved_ar, + BOOL_OR(ar.id IS NOT NULL) on_ar + FROM objectivelist ol + JOIN "Objectives" o + ON ol.oid = o.id + LEFT JOIN "ActivityReportObjectives" aro + ON o.id = aro."objectiveId" + LEFT JOIN "ActivityReports" ar + ON aro."activityReportId" = ar.id + AND ar."calculatedStatus" != 'deleted' + GROUP BY 1 + ; + -- 2. Calculate correct onApprovedAR values for goals + DROP TABLE IF EXISTS goals_on_ars; + CREATE TEMP TABLE goals_on_ars + AS + WITH goallist AS ( + SELECT DISTINCT gid FROM args_to_delete + EXCEPT + SELECT id FROM deleted_goals + ) + SELECT + g.id gid, + BOOL_OR( + (ar.id IS NOT NULL AND ar."calculatedStatus" = 'approved') + OR + COALESCE(ooaa.on_approved_ar,FALSE) + ) on_approved_ar, + BOOL_OR(ar.id IS NOT NULL OR COALESCE(ooaa.on_ar,FALSE)) on_ar + FROM goallist gl + JOIN "Goals" g + ON g.id = gl.gid + LEFT JOIN "ActivityReportGoals" arg + ON g.id = arg."goalId" + LEFT JOIN "ActivityReports" ar + ON arg."activityReportId" = ar.id + AND ar."calculatedStatus" != 'deleted' + LEFT JOIN "Objectives" o + ON o."goalId" = g.id + LEFT JOIN objectives_on_ars ooaa + ON ooaa.oid = o.id + GROUP BY 1 + ; + -- 3. Calculate onApprovedAR stats for objectives + DROP TABLE IF EXISTS initial_obj_approved_ar_stats; + CREATE TEMP TABLE initial_obj_approved_ar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_objectives + FROM "Objectives" o + JOIN objectives_on_ars + ON o.id = oid + ; + -- 4. Calculate onAR stats for objectives + DROP TABLE IF EXISTS initial_obj_onar_stats; + CREATE TEMP TABLE initial_obj_onar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_ar = "onAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_objectives + FROM "Objectives" o + JOIN objectives_on_ars + ON o.id = oid + ; + -- 5. Calculate onApprovedAR stats for goals + DROP TABLE IF EXISTS initial_goal_approved_ar_stats; + CREATE TEMP TABLE initial_goal_approved_ar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_goals + FROM "Goals" g + JOIN goals_on_ars + ON g.id = gid + ; + -- 6. Calculate onAR stats for goals + DROP TABLE IF EXISTS initial_goal_onar_stats; + CREATE TEMP TABLE initial_goal_onar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_ar = "onAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_goals + FROM "Goals" g + JOIN goals_on_ars + ON g.id = gid + ; + -- 7. Update onApprovedAR values for objectives and save the results + DROP TABLE IF EXISTS corrected_approved_objectives; + CREATE TEMP TABLE corrected_approved_objectives + AS + WITH updater AS ( + UPDATE "Objectives" o + SET "onApprovedAR" = on_approved_ar + FROM objectives_on_ars + WHERE o.id = oid + AND ("onApprovedAR" != on_approved_ar OR "onApprovedAR" IS NULL) + RETURNING + oid, + on_approved_ar + ) SELECT * FROM updater + ; + -- 8. Update onAR values for objectives and save the results + DROP TABLE IF EXISTS corrected_onar_objectives; + CREATE TEMP TABLE corrected_onar_objectives + AS + WITH updater AS ( + UPDATE "Objectives" o + SET "onAR" = on_ar + FROM objectives_on_ars + WHERE o.id = oid + AND ("onAR" != on_ar OR "onAR" IS NULL) + RETURNING + oid, + on_ar + ) SELECT * FROM updater + ; + -- 9. Update onApprovedAR values for goals and save the results + DROP TABLE IF EXISTS corrected_approved_goals; + CREATE TEMP TABLE corrected_approved_goals + AS + WITH updater AS ( + UPDATE "Goals" g + SET "onApprovedAR" = on_approved_ar + FROM goals_on_ars + WHERE g.id = gid + AND ("onApprovedAR" != on_approved_ar OR "onApprovedAR" IS NULL) + RETURNING + gid, + on_approved_ar + ) SELECT * FROM updater + ; + -- 10. Update onAR values for goals and save the results + DROP TABLE IF EXISTS corrected_onar_goals; + CREATE TEMP TABLE corrected_onar_goals + AS + WITH updater AS ( + UPDATE "Goals" g + SET "onAR" = on_ar + FROM goals_on_ars + WHERE g.id = gid + AND ("onAR" != on_ar OR "onAR" IS NULL) + RETURNING + gid, + on_ar + ) SELECT * FROM updater + ; + -- produce stats on what happened + -- 11. Final onApprovedAR stats for objectives + DROP TABLE IF EXISTS final_obj_approved_ar_stats; + CREATE TEMP TABLE final_obj_approved_ar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_objectives + FROM "Objectives" o + JOIN objectives_on_ars + ON o.id = oid + ; + -- 12. Final onAR stats for objectives + DROP TABLE IF EXISTS final_obj_onar_stats; + CREATE TEMP TABLE final_obj_onar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_ar = "onAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_objectives + FROM "Objectives" o + JOIN objectives_on_ars + ON o.id = oid + ; + -- 13. Final onApprovedAR stats for goals + DROP TABLE IF EXISTS final_goal_approved_ar_stats; + CREATE TEMP TABLE final_goal_approved_ar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_goals + FROM "Goals" g + JOIN goals_on_ars + ON g.id = gid + ; + -- 14. Final onAR stats for goals + DROP TABLE IF EXISTS final_goal_onar_stats; + CREATE TEMP TABLE final_goal_onar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_ar = "onAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_goals + FROM "Goals" g + JOIN goals_on_ars + ON g.id = gid + ; + -- make a nice little table to see the math + SELECT + 1 AS order, + 'objective onApprovedAR starting stats' description, + matching_values, + incorrect_values, + should_be_marked_true_but_isnt, + marked_true_but_shouldnt_be, + total_objectives total + FROM initial_obj_approved_ar_stats + UNION + SELECT + 2, + 'objective onApprovedAR values changed', + NULL, + NULL, + SUM(CASE WHEN on_approved_ar THEN 1 ELSE 0 END), + SUM(CASE WHEN NOT on_approved_ar THEN 1 ELSE 0 END), + COUNT(*) + FROM corrected_approved_objectives + UNION + SELECT 3,'objective onApprovedAR ending stats', * FROM final_obj_approved_ar_stats + UNION + SELECT 4,'objective onAR starting stats', * FROM initial_obj_onar_stats + UNION + SELECT + 5, + 'objective onAR values changed', + NULL, + NULL, + SUM(CASE WHEN on_ar THEN 1 ELSE 0 END), + SUM(CASE WHEN NOT on_ar THEN 1 ELSE 0 END), + COUNT(*) + FROM corrected_onar_objectives + UNION + SELECT 6,'objective onAR ending stats', * FROM final_obj_onar_stats + UNION + SELECT 7,'goal onApprovedAR starting stats', * FROM initial_goal_approved_ar_stats + UNION + SELECT + 8, + 'goal onApprovedAR values changed', + NULL, + NULL, + SUM(CASE WHEN on_approved_ar THEN 1 ELSE 0 END), + SUM(CASE WHEN NOT on_approved_ar THEN 1 ELSE 0 END), + COUNT(*) + FROM corrected_approved_goals + UNION + SELECT 9,'goal onApprovedAR ending stats', * FROM final_goal_approved_ar_stats + UNION + SELECT 10,'goal onAR starting stats', * FROM initial_goal_onar_stats + UNION + SELECT + 11, + 'goal onAR values changed', + NULL, + NULL, + SUM(CASE WHEN on_ar THEN 1 ELSE 0 END), + SUM(CASE WHEN NOT on_ar THEN 1 ELSE 0 END), + COUNT(*) + FROM corrected_onar_goals + UNION + SELECT 12,'goal onAR ending stats', * FROM final_goal_onar_stats + ORDER BY 1 + ; + `, { transaction }); }); }, From c9fa7c5773c87194e79b9680a81ee4a8202bd58c Mon Sep 17 00:00:00 2001 From: Nathan Powell Date: Mon, 8 Jul 2024 09:12:12 -0700 Subject: [PATCH 19/26] push migration date later --- ...240708000000-remove_national_center_ars.js | 917 ++++++++++++++++++ 1 file changed, 917 insertions(+) create mode 100644 src/migrations/20240708000000-remove_national_center_ars.js diff --git a/src/migrations/20240708000000-remove_national_center_ars.js b/src/migrations/20240708000000-remove_national_center_ars.js new file mode 100644 index 0000000000..c4dbc9863e --- /dev/null +++ b/src/migrations/20240708000000-remove_national_center_ars.js @@ -0,0 +1,917 @@ +const { + prepMigration, +} = require('../lib/migration'); + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface) { + await queryInterface.sequelize.transaction(async (transaction) => { + const sessionSig = __filename; + await prepMigration(queryInterface, transaction, sessionSig); + + await queryInterface.sequelize.query(` + --------------------------------------------------- + -- NOTE: + -- Files and Resources are most properly managed by + -- maintenance jobs, so this and similar migrations + -- won't delete them directly. Deleting the link + -- records will give the maintenance job the info + -- it needs to perform its housekeeping. + --------------------------------------------------- + -------- Deleting unwanted ARs -------- + -- Create the AR deletion list + -- Remove AR link records: ------------- + -- ActivityRecipients + -- ActivityReportApprovers + -- ActivityReportCollaborators + -- ActivityReportFiles (no need to remove Files) + -- ActivityReportResources (no need to remove Resources) + + -- Create the NextSteps deletion list + -- Remove NextSteps link records: ------------- + -- NextStepResources + -- remove NextSteps ------------- + + -- Create the ARO deletion list + -- Remove ARO link records: ------------- + -- ActivityReportObjectiveFiles + -- ActivityReportObjectiveResources + -- ActivityReportObjectiveTopics + -- ActivityReportObjectiveCourses + -- remove AROs ------------------- + + -- Create the orphaned Objective deletion list + -- Remove Objective link records: ------------- + -- Delete ObjectiveCollaborators + -- remove Objectives ------------- + + -- Create the ARG deletion list + -- Remove ARG link records: ------------- + -- ActivityReportGoalFieldResponses + -- ActivityReportGoalResources + -- remove ARGs ------------------- + + -- Create the orphaned Goal deletions list + -- ( check if isFromSmartsheetTtaPlan, isRttapa) + -- Remove Goal link records: ------------- + -- EventReportPilotGoals + -- GoalFieldResponses + -- GoalResources + -- remove Goals ------------------ + + -- Create the orphaned ObjectiveTemplate deletion list + -- Create the orphaned GoalTemplate deletion list + -- Remove GoalTemplate link records: ------------- + -- GoalTemplateObjectiveTemplates + -- Remove ObjectiveTemplates -------- + -- Remove GoalTemplates ------------- + + -- Remove ARs ----------------------- + + -- Test query + + -- Correct the onApprovedAR and onAR values for the goals + -- and objectives that were not deleted + + ------------------------------------------------------------------------------------------------------------------- + -------- Deleting unwanted ARs -------- + -- Create the AR deletion list + DROP TABLE IF EXISTS ars_to_delete; + CREATE TEMP TABLE ars_to_delete + AS + SELECT id arid + FROM "ActivityReports" + WHERE id IN (24998, 24645, 24297, 24122, 27517, 30829, 29864, 6442, 23057, 23718, 25205, 25792, 25577, 25573, 26478, 26210, 27117, 26918, 28451, 28117, 27669, 29542, 29101, 29024, 30137, 29762, 31201) + AND "regionId" = 10 + ; + + -- Remove AR link records: ------------- + DROP TABLE IF EXISTS deleted_activityrecipients; + CREATE TEMP TABLE deleted_activityrecipients AS + WITH deletes AS ( + DELETE FROM "ActivityRecipients" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id + ) + SELECT id FROM deletes + ; + DROP TABLE IF EXISTS deleted_activityreportapprovers; + CREATE TEMP TABLE deleted_activityreportapprovers AS + WITH deletes AS ( + DELETE FROM "ActivityReportApprovers" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id + ) + SELECT id FROM deletes + ; + DROP TABLE IF EXISTS deleted_activityreportcollaborators; + CREATE TEMP TABLE deleted_activityreportcollaborators AS + WITH deletes AS ( + DELETE FROM "ActivityReportCollaborators" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id + ) + SELECT id FROM deletes + ; + DROP TABLE IF EXISTS deleted_activityreportfiles; + CREATE TEMP TABLE deleted_activityreportfiles AS + WITH deletes AS ( + DELETE FROM "ActivityReportFiles" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id, + "fileId" fid + ) + SELECT id, fid FROM deletes + ; + DROP TABLE IF EXISTS deleted_activityreportresources; + CREATE TEMP TABLE deleted_activityreportresources AS + WITH deletes AS ( + DELETE FROM "ActivityReportResources" + USING ars_to_delete + WHERE "activityReportId" = arid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + + + + -- Create the NextSteps deletion list + DROP TABLE IF EXISTS nextsteps_to_delete; + CREATE TEMP TABLE nextsteps_to_delete + AS + SELECT + id nsid + FROM "NextSteps" + JOIN ars_to_delete + ON "activityReportId" = arid + ; + -- Remove NextSteps link records: ------------- + DROP TABLE IF EXISTS deleted_nextstepresources; + CREATE TEMP TABLE deleted_nextstepresources AS + WITH deletes AS ( + DELETE FROM "NextStepResources" + USING nextsteps_to_delete + WHERE "nextStepId" = nsid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + -- remove NextSteps ------------- + DROP TABLE IF EXISTS deleted_nextsteps; + CREATE TEMP TABLE deleted_nextsteps AS + WITH deletes AS ( + DELETE FROM "NextSteps" + USING nextsteps_to_delete + WHERE id = nsid + RETURNING + id + ) + SELECT id FROM deletes + ; + + + -- Create the ARO deletion list + DROP TABLE IF EXISTS aros_to_delete; + CREATE TEMP TABLE aros_to_delete + AS + SELECT + id aroid, + "objectiveId" oid + FROM "ActivityReportObjectives" + JOIN ars_to_delete + ON "activityReportId" = arid + ; + -- Remove ARO link records: ------------- + DROP TABLE IF EXISTS deleted_activityreportobjectivefiles; + CREATE TEMP TABLE deleted_activityreportobjectivefiles AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectiveFiles" + USING aros_to_delete + WHERE "activityReportObjectiveId" = aroid + RETURNING + id, + "fileId" fid + ) + SELECT id, fid FROM deletes + ; + DROP TABLE IF EXISTS deleted_activityreportobjectiveresources; + CREATE TEMP TABLE deleted_activityreportobjectiveresources AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectiveResources" + USING aros_to_delete + WHERE "activityReportObjectiveId" = aroid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + DROP TABLE IF EXISTS deleted_activityreportobjectivetopics; + CREATE TEMP TABLE deleted_activityreportobjectivetopics AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectiveTopics" + USING aros_to_delete + WHERE "activityReportObjectiveId" = aroid + RETURNING + id + ) + SELECT id FROM deletes + ; + DROP TABLE IF EXISTS deleted_activityreportobjectivecourses; + CREATE TEMP TABLE deleted_activityreportobjectivecourses AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectiveCourses" + USING aros_to_delete + WHERE "activityReportObjectiveId" = aroid + RETURNING + id + ) + SELECT id FROM deletes + ; + -- remove AROs ------------------- + DROP TABLE IF EXISTS deleted_aros; + CREATE TEMP TABLE deleted_aros AS + WITH deletes AS ( + DELETE FROM "ActivityReportObjectives" + USING aros_to_delete + WHERE id = aroid + RETURNING + id, + "objectiveId" oid + ) + SELECT id, oid FROM deletes + ; + + -- Create the orphaned Objective deletion list + DROP TABLE IF EXISTS objectives_to_delete; + CREATE TEMP TABLE objectives_to_delete + AS + SELECT DISTINCT oid + FROM deleted_aros + EXCEPT + SELECT DISTINCT "objectiveId" + FROM "ActivityReportObjectives" + ; + -- Remove Objective link records: ------------- + -- Delete ObjectiveCollaborators + DROP TABLE IF EXISTS deleted_objectivecollaborators; + CREATE TEMP TABLE deleted_objectivecollaborators AS + WITH deletes AS ( + DELETE FROM "ObjectiveCollaborators" + USING objectives_to_delete + WHERE "objectiveId" = oid + RETURNING + id + ) + SELECT id FROM deletes + ; + + -- remove Objectives ------------------- + DROP TABLE IF EXISTS deleted_objectives; + CREATE TEMP TABLE deleted_objectives AS + WITH deletes AS ( + DELETE FROM "Objectives" + USING objectives_to_delete + WHERE id = oid + RETURNING + id, + "goalId" gid, + "objectiveTemplateId" otid + ) + SELECT id, gid, otid FROM deletes + ; + + -- Create the ARG deletion list + DROP TABLE IF EXISTS args_to_delete; + CREATE TEMP TABLE args_to_delete + AS + SELECT DISTINCT + id argid, + "goalId" gid + FROM "ActivityReportGoals" + JOIN ars_to_delete + ON "activityReportId" = arid + ; + -- Remove ARG link records: ------------- + DROP TABLE IF EXISTS deleted_activityreportgoalfieldresponses; + CREATE TEMP TABLE deleted_activityreportgoalfieldresponses AS + WITH deletes AS ( + DELETE FROM "ActivityReportGoalFieldResponses" + USING args_to_delete + WHERE "activityReportGoalId" = argid + RETURNING + id + ) + SELECT id FROM deletes + ; + DROP TABLE IF EXISTS deleted_activityreportgoalresources; + CREATE TEMP TABLE deleted_activityreportgoalresources AS + WITH deletes AS ( + DELETE FROM "ActivityReportGoalResources" + USING args_to_delete + WHERE "activityReportGoalId" = argid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + -- remove ARGs ------------------- + DROP TABLE IF EXISTS deleted_args; + CREATE TEMP TABLE deleted_args AS + WITH deletes AS ( + DELETE FROM "ActivityReportGoals" + USING args_to_delete + WHERE id = argid + RETURNING + id, + "goalId" gid + ) + SELECT id, gid FROM deletes + ; + + -- Create the orphaned Goal deletions list + DROP TABLE IF EXISTS goals_to_delete; + CREATE TEMP TABLE goals_to_delete + AS + SELECT DISTINCT gid + FROM deleted_args dargs + JOIN "Goals" g + ON gid = g.id + WHERE (g."isRttapa" IS NULL OR g."isRttapa" != 'Yes') + AND g."isFromSmartsheetTtaPlan" != TRUE + AND g."createdVia" != 'merge' + EXCEPT + SELECT gid + FROM ( + SELECT DISTINCT "goalId" gid + FROM "ActivityReportGoals" + UNION + SELECT DISTINCT "goalId" + FROM "Objectives" + UNION + SELECT DISTINCT "goalId" + FROM "EventReportPilotGoals" + ) keepers + ; + -- Remove Goal link records: ------------- + DROP TABLE IF EXISTS deleted_goalcollaborators; + CREATE TEMP TABLE deleted_goalcollaborators AS + WITH deletes AS ( + DELETE FROM "GoalCollaborators" + USING goals_to_delete + WHERE "goalId" = gid + RETURNING + id + ) + SELECT id FROM deletes + ; + DROP TABLE IF EXISTS deleted_goalfieldresponses; + CREATE TEMP TABLE deleted_goalfieldresponses AS + WITH deletes AS ( + DELETE FROM "GoalFieldResponses" + USING goals_to_delete + WHERE "goalId" = gid + RETURNING + id + ) + SELECT id FROM deletes + ; + DROP TABLE IF EXISTS deleted_goalresources; + CREATE TEMP TABLE deleted_goalresources AS + WITH deletes AS ( + DELETE FROM "GoalResources" + USING goals_to_delete + WHERE "goalId" = gid + RETURNING + id, + "resourceId" resourceid + ) + SELECT id, resourceid FROM deletes + ; + DROP TABLE IF EXISTS deleted_goalstatuschanges; + CREATE TEMP TABLE deleted_goalstatuschanges AS + WITH deletes AS ( + DELETE FROM "GoalStatusChanges" + USING goals_to_delete + WHERE "goalId" = gid + RETURNING + id, + "goalId" gid + ) + SELECT id, gid FROM deletes + ; + -- remove Goals ------------------- + DROP TABLE IF EXISTS deleted_goals; + CREATE TEMP TABLE deleted_goals AS + WITH deletes AS ( + DELETE FROM "Goals" + USING goals_to_delete + WHERE id = gid + RETURNING + id, + "goalTemplateId" gtid + ) + SELECT id, gtid FROM deletes + ; + + -- Create the orphaned ObjectiveTemplate deletion list + DROP TABLE IF EXISTS ots_to_delete; + CREATE TEMP TABLE ots_to_delete + AS + SELECT DISTINCT otid + FROM deleted_objectives + EXCEPT + SELECT DISTINCT "objectiveTemplateId" + FROM "Objectives" + ; + + -- Create the orphaned GoalTemplate deletion list + DROP TABLE IF EXISTS gts_to_delete; + CREATE TEMP TABLE gts_to_delete + AS + SELECT DISTINCT gtid + FROM deleted_goals + EXCEPT + SELECT DISTINCT "goalTemplateId" + FROM "Goals" + ; + -- Remove GoalTemplate link records: ------------- + DROP TABLE IF EXISTS deleted_goaltemplateobjectivetemplates; + CREATE TEMP TABLE deleted_goaltemplateobjectivetemplates AS + WITH unified_deletes AS ( + SELECT DISTINCT id gtotid + FROM "GoalTemplateObjectiveTemplates" + JOIN ots_to_delete + ON otid = "objectiveTemplateId" + UNION + SELECT DISTINCT id gtotid + FROM "GoalTemplateObjectiveTemplates" + JOIN gts_to_delete + ON gtid = "goalTemplateId" + ), + deletes AS ( + DELETE FROM "GoalTemplateObjectiveTemplates" + USING unified_deletes + WHERE id = gtotid + RETURNING + id + ) + SELECT id FROM deletes + ; + -- Remove ObjectiveTemplates -------- + DROP TABLE IF EXISTS deleted_objectivetemplates; + CREATE TEMP TABLE deleted_objectivetemplates AS + WITH deletes AS ( + DELETE FROM "ObjectiveTemplates" + USING ots_to_delete + WHERE id = otid + RETURNING + id + ) + SELECT id FROM deletes + ; + -- Remove GoalTemplates ------------- + DROP TABLE IF EXISTS deleted_goaltemplates; + CREATE TEMP TABLE deleted_goaltemplates AS + WITH deletes AS ( + DELETE FROM "GoalTemplates" + USING gts_to_delete + WHERE id = gtid + RETURNING + id + ) + SELECT id FROM deletes + ; + + -- Remove ARs ------------- + DROP TABLE IF EXISTS deleted_ars; + CREATE TEMP TABLE deleted_ars AS + WITH deletes AS ( + DELETE FROM "ActivityReports" + USING ars_to_delete + WHERE id = arid + RETURNING + id + ) + SELECT id FROM deletes + ; + + + -- Stats ---------------------------- + SELECT 1,'ars_to_delete', count(*) FROM ars_to_delete + UNION + SELECT 2,'deleted_activityreportapprovers', count(*) FROM deleted_activityreportapprovers + UNION + SELECT 3,'deleted_activityreportcollaborators', count(*) FROM deleted_activityreportcollaborators + UNION + SELECT 4,'deleted_activityreportfiles', count(*) FROM deleted_activityreportfiles + UNION + SELECT 5,'deleted_activityreportresources', count(*) FROM deleted_activityreportresources + UNION + SELECT 6,'nextsteps_to_delete', count(*) FROM nextsteps_to_delete + UNION + SELECT 7,'deleted_nextstepresources', count(*) FROM deleted_nextstepresources + UNION + SELECT 8,'deleted_nextsteps', count(*) FROM deleted_nextsteps + UNION + SELECT 9,'aros_to_delete', count(*) FROM aros_to_delete + UNION + SELECT 10,'deleted_activityreportobjectivefiles', count(*) FROM deleted_activityreportobjectivefiles + UNION + SELECT 11,'deleted_activityreportobjectiveresources', count(*) FROM deleted_activityreportobjectiveresources + UNION + SELECT 12,'deleted_activityreportobjectivetopics', count(*) FROM deleted_activityreportobjectivetopics + UNION + SELECT 12,'deleted_activityreportobjectivecourses', count(*) FROM deleted_activityreportobjectivetopics + UNION + SELECT 13,'deleted_aros', count(*) FROM deleted_aros + UNION + SELECT 14,'objectives_to_delete', count(*) FROM objectives_to_delete + UNION + SELECT 14,'deleted_objectivecollaborators', count(*) FROM objectives_to_delete + UNION + SELECT 15,'deleted_objectives', count(*) FROM deleted_objectives + UNION + SELECT 16,'args_to_delete', count(*) FROM args_to_delete + UNION + SELECT 17,'deleted_activityreportgoalfieldresponses', count(*) FROM deleted_activityreportgoalfieldresponses + UNION + SELECT 18,'deleted_activityreportgoalresources', count(*) FROM deleted_activityreportgoalresources + UNION + SELECT 19,'deleted_args', count(*) FROM deleted_args + UNION + SELECT 20,'goals_to_delete', count(*) FROM goals_to_delete + UNION + SELECT 21,'deleted_goalcollaborators', count(*) FROM deleted_goalcollaborators + UNION + SELECT 22,'deleted_goalfieldresponses', count(*) FROM deleted_goalfieldresponses + UNION + SELECT 23,'deleted_goalresources', count(*) FROM deleted_goalresources + UNION + SELECT 24,'deleted_goalstatuschanges', count(*) FROM deleted_goalstatuschanges + UNION + SELECT 25,'deleted_goals', count(*) FROM deleted_goals + UNION + SELECT 26,'ots_to_delete', count(*) FROM ots_to_delete + UNION + SELECT 27,'gts_to_delete', count(*) FROM gts_to_delete + UNION + SELECT 28,'deleted_goaltemplateobjectivetemplates', count(*) FROM deleted_goaltemplateobjectivetemplates + UNION + SELECT 29,'deleted_objectivetemplates', count(*) FROM deleted_objectivetemplates + UNION + SELECT 30,'deleted_goaltemplates', count(*) FROM deleted_goaltemplates + UNION + SELECT 31,'deleted_ars', count(*) FROM deleted_ars + ORDER BY 1 + ; + + -- Reset the onApprovedAR and onAR values for the goals and objectives that + -- were not deleted + -- 1. Calculate correct onApprovedAR values for objectives + DROP TABLE IF EXISTS objectives_on_ars; + CREATE TEMP TABLE objectives_on_ars + AS + WITH objectivelist AS ( + SELECT DISTINCT oid FROM aros_to_delete + EXCEPT + SELECT id FROM deleted_objectives + ) + SELECT + o.id oid, + BOOL_OR(ar.id IS NOT NULL AND ar."calculatedStatus" = 'approved') on_approved_ar, + BOOL_OR(ar.id IS NOT NULL) on_ar + FROM objectivelist ol + JOIN "Objectives" o + ON ol.oid = o.id + LEFT JOIN "ActivityReportObjectives" aro + ON o.id = aro."objectiveId" + LEFT JOIN "ActivityReports" ar + ON aro."activityReportId" = ar.id + AND ar."calculatedStatus" != 'deleted' + GROUP BY 1 + ; + -- 2. Calculate correct onApprovedAR values for goals + DROP TABLE IF EXISTS goals_on_ars; + CREATE TEMP TABLE goals_on_ars + AS + WITH goallist AS ( + SELECT DISTINCT gid FROM args_to_delete + EXCEPT + SELECT id FROM deleted_goals + ) + SELECT + g.id gid, + BOOL_OR( + (ar.id IS NOT NULL AND ar."calculatedStatus" = 'approved') + OR + COALESCE(ooaa.on_approved_ar,FALSE) + ) on_approved_ar, + BOOL_OR(ar.id IS NOT NULL OR COALESCE(ooaa.on_ar,FALSE)) on_ar + FROM goallist gl + JOIN "Goals" g + ON g.id = gl.gid + LEFT JOIN "ActivityReportGoals" arg + ON g.id = arg."goalId" + LEFT JOIN "ActivityReports" ar + ON arg."activityReportId" = ar.id + AND ar."calculatedStatus" != 'deleted' + LEFT JOIN "Objectives" o + ON o."goalId" = g.id + LEFT JOIN objectives_on_ars ooaa + ON ooaa.oid = o.id + GROUP BY 1 + ; + -- 3. Calculate onApprovedAR stats for objectives + DROP TABLE IF EXISTS initial_obj_approved_ar_stats; + CREATE TEMP TABLE initial_obj_approved_ar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_objectives + FROM "Objectives" o + JOIN objectives_on_ars + ON o.id = oid + ; + -- 4. Calculate onAR stats for objectives + DROP TABLE IF EXISTS initial_obj_onar_stats; + CREATE TEMP TABLE initial_obj_onar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_ar = "onAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_objectives + FROM "Objectives" o + JOIN objectives_on_ars + ON o.id = oid + ; + -- 5. Calculate onApprovedAR stats for goals + DROP TABLE IF EXISTS initial_goal_approved_ar_stats; + CREATE TEMP TABLE initial_goal_approved_ar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_goals + FROM "Goals" g + JOIN goals_on_ars + ON g.id = gid + ; + -- 6. Calculate onAR stats for goals + DROP TABLE IF EXISTS initial_goal_onar_stats; + CREATE TEMP TABLE initial_goal_onar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_ar = "onAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_goals + FROM "Goals" g + JOIN goals_on_ars + ON g.id = gid + ; + -- 7. Update onApprovedAR values for objectives and save the results + DROP TABLE IF EXISTS corrected_approved_objectives; + CREATE TEMP TABLE corrected_approved_objectives + AS + WITH updater AS ( + UPDATE "Objectives" o + SET "onApprovedAR" = on_approved_ar + FROM objectives_on_ars + WHERE o.id = oid + AND ("onApprovedAR" != on_approved_ar OR "onApprovedAR" IS NULL) + RETURNING + oid, + on_approved_ar + ) SELECT * FROM updater + ; + -- 8. Update onAR values for objectives and save the results + DROP TABLE IF EXISTS corrected_onar_objectives; + CREATE TEMP TABLE corrected_onar_objectives + AS + WITH updater AS ( + UPDATE "Objectives" o + SET "onAR" = on_ar + FROM objectives_on_ars + WHERE o.id = oid + AND ("onAR" != on_ar OR "onAR" IS NULL) + RETURNING + oid, + on_ar + ) SELECT * FROM updater + ; + -- 9. Update onApprovedAR values for goals and save the results + DROP TABLE IF EXISTS corrected_approved_goals; + CREATE TEMP TABLE corrected_approved_goals + AS + WITH updater AS ( + UPDATE "Goals" g + SET "onApprovedAR" = on_approved_ar + FROM goals_on_ars + WHERE g.id = gid + AND ("onApprovedAR" != on_approved_ar OR "onApprovedAR" IS NULL) + RETURNING + gid, + on_approved_ar + ) SELECT * FROM updater + ; + -- 10. Update onAR values for goals and save the results + DROP TABLE IF EXISTS corrected_onar_goals; + CREATE TEMP TABLE corrected_onar_goals + AS + WITH updater AS ( + UPDATE "Goals" g + SET "onAR" = on_ar + FROM goals_on_ars + WHERE g.id = gid + AND ("onAR" != on_ar OR "onAR" IS NULL) + RETURNING + gid, + on_ar + ) SELECT * FROM updater + ; + -- produce stats on what happened + -- 11. Final onApprovedAR stats for objectives + DROP TABLE IF EXISTS final_obj_approved_ar_stats; + CREATE TEMP TABLE final_obj_approved_ar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_objectives + FROM "Objectives" o + JOIN objectives_on_ars + ON o.id = oid + ; + -- 12. Final onAR stats for objectives + DROP TABLE IF EXISTS final_obj_onar_stats; + CREATE TEMP TABLE final_obj_onar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_ar = "onAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_objectives + FROM "Objectives" o + JOIN objectives_on_ars + ON o.id = oid + ; + -- 13. Final onApprovedAR stats for goals + DROP TABLE IF EXISTS final_goal_approved_ar_stats; + CREATE TEMP TABLE final_goal_approved_ar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_goals + FROM "Goals" g + JOIN goals_on_ars + ON g.id = gid + ; + -- 14. Final onAR stats for goals + DROP TABLE IF EXISTS final_goal_onar_stats; + CREATE TEMP TABLE final_goal_onar_stats + AS + SELECT + COUNT(*) FILTER (WHERE on_ar = "onAR" + ) matching_values, + COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" + ) incorrect_values, + COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) + ) should_be_marked_true_but_isnt, + COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) + ) marked_true_but_shouldnt_be, + COUNT(*) total_goals + FROM "Goals" g + JOIN goals_on_ars + ON g.id = gid + ; + -- make a nice little table to see the math + SELECT + 1 AS order, + 'objective onApprovedAR starting stats' description, + matching_values, + incorrect_values, + should_be_marked_true_but_isnt, + marked_true_but_shouldnt_be, + total_objectives total + FROM initial_obj_approved_ar_stats + UNION + SELECT + 2, + 'objective onApprovedAR values changed', + NULL, + NULL, + SUM(CASE WHEN on_approved_ar THEN 1 ELSE 0 END), + SUM(CASE WHEN NOT on_approved_ar THEN 1 ELSE 0 END), + COUNT(*) + FROM corrected_approved_objectives + UNION + SELECT 3,'objective onApprovedAR ending stats', * FROM final_obj_approved_ar_stats + UNION + SELECT 4,'objective onAR starting stats', * FROM initial_obj_onar_stats + UNION + SELECT + 5, + 'objective onAR values changed', + NULL, + NULL, + SUM(CASE WHEN on_ar THEN 1 ELSE 0 END), + SUM(CASE WHEN NOT on_ar THEN 1 ELSE 0 END), + COUNT(*) + FROM corrected_onar_objectives + UNION + SELECT 6,'objective onAR ending stats', * FROM final_obj_onar_stats + UNION + SELECT 7,'goal onApprovedAR starting stats', * FROM initial_goal_approved_ar_stats + UNION + SELECT + 8, + 'goal onApprovedAR values changed', + NULL, + NULL, + SUM(CASE WHEN on_approved_ar THEN 1 ELSE 0 END), + SUM(CASE WHEN NOT on_approved_ar THEN 1 ELSE 0 END), + COUNT(*) + FROM corrected_approved_goals + UNION + SELECT 9,'goal onApprovedAR ending stats', * FROM final_goal_approved_ar_stats + UNION + SELECT 10,'goal onAR starting stats', * FROM initial_goal_onar_stats + UNION + SELECT + 11, + 'goal onAR values changed', + NULL, + NULL, + SUM(CASE WHEN on_ar THEN 1 ELSE 0 END), + SUM(CASE WHEN NOT on_ar THEN 1 ELSE 0 END), + COUNT(*) + FROM corrected_onar_goals + UNION + SELECT 12,'goal onAR ending stats', * FROM final_goal_onar_stats + ORDER BY 1 + ; + + `, { transaction }); + }); + }, + + down: async () => { + }, +}; From 441d1f843c893e389c751af74c7b28804e72feb3 Mon Sep 17 00:00:00 2001 From: Nathan Powell Date: Mon, 8 Jul 2024 09:43:11 -0700 Subject: [PATCH 20/26] push migration date later --- ...240702000000-remove_national_center_ars.js | 917 ------------------ 1 file changed, 917 deletions(-) delete mode 100644 src/migrations/20240702000000-remove_national_center_ars.js diff --git a/src/migrations/20240702000000-remove_national_center_ars.js b/src/migrations/20240702000000-remove_national_center_ars.js deleted file mode 100644 index c4dbc9863e..0000000000 --- a/src/migrations/20240702000000-remove_national_center_ars.js +++ /dev/null @@ -1,917 +0,0 @@ -const { - prepMigration, -} = require('../lib/migration'); - -/** @type {import('sequelize-cli').Migration} */ -module.exports = { - async up(queryInterface) { - await queryInterface.sequelize.transaction(async (transaction) => { - const sessionSig = __filename; - await prepMigration(queryInterface, transaction, sessionSig); - - await queryInterface.sequelize.query(` - --------------------------------------------------- - -- NOTE: - -- Files and Resources are most properly managed by - -- maintenance jobs, so this and similar migrations - -- won't delete them directly. Deleting the link - -- records will give the maintenance job the info - -- it needs to perform its housekeeping. - --------------------------------------------------- - -------- Deleting unwanted ARs -------- - -- Create the AR deletion list - -- Remove AR link records: ------------- - -- ActivityRecipients - -- ActivityReportApprovers - -- ActivityReportCollaborators - -- ActivityReportFiles (no need to remove Files) - -- ActivityReportResources (no need to remove Resources) - - -- Create the NextSteps deletion list - -- Remove NextSteps link records: ------------- - -- NextStepResources - -- remove NextSteps ------------- - - -- Create the ARO deletion list - -- Remove ARO link records: ------------- - -- ActivityReportObjectiveFiles - -- ActivityReportObjectiveResources - -- ActivityReportObjectiveTopics - -- ActivityReportObjectiveCourses - -- remove AROs ------------------- - - -- Create the orphaned Objective deletion list - -- Remove Objective link records: ------------- - -- Delete ObjectiveCollaborators - -- remove Objectives ------------- - - -- Create the ARG deletion list - -- Remove ARG link records: ------------- - -- ActivityReportGoalFieldResponses - -- ActivityReportGoalResources - -- remove ARGs ------------------- - - -- Create the orphaned Goal deletions list - -- ( check if isFromSmartsheetTtaPlan, isRttapa) - -- Remove Goal link records: ------------- - -- EventReportPilotGoals - -- GoalFieldResponses - -- GoalResources - -- remove Goals ------------------ - - -- Create the orphaned ObjectiveTemplate deletion list - -- Create the orphaned GoalTemplate deletion list - -- Remove GoalTemplate link records: ------------- - -- GoalTemplateObjectiveTemplates - -- Remove ObjectiveTemplates -------- - -- Remove GoalTemplates ------------- - - -- Remove ARs ----------------------- - - -- Test query - - -- Correct the onApprovedAR and onAR values for the goals - -- and objectives that were not deleted - - ------------------------------------------------------------------------------------------------------------------- - -------- Deleting unwanted ARs -------- - -- Create the AR deletion list - DROP TABLE IF EXISTS ars_to_delete; - CREATE TEMP TABLE ars_to_delete - AS - SELECT id arid - FROM "ActivityReports" - WHERE id IN (24998, 24645, 24297, 24122, 27517, 30829, 29864, 6442, 23057, 23718, 25205, 25792, 25577, 25573, 26478, 26210, 27117, 26918, 28451, 28117, 27669, 29542, 29101, 29024, 30137, 29762, 31201) - AND "regionId" = 10 - ; - - -- Remove AR link records: ------------- - DROP TABLE IF EXISTS deleted_activityrecipients; - CREATE TEMP TABLE deleted_activityrecipients AS - WITH deletes AS ( - DELETE FROM "ActivityRecipients" - USING ars_to_delete - WHERE "activityReportId" = arid - RETURNING - id - ) - SELECT id FROM deletes - ; - DROP TABLE IF EXISTS deleted_activityreportapprovers; - CREATE TEMP TABLE deleted_activityreportapprovers AS - WITH deletes AS ( - DELETE FROM "ActivityReportApprovers" - USING ars_to_delete - WHERE "activityReportId" = arid - RETURNING - id - ) - SELECT id FROM deletes - ; - DROP TABLE IF EXISTS deleted_activityreportcollaborators; - CREATE TEMP TABLE deleted_activityreportcollaborators AS - WITH deletes AS ( - DELETE FROM "ActivityReportCollaborators" - USING ars_to_delete - WHERE "activityReportId" = arid - RETURNING - id - ) - SELECT id FROM deletes - ; - DROP TABLE IF EXISTS deleted_activityreportfiles; - CREATE TEMP TABLE deleted_activityreportfiles AS - WITH deletes AS ( - DELETE FROM "ActivityReportFiles" - USING ars_to_delete - WHERE "activityReportId" = arid - RETURNING - id, - "fileId" fid - ) - SELECT id, fid FROM deletes - ; - DROP TABLE IF EXISTS deleted_activityreportresources; - CREATE TEMP TABLE deleted_activityreportresources AS - WITH deletes AS ( - DELETE FROM "ActivityReportResources" - USING ars_to_delete - WHERE "activityReportId" = arid - RETURNING - id, - "resourceId" resourceid - ) - SELECT id, resourceid FROM deletes - ; - - - - -- Create the NextSteps deletion list - DROP TABLE IF EXISTS nextsteps_to_delete; - CREATE TEMP TABLE nextsteps_to_delete - AS - SELECT - id nsid - FROM "NextSteps" - JOIN ars_to_delete - ON "activityReportId" = arid - ; - -- Remove NextSteps link records: ------------- - DROP TABLE IF EXISTS deleted_nextstepresources; - CREATE TEMP TABLE deleted_nextstepresources AS - WITH deletes AS ( - DELETE FROM "NextStepResources" - USING nextsteps_to_delete - WHERE "nextStepId" = nsid - RETURNING - id, - "resourceId" resourceid - ) - SELECT id, resourceid FROM deletes - ; - -- remove NextSteps ------------- - DROP TABLE IF EXISTS deleted_nextsteps; - CREATE TEMP TABLE deleted_nextsteps AS - WITH deletes AS ( - DELETE FROM "NextSteps" - USING nextsteps_to_delete - WHERE id = nsid - RETURNING - id - ) - SELECT id FROM deletes - ; - - - -- Create the ARO deletion list - DROP TABLE IF EXISTS aros_to_delete; - CREATE TEMP TABLE aros_to_delete - AS - SELECT - id aroid, - "objectiveId" oid - FROM "ActivityReportObjectives" - JOIN ars_to_delete - ON "activityReportId" = arid - ; - -- Remove ARO link records: ------------- - DROP TABLE IF EXISTS deleted_activityreportobjectivefiles; - CREATE TEMP TABLE deleted_activityreportobjectivefiles AS - WITH deletes AS ( - DELETE FROM "ActivityReportObjectiveFiles" - USING aros_to_delete - WHERE "activityReportObjectiveId" = aroid - RETURNING - id, - "fileId" fid - ) - SELECT id, fid FROM deletes - ; - DROP TABLE IF EXISTS deleted_activityreportobjectiveresources; - CREATE TEMP TABLE deleted_activityreportobjectiveresources AS - WITH deletes AS ( - DELETE FROM "ActivityReportObjectiveResources" - USING aros_to_delete - WHERE "activityReportObjectiveId" = aroid - RETURNING - id, - "resourceId" resourceid - ) - SELECT id, resourceid FROM deletes - ; - DROP TABLE IF EXISTS deleted_activityreportobjectivetopics; - CREATE TEMP TABLE deleted_activityreportobjectivetopics AS - WITH deletes AS ( - DELETE FROM "ActivityReportObjectiveTopics" - USING aros_to_delete - WHERE "activityReportObjectiveId" = aroid - RETURNING - id - ) - SELECT id FROM deletes - ; - DROP TABLE IF EXISTS deleted_activityreportobjectivecourses; - CREATE TEMP TABLE deleted_activityreportobjectivecourses AS - WITH deletes AS ( - DELETE FROM "ActivityReportObjectiveCourses" - USING aros_to_delete - WHERE "activityReportObjectiveId" = aroid - RETURNING - id - ) - SELECT id FROM deletes - ; - -- remove AROs ------------------- - DROP TABLE IF EXISTS deleted_aros; - CREATE TEMP TABLE deleted_aros AS - WITH deletes AS ( - DELETE FROM "ActivityReportObjectives" - USING aros_to_delete - WHERE id = aroid - RETURNING - id, - "objectiveId" oid - ) - SELECT id, oid FROM deletes - ; - - -- Create the orphaned Objective deletion list - DROP TABLE IF EXISTS objectives_to_delete; - CREATE TEMP TABLE objectives_to_delete - AS - SELECT DISTINCT oid - FROM deleted_aros - EXCEPT - SELECT DISTINCT "objectiveId" - FROM "ActivityReportObjectives" - ; - -- Remove Objective link records: ------------- - -- Delete ObjectiveCollaborators - DROP TABLE IF EXISTS deleted_objectivecollaborators; - CREATE TEMP TABLE deleted_objectivecollaborators AS - WITH deletes AS ( - DELETE FROM "ObjectiveCollaborators" - USING objectives_to_delete - WHERE "objectiveId" = oid - RETURNING - id - ) - SELECT id FROM deletes - ; - - -- remove Objectives ------------------- - DROP TABLE IF EXISTS deleted_objectives; - CREATE TEMP TABLE deleted_objectives AS - WITH deletes AS ( - DELETE FROM "Objectives" - USING objectives_to_delete - WHERE id = oid - RETURNING - id, - "goalId" gid, - "objectiveTemplateId" otid - ) - SELECT id, gid, otid FROM deletes - ; - - -- Create the ARG deletion list - DROP TABLE IF EXISTS args_to_delete; - CREATE TEMP TABLE args_to_delete - AS - SELECT DISTINCT - id argid, - "goalId" gid - FROM "ActivityReportGoals" - JOIN ars_to_delete - ON "activityReportId" = arid - ; - -- Remove ARG link records: ------------- - DROP TABLE IF EXISTS deleted_activityreportgoalfieldresponses; - CREATE TEMP TABLE deleted_activityreportgoalfieldresponses AS - WITH deletes AS ( - DELETE FROM "ActivityReportGoalFieldResponses" - USING args_to_delete - WHERE "activityReportGoalId" = argid - RETURNING - id - ) - SELECT id FROM deletes - ; - DROP TABLE IF EXISTS deleted_activityreportgoalresources; - CREATE TEMP TABLE deleted_activityreportgoalresources AS - WITH deletes AS ( - DELETE FROM "ActivityReportGoalResources" - USING args_to_delete - WHERE "activityReportGoalId" = argid - RETURNING - id, - "resourceId" resourceid - ) - SELECT id, resourceid FROM deletes - ; - -- remove ARGs ------------------- - DROP TABLE IF EXISTS deleted_args; - CREATE TEMP TABLE deleted_args AS - WITH deletes AS ( - DELETE FROM "ActivityReportGoals" - USING args_to_delete - WHERE id = argid - RETURNING - id, - "goalId" gid - ) - SELECT id, gid FROM deletes - ; - - -- Create the orphaned Goal deletions list - DROP TABLE IF EXISTS goals_to_delete; - CREATE TEMP TABLE goals_to_delete - AS - SELECT DISTINCT gid - FROM deleted_args dargs - JOIN "Goals" g - ON gid = g.id - WHERE (g."isRttapa" IS NULL OR g."isRttapa" != 'Yes') - AND g."isFromSmartsheetTtaPlan" != TRUE - AND g."createdVia" != 'merge' - EXCEPT - SELECT gid - FROM ( - SELECT DISTINCT "goalId" gid - FROM "ActivityReportGoals" - UNION - SELECT DISTINCT "goalId" - FROM "Objectives" - UNION - SELECT DISTINCT "goalId" - FROM "EventReportPilotGoals" - ) keepers - ; - -- Remove Goal link records: ------------- - DROP TABLE IF EXISTS deleted_goalcollaborators; - CREATE TEMP TABLE deleted_goalcollaborators AS - WITH deletes AS ( - DELETE FROM "GoalCollaborators" - USING goals_to_delete - WHERE "goalId" = gid - RETURNING - id - ) - SELECT id FROM deletes - ; - DROP TABLE IF EXISTS deleted_goalfieldresponses; - CREATE TEMP TABLE deleted_goalfieldresponses AS - WITH deletes AS ( - DELETE FROM "GoalFieldResponses" - USING goals_to_delete - WHERE "goalId" = gid - RETURNING - id - ) - SELECT id FROM deletes - ; - DROP TABLE IF EXISTS deleted_goalresources; - CREATE TEMP TABLE deleted_goalresources AS - WITH deletes AS ( - DELETE FROM "GoalResources" - USING goals_to_delete - WHERE "goalId" = gid - RETURNING - id, - "resourceId" resourceid - ) - SELECT id, resourceid FROM deletes - ; - DROP TABLE IF EXISTS deleted_goalstatuschanges; - CREATE TEMP TABLE deleted_goalstatuschanges AS - WITH deletes AS ( - DELETE FROM "GoalStatusChanges" - USING goals_to_delete - WHERE "goalId" = gid - RETURNING - id, - "goalId" gid - ) - SELECT id, gid FROM deletes - ; - -- remove Goals ------------------- - DROP TABLE IF EXISTS deleted_goals; - CREATE TEMP TABLE deleted_goals AS - WITH deletes AS ( - DELETE FROM "Goals" - USING goals_to_delete - WHERE id = gid - RETURNING - id, - "goalTemplateId" gtid - ) - SELECT id, gtid FROM deletes - ; - - -- Create the orphaned ObjectiveTemplate deletion list - DROP TABLE IF EXISTS ots_to_delete; - CREATE TEMP TABLE ots_to_delete - AS - SELECT DISTINCT otid - FROM deleted_objectives - EXCEPT - SELECT DISTINCT "objectiveTemplateId" - FROM "Objectives" - ; - - -- Create the orphaned GoalTemplate deletion list - DROP TABLE IF EXISTS gts_to_delete; - CREATE TEMP TABLE gts_to_delete - AS - SELECT DISTINCT gtid - FROM deleted_goals - EXCEPT - SELECT DISTINCT "goalTemplateId" - FROM "Goals" - ; - -- Remove GoalTemplate link records: ------------- - DROP TABLE IF EXISTS deleted_goaltemplateobjectivetemplates; - CREATE TEMP TABLE deleted_goaltemplateobjectivetemplates AS - WITH unified_deletes AS ( - SELECT DISTINCT id gtotid - FROM "GoalTemplateObjectiveTemplates" - JOIN ots_to_delete - ON otid = "objectiveTemplateId" - UNION - SELECT DISTINCT id gtotid - FROM "GoalTemplateObjectiveTemplates" - JOIN gts_to_delete - ON gtid = "goalTemplateId" - ), - deletes AS ( - DELETE FROM "GoalTemplateObjectiveTemplates" - USING unified_deletes - WHERE id = gtotid - RETURNING - id - ) - SELECT id FROM deletes - ; - -- Remove ObjectiveTemplates -------- - DROP TABLE IF EXISTS deleted_objectivetemplates; - CREATE TEMP TABLE deleted_objectivetemplates AS - WITH deletes AS ( - DELETE FROM "ObjectiveTemplates" - USING ots_to_delete - WHERE id = otid - RETURNING - id - ) - SELECT id FROM deletes - ; - -- Remove GoalTemplates ------------- - DROP TABLE IF EXISTS deleted_goaltemplates; - CREATE TEMP TABLE deleted_goaltemplates AS - WITH deletes AS ( - DELETE FROM "GoalTemplates" - USING gts_to_delete - WHERE id = gtid - RETURNING - id - ) - SELECT id FROM deletes - ; - - -- Remove ARs ------------- - DROP TABLE IF EXISTS deleted_ars; - CREATE TEMP TABLE deleted_ars AS - WITH deletes AS ( - DELETE FROM "ActivityReports" - USING ars_to_delete - WHERE id = arid - RETURNING - id - ) - SELECT id FROM deletes - ; - - - -- Stats ---------------------------- - SELECT 1,'ars_to_delete', count(*) FROM ars_to_delete - UNION - SELECT 2,'deleted_activityreportapprovers', count(*) FROM deleted_activityreportapprovers - UNION - SELECT 3,'deleted_activityreportcollaborators', count(*) FROM deleted_activityreportcollaborators - UNION - SELECT 4,'deleted_activityreportfiles', count(*) FROM deleted_activityreportfiles - UNION - SELECT 5,'deleted_activityreportresources', count(*) FROM deleted_activityreportresources - UNION - SELECT 6,'nextsteps_to_delete', count(*) FROM nextsteps_to_delete - UNION - SELECT 7,'deleted_nextstepresources', count(*) FROM deleted_nextstepresources - UNION - SELECT 8,'deleted_nextsteps', count(*) FROM deleted_nextsteps - UNION - SELECT 9,'aros_to_delete', count(*) FROM aros_to_delete - UNION - SELECT 10,'deleted_activityreportobjectivefiles', count(*) FROM deleted_activityreportobjectivefiles - UNION - SELECT 11,'deleted_activityreportobjectiveresources', count(*) FROM deleted_activityreportobjectiveresources - UNION - SELECT 12,'deleted_activityreportobjectivetopics', count(*) FROM deleted_activityreportobjectivetopics - UNION - SELECT 12,'deleted_activityreportobjectivecourses', count(*) FROM deleted_activityreportobjectivetopics - UNION - SELECT 13,'deleted_aros', count(*) FROM deleted_aros - UNION - SELECT 14,'objectives_to_delete', count(*) FROM objectives_to_delete - UNION - SELECT 14,'deleted_objectivecollaborators', count(*) FROM objectives_to_delete - UNION - SELECT 15,'deleted_objectives', count(*) FROM deleted_objectives - UNION - SELECT 16,'args_to_delete', count(*) FROM args_to_delete - UNION - SELECT 17,'deleted_activityreportgoalfieldresponses', count(*) FROM deleted_activityreportgoalfieldresponses - UNION - SELECT 18,'deleted_activityreportgoalresources', count(*) FROM deleted_activityreportgoalresources - UNION - SELECT 19,'deleted_args', count(*) FROM deleted_args - UNION - SELECT 20,'goals_to_delete', count(*) FROM goals_to_delete - UNION - SELECT 21,'deleted_goalcollaborators', count(*) FROM deleted_goalcollaborators - UNION - SELECT 22,'deleted_goalfieldresponses', count(*) FROM deleted_goalfieldresponses - UNION - SELECT 23,'deleted_goalresources', count(*) FROM deleted_goalresources - UNION - SELECT 24,'deleted_goalstatuschanges', count(*) FROM deleted_goalstatuschanges - UNION - SELECT 25,'deleted_goals', count(*) FROM deleted_goals - UNION - SELECT 26,'ots_to_delete', count(*) FROM ots_to_delete - UNION - SELECT 27,'gts_to_delete', count(*) FROM gts_to_delete - UNION - SELECT 28,'deleted_goaltemplateobjectivetemplates', count(*) FROM deleted_goaltemplateobjectivetemplates - UNION - SELECT 29,'deleted_objectivetemplates', count(*) FROM deleted_objectivetemplates - UNION - SELECT 30,'deleted_goaltemplates', count(*) FROM deleted_goaltemplates - UNION - SELECT 31,'deleted_ars', count(*) FROM deleted_ars - ORDER BY 1 - ; - - -- Reset the onApprovedAR and onAR values for the goals and objectives that - -- were not deleted - -- 1. Calculate correct onApprovedAR values for objectives - DROP TABLE IF EXISTS objectives_on_ars; - CREATE TEMP TABLE objectives_on_ars - AS - WITH objectivelist AS ( - SELECT DISTINCT oid FROM aros_to_delete - EXCEPT - SELECT id FROM deleted_objectives - ) - SELECT - o.id oid, - BOOL_OR(ar.id IS NOT NULL AND ar."calculatedStatus" = 'approved') on_approved_ar, - BOOL_OR(ar.id IS NOT NULL) on_ar - FROM objectivelist ol - JOIN "Objectives" o - ON ol.oid = o.id - LEFT JOIN "ActivityReportObjectives" aro - ON o.id = aro."objectiveId" - LEFT JOIN "ActivityReports" ar - ON aro."activityReportId" = ar.id - AND ar."calculatedStatus" != 'deleted' - GROUP BY 1 - ; - -- 2. Calculate correct onApprovedAR values for goals - DROP TABLE IF EXISTS goals_on_ars; - CREATE TEMP TABLE goals_on_ars - AS - WITH goallist AS ( - SELECT DISTINCT gid FROM args_to_delete - EXCEPT - SELECT id FROM deleted_goals - ) - SELECT - g.id gid, - BOOL_OR( - (ar.id IS NOT NULL AND ar."calculatedStatus" = 'approved') - OR - COALESCE(ooaa.on_approved_ar,FALSE) - ) on_approved_ar, - BOOL_OR(ar.id IS NOT NULL OR COALESCE(ooaa.on_ar,FALSE)) on_ar - FROM goallist gl - JOIN "Goals" g - ON g.id = gl.gid - LEFT JOIN "ActivityReportGoals" arg - ON g.id = arg."goalId" - LEFT JOIN "ActivityReports" ar - ON arg."activityReportId" = ar.id - AND ar."calculatedStatus" != 'deleted' - LEFT JOIN "Objectives" o - ON o."goalId" = g.id - LEFT JOIN objectives_on_ars ooaa - ON ooaa.oid = o.id - GROUP BY 1 - ; - -- 3. Calculate onApprovedAR stats for objectives - DROP TABLE IF EXISTS initial_obj_approved_ar_stats; - CREATE TEMP TABLE initial_obj_approved_ar_stats - AS - SELECT - COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" - ) matching_values, - COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" - ) incorrect_values, - COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) - ) should_be_marked_true_but_isnt, - COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) - ) marked_true_but_shouldnt_be, - COUNT(*) total_objectives - FROM "Objectives" o - JOIN objectives_on_ars - ON o.id = oid - ; - -- 4. Calculate onAR stats for objectives - DROP TABLE IF EXISTS initial_obj_onar_stats; - CREATE TEMP TABLE initial_obj_onar_stats - AS - SELECT - COUNT(*) FILTER (WHERE on_ar = "onAR" - ) matching_values, - COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" - ) incorrect_values, - COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) - ) should_be_marked_true_but_isnt, - COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) - ) marked_true_but_shouldnt_be, - COUNT(*) total_objectives - FROM "Objectives" o - JOIN objectives_on_ars - ON o.id = oid - ; - -- 5. Calculate onApprovedAR stats for goals - DROP TABLE IF EXISTS initial_goal_approved_ar_stats; - CREATE TEMP TABLE initial_goal_approved_ar_stats - AS - SELECT - COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" - ) matching_values, - COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" - ) incorrect_values, - COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) - ) should_be_marked_true_but_isnt, - COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) - ) marked_true_but_shouldnt_be, - COUNT(*) total_goals - FROM "Goals" g - JOIN goals_on_ars - ON g.id = gid - ; - -- 6. Calculate onAR stats for goals - DROP TABLE IF EXISTS initial_goal_onar_stats; - CREATE TEMP TABLE initial_goal_onar_stats - AS - SELECT - COUNT(*) FILTER (WHERE on_ar = "onAR" - ) matching_values, - COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" - ) incorrect_values, - COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) - ) should_be_marked_true_but_isnt, - COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) - ) marked_true_but_shouldnt_be, - COUNT(*) total_goals - FROM "Goals" g - JOIN goals_on_ars - ON g.id = gid - ; - -- 7. Update onApprovedAR values for objectives and save the results - DROP TABLE IF EXISTS corrected_approved_objectives; - CREATE TEMP TABLE corrected_approved_objectives - AS - WITH updater AS ( - UPDATE "Objectives" o - SET "onApprovedAR" = on_approved_ar - FROM objectives_on_ars - WHERE o.id = oid - AND ("onApprovedAR" != on_approved_ar OR "onApprovedAR" IS NULL) - RETURNING - oid, - on_approved_ar - ) SELECT * FROM updater - ; - -- 8. Update onAR values for objectives and save the results - DROP TABLE IF EXISTS corrected_onar_objectives; - CREATE TEMP TABLE corrected_onar_objectives - AS - WITH updater AS ( - UPDATE "Objectives" o - SET "onAR" = on_ar - FROM objectives_on_ars - WHERE o.id = oid - AND ("onAR" != on_ar OR "onAR" IS NULL) - RETURNING - oid, - on_ar - ) SELECT * FROM updater - ; - -- 9. Update onApprovedAR values for goals and save the results - DROP TABLE IF EXISTS corrected_approved_goals; - CREATE TEMP TABLE corrected_approved_goals - AS - WITH updater AS ( - UPDATE "Goals" g - SET "onApprovedAR" = on_approved_ar - FROM goals_on_ars - WHERE g.id = gid - AND ("onApprovedAR" != on_approved_ar OR "onApprovedAR" IS NULL) - RETURNING - gid, - on_approved_ar - ) SELECT * FROM updater - ; - -- 10. Update onAR values for goals and save the results - DROP TABLE IF EXISTS corrected_onar_goals; - CREATE TEMP TABLE corrected_onar_goals - AS - WITH updater AS ( - UPDATE "Goals" g - SET "onAR" = on_ar - FROM goals_on_ars - WHERE g.id = gid - AND ("onAR" != on_ar OR "onAR" IS NULL) - RETURNING - gid, - on_ar - ) SELECT * FROM updater - ; - -- produce stats on what happened - -- 11. Final onApprovedAR stats for objectives - DROP TABLE IF EXISTS final_obj_approved_ar_stats; - CREATE TEMP TABLE final_obj_approved_ar_stats - AS - SELECT - COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" - ) matching_values, - COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" - ) incorrect_values, - COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) - ) should_be_marked_true_but_isnt, - COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) - ) marked_true_but_shouldnt_be, - COUNT(*) total_objectives - FROM "Objectives" o - JOIN objectives_on_ars - ON o.id = oid - ; - -- 12. Final onAR stats for objectives - DROP TABLE IF EXISTS final_obj_onar_stats; - CREATE TEMP TABLE final_obj_onar_stats - AS - SELECT - COUNT(*) FILTER (WHERE on_ar = "onAR" - ) matching_values, - COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" - ) incorrect_values, - COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) - ) should_be_marked_true_but_isnt, - COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) - ) marked_true_but_shouldnt_be, - COUNT(*) total_objectives - FROM "Objectives" o - JOIN objectives_on_ars - ON o.id = oid - ; - -- 13. Final onApprovedAR stats for goals - DROP TABLE IF EXISTS final_goal_approved_ar_stats; - CREATE TEMP TABLE final_goal_approved_ar_stats - AS - SELECT - COUNT(*) FILTER (WHERE on_approved_ar = "onApprovedAR" - ) matching_values, - COUNT(*) FILTER (WHERE "onApprovedAR" IS NOT NULL AND on_approved_ar != "onApprovedAR" - ) incorrect_values, - COUNT(*) FILTER (WHERE on_approved_ar AND (NOT "onApprovedAR" OR "onApprovedAR" IS NULL) - ) should_be_marked_true_but_isnt, - COUNT(*) FILTER (WHERE NOT on_approved_ar AND ("onApprovedAR" OR "onApprovedAR" IS NULL) - ) marked_true_but_shouldnt_be, - COUNT(*) total_goals - FROM "Goals" g - JOIN goals_on_ars - ON g.id = gid - ; - -- 14. Final onAR stats for goals - DROP TABLE IF EXISTS final_goal_onar_stats; - CREATE TEMP TABLE final_goal_onar_stats - AS - SELECT - COUNT(*) FILTER (WHERE on_ar = "onAR" - ) matching_values, - COUNT(*) FILTER (WHERE "onAR" IS NOT NULL AND on_ar != "onAR" - ) incorrect_values, - COUNT(*) FILTER (WHERE on_ar AND (NOT "onAR" OR "onAR" IS NULL) - ) should_be_marked_true_but_isnt, - COUNT(*) FILTER (WHERE NOT on_ar AND ("onAR" OR "onAR" IS NULL) - ) marked_true_but_shouldnt_be, - COUNT(*) total_goals - FROM "Goals" g - JOIN goals_on_ars - ON g.id = gid - ; - -- make a nice little table to see the math - SELECT - 1 AS order, - 'objective onApprovedAR starting stats' description, - matching_values, - incorrect_values, - should_be_marked_true_but_isnt, - marked_true_but_shouldnt_be, - total_objectives total - FROM initial_obj_approved_ar_stats - UNION - SELECT - 2, - 'objective onApprovedAR values changed', - NULL, - NULL, - SUM(CASE WHEN on_approved_ar THEN 1 ELSE 0 END), - SUM(CASE WHEN NOT on_approved_ar THEN 1 ELSE 0 END), - COUNT(*) - FROM corrected_approved_objectives - UNION - SELECT 3,'objective onApprovedAR ending stats', * FROM final_obj_approved_ar_stats - UNION - SELECT 4,'objective onAR starting stats', * FROM initial_obj_onar_stats - UNION - SELECT - 5, - 'objective onAR values changed', - NULL, - NULL, - SUM(CASE WHEN on_ar THEN 1 ELSE 0 END), - SUM(CASE WHEN NOT on_ar THEN 1 ELSE 0 END), - COUNT(*) - FROM corrected_onar_objectives - UNION - SELECT 6,'objective onAR ending stats', * FROM final_obj_onar_stats - UNION - SELECT 7,'goal onApprovedAR starting stats', * FROM initial_goal_approved_ar_stats - UNION - SELECT - 8, - 'goal onApprovedAR values changed', - NULL, - NULL, - SUM(CASE WHEN on_approved_ar THEN 1 ELSE 0 END), - SUM(CASE WHEN NOT on_approved_ar THEN 1 ELSE 0 END), - COUNT(*) - FROM corrected_approved_goals - UNION - SELECT 9,'goal onApprovedAR ending stats', * FROM final_goal_approved_ar_stats - UNION - SELECT 10,'goal onAR starting stats', * FROM initial_goal_onar_stats - UNION - SELECT - 11, - 'goal onAR values changed', - NULL, - NULL, - SUM(CASE WHEN on_ar THEN 1 ELSE 0 END), - SUM(CASE WHEN NOT on_ar THEN 1 ELSE 0 END), - COUNT(*) - FROM corrected_onar_goals - UNION - SELECT 12,'goal onAR ending stats', * FROM final_goal_onar_stats - ORDER BY 1 - ; - - `, { transaction }); - }); - }, - - down: async () => { - }, -}; From f89fd6018a28baffb23852a7d5171b072006dd79 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 9 Jul 2024 09:35:33 -0400 Subject: [PATCH 21/26] fixes per Matt --- frontend/src/App.js | 3 +- .../src/components/DisplayWithPermission.js | 4 +- frontend/src/components/FeatureFlag.js | 4 +- frontend/src/components/SomethingWentWrong.js | 6 ++- .../src/components/SomethingWentWrong.scss | 3 +- .../__tests__/DisplayWithPermission.js | 3 +- .../src/components/__tests__/FeatureFlag.js | 3 +- frontend/src/pages/AccountManagement/Group.js | 13 ++---- .../AccountManagement/__tests__/Group.js | 43 +++++++++++-------- .../pages/RecipientRecord/__tests__/index.js | 20 +++++---- frontend/src/pages/RecipientRecord/index.js | 19 ++------ .../RecipientRecord/pages/CommunicationLog.js | 4 +- .../pages/RecipientRecord/pages/Profile.js | 2 +- .../pages/__tests__/CommunicationLog.js | 26 ++++++++--- 14 files changed, 84 insertions(+), 69 deletions(-) diff --git a/frontend/src/App.js b/frontend/src/App.js index efbf21c097..833bfea09b 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -22,7 +22,6 @@ import TrainingReports from './pages/TrainingReports'; import ResourcesDashboard from './pages/ResourcesDashboard'; import CourseDashboard from './pages/CourseDashboard'; import Unauthenticated from './pages/Unauthenticated'; -import NotFound from './pages/NotFound'; import Home from './pages/Home'; import Landing from './pages/Landing'; import ActivityReport from './pages/ActivityReport'; @@ -446,7 +445,7 @@ function App() { ( - + )} /> diff --git a/frontend/src/components/DisplayWithPermission.js b/frontend/src/components/DisplayWithPermission.js index a625ff5618..7fabca2e90 100644 --- a/frontend/src/components/DisplayWithPermission.js +++ b/frontend/src/components/DisplayWithPermission.js @@ -1,8 +1,8 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import NotFound from '../pages/NotFound'; import UserContext from '../UserContext'; import isAdmin from '../permissions'; +import SomethingWentWrong from './SomethingWentWrong'; export default function DisplayWithPermission({ scopes, renderNotFound, children, @@ -16,7 +16,7 @@ export default function DisplayWithPermission({ if (!admin && !userHasScope) { if (renderNotFound) { - return ; + return ; } return <>; } diff --git a/frontend/src/components/FeatureFlag.js b/frontend/src/components/FeatureFlag.js index 2a89054b4f..6a29671af4 100644 --- a/frontend/src/components/FeatureFlag.js +++ b/frontend/src/components/FeatureFlag.js @@ -1,8 +1,8 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; -import NotFound from '../pages/NotFound'; import UserContext from '../UserContext'; import isAdmin from '../permissions'; +import SomethingWentWrong from './SomethingWentWrong'; export default function FeatureFlag({ flag, renderNotFound, children, @@ -12,7 +12,7 @@ export default function FeatureFlag({ if (!admin && user.flags && !user.flags.includes(flag)) { if (renderNotFound) { - return ; + return ; } return <>; } diff --git a/frontend/src/components/SomethingWentWrong.js b/frontend/src/components/SomethingWentWrong.js index d40c06c7e0..3dcf1e1493 100644 --- a/frontend/src/components/SomethingWentWrong.js +++ b/frontend/src/components/SomethingWentWrong.js @@ -175,7 +175,11 @@ function SomethingWentWrong({ errorResponseCode }) { } SomethingWentWrong.propTypes = { - errorResponseCode: PropTypes.number.isRequired, + errorResponseCode: PropTypes.number, +}; + +SomethingWentWrong.defaultProps = { + errorResponseCode: 404, }; export default SomethingWentWrong; diff --git a/frontend/src/components/SomethingWentWrong.scss b/frontend/src/components/SomethingWentWrong.scss index 5f0e7691ce..89041ac9a0 100644 --- a/frontend/src/components/SomethingWentWrong.scss +++ b/frontend/src/components/SomethingWentWrong.scss @@ -11,7 +11,8 @@ font-weight: 700; } -.smart-hub--something-went-wrong p { +.smart-hub--something-went-wrong p, +.smart-hub--something-went-wrong ul li button { color: $base-dark; font-size: 1.25rem; font-weight: 400; diff --git a/frontend/src/components/__tests__/DisplayWithPermission.js b/frontend/src/components/__tests__/DisplayWithPermission.js index c8588d03f9..9b1099af10 100644 --- a/frontend/src/components/__tests__/DisplayWithPermission.js +++ b/frontend/src/components/__tests__/DisplayWithPermission.js @@ -72,6 +72,7 @@ describe('display with permissions', () => { }; const renderNotFound = true; renderDisplayWithPermission([READ_WRITE_TRAINING_REPORTS], user, renderNotFound); - expect(screen.getByRole('link', { name: /home page/i })).toBeVisible(); + expect(screen.getByRole('heading', { name: /404 error/i })).toBeVisible(); + expect(screen.getByRole('heading', { name: /page not found/i })).toBeVisible(); }); }); diff --git a/frontend/src/components/__tests__/FeatureFlag.js b/frontend/src/components/__tests__/FeatureFlag.js index ed8fd84203..b4594a5910 100644 --- a/frontend/src/components/__tests__/FeatureFlag.js +++ b/frontend/src/components/__tests__/FeatureFlag.js @@ -71,6 +71,7 @@ describe('feature flag', () => { }; const renderNotFound = true; renderFeatureFlag(flag, user, renderNotFound); - expect(screen.getByRole('link', { name: /home page/i })).toBeVisible(); + expect(screen.getByRole('heading', { name: /404 error/i })).toBeVisible(); + expect(screen.getByRole('heading', { name: /page not found/i })).toBeVisible(); }); }); diff --git a/frontend/src/pages/AccountManagement/Group.js b/frontend/src/pages/AccountManagement/Group.js index 1bb94dbad6..fa38ba8413 100644 --- a/frontend/src/pages/AccountManagement/Group.js +++ b/frontend/src/pages/AccountManagement/Group.js @@ -4,16 +4,15 @@ import { Helmet } from 'react-helmet'; import { Link } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; -import { Alert } from '@trussworks/react-uswds'; import colors from '../../colors'; import { fetchGroup } from '../../fetchers/groups'; import AppLoadingContext from '../../AppLoadingContext'; import WidgetCard from '../../components/WidgetCard'; import ReadOnlyField from '../../components/ReadOnlyField'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; export default function Group({ match }) { const { groupId } = match.params; - const [error, setError] = useState(null); const [group, setGroup] = useState({ name: '', @@ -21,6 +20,7 @@ export default function Group({ match }) { }); const { setIsAppLoading } = useContext(AppLoadingContext); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); useEffect(() => { async function getGroup() { @@ -29,7 +29,7 @@ export default function Group({ match }) { const existingGroupData = await fetchGroup(groupId); setGroup(existingGroupData); } catch (err) { - setError('There was an error fetching your group'); + setErrorResponseCode(err.status); } finally { setIsAppLoading(false); } @@ -39,7 +39,7 @@ export default function Group({ match }) { if (groupId) { getGroup(); } - }, [groupId, setIsAppLoading]); + }, [groupId, setIsAppLoading, setErrorResponseCode]); if (!group) { return null; @@ -85,11 +85,6 @@ export default function Group({ match }) { {group.name}} > - {error ? ( - - {error} - - ) : null} {group && group.creator ? group.creator.name : ''} diff --git a/frontend/src/pages/AccountManagement/__tests__/Group.js b/frontend/src/pages/AccountManagement/__tests__/Group.js index 5d1663e5ed..9ae5a29627 100644 --- a/frontend/src/pages/AccountManagement/__tests__/Group.js +++ b/frontend/src/pages/AccountManagement/__tests__/Group.js @@ -3,12 +3,14 @@ import { act, render, screen, + waitFor, } from '@testing-library/react'; import fetchMock from 'fetch-mock'; import join from 'url-join'; import { MemoryRouter } from 'react-router'; import Group from '../Group'; import AppLoadingContext from '../../../AppLoadingContext'; +import SomethingWentWrong from '../../../SomethingWentWrongContext'; const endpoint = join('/', 'api', 'groups'); @@ -17,11 +19,13 @@ describe('Group', () => { fetchMock.restore(); }); - const renderGroup = (groupId) => { + const renderGroup = (groupId, setErrorResponseCode = jest.fn()) => { render( - + + + , ); @@ -76,35 +80,36 @@ describe('Group', () => { it('handles null response', async () => { fetchMock.get(join(endpoint, '1'), null); - - act(() => { - renderGroup(1); + const setErrorResponseCode = jest.fn(); + act(async () => { + renderGroup(1, setErrorResponseCode); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalledWith(null); + }); }); - - const error = await screen.findByText('There was an error fetching your group'); - expect(error).toBeInTheDocument(); }); it('handles 404', async () => { fetchMock.get(join(endpoint, '1'), 404); - - act(() => { - renderGroup(1); + const setErrorResponseCode = jest.fn(); + act(async () => { + renderGroup(1, setErrorResponseCode); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalledWith(404); + }); }); - - const error = await screen.findByText('There was an error fetching your group'); - expect(error).toBeInTheDocument(); }); it('handles 500', async () => { fetchMock.get(join(endpoint, '1'), 500); - act(() => { - renderGroup(1); + const setErrorResponseCode = jest.fn(); + await act(async () => { + renderGroup(1, setErrorResponseCode); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalledWith(500); + }); }); - - const error = await screen.findByText('There was an error fetching your group'); - expect(error).toBeInTheDocument(); }); it('handles no group id', async () => { diff --git a/frontend/src/pages/RecipientRecord/__tests__/index.js b/frontend/src/pages/RecipientRecord/__tests__/index.js index b27808c553..02e2d9d93e 100644 --- a/frontend/src/pages/RecipientRecord/__tests__/index.js +++ b/frontend/src/pages/RecipientRecord/__tests__/index.js @@ -90,7 +90,7 @@ describe('recipient record page', () => { ], }; - function renderRecipientRecord(history = memoryHistory, regionId = '45') { + function renderRecipientRecord(history = memoryHistory, regionId = '45', setErrorResponseCode = jest.fn()) { const match = { path: '', url: '', @@ -102,7 +102,7 @@ describe('recipient record page', () => { render( - + { it('handles recipient not found', async () => { fetchMock.get('/api/recipient/1/region/45/merge-permissions', { canMergeGoalsForRecipient: false }); fetchMock.get('/api/recipient/1?region.in[]=45', 404); - act(() => renderRecipientRecord()); - const error = await screen.findByText('Recipient record not found'); - expect(error).toBeInTheDocument(); + const setErrorResponseCode = jest.fn(); + await act(async () => renderRecipientRecord(memoryHistory, '45', setErrorResponseCode)); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalledWith(404); + }); }); it('handles fetch error', async () => { fetchMock.get('/api/recipient/1/region/45/merge-permissions', { canMergeGoalsForRecipient: false }); fetchMock.get('/api/recipient/1?region.in[]=45', 500); - act(() => renderRecipientRecord()); - const error = await screen.findByText('There was an error fetching recipient data'); - expect(error).toBeInTheDocument(); + const setErrorResponseCode = jest.fn(); + act(() => renderRecipientRecord(memoryHistory, '45', setErrorResponseCode)); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalledWith(500); + }); }); it('navigates to the profile page', async () => { diff --git a/frontend/src/pages/RecipientRecord/index.js b/frontend/src/pages/RecipientRecord/index.js index 4f29bc68ec..03a7f6ac4a 100644 --- a/frontend/src/pages/RecipientRecord/index.js +++ b/frontend/src/pages/RecipientRecord/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import PropTypes from 'prop-types'; import ReactRouterPropTypes from 'react-router-prop-types'; import useDeepCompareEffect from 'use-deep-compare-effect'; @@ -8,7 +8,6 @@ import { Switch, Route } from 'react-router'; import { DECIMAL_BASE } from '@ttahub/common'; import { getMergeGoalPermissions, getRecipient } from '../../fetchers/recipient'; import RecipientTabs from './components/RecipientTabs'; -import { HTTPError } from '../../fetchers'; import './index.scss'; import Profile from './pages/Profile'; import TTAHistory from './pages/TTAHistory'; @@ -23,6 +22,7 @@ import CommunicationLogForm from './pages/CommunicationLogForm'; import ViewCommunicationLog from './pages/ViewCommunicationLog'; import { GrantDataProvider } from './pages/GrantDataContext'; import ViewGoals from './pages/ViewGoals'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; export function PageWithHeading({ children, @@ -34,7 +34,6 @@ export function PageWithHeading({ slug, }) { const headerMargin = backLink.props.children ? 'margin-top-0' : 'margin-top-5'; - return (
    @@ -77,6 +76,7 @@ PageWithHeading.defaultProps = { }; export default function RecipientRecord({ match, hasAlerts }) { + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const { recipientId, regionId } = match.params; const [loading, setLoading] = useState(true); @@ -92,7 +92,6 @@ export default function RecipientRecord({ match, hasAlerts }) { recipientName: '', }); - const [error, setError] = useState(); const [canMergeGoals, setCanMergeGoals] = useState(false); useEffect(() => { @@ -124,11 +123,7 @@ export default function RecipientRecord({ match, hasAlerts }) { }); } } catch (e) { - if (e instanceof HTTPError && e.status === 404) { - setError('Recipient record not found'); - } else { - setError('There was an error fetching recipient data'); - } + setErrorResponseCode(e.status); } finally { setLoading(false); } @@ -162,7 +157,6 @@ export default function RecipientRecord({ match, hasAlerts }) { @@ -202,7 +195,6 @@ export default function RecipientRecord({ match, hasAlerts }) { @@ -327,7 +318,6 @@ export default function RecipientRecord({ match, hasAlerts }) { @@ -345,7 +335,6 @@ export default function RecipientRecord({ match, hasAlerts }) { diff --git a/frontend/src/pages/RecipientRecord/pages/CommunicationLog.js b/frontend/src/pages/RecipientRecord/pages/CommunicationLog.js index 81c3532fd1..dc81637578 100644 --- a/frontend/src/pages/RecipientRecord/pages/CommunicationLog.js +++ b/frontend/src/pages/RecipientRecord/pages/CommunicationLog.js @@ -19,6 +19,7 @@ import { methodFilter, resultFilter, } from '../../../components/filter/communicationLogFilters'; +import SomethingWentWrongContext from '../../../SomethingWentWrongContext'; const COMMUNICATION_LOG_PER_PAGE = 10; const FILTER_KEY = 'communication-log-filters'; @@ -43,6 +44,7 @@ export default function CommunicationLog({ regionId, recipientId }) { }); const { user } = useContext(UserContext); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const { setIsAppLoading } = useContext(AppLoadingContext); const { @@ -71,7 +73,7 @@ export default function CommunicationLog({ regionId, recipientId }) { setLogs(response); } catch (err) { - setError('Error fetching communication logs'); + setErrorResponseCode(err.status); } finally { setIsAppLoading(false); } diff --git a/frontend/src/pages/RecipientRecord/pages/Profile.js b/frontend/src/pages/RecipientRecord/pages/Profile.js index 960c453bf8..c3211bcd92 100644 --- a/frontend/src/pages/RecipientRecord/pages/Profile.js +++ b/frontend/src/pages/RecipientRecord/pages/Profile.js @@ -15,7 +15,7 @@ export default function Profile({ regionId, recipientId, }) { - const activeGrants = recipientSummary.grants.filter((grant) => grant.status === 'Active'); + const activeGrants = (recipientSummary.grants || []).filter((grant) => grant.status === 'Active'); const { hasMonitoringData, hasClassData } = useGrantData(); return ( diff --git a/frontend/src/pages/RecipientRecord/pages/__tests__/CommunicationLog.js b/frontend/src/pages/RecipientRecord/pages/__tests__/CommunicationLog.js index c59d88ec32..125d959167 100644 --- a/frontend/src/pages/RecipientRecord/pages/__tests__/CommunicationLog.js +++ b/frontend/src/pages/RecipientRecord/pages/__tests__/CommunicationLog.js @@ -12,18 +12,21 @@ import CommunicationLog from '../CommunicationLog'; import AppLoadingContext from '../../../../AppLoadingContext'; import UserContext from '../../../../UserContext'; import AriaLiveContext from '../../../../AriaLiveContext'; +import SomethingWentWrongContext from '../../../../SomethingWentWrongContext'; describe('CommunicationLog', () => { const history = createMemoryHistory(); - const renderTest = () => { + const renderTest = (setErrorResponseCode = jest.fn) => { render( {} }}> {} }}> - - - - - + + + + + + + , ); @@ -90,4 +93,15 @@ describe('CommunicationLog', () => { await waitFor(() => expect(fetchMock.called(filteredUrl)).toBe(true)); }); + + it('correctly calls setErrorResponseCode when a 500 is returned', async () => { + fetchMock.get('/api/communication-logs/region/5/recipient/1?sortBy=communicationDate&direction=desc&offset=0&limit=10&format=json&result.in[]=RTTAPA%20declined', 500); + const setErrorResponseCode = jest.fn(); + await act(async () => { + renderTest(setErrorResponseCode); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalledWith(500); + }); + }); + }); }); From de264e9600a8bfeecff7bed3ad11f4a02cfacc27 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 9 Jul 2024 11:37:06 -0400 Subject: [PATCH 22/26] cleanup and fixes --- .../src/pages/AccountManagement/MyGroups.js | 6 ++- .../AccountManagement/__tests__/MyGroups.js | 28 ++++++------ .../pages/RecipientRecord/__tests__/index.js | 5 ++- .../RecipientRecord/pages/CommunicationLog.js | 4 +- .../ViewCommunicationLog/__tests__/index.js | 45 ++++++++++--------- .../pages/ViewCommunicationLog/index.js | 20 +++------ .../pages/__tests__/CommunicationLog.js | 26 +++-------- 7 files changed, 60 insertions(+), 74 deletions(-) diff --git a/frontend/src/pages/AccountManagement/MyGroups.js b/frontend/src/pages/AccountManagement/MyGroups.js index 73870a2194..a1dceb8fbc 100644 --- a/frontend/src/pages/AccountManagement/MyGroups.js +++ b/frontend/src/pages/AccountManagement/MyGroups.js @@ -21,6 +21,7 @@ import { import { MyGroupsContext } from '../../components/MyGroupsProvider'; import AppLoadingContext from '../../AppLoadingContext'; import QuestionTooltip from '../../components/GoalForm/QuestionTooltip'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; const mapSelectedRecipients = (grants) => grants.map((grant) => ({ value: grant.id, @@ -73,6 +74,7 @@ export default function MyGroups({ match }) { }, }); const { user } = useContext(UserContext); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const watchIsPrivate = watch(GROUP_FIELD_NAMES.IS_PRIVATE); const watchShareWithEveryone = watch(GROUP_FIELD_NAMES.SHARE_WITH_EVERYONE); const watchCoOwners = watch(GROUP_FIELD_NAMES.CO_OWNERS); @@ -112,7 +114,7 @@ export default function MyGroups({ match }) { }); } } catch (err) { - setError('There was an error fetching your group'); + setErrorResponseCode(err.status); } finally { setIsAppLoading(false); } @@ -122,7 +124,7 @@ export default function MyGroups({ match }) { if (groupId && usersFetched && recipientsFetched) { getGroup(); } - }, [groupId, setIsAppLoading, reset, usersFetched, recipientsFetched]); + }, [groupId, setIsAppLoading, reset, usersFetched, recipientsFetched, setErrorResponseCode]); const isCreator = !groupId || (groupCreator && user.id === groupCreator.id); diff --git a/frontend/src/pages/AccountManagement/__tests__/MyGroups.js b/frontend/src/pages/AccountManagement/__tests__/MyGroups.js index c61ea5508c..9516cd8f71 100644 --- a/frontend/src/pages/AccountManagement/__tests__/MyGroups.js +++ b/frontend/src/pages/AccountManagement/__tests__/MyGroups.js @@ -14,6 +14,7 @@ import MyGroups, { GROUP_FIELD_NAMES } from '../MyGroups'; import MyGroupsProvider from '../../../components/MyGroupsProvider'; import AppLoadingContext from '../../../AppLoadingContext'; import UserContext from '../../../UserContext'; +import SomethingWentWrongContext from '../../../SomethingWentWrongContext'; const error = 'This group name already exists, please use a different name'; @@ -22,15 +23,17 @@ const user = { }; describe('MyGroups', () => { - const renderMyGroups = (groupId = null) => { + const renderMyGroups = (groupId = null, setErrorResponseCode = jest.fn()) => { render( - - - - - + + + + + + + , ); @@ -214,13 +217,12 @@ describe('MyGroups', () => { it('handles fetch errors', async () => { fetchMock.get('/api/group/1', 500); - - act(() => { - renderMyGroups(1); - }); - - await waitFor(() => { - expect(screen.getByText(/There was an error fetching your group/i)).toBeInTheDocument(); + const setErrorResponseCode = jest.fn(); + await act(async () => { + renderMyGroups(1, setErrorResponseCode); + await waitFor(() => { + expect(setErrorResponseCode).toHaveBeenCalled(); + }); }); }); diff --git a/frontend/src/pages/RecipientRecord/__tests__/index.js b/frontend/src/pages/RecipientRecord/__tests__/index.js index 02e2d9d93e..f017adce5e 100644 --- a/frontend/src/pages/RecipientRecord/__tests__/index.js +++ b/frontend/src/pages/RecipientRecord/__tests__/index.js @@ -275,9 +275,10 @@ describe('recipient record page', () => { fetchMock.get('/api/recipient/1?region.in[]=45', theMightyRecipient); fetchMock.get('/api/communication-logs/region/1/log/1', 404); memoryHistory.push('/recipient-tta-records/45/region/1/communication/1/view'); - act(() => renderRecipientRecord()); + const setErrorResponseCode = jest.fn(); + act(() => renderRecipientRecord(memoryHistory, '45', setErrorResponseCode)); await waitFor(() => expect(screen.queryByText(/loading.../)).toBeNull()); - await screen.findByText(/There was an error fetching the communication log/i); + await waitFor(() => expect(setErrorResponseCode).toHaveBeenCalledWith(404)); }); it('navigates to the communication log form', async () => { diff --git a/frontend/src/pages/RecipientRecord/pages/CommunicationLog.js b/frontend/src/pages/RecipientRecord/pages/CommunicationLog.js index dc81637578..81c3532fd1 100644 --- a/frontend/src/pages/RecipientRecord/pages/CommunicationLog.js +++ b/frontend/src/pages/RecipientRecord/pages/CommunicationLog.js @@ -19,7 +19,6 @@ import { methodFilter, resultFilter, } from '../../../components/filter/communicationLogFilters'; -import SomethingWentWrongContext from '../../../SomethingWentWrongContext'; const COMMUNICATION_LOG_PER_PAGE = 10; const FILTER_KEY = 'communication-log-filters'; @@ -44,7 +43,6 @@ export default function CommunicationLog({ regionId, recipientId }) { }); const { user } = useContext(UserContext); - const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const { setIsAppLoading } = useContext(AppLoadingContext); const { @@ -73,7 +71,7 @@ export default function CommunicationLog({ regionId, recipientId }) { setLogs(response); } catch (err) { - setErrorResponseCode(err.status); + setError('Error fetching communication logs'); } finally { setIsAppLoading(false); } diff --git a/frontend/src/pages/RecipientRecord/pages/ViewCommunicationLog/__tests__/index.js b/frontend/src/pages/RecipientRecord/pages/ViewCommunicationLog/__tests__/index.js index 48fa850182..e91554ed39 100644 --- a/frontend/src/pages/RecipientRecord/pages/ViewCommunicationLog/__tests__/index.js +++ b/frontend/src/pages/RecipientRecord/pages/ViewCommunicationLog/__tests__/index.js @@ -10,6 +10,7 @@ import UserContext from '../../../../../UserContext'; import AppLoadingContext from '../../../../../AppLoadingContext'; import { NOT_STARTED, COMPLETE } from '../../../../../components/Navigator/constants'; import ViewCommunicationForm from '../index'; +import SomethingWentWrongContext from '../../../../../SomethingWentWrongContext'; const RECIPIENT_ID = 1; const REGION_ID = 1; @@ -26,23 +27,26 @@ describe('ViewCommunicationForm', () => { const renderTest = ( communicationLogId = '1', + setErrorResponseCode = jest.fn(), ) => render( - - - + + + + + , ); @@ -103,13 +107,14 @@ describe('ViewCommunicationForm', () => { it('shows error message', async () => { const url = `${communicationLogUrl}/region/${REGION_ID}/log/1`; + const setErrorResponseCode = jest.fn(); fetchMock.get(url, 500); - - await act(() => waitFor(() => { - renderTest(); - })); - - expect(await screen.findByText(/There was an error fetching the communication log/i)).toBeInTheDocument(); + await act(async () => { + await waitFor(() => { + renderTest('1', setErrorResponseCode); + expect(setErrorResponseCode).toHaveBeenCalledWith(500); + }); + }); }); it('should render the view without edit button', async () => { diff --git a/frontend/src/pages/RecipientRecord/pages/ViewCommunicationLog/index.js b/frontend/src/pages/RecipientRecord/pages/ViewCommunicationLog/index.js index a1a93cf2ff..65d972fad9 100644 --- a/frontend/src/pages/RecipientRecord/pages/ViewCommunicationLog/index.js +++ b/frontend/src/pages/RecipientRecord/pages/ViewCommunicationLog/index.js @@ -2,7 +2,6 @@ import React, { useEffect, useState, useContext } from 'react'; import moment from 'moment'; import PropTypes from 'prop-types'; import { Helmet } from 'react-helmet'; -import { Alert } from '@trussworks/react-uswds'; import ReactRouterPropTypes from 'react-router-prop-types'; import { Link } from 'react-router-dom'; import Container from '../../../../components/Container'; @@ -13,6 +12,7 @@ import BackLink from '../../../../components/BackLink'; import UserContext from '../../../../UserContext'; import DisplayNextSteps from './components/DisplayNextSteps'; import LogLine from './components/LogLine'; +import SomethingWentWrongContext from '../../../../SomethingWentWrongContext'; export default function ViewCommunicationLog({ match, recipientName }) { const { @@ -24,9 +24,9 @@ export default function ViewCommunicationLog({ match, recipientName }) { } = match; const { user } = useContext(UserContext); + const { setErrorResponseCode } = useContext(SomethingWentWrongContext); const { setIsAppLoading } = useContext(AppLoadingContext); const [log, setLog] = useState(); - const [error, setError] = useState(); const isAuthor = log && log.author && log.author.id === user.id; @@ -37,29 +37,21 @@ export default function ViewCommunicationLog({ match, recipientName }) { const response = await getCommunicationLogById(regionId, communicationLogId); setLog(response); } catch (err) { - setError('There was an error fetching the communication log.'); + setErrorResponseCode(err.status); } finally { setIsAppLoading(false); } } - if (!log && !error) { + if (!log) { fetchLog(); } - }, [communicationLogId, error, log, regionId, setIsAppLoading]); + }, [communicationLogId, log, regionId, setIsAppLoading, setErrorResponseCode]); - if (!log && !error) { + if (!log) { return null; } - if (error) { - return ( - - {error} - - ); - } - return ( <> diff --git a/frontend/src/pages/RecipientRecord/pages/__tests__/CommunicationLog.js b/frontend/src/pages/RecipientRecord/pages/__tests__/CommunicationLog.js index 125d959167..c59d88ec32 100644 --- a/frontend/src/pages/RecipientRecord/pages/__tests__/CommunicationLog.js +++ b/frontend/src/pages/RecipientRecord/pages/__tests__/CommunicationLog.js @@ -12,21 +12,18 @@ import CommunicationLog from '../CommunicationLog'; import AppLoadingContext from '../../../../AppLoadingContext'; import UserContext from '../../../../UserContext'; import AriaLiveContext from '../../../../AriaLiveContext'; -import SomethingWentWrongContext from '../../../../SomethingWentWrongContext'; describe('CommunicationLog', () => { const history = createMemoryHistory(); - const renderTest = (setErrorResponseCode = jest.fn) => { + const renderTest = () => { render( {} }}> {} }}> - - - - - - - + + + + + , ); @@ -93,15 +90,4 @@ describe('CommunicationLog', () => { await waitFor(() => expect(fetchMock.called(filteredUrl)).toBe(true)); }); - - it('correctly calls setErrorResponseCode when a 500 is returned', async () => { - fetchMock.get('/api/communication-logs/region/5/recipient/1?sortBy=communicationDate&direction=desc&offset=0&limit=10&format=json&result.in[]=RTTAPA%20declined', 500); - const setErrorResponseCode = jest.fn(); - await act(async () => { - renderTest(setErrorResponseCode); - await waitFor(() => { - expect(setErrorResponseCode).toHaveBeenCalledWith(500); - }); - }); - }); }); From eaa0bed6dafd52d9a53e6925c548eb59a855b9e5 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 9 Jul 2024 13:31:18 -0400 Subject: [PATCH 23/26] see if this fixes it --- cucumber/features/notFound.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber/features/notFound.feature b/cucumber/features/notFound.feature index 8db1b71901..c6eb900e0a 100644 --- a/cucumber/features/notFound.feature +++ b/cucumber/features/notFound.feature @@ -2,4 +2,4 @@ Feature: Not Found Page Scenario: User is shown a 404 page if route is not found Given I am logged in And I go to an unknown page - Then I see the "Not Found" alert message + Then I see the "404 error" alert message From 5690da46c2cc66b7d1a8b3f1f9871e4566234b17 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 9 Jul 2024 13:36:09 -0400 Subject: [PATCH 24/26] find class --- cucumber/features/steps/notFoundSteps.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cucumber/features/steps/notFoundSteps.js b/cucumber/features/steps/notFoundSteps.js index 7bce974b2b..17976cb707 100644 --- a/cucumber/features/steps/notFoundSteps.js +++ b/cucumber/features/steps/notFoundSteps.js @@ -12,7 +12,10 @@ Given('I go to an unknown page', async () => { Then('I see the {string} alert message', async (heading) => { const page = scope.context.currentPage; - const value = await page.$eval('.usa-alert__heading', (el) => el.textContent); - + // find a div with the class 'smart-hub--something-went-wrong'.. + const value = await page.evaluate(() => { + const element = document.querySelector('.smart-hub--something-went-wrong'); + return element ? element.innerText : ''; + }); assertTrue(value.includes(heading)); }); From ee860fb5013a5f876f302d31f67f387e7779cda2 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Tue, 9 Jul 2024 15:44:38 -0400 Subject: [PATCH 25/26] fixes per ux review with Kelly --- frontend/src/App.js | 9 ++- frontend/src/components/SomethingWentWrong.js | 67 +++++-------------- .../src/components/SomethingWentWrong.scss | 7 +- .../__tests__/DisplayWithPermission.js | 16 ++++- .../src/components/__tests__/FeatureFlag.js | 15 ++++- .../__tests__/SomethingWentWrong.js | 17 +++-- 6 files changed, 68 insertions(+), 63 deletions(-) diff --git a/frontend/src/App.js b/frontend/src/App.js index 833bfea09b..a0c4bfb219 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -80,6 +80,7 @@ function App() { const [areThereUnreadNotifications, setAreThereUnreadNotifications] = useState(false); const [errorResponseCode, setErrorResponseCode] = useState(null); + const [showingNotFound, setShowingNotFound] = useState(false); useGaUserData(user); @@ -461,12 +462,14 @@ function App() { - {authenticated && !errorResponseCode && ( + {authenticated && !errorResponseCode && !showingNotFound && ( <> Skip to main content @@ -504,7 +507,7 @@ function App() { {authenticated && errorResponseCode && ( - + )} {authenticated && !errorResponseCode && renderAuthenticatedRoutes()} diff --git a/frontend/src/components/SomethingWentWrong.js b/frontend/src/components/SomethingWentWrong.js index 3dcf1e1493..d579905a6c 100644 --- a/frontend/src/components/SomethingWentWrong.js +++ b/frontend/src/components/SomethingWentWrong.js @@ -8,14 +8,19 @@ import './SomethingWentWrong.scss'; /* eslint-disable max-len */ -function SomethingWentWrong({ errorResponseCode }) { - const { setErrorResponseCode } = useContext(SomethingWentWrongContext); +function SomethingWentWrong({ passedErrorResponseCode }) { + const { + setErrorResponseCode, errorResponseCode, setShowingNotFound, showingNotFound, + } = useContext(SomethingWentWrongContext); const { setIsAppLoading, isAppLoading } = useContext(AppLoadingContext); const history = useHistory(); // Make sure if something was loading when an error occurred, we stop the loading spinner. if (isAppLoading) setIsAppLoading(false); + // Make sure if we are showing not found we hide the NAV. + if (!errorResponseCode && (!showingNotFound && passedErrorResponseCode === 404)) setShowingNotFound(true); + const supportLink = 'https://app.smartsheetgov.com/b/form/f0b4725683f04f349a939bd2e3f5425a'; const getSupportLink = () => ( support @@ -23,52 +28,15 @@ function SomethingWentWrong({ errorResponseCode }) { const onHomeClick = () => { setErrorResponseCode(null); + setShowingNotFound(false); history.push('/'); }; const responseCodeMessages = [ { - code: 401, - message: '401 error - unauthorized', - title: 'Unauthorized access', - body: ( -

    - Sorry, but it looks like you're trying to access a restricted area. Here's what you can do: -

      -
    • - Double-check permissions: - {' '} - Ensure you have the proper clearance to access this page -
      - Contact - {' '} - {getSupportLink()} - {' '} - and ask them to check your permissions. -
      -
    • -
    • - Login again: - {' '} - Try logging in again. Maybe that's the missing key. -
    • -
    • - Explore elsewhere: - {' '} - Return to the main area and explore other permitted sections. -
    • -
    - If you believe this is an error or need further assistance, get in touch with - {' '} - {getSupportLink()} - . -

    - ), - }, - { - code: 403, + codes: [401, 403], message: '403 error - forbidden', - title: 'Restricted access', + title: 'Restricted access.', body: (

    Sorry, but it looks like you're trying to access a restricted area. Here's what you can do: @@ -104,9 +72,9 @@ function SomethingWentWrong({ errorResponseCode }) { ), }, { - code: 404, + codes: [404], message: '404 error', - title: 'Page not found', + title: 'Page not found.', body: (

    Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do: @@ -129,9 +97,9 @@ function SomethingWentWrong({ errorResponseCode }) { ), }, { - code: 500, + codes: [500], message: null, - title: 'Something went wrong', + title: 'Something went wrong.', body: (

    Well, this is awkward. It seems like the page you're looking for has taken a detour into the unknown. Here's what you can do: @@ -152,10 +120,11 @@ function SomethingWentWrong({ errorResponseCode }) { Thanks for your understanding and patience!

    ), + }, ]; - const messageToDisplay = responseCodeMessages.find((msg) => msg.code === errorResponseCode) || responseCodeMessages.find((msg) => msg.code === 500); + const messageToDisplay = responseCodeMessages.find((msg) => msg.codes.includes(passedErrorResponseCode)) || responseCodeMessages.find((msg) => msg.code === 500); return (
    @@ -175,11 +144,11 @@ function SomethingWentWrong({ errorResponseCode }) { } SomethingWentWrong.propTypes = { - errorResponseCode: PropTypes.number, + passedErrorResponseCode: PropTypes.number, }; SomethingWentWrong.defaultProps = { - errorResponseCode: 404, + passedErrorResponseCode: 404, }; export default SomethingWentWrong; diff --git a/frontend/src/components/SomethingWentWrong.scss b/frontend/src/components/SomethingWentWrong.scss index 89041ac9a0..3f7c0b2e61 100644 --- a/frontend/src/components/SomethingWentWrong.scss +++ b/frontend/src/components/SomethingWentWrong.scss @@ -9,11 +9,16 @@ .smart-hub--something-went-wrong h1 { font-size: 2.5rem; font-weight: 700; + font-family: Merriweather, serif; +} + +.smart-hub--something-went-wrong-body { + max-width: 700px; } .smart-hub--something-went-wrong p, .smart-hub--something-went-wrong ul li button { - color: $base-dark; + color: $base-darkest; font-size: 1.25rem; font-weight: 400; } diff --git a/frontend/src/components/__tests__/DisplayWithPermission.js b/frontend/src/components/__tests__/DisplayWithPermission.js index 9b1099af10..e3863ad3a8 100644 --- a/frontend/src/components/__tests__/DisplayWithPermission.js +++ b/frontend/src/components/__tests__/DisplayWithPermission.js @@ -8,6 +8,7 @@ import { Router } from 'react-router'; import { createMemoryHistory } from 'history'; import DisplayWithPermission from '../DisplayWithPermission'; import UserContext from '../../UserContext'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; const { ADMIN, READ_WRITE_TRAINING_REPORTS, READ_ACTIVITY_REPORTS } = SCOPE_IDS; @@ -18,9 +19,18 @@ describe('display with permissions', () => { render( - -

    This is a test

    -
    + + + +

    This is a test

    +
    +
    , ); diff --git a/frontend/src/components/__tests__/FeatureFlag.js b/frontend/src/components/__tests__/FeatureFlag.js index b4594a5910..44ec216763 100644 --- a/frontend/src/components/__tests__/FeatureFlag.js +++ b/frontend/src/components/__tests__/FeatureFlag.js @@ -8,6 +8,7 @@ import { Router } from 'react-router'; import { createMemoryHistory } from 'history'; import FeatureFlag from '../FeatureFlag'; import UserContext from '../../UserContext'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; const { ADMIN } = SCOPE_IDS; @@ -18,9 +19,17 @@ describe('feature flag', () => { render( - -

    This is a test

    -
    + + +

    This is a test

    +
    +
    , ); diff --git a/frontend/src/components/__tests__/SomethingWentWrong.js b/frontend/src/components/__tests__/SomethingWentWrong.js index 3902eaf977..c3ed69b7ae 100644 --- a/frontend/src/components/__tests__/SomethingWentWrong.js +++ b/frontend/src/components/__tests__/SomethingWentWrong.js @@ -3,6 +3,7 @@ import { render, screen } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router'; import SomethingWentWrong from '../SomethingWentWrong'; +import SomethingWentWrongContext from '../../SomethingWentWrongContext'; const history = createMemoryHistory(); @@ -10,7 +11,15 @@ const renderSomethingWentWrong = ( responseCode = 500, ) => render( - + + + , ); @@ -19,8 +28,8 @@ describe('SomethingWentWrong component', () => { it('renders a 401 error message', async () => { renderSomethingWentWrong(401); - expect(screen.getByText('401 error - unauthorized')).toBeInTheDocument(); - expect(screen.getByRole('heading', { name: /unauthorized access/i })).toBeInTheDocument(); + expect(screen.getByText('403 error - forbidden')).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /restricted access/i })).toBeInTheDocument(); expect(screen.getByText(/Sorry, but it looks like you're trying to access a restricted area./i)).toBeInTheDocument(); expect(screen.getByText(/Double-check permissions:/i)).toBeInTheDocument(); expect(screen.getByText(/Ensure you have the proper clearance to access this page/i)).toBeInTheDocument(); @@ -52,7 +61,7 @@ describe('SomethingWentWrong component', () => { renderSomethingWentWrong(404); expect(screen.getByText('404 error')).toBeInTheDocument(); - expect(screen.getByText('Page not found')).toBeInTheDocument(); + expect(screen.getByText('Page not found.')).toBeInTheDocument(); expect(screen.getByText(/Well, this is awkward. It seems like the page/i)).toBeInTheDocument(); expect(screen.getByText(/home/i)).toBeInTheDocument(); expect(screen.getByText(/support/i)).toBeInTheDocument(); From accb65e990f7017fa640817d4654f07a7e7fbb87 Mon Sep 17 00:00:00 2001 From: Adam Levin Date: Wed, 10 Jul 2024 09:42:23 -0400 Subject: [PATCH 26/26] fix link colors --- frontend/src/components/SomethingWentWrong.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frontend/src/components/SomethingWentWrong.scss b/frontend/src/components/SomethingWentWrong.scss index 3f7c0b2e61..af1e4ae6f1 100644 --- a/frontend/src/components/SomethingWentWrong.scss +++ b/frontend/src/components/SomethingWentWrong.scss @@ -6,6 +6,16 @@ font-weight: 700; } +.smart-hub--something-went-wrong .smart-hub--something-went-wrong-body a, +.smart-hub--something-went-wrong .smart-hub--something-went-wrong-body button + { + color: $text-link; +} + +.smart-hub--something-went-wrong .smart-hub--something-went-wrong-body a:visited { + color: $text-visited; +} + .smart-hub--something-went-wrong h1 { font-size: 2.5rem; font-weight: 700;