diff --git a/colorsjschecksum b/colorsjschecksum
index d9761a45bb..15e52b0835 100644
--- a/colorsjschecksum
+++ b/colorsjschecksum
@@ -1 +1 @@
-69962956f5817a804f1a491395986eb3c612a6feb4d76315f58c5b273cdf25a7
\ No newline at end of file
+5dfc9c0a9f719af9ac72655dee86b64b9122ee7cda857e6ac68ae4a3dc76ade6
\ 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/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
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));
});
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 1c94bedc19..a0c4bfb219 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';
@@ -10,6 +12,7 @@ import { HTTPError } from './fetchers';
import { getSiteAlerts } from './fetchers/siteAlerts';
import FeatureFlag from './components/FeatureFlag';
import UserContext from './UserContext';
+import SomethingWentWrongContext from './SomethingWentWrongContext';
import SiteNav from './components/SiteNav';
import Header from './components/Header';
@@ -19,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';
@@ -58,6 +60,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 +79,8 @@ function App() {
const [notifications, setNotifications] = useState({ whatsNew: '' });
const [areThereUnreadNotifications, setAreThereUnreadNotifications] = useState(false);
+ const [errorResponseCode, setErrorResponseCode] = useState(null);
+ const [showingNotFound, setShowingNotFound] = useState(false);
useGaUserData(user);
@@ -441,7 +446,7 @@ function App() {
(
-
+
)}
/>
@@ -456,9 +461,15 @@ function App() {
-
-
- {authenticated && (
+
+
+
+ {authenticated && !errorResponseCode && !showingNotFound && (
<>
Skip to main content
@@ -475,30 +486,37 @@ function App() {
/>
>
- )}
-
-
-
-
- {!authenticated && (authError === 403
- ?
- : (
-
-
+ )}
+
+
+
+
+ {!authenticated && (authError === 403
+ ?
+ : (
+
+
+
+ )
+ )}
+ {authenticated && errorResponseCode
+ && (
+
+
- )
- )}
- {authenticated && renderAuthenticatedRoutes()}
-
-
-
-
-
+ )}
+ {authenticated && !errorResponseCode && renderAuthenticatedRoutes()}
+
+
+
+
+
+
>
);
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/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
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/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/components/SomethingWentWrong.js b/frontend/src/components/SomethingWentWrong.js
new file mode 100644
index 0000000000..d579905a6c
--- /dev/null
+++ b/frontend/src/components/SomethingWentWrong.js
@@ -0,0 +1,154 @@
+import React, { useContext } from 'react';
+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({ 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
+ );
+
+ const onHomeClick = () => {
+ setErrorResponseCode(null);
+ setShowingNotFound(false);
+ history.push('/');
+ };
+
+ const responseCodeMessages = [
+ {
+ codes: [401, 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:
+
+ If you believe this is an error or need further assistance, get in touch with
+ {' '}
+ {getSupportLink()}
+ .
+
+ ),
+ },
+ {
+ codes: [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
+ {' '}
+
+
+ -
+ Contact
+ {' '}
+ {getSupportLink()}
+ {' '}
+ for help
+
+
+ Thanks for your understanding and patience!
+
+ ),
+ },
+ {
+ codes: [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!
+
+ ),
+
+ },
+ ];
+
+ const messageToDisplay = responseCodeMessages.find((msg) => msg.codes.includes(passedErrorResponseCode)) || responseCodeMessages.find((msg) => msg.code === 500);
+
+ return (
+
+ {
+ messageToDisplay.message && (
+
{messageToDisplay.message}
+ )
+ }
+
{messageToDisplay.title}
+
+ {
+ messageToDisplay.body
+ }
+
+
+ );
+}
+
+SomethingWentWrong.propTypes = {
+ passedErrorResponseCode: PropTypes.number,
+};
+
+SomethingWentWrong.defaultProps = {
+ passedErrorResponseCode: 404,
+};
+
+export default SomethingWentWrong;
diff --git a/frontend/src/components/SomethingWentWrong.scss b/frontend/src/components/SomethingWentWrong.scss
new file mode 100644
index 0000000000..af1e4ae6f1
--- /dev/null
+++ b/frontend/src/components/SomethingWentWrong.scss
@@ -0,0 +1,38 @@
+@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 .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;
+ 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-darkest;
+ 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/components/__tests__/DisplayWithPermission.js b/frontend/src/components/__tests__/DisplayWithPermission.js
index c8588d03f9..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
+
+
,
);
@@ -72,6 +82,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..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
+
+
,
);
@@ -71,6 +80,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/components/__tests__/SomethingWentWrong.js b/frontend/src/components/__tests__/SomethingWentWrong.js
new file mode 100644
index 0000000000..c3ed69b7ae
--- /dev/null
+++ b/frontend/src/components/__tests__/SomethingWentWrong.js
@@ -0,0 +1,78 @@
+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 SomethingWentWrongContext from '../../SomethingWentWrongContext';
+
+const history = createMemoryHistory();
+
+const renderSomethingWentWrong = (
+ 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('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();
+ 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);
+
+ 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();
+ 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();
+ 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/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/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__/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/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/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 e8902bc549..41bded708a 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 SomethingWentWrongContext from '../../SomethingWentWrongContext';
const defaultValues = {
ECLKCResourcesUsed: [],
@@ -202,6 +203,7 @@ function ActivityReport({
const [creatorNameWithRole, updateCreatorRoleWithName] = useState('');
const reportId = useRef();
const { user } = useContext(UserContext);
+ const { setErrorResponseCode } = useContext(SomethingWentWrongContext);
const {
socket,
@@ -256,7 +258,13 @@ function ActivityReport({
reportId.current = activityReportId;
if (activityReportId !== 'new') {
- const fetchedReport = await getReport(activityReportId);
+ let fetchedReport;
+ try {
+ fetchedReport = await getReport(activityReportId);
+ } catch (e) {
+ // If error retrieving the report show the "something went wrong" page.
+ setErrorResponseCode(e.status);
+ }
report = convertReportToFormData(fetchedReport);
} else {
report = {
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..4f3cadcd63 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());
@@ -147,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,
@@ -204,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 () => {
@@ -224,18 +229,22 @@ describe('Activity report print and share view', () => {
});
it('handles authorization errors', async () => {
- act(() => renderApprovedActivityReport(5007));
+ const setErrorResponseCode = jest.fn();
+ act(() => renderApprovedActivityReport(5007, user, setErrorResponseCode));
await waitFor(() => {
- expect(screen.getByText(/sorry, you are not allowed to view this report/i)).toBeInTheDocument();
+ expect(fetchMock.called('/api/activity-reports/5007')).toBeTruthy();
+ expect(setErrorResponseCode).toHaveBeenCalledWith(401);
});
});
it('handles data errors', async () => {
- act(() => renderApprovedActivityReport(5002));
+ const setErrorResponseCode = jest.fn();
+ act(() => renderApprovedActivityReport(5002, user, setErrorResponseCode));
await waitFor(() => {
- expect(screen.getByText(/sorry, something went wrong\./i)).toBeInTheDocument();
+ expect(fetchMock.called('/api/activity-reports/5002')).toBeTruthy();
+ expect(setErrorResponseCode).toHaveBeenCalledWith(500);
});
});
@@ -306,17 +315,13 @@ 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 () => {
- 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);
+ });
});
});
@@ -338,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/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,
diff --git a/frontend/src/pages/RecipientRecord/__tests__/index.js b/frontend/src/pages/RecipientRecord/__tests__/index.js
index 46fe31fe71..f017adce5e 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';
@@ -89,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: '',
@@ -101,20 +102,22 @@ describe('recipient record page', () => {
render(
-
-
-
+
+
+
-
-
-
-
+ >
+
+
+
+
+
,
);
}
@@ -192,17 +195,21 @@ describe('recipient record page', () => {
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 () => {
@@ -248,7 +255,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);
});
@@ -268,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/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/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/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/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/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
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 () => {
+ },
+};
diff --git a/src/models/hooks/resource.js b/src/models/hooks/resource.js
index a7dd8c4d9c..65817805e2 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 {
diff --git a/src/models/hooks/resource.test.js b/src/models/hooks/resource.test.js
deleted file mode 100644
index 6f57981c92..0000000000
--- a/src/models/hooks/resource.test.js
+++ /dev/null
@@ -1,43 +0,0 @@
-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');
- });
- });
-});