From 223030d76eecf18f8641901807481aa074842e27 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 10 Nov 2021 13:18:24 -0500 Subject: [PATCH 01/13] add approvedat migration --- .../20211110181535-add-ar-approved-date.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/migrations/20211110181535-add-ar-approved-date.js diff --git a/src/migrations/20211110181535-add-ar-approved-date.js b/src/migrations/20211110181535-add-ar-approved-date.js new file mode 100644 index 0000000000..68f864b31e --- /dev/null +++ b/src/migrations/20211110181535-add-ar-approved-date.js @@ -0,0 +1,18 @@ +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn( + 'ActivityReports', + 'approvedAt', + { + comment: 'Timestamp when a report was approved', + type: Sequelize.DATE, + defaultValue: null, + allowNull: true, + }, + ); + }, + + down: async (queryInterface) => { + await queryInterface.removeColumn('ActivityReports', 'approvedAt'); + }, +}; From 45c726def9e7d6a76205bb61af967c5d61f549a7 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 10 Nov 2021 13:59:53 -0500 Subject: [PATCH 02/13] update approvedAt on approval --- src/models/activityReportApprover.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/models/activityReportApprover.js b/src/models/activityReportApprover.js index 915453e39f..2bb0c3de78 100644 --- a/src/models/activityReportApprover.js +++ b/src/models/activityReportApprover.js @@ -212,9 +212,21 @@ module.exports = (sequelize, DataTypes) => { }).map((a) => a.status); const newCalculatedStatus = calculateReportStatus(instance.status, approverStatuses); - await sequelize.models.ActivityReport.update({ + + /* + * Here we check to see if the report will be approved and update the approvedAt + * as appropriate + */ + const approvedAt = newCalculatedStatus === REPORT_STATUSES.APPROVED ? new Date() : null; + + const updatedFields = approvedAt ? { calculatedStatus: newCalculatedStatus, - }, { + approvedAt, + } : { + calculatedStatus: newCalculatedStatus, + }; + + await sequelize.models.ActivityReport.update(updatedFields, { where: { id: instance.activityReportId }, transaction: options.transaction, hooks: false, From c712df7035e6978aed7ad1a692f2c4fc0c37bf1b Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 10 Nov 2021 15:06:13 -0500 Subject: [PATCH 03/13] clean up constants, add new one --- frontend/src/Constants.js | 1 + frontend/src/components/DatePicker.js | 16 +++++++--------- .../pages/RegionalDashboard/formatDateRange.js | 3 ++- src/constants.js | 2 ++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/frontend/src/Constants.js b/frontend/src/Constants.js index 88b1e6ee5c..7afc6955f9 100644 --- a/frontend/src/Constants.js +++ b/frontend/src/Constants.js @@ -103,4 +103,5 @@ export const ESCAPE_KEY_CODE = 27; export const ESCAPE_KEY_CODES = ['Escape', 'Esc']; export const DATE_FMT = 'YYYY/MM/DD'; +export const DATE_DISPLAY_FORMAT = 'MM/DD/YYYY'; export const EARLIEST_INC_FILTER_DATE = moment('2020-08-31'); diff --git a/frontend/src/components/DatePicker.js b/frontend/src/components/DatePicker.js index 261ea029d7..3ca5f6e472 100644 --- a/frontend/src/components/DatePicker.js +++ b/frontend/src/components/DatePicker.js @@ -16,11 +16,9 @@ import { SingleDatePicker } from 'react-dates'; import { OPEN_UP, OPEN_DOWN } from 'react-dates/constants'; import { Controller } from 'react-hook-form/dist/index.ie11'; import moment from 'moment'; - +import { DATE_DISPLAY_FORMAT } from '../Constants'; import './DatePicker.css'; -const dateFmt = 'MM/DD/YYYY'; - const DateInput = ({ control, minDate, name, disabled, maxDate, openUp, required, ariaName, maxDateInclusive, }) => { @@ -29,17 +27,17 @@ const DateInput = ({ const openDirection = openUp ? OPEN_UP : OPEN_DOWN; const isOutsideRange = (date) => { - const isBefore = minDate && date.isBefore(moment(minDate, dateFmt)); + const isBefore = minDate && date.isBefore(moment(minDate, DATE_DISPLAY_FORMAT)); // If max date is inclusive (maxDateInclusive == true) // allow the user to pick a start date that is the same as the maxDate // otherwise, only the day before is allowed let isAfter = false; if (maxDateInclusive) { - const newDate = moment(maxDate, dateFmt).add(1, 'days'); - isAfter = maxDate && date.isAfter(newDate, dateFmt); + const newDate = moment(maxDate, DATE_DISPLAY_FORMAT).add(1, 'days'); + isAfter = maxDate && date.isAfter(newDate, DATE_DISPLAY_FORMAT); } else { - isAfter = maxDate && date.isAfter(moment(maxDate, dateFmt)); + isAfter = maxDate && date.isAfter(moment(maxDate, DATE_DISPLAY_FORMAT)); } return isBefore || isAfter; @@ -52,7 +50,7 @@ const DateInput = ({
mm/dd/yyyy
{ - const date = value ? moment(value, dateFmt) : null; + const date = value ? moment(value, DATE_DISPLAY_FORMAT) : null; return (
{ - const newDate = d ? d.format(dateFmt) : d; + const newDate = d ? d.format(DATE_DISPLAY_FORMAT) : d; onChange(newDate); const input = document.getElementById(name); if (input) input.focus(); diff --git a/frontend/src/pages/RegionalDashboard/formatDateRange.js b/frontend/src/pages/RegionalDashboard/formatDateRange.js index 1b9244cefb..0e4a82926c 100644 --- a/frontend/src/pages/RegionalDashboard/formatDateRange.js +++ b/frontend/src/pages/RegionalDashboard/formatDateRange.js @@ -1,5 +1,6 @@ import moment from 'moment'; -import { DATETIME_DATE_FORMAT, DATE_FORMAT } from './constants'; +import { DATETIME_DATE_FORMAT } from './constants'; +import { DATE_DISPLAY_FORMAT as DATE_FORMAT } from '../../Constants'; export default function formatDateRange(format = { lastThirtyDays: false, withSpaces: false, forDateTime: false, sep: '-', string: '', diff --git a/src/constants.js b/src/constants.js index a208f5a4d1..e0bd21baba 100644 --- a/src/constants.js +++ b/src/constants.js @@ -18,6 +18,8 @@ export const FILE_STATUSES = { REJECTED: 'REJECTED', }; +export const DATE_FORMAT = 'MM/DD/YYYY'; + export const DECIMAL_BASE = 10; export const REPORTS_PER_PAGE = 10; From adcabcc7f1a7f2e29a7424094d4e68cb607876dd Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 10 Nov 2021 15:07:23 -0500 Subject: [PATCH 04/13] add approved and created dates to fe landing page --- .../ActivityReportsTable/ReportRow.js | 20 +++++++++---------- .../components/ActivityReportsTable/index.js | 3 ++- .../src/pages/ApprovedActivityReport/index.js | 5 +++-- frontend/src/pages/Landing/MyAlerts.js | 7 ++++++- .../src/pages/RegionalDashboard/constants.js | 1 - src/lib/orderReportsBy.js | 2 ++ 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/ActivityReportsTable/ReportRow.js b/frontend/src/components/ActivityReportsTable/ReportRow.js index 47e0b9ef27..7885f9f62f 100644 --- a/frontend/src/components/ActivityReportsTable/ReportRow.js +++ b/frontend/src/components/ActivityReportsTable/ReportRow.js @@ -1,13 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Tag, Checkbox, -} from '@trussworks/react-uswds'; +import { Checkbox } from '@trussworks/react-uswds'; import { Link, useHistory } from 'react-router-dom'; - +import moment from 'moment'; import ContextMenu from '../ContextMenu'; import { getReportsDownloadURL } from '../../fetchers/helpers'; import TooltipWithCollection from '../TooltipWithCollection'; +import { DATE_DISPLAY_FORMAT } from '../../Constants'; function ReportRow({ report, openMenuUp, handleReportSelect, isChecked, @@ -22,6 +21,8 @@ function ReportRow({ collaborators, lastSaved, calculatedStatus, + approvedAt, + createdAt, legacyId, } = report; @@ -89,6 +90,7 @@ function ReportRow({ {authorName} + {moment(createdAt).format(DATE_DISPLAY_FORMAT)} @@ -96,13 +98,7 @@ function ReportRow({ {lastSaved} - - - {calculatedStatus === 'needs_action' ? 'Needs action' : calculatedStatus} - - + {approvedAt && moment(approvedAt).format(DATE_DISPLAY_FORMAT)} @@ -122,6 +118,8 @@ export const reportPropTypes = { }), }), })).isRequired, + approvedAt: PropTypes.string, + createdAt: PropTypes.string, startDate: PropTypes.string.isRequired, author: PropTypes.shape({ fullName: PropTypes.string, diff --git a/frontend/src/components/ActivityReportsTable/index.js b/frontend/src/components/ActivityReportsTable/index.js index 4ce9d5bfe0..35545ea4f9 100644 --- a/frontend/src/components/ActivityReportsTable/index.js +++ b/frontend/src/components/ActivityReportsTable/index.js @@ -299,10 +299,11 @@ function ActivityReportsTable({ {renderColumnHeader('Grantee', 'activityRecipients')} {renderColumnHeader('Start date', 'startDate')} {renderColumnHeader('Creator', 'author')} + {renderColumnHeader('Created date', 'createdAt')} {renderColumnHeader('Topic(s)', 'topics')} {renderColumnHeader('Collaborator(s)', 'collaborators')} {renderColumnHeader('Last saved', 'updatedAt')} - {renderColumnHeader('Status', 'calculatedStatus')} + {renderColumnHeader('Approved date', 'approvedAt')} diff --git a/frontend/src/pages/ApprovedActivityReport/index.js b/frontend/src/pages/ApprovedActivityReport/index.js index b47b92fbb0..f3a695594a 100644 --- a/frontend/src/pages/ApprovedActivityReport/index.js +++ b/frontend/src/pages/ApprovedActivityReport/index.js @@ -11,6 +11,7 @@ import ViewTable from './components/ViewTable'; import { getReport, unlockReport } from '../../fetchers/activityReports'; import { allRegionsUserHasPermissionTo, canUnlockReports } from '../../permissions'; import Modal from '../../components/Modal'; +import { DATE_DISPLAY_FORMAT } from '../../Constants'; /** * @@ -192,8 +193,8 @@ export default function ApprovedActivityReport({ match, user }) { setParticipantCount(newCount); setReasons(formatSimpleArray(report.reason)); setProgramType(formatSimpleArray(report.programTypes)); - setStartDate(moment(report.startDate, 'MM/DD/YYYY').format('MMMM D, YYYY')); - setEndDate(moment(report.endDate, 'MM/DD/YYYY').format('MMMM D, YYYY')); + setStartDate(moment(report.startDate, DATE_DISPLAY_FORMAT).format('MMMM D, YYYY')); + setEndDate(moment(report.endDate, DATE_DISPLAY_FORMAT).format('MMMM D, YYYY')); setDuration(`${report.duration} hours`); setMethod(formatMethod(report.ttaType, report.virtualDeliveryType)); setRequester(formatRequester(report.requester)); diff --git a/frontend/src/pages/Landing/MyAlerts.js b/frontend/src/pages/Landing/MyAlerts.js index 77b858de56..c6fdbbd0de 100644 --- a/frontend/src/pages/Landing/MyAlerts.js +++ b/frontend/src/pages/Landing/MyAlerts.js @@ -5,7 +5,7 @@ import { Tag, Table, useModal, connectModal, } from '@trussworks/react-uswds'; import { Link, useHistory } from 'react-router-dom'; - +import moment from 'moment'; import DeleteReportModal from '../../components/DeleteReportModal'; import Container from '../../components/Container'; import ContextMenu from '../../components/ContextMenu'; @@ -44,6 +44,7 @@ function ReportsRow({ reports, removeAlert, message }) { calculatedStatus, pendingApprovals, approvers, + createdAt, } = report; const justSubmitted = message && message.reportId === id; @@ -96,6 +97,9 @@ function ReportsRow({ reports, removeAlert, message }) { {author ? author.fullName : ''} + + {moment(createdAt).format('MM/DD/YYYY')} + @@ -311,6 +315,7 @@ function MyAlerts(props) { {renderColumnHeader('Grantee', 'activityRecipients')} {renderColumnHeader('Start date', 'startDate')} {renderColumnHeader('Creator', 'author')} + {renderColumnHeader('Created date', 'createdAt')} {renderColumnHeader('Collaborator(s)', 'collaborators')} {renderColumnHeader('Approvers(s)', 'approvals', true)} {renderColumnHeader('Status', 'calculatedStatus')} diff --git a/frontend/src/pages/RegionalDashboard/constants.js b/frontend/src/pages/RegionalDashboard/constants.js index 712a08346d..b2a57f9465 100644 --- a/frontend/src/pages/RegionalDashboard/constants.js +++ b/frontend/src/pages/RegionalDashboard/constants.js @@ -1,4 +1,3 @@ -export const DATE_FORMAT = 'MM/DD/YYYY'; export const DATETIME_DATE_FORMAT = 'YYYY/MM/DD'; export const DATE_OPTIONS = [ diff --git a/src/lib/orderReportsBy.js b/src/lib/orderReportsBy.js index 21fbb2d4fa..6d6131434b 100644 --- a/src/lib/orderReportsBy.js +++ b/src/lib/orderReportsBy.js @@ -42,6 +42,8 @@ const orderReportsBy = (sortBy, sortDir) => { case 'calculatedStatus': case 'startDate': case 'updatedAt': + case 'approvedAt': + case 'createdAt': result = [[sortBy, sortDir]]; break; default: From 758665a7b328b35093bafdb7dc0920d34e45118c Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 10 Nov 2021 15:08:08 -0500 Subject: [PATCH 05/13] add approved and created date to csv download --- src/lib/transform.js | 22 ++++++++++++++++++++++ src/models/activityReport.js | 4 ++++ src/routes/activityReports/handlers.js | 11 +++++++++++ src/services/activityReports.js | 8 +++++++- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/lib/transform.js b/src/lib/transform.js index c55a0d9db0..3fb3ac89bd 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -1,3 +1,23 @@ +import moment from 'moment'; +import { DATE_FORMAT } from '../constants'; + +function transformDate(field) { + async function transformer(instance) { + let value = ''; + const date = instance[field]; + if (date) { + value = moment(date).format(DATE_FORMAT); + } + const obj = {}; + Object.defineProperty(obj, field, { + value, + enumerable: true, + }); + return Promise.resolve(obj); + } + return transformer; +} + /** * @param {string} field name to be retrieved * @returns {function} Function that will return a simple value wrapped in a Promise @@ -167,6 +187,8 @@ const arTransformers = [ 'context', 'additionalNotes', 'lastSaved', + transformDate('createdAt'), + transformDate('approvedAt'), ]; /** diff --git a/src/models/activityReport.js b/src/models/activityReport.js index 4dfe0b3942..14b5e4d8b6 100644 --- a/src/models/activityReport.js +++ b/src/models/activityReport.js @@ -243,6 +243,10 @@ export default (sequelize, DataTypes) => { return moment(this.updatedAt).format('MM/DD/YYYY'); }, }, + approvedAt: { + allowNull: true, + type: DataTypes.DATE, + }, imported: { type: DataTypes.JSONB, comment: 'Storage for raw values from smartsheet CSV imports', diff --git a/src/routes/activityReports/handlers.js b/src/routes/activityReports/handlers.js index 3955e868ea..e65194f5bc 100644 --- a/src/routes/activityReports/handlers.js +++ b/src/routes/activityReports/handlers.js @@ -164,6 +164,14 @@ async function sendActivityReportCSV(reports, res) { key: 'granteeNextSteps', header: 'Grantee next steps', }, + { + key: 'createdAt', + header: 'Created date', + }, + { + key: 'approvedAt', + header: 'Approved date', + }, { key: 'lastSaved', header: 'Last saved', @@ -615,6 +623,9 @@ export async function downloadReports(req, res) { req.query, ); + const { rows } = reportsWithCount; + console.log(rows.map((r) => r.approvedAt)); + const { format = 'json' } = req.query || {}; if (!reportsWithCount) { diff --git a/src/services/activityReports.js b/src/services/activityReports.js index 2cf928afbf..f2daea0816 100644 --- a/src/services/activityReports.js +++ b/src/services/activityReports.js @@ -337,6 +337,8 @@ export function activityReports( 'updatedAt', 'sortedTopics', 'legacyId', + 'createdAt', + 'approvedAt', sequelize.literal( '(SELECT name as authorName FROM "Users" WHERE "Users"."id" = "ActivityReport"."userId")', ), @@ -461,6 +463,7 @@ export async function activityReportAlerts(userId, { 'calculatedStatus', 'regionId', 'userId', + 'createdAt', sequelize.literal( '(SELECT name as authorName FROM "Users" WHERE "Users"."id" = "ActivityReport"."userId")', ), @@ -674,7 +677,10 @@ async function getDownloadableActivityReports(where) { return ActivityReport.findAndCountAll( { where, - attributes: { include: ['displayId'], exclude: ['imported', 'legacyId', 'oldManagerNotes', 'additionalNotes', 'approvers'] }, + attributes: { + include: ['displayId', 'createdAt', 'approvedAt'], + exclude: ['imported', 'legacyId', 'oldManagerNotes', 'additionalNotes', 'approvers'], + }, include: [ { model: Objective, From 281d6ac8a491e62d4c9631e2255f67ecde1956d8 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 10 Nov 2021 15:09:28 -0500 Subject: [PATCH 06/13] remove console statement --- src/routes/activityReports/handlers.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/routes/activityReports/handlers.js b/src/routes/activityReports/handlers.js index e65194f5bc..ae75c4fc4a 100644 --- a/src/routes/activityReports/handlers.js +++ b/src/routes/activityReports/handlers.js @@ -623,9 +623,6 @@ export async function downloadReports(req, res) { req.query, ); - const { rows } = reportsWithCount; - console.log(rows.map((r) => r.approvedAt)); - const { format = 'json' } = req.query || {}; if (!reportsWithCount) { From be496320a28a77ea51d549a500189bc99b49a725 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 10 Nov 2021 15:44:30 -0500 Subject: [PATCH 07/13] fixing tests --- frontend/src/pages/Landing/__tests__/index.js | 16 ++++++++-------- src/lib/transform.test.js | 1 + .../handlersWithDatabaseCalls.test.js | 3 +++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/src/pages/Landing/__tests__/index.js b/frontend/src/pages/Landing/__tests__/index.js index 7727057d1f..b3dc76a5e6 100644 --- a/frontend/src/pages/Landing/__tests__/index.js +++ b/frontend/src/pages/Landing/__tests__/index.js @@ -326,7 +326,7 @@ describe('My alerts sorting', () => { it('is enabled for Status', async () => { const statusColumnHeaders = await screen.findAllByText(/status/i); - expect(statusColumnHeaders.length).toBe(2); + expect(statusColumnHeaders.length).toBe(1); fetchMock.reset(); fireEvent.click(statusColumnHeaders[0]); @@ -356,7 +356,7 @@ describe('My alerts sorting', () => { fireEvent.click(columnHeaders[0]); await waitFor(() => expect(screen.getAllByRole('cell')[0]).toHaveTextContent(/r14-ar-1/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[8]).toHaveTextContent(/r14-ar-2/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[9]).toHaveTextContent(/r14-ar-2/i)); }); it('is enabled for Grantee', async () => { @@ -376,7 +376,7 @@ describe('My alerts sorting', () => { const textContent = /Johnston-Romaguera Johnston-Romaguera Grantee Name click to visually reveal the recipients for R14-AR-1$/i; await waitFor(() => expect(screen.getAllByRole('cell')[1]).toHaveTextContent(textContent)); - await waitFor(() => expect(screen.getAllByRole('cell')[9]).toHaveTextContent(/qris system/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[10]).toHaveTextContent(/qris system/i)); }); it('is enabled for Start date', async () => { @@ -394,7 +394,7 @@ describe('My alerts sorting', () => { fireEvent.click(columnHeaders[0]); await waitFor(() => expect(screen.getAllByRole('cell')[2]).toHaveTextContent(/02\/01\/2021/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[10]).toHaveTextContent(/02\/08\/2021/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[11]).toHaveTextContent(/02\/08\/2021/i)); }); it('is enabled for Creator', async () => { @@ -412,7 +412,7 @@ describe('My alerts sorting', () => { fireEvent.click(columnHeaders[0]); await waitFor(() => expect(screen.getAllByRole('cell')[3]).toHaveTextContent(/kiwi, gs/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[11]).toHaveTextContent(/kiwi, ttac/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[12]).toHaveTextContent(/kiwi, ttac/i)); }); it('is enabled for Collaborator(s)', async () => { @@ -427,8 +427,8 @@ describe('My alerts sorting', () => { const firstCell = /Cucumber User, GS Hermione Granger, SS click to visually reveal the collaborators for R14-AR-2$/i; const secondCell = /Orange, GS Hermione Granger, SS click to visually reveal the collaborators for R14-AR-1$/i; - await waitFor(() => expect(screen.getAllByRole('cell')[4]).toHaveTextContent(firstCell)); - await waitFor(() => expect(screen.getAllByRole('cell')[12]).toHaveTextContent(secondCell)); + await waitFor(() => expect(screen.getAllByRole('cell')[5]).toHaveTextContent(firstCell)); + await waitFor(() => expect(screen.getAllByRole('cell')[14]).toHaveTextContent(secondCell)); }); }); @@ -478,7 +478,7 @@ describe('Landing Page error', () => { }; renderLanding(user); const rowCells = await screen.findAllByRole('cell'); - expect(rowCells.length).toBe(9); + expect(rowCells.length).toBe(10); const grantee = rowCells[1]; expect(grantee).toHaveTextContent(''); }); diff --git a/src/lib/transform.test.js b/src/lib/transform.test.js index f5eae76208..7120ccde8a 100644 --- a/src/lib/transform.test.js +++ b/src/lib/transform.test.js @@ -113,6 +113,7 @@ describe('activityReportToCsvRecord', () => { author: mockAuthor, lastUpdatedBy: mockAuthor, collaborators: mockCollaborators, + approvedAt: new Date(), }; it('transforms arrays of strings into strings', async () => { diff --git a/src/routes/activityReports/handlersWithDatabaseCalls.test.js b/src/routes/activityReports/handlersWithDatabaseCalls.test.js index 0113cd6f53..34ecbd7506 100644 --- a/src/routes/activityReports/handlersWithDatabaseCalls.test.js +++ b/src/routes/activityReports/handlersWithDatabaseCalls.test.js @@ -103,6 +103,7 @@ describe('submitReport', () => { expect(assignedNotification).toHaveBeenCalled(); expect(mockResponse.json).toHaveBeenCalledWith(expect.objectContaining({ calculatedStatus: REPORT_STATUSES.SUBMITTED, + approvedAt: null, approvers: [ expect.objectContaining({ status: null, note: null }), expect.objectContaining({ status: null, note: null }), @@ -131,6 +132,7 @@ describe('submitReport', () => { await submitReport(request, mockResponse); expect(assignedNotification).toHaveBeenCalled(); expect(mockResponse.json).toHaveBeenCalledWith(expect.objectContaining({ + approvedAt: null, calculatedStatus: REPORT_STATUSES.SUBMITTED, approvers: [ expect.objectContaining({ status: null, note: 'make changes x, y, z' }), @@ -155,6 +157,7 @@ describe('submitReport', () => { // check that testing condition is correct expect(reviewedReport.calculatedStatus).toEqual(REPORT_STATUSES.APPROVED); + expect(reviewedReport.approvedAt).toBeTruthy(); // Create request to unlock const request = { From 79e1fea79b861a6b05107194ddd29e99dd0e4dba Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 10 Nov 2021 16:56:20 -0500 Subject: [PATCH 08/13] partial fixes for ui tests --- .../ActivityReportsTable/__tests__/index.js | 20 +++++++++---------- frontend/src/pages/Landing/__tests__/index.js | 18 +++++------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/ActivityReportsTable/__tests__/index.js b/frontend/src/components/ActivityReportsTable/__tests__/index.js index 7c084ed238..97f489a148 100644 --- a/frontend/src/components/ActivityReportsTable/__tests__/index.js +++ b/frontend/src/components/ActivityReportsTable/__tests__/index.js @@ -348,8 +348,8 @@ describe('Table sorting', () => { ); fireEvent.click(columnHeader); - await waitFor(() => expect(screen.getAllByRole('cell')[6]).toHaveTextContent(/02\/04\/2021/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[15]).toHaveTextContent(/02\/05\/2021/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[7]).toHaveTextContent(/02\/04\/2021/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[17]).toHaveTextContent(/02\/05\/2021/i)); }); it('clicking Collaborators column header will sort by collaborators', async () => { @@ -361,8 +361,8 @@ describe('Table sorting', () => { ); await act(async () => fireEvent.click(columnHeader)); - await waitFor(() => expect(screen.getAllByRole('cell')[5]).toHaveTextContent('Cucumber User, GS Hermione Granger, SS')); - await waitFor(() => expect(screen.getAllByRole('cell')[14]).toHaveTextContent('Orange, GS Hermione Granger, SS')); + await waitFor(() => expect(screen.getAllByRole('cell')[6]).toHaveTextContent('Cucumber User, GS Hermione Granger, SS')); + await waitFor(() => expect(screen.getAllByRole('cell')[16]).toHaveTextContent('Orange, GS Hermione Granger, SS')); }); it('clicking Topics column header will sort by topics', async () => { @@ -374,8 +374,8 @@ describe('Table sorting', () => { ); await act(async () => fireEvent.click(columnHeader)); - await waitFor(() => expect(screen.getAllByRole('cell')[4]).toHaveTextContent('')); - await waitFor(() => expect(screen.getAllByRole('cell')[13]).toHaveTextContent(/Behavioral \/ Mental Health CLASS: Instructional Support click to visually reveal the topics for R14-AR-1$/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[4]).toHaveTextContent('11/10/2021')); + await waitFor(() => expect(screen.getAllByRole('cell')[15]).toHaveTextContent(/Behavioral \/ Mental Health CLASS: Instructional Support click to visually reveal the topics for R14-AR-1$/i)); }); it('clicking Creator column header will sort by author', async () => { @@ -388,7 +388,7 @@ describe('Table sorting', () => { fireEvent.click(columnHeader); await waitFor(() => expect(screen.getAllByRole('cell')[3]).toHaveTextContent('Kiwi, GS')); - await waitFor(() => expect(screen.getAllByRole('cell')[12]).toHaveTextContent('Kiwi, TTAC')); + await waitFor(() => expect(screen.getAllByRole('cell')[13]).toHaveTextContent('Kiwi, TTAC')); }); it('clicking Start date column header will sort by start date', async () => { @@ -401,7 +401,7 @@ describe('Table sorting', () => { fireEvent.click(columnHeader); await waitFor(() => expect(screen.getAllByRole('cell')[2]).toHaveTextContent('02/01/2021')); - await waitFor(() => expect(screen.getAllByRole('cell')[11]).toHaveTextContent('02/08/2021')); + await waitFor(() => expect(screen.getAllByRole('cell')[12]).toHaveTextContent('02/08/2021')); }); it('clicking Grantee column header will sort by grantee', async () => { @@ -462,8 +462,8 @@ describe('Table sorting', () => { ); fireEvent.click(pageOne); - await waitFor(() => expect(screen.getAllByRole('cell')[6]).toHaveTextContent(/02\/05\/2021/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[15]).toHaveTextContent(/02\/04\/2021/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[7]).toHaveTextContent(/02\/05\/2021/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[17]).toHaveTextContent(/02\/04\/2021/i)); }); it('clicking on the second page updates to, from and total', async () => { diff --git a/frontend/src/pages/Landing/__tests__/index.js b/frontend/src/pages/Landing/__tests__/index.js index b3dc76a5e6..943f18bc74 100644 --- a/frontend/src/pages/Landing/__tests__/index.js +++ b/frontend/src/pages/Landing/__tests__/index.js @@ -217,14 +217,6 @@ describe('Landing Page', () => { expect(lastSavedDates.length).toBe(1); }); - test('displays the correct statuses', async () => { - const draft = await screen.findByText(/draft/i); - const needsAction = await screen.findByText(/needs action/i); - - expect(draft).toBeVisible(); - expect(needsAction).toBeVisible(); - }); - test('displays the options buttons', async () => { const optionButtons = await screen.findAllByRole('button', { name: /actions for activity report r14-ar-2/i, @@ -331,15 +323,15 @@ describe('My alerts sorting', () => { fireEvent.click(statusColumnHeaders[0]); - await waitFor(() => expect(screen.getAllByRole('cell')[6]).toHaveTextContent(/draft/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[14]).toHaveTextContent(/needs action/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[7]).toHaveTextContent(/draft/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[15]).toHaveTextContent(/needs action/i)); fetchMock.get('/api/activity-reports/alerts?sortBy=calculatedStatus&sortDir=desc&offset=0&limit=10®ion.in[]=1', { alertsCount: 2, alerts: activityReportsSorted }); fireEvent.click(statusColumnHeaders[0]); - await waitFor(() => expect(screen.getAllByRole('cell')[6]).toHaveTextContent(/needs action/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[14]).toHaveTextContent(/draft/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[7]).toHaveTextContent(/needs action/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[15]).toHaveTextContent(/draft/i)); }); it('is enabled for Report ID', async () => { @@ -412,7 +404,7 @@ describe('My alerts sorting', () => { fireEvent.click(columnHeaders[0]); await waitFor(() => expect(screen.getAllByRole('cell')[3]).toHaveTextContent(/kiwi, gs/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[12]).toHaveTextContent(/kiwi, ttac/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[13]).toHaveTextContent(/kiwi, ttac/i)); }); it('is enabled for Collaborator(s)', async () => { From 34bca8d198320b8def5918326deed61234c2f307 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 11 Nov 2021 08:46:24 -0500 Subject: [PATCH 09/13] clean up prop type --- frontend/src/components/ActivityReportsTable/ReportRow.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ActivityReportsTable/ReportRow.js b/frontend/src/components/ActivityReportsTable/ReportRow.js index 7885f9f62f..d0aeb31522 100644 --- a/frontend/src/components/ActivityReportsTable/ReportRow.js +++ b/frontend/src/components/ActivityReportsTable/ReportRow.js @@ -127,7 +127,11 @@ export const reportPropTypes = { name: PropTypes.string, }).isRequired, topics: PropTypes.arrayOf(PropTypes.string).isRequired, - collaborators: PropTypes.arrayOf(PropTypes.string).isRequired, + collaborators: PropTypes.arrayOf( + PropTypes.shape({ + fullName: PropTypes.string, + }), + ).isRequired, lastSaved: PropTypes.string.isRequired, calculatedStatus: PropTypes.string.isRequired, legacyId: PropTypes.string, From 7b27f9550682270df0f4f76d5d216912fb8cd350 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 11 Nov 2021 08:53:56 -0500 Subject: [PATCH 10/13] cleanup UI tests --- .../ActivityReportsTable/__tests__/index.js | 23 ------------------- frontend/src/pages/Landing/__tests__/index.js | 7 +++--- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/ActivityReportsTable/__tests__/index.js b/frontend/src/components/ActivityReportsTable/__tests__/index.js index 97f489a148..406a7a1584 100644 --- a/frontend/src/components/ActivityReportsTable/__tests__/index.js +++ b/frontend/src/components/ActivityReportsTable/__tests__/index.js @@ -317,28 +317,6 @@ describe('Table sorting', () => { await screen.findByText('Activity Reports'); }); - it('clicking status column header will sort by status', async () => { - const statusColumnHeader = await screen.findByText(/status/i); - fetchMock.reset(); - fetchMock.get( - '/api/activity-reports?sortBy=calculatedStatus&sortDir=asc&offset=0&limit=10®ion.in[]=1', - { count: 2, rows: activityReportsSorted }, - ); - - fireEvent.click(statusColumnHeader); - await waitFor(() => expect(screen.getAllByRole('cell')[7]).toHaveTextContent(/needs action/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[16]).toHaveTextContent(/draft/i)); - - fetchMock.get( - '/api/activity-reports?sortBy=calculatedStatus&sortDir=desc&offset=0&limit=10®ion.in[]=1', - { count: 2, rows: activityReports }, - ); - - fireEvent.click(statusColumnHeader); - await waitFor(() => expect(screen.getAllByRole('cell')[7]).toHaveTextContent(/draft/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[16]).toHaveTextContent(/needs action/i)); - }); - it('clicking Last saved column header will sort by updatedAt', async () => { const columnHeader = await screen.findByText(/last saved/i); @@ -374,7 +352,6 @@ describe('Table sorting', () => { ); await act(async () => fireEvent.click(columnHeader)); - await waitFor(() => expect(screen.getAllByRole('cell')[4]).toHaveTextContent('11/10/2021')); await waitFor(() => expect(screen.getAllByRole('cell')[15]).toHaveTextContent(/Behavioral \/ Mental Health CLASS: Instructional Support click to visually reveal the topics for R14-AR-1$/i)); }); diff --git a/frontend/src/pages/Landing/__tests__/index.js b/frontend/src/pages/Landing/__tests__/index.js index 943f18bc74..a1b3321665 100644 --- a/frontend/src/pages/Landing/__tests__/index.js +++ b/frontend/src/pages/Landing/__tests__/index.js @@ -64,6 +64,7 @@ describe('Landing Page', () => { alerts: [], }); fetchMock.get(overviewUrlWithRegionOne, overviewRegionOne); + const user = { name: 'test@test.com', permissions: [ @@ -324,14 +325,14 @@ describe('My alerts sorting', () => { fireEvent.click(statusColumnHeaders[0]); await waitFor(() => expect(screen.getAllByRole('cell')[7]).toHaveTextContent(/draft/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[15]).toHaveTextContent(/needs action/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[16]).toHaveTextContent(/needs action/i)); fetchMock.get('/api/activity-reports/alerts?sortBy=calculatedStatus&sortDir=desc&offset=0&limit=10®ion.in[]=1', { alertsCount: 2, alerts: activityReportsSorted }); fireEvent.click(statusColumnHeaders[0]); await waitFor(() => expect(screen.getAllByRole('cell')[7]).toHaveTextContent(/needs action/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[15]).toHaveTextContent(/draft/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[16]).toHaveTextContent(/draft/i)); }); it('is enabled for Report ID', async () => { @@ -404,7 +405,7 @@ describe('My alerts sorting', () => { fireEvent.click(columnHeaders[0]); await waitFor(() => expect(screen.getAllByRole('cell')[3]).toHaveTextContent(/kiwi, gs/i)); - await waitFor(() => expect(screen.getAllByRole('cell')[13]).toHaveTextContent(/kiwi, ttac/i)); + await waitFor(() => expect(screen.getAllByRole('cell')[12]).toHaveTextContent(/kiwi, ttac/i)); }); it('is enabled for Collaborator(s)', async () => { From 1b9581100994404f72b22dabec5a575dfa8aac0b Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 11 Nov 2021 09:58:23 -0500 Subject: [PATCH 11/13] add backend tests --- src/routes/activityReports/handlersWithDatabaseCalls.test.js | 3 --- src/services/activityReportApprovers.test.js | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/routes/activityReports/handlersWithDatabaseCalls.test.js b/src/routes/activityReports/handlersWithDatabaseCalls.test.js index 34ecbd7506..0113cd6f53 100644 --- a/src/routes/activityReports/handlersWithDatabaseCalls.test.js +++ b/src/routes/activityReports/handlersWithDatabaseCalls.test.js @@ -103,7 +103,6 @@ describe('submitReport', () => { expect(assignedNotification).toHaveBeenCalled(); expect(mockResponse.json).toHaveBeenCalledWith(expect.objectContaining({ calculatedStatus: REPORT_STATUSES.SUBMITTED, - approvedAt: null, approvers: [ expect.objectContaining({ status: null, note: null }), expect.objectContaining({ status: null, note: null }), @@ -132,7 +131,6 @@ describe('submitReport', () => { await submitReport(request, mockResponse); expect(assignedNotification).toHaveBeenCalled(); expect(mockResponse.json).toHaveBeenCalledWith(expect.objectContaining({ - approvedAt: null, calculatedStatus: REPORT_STATUSES.SUBMITTED, approvers: [ expect.objectContaining({ status: null, note: 'make changes x, y, z' }), @@ -157,7 +155,6 @@ describe('submitReport', () => { // check that testing condition is correct expect(reviewedReport.calculatedStatus).toEqual(REPORT_STATUSES.APPROVED); - expect(reviewedReport.approvedAt).toBeTruthy(); // Create request to unlock const request = { diff --git a/src/services/activityReportApprovers.test.js b/src/services/activityReportApprovers.test.js index 7a6941301e..60fa1ee60e 100644 --- a/src/services/activityReportApprovers.test.js +++ b/src/services/activityReportApprovers.test.js @@ -106,6 +106,7 @@ describe('activityReportApprovers services', () => { expect(approver.status).toEqual(APPROVER_STATUSES.NEEDS_ACTION); }); const updatedReport = await activityReportById(report1.id); + expect(updatedReport.approvedAt).toBeNull(); expect(updatedReport.submissionStatus).toEqual(REPORT_STATUSES.SUBMITTED); expect(updatedReport.calculatedStatus).toEqual(REPORT_STATUSES.NEEDS_ACTION); }); @@ -124,6 +125,7 @@ describe('activityReportApprovers services', () => { }); expect(approver.status).toEqual(APPROVER_STATUSES.APPROVED); const updatedReport = await activityReportById(report2.id); + expect(updatedReport.approvedAt).toBeTruthy(); expect(updatedReport.submissionStatus).toEqual(REPORT_STATUSES.SUBMITTED); expect(updatedReport.calculatedStatus).toEqual(REPORT_STATUSES.APPROVED); }); From cc9f27ee85fffdb992bbfe1a11da79a1d6a22ec1 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 11 Nov 2021 13:33:37 -0500 Subject: [PATCH 12/13] add approved and created date to ar, table css fixes --- .../src/components/ActivityReportsTable/ReportRow.js | 9 ++++++--- .../src/components/ActivityReportsTable/index.css | 4 +++- frontend/src/components/Tooltip.css | 2 +- frontend/src/components/TooltipWithCollection.js | 6 +++++- frontend/src/pages/Landing/MyAlerts.js | 12 +++++++++--- frontend/src/pages/Landing/index.css | 10 ++++++++-- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/ActivityReportsTable/ReportRow.js b/frontend/src/components/ActivityReportsTable/ReportRow.js index d0aeb31522..e5412280e0 100644 --- a/frontend/src/components/ActivityReportsTable/ReportRow.js +++ b/frontend/src/components/ActivityReportsTable/ReportRow.js @@ -6,6 +6,7 @@ import moment from 'moment'; import ContextMenu from '../ContextMenu'; import { getReportsDownloadURL } from '../../fetchers/helpers'; import TooltipWithCollection from '../TooltipWithCollection'; +import Tooltip from '../Tooltip'; import { DATE_DISPLAY_FORMAT } from '../../Constants'; function ReportRow({ @@ -86,9 +87,11 @@ function ReportRow({ {startDate} - - {authorName} - + {moment(createdAt).format(DATE_DISPLAY_FORMAT)} diff --git a/frontend/src/components/ActivityReportsTable/index.css b/frontend/src/components/ActivityReportsTable/index.css index b820f880e6..0d413f937a 100644 --- a/frontend/src/components/ActivityReportsTable/index.css +++ b/frontend/src/components/ActivityReportsTable/index.css @@ -1,3 +1,5 @@ -.usa-table-container--scrollable { +.usa-table-container--scrollable, +.usa-checkbox__label { margin-top: 0px; } + diff --git a/frontend/src/components/Tooltip.css b/frontend/src/components/Tooltip.css index 84e882b5eb..9c65eb6263 100644 --- a/frontend/src/components/Tooltip.css +++ b/frontend/src/components/Tooltip.css @@ -21,7 +21,7 @@ display: inline-block; overflow-x: hidden; overflow-y: visible; - width: 173.5px; + max-width: 175px; text-overflow: ellipsis; vertical-align: middle; } diff --git a/frontend/src/components/TooltipWithCollection.js b/frontend/src/components/TooltipWithCollection.js index 7cd02c5566..77fcc1d19f 100644 --- a/frontend/src/components/TooltipWithCollection.js +++ b/frontend/src/components/TooltipWithCollection.js @@ -30,7 +30,11 @@ export default function TooltipWithCollection({ collection, collectionTitle }) { if (collection.length === 1) { return ( - {tooltip} + ); } diff --git a/frontend/src/pages/Landing/MyAlerts.js b/frontend/src/pages/Landing/MyAlerts.js index c6fdbbd0de..7fb04655de 100644 --- a/frontend/src/pages/Landing/MyAlerts.js +++ b/frontend/src/pages/Landing/MyAlerts.js @@ -93,9 +93,15 @@ function ReportsRow({ reports, removeAlert, message }) { {startDate} - - {author ? author.fullName : ''} - + { author + ? ( + + ) : } {moment(createdAt).format('MM/DD/YYYY')} diff --git a/frontend/src/pages/Landing/index.css b/frontend/src/pages/Landing/index.css index 53a60971d7..84034f28cd 100644 --- a/frontend/src/pages/Landing/index.css +++ b/frontend/src/pages/Landing/index.css @@ -140,10 +140,16 @@ h1.landing { } .landing .usa-table td { - font-size: 15px; - white-space: nowrap; background-color: transparent; border-style: none; + font-size: 15px; + min-width: 120px; + white-space: nowrap; +} + +.landing .usa-table td:first-child, +.landing .usa-table td:last-child{ + min-width: auto; } .landing .usa-table .usa-checkbox__label { From 9ba70c6726fa3d468df09154b536afbcc99cf6c5 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 11 Nov 2021 13:57:54 -0500 Subject: [PATCH 13/13] fix failing tooltip test --- frontend/src/components/__tests__/TooltipWithCollection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/__tests__/TooltipWithCollection.js b/frontend/src/components/__tests__/TooltipWithCollection.js index 2eb9edd990..989dc1e479 100644 --- a/frontend/src/components/__tests__/TooltipWithCollection.js +++ b/frontend/src/components/__tests__/TooltipWithCollection.js @@ -43,8 +43,8 @@ describe('TooltipWithCollection', () => { it('renders a single span when passed a one item array', async () => { renderTooltip(['Jimbo']); - const jimbo = screen.getByText('Jimbo'); + const jimbo = screen.getAllByText('Jimbo')[1]; expect(jimbo).toBeVisible(); - expect(jimbo.parentElement).toHaveClass('smarthub-ellipsis'); + expect(jimbo.parentElement.parentElement).toHaveClass('smart-hub--ellipsis'); }); });