From aa530b733b785b0b29c822bfc5d346e28f908cb9 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Wed, 12 Jun 2024 10:55:49 -0400 Subject: [PATCH 01/21] Start adding missing data to CSV --- src/lib/transform.js | 85 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/src/lib/transform.js b/src/lib/transform.js index bd6f86cf9c..ea27fda03e 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -210,6 +210,8 @@ function makeGoalsObjectFromActivityReportGoals(goalRecords) { name = null, status = null, createdVia = null, + goalTemplateId = null, + source = null, } = goal || {}; const goalNameIndex = Object.values(goals).findIndex((n) => n === name); if (goalNameIndex === -1) { @@ -217,6 +219,8 @@ function makeGoalsObjectFromActivityReportGoals(goalRecords) { goals[`goal-${goalCsvRecordNumber}`] = name; goals[`goal-${goalCsvRecordNumber}-status`] = status; goals[`goal-${goalCsvRecordNumber}-created-from`] = createdVia; + goals[`goal-${goalCsvRecordNumber}-source`] = source; + goals[`goal-${goalCsvRecordNumber}-goal-template-id`] = goalTemplateId; goalCsvRecordNumber += 1; return; } @@ -228,6 +232,20 @@ function makeGoalsObjectFromActivityReportGoals(goalRecords) { return goals; } +function updateObjectiveWithRelatedModelData( + relation, + relationLabel, + relationKey, + accum, + objectiveId, +) { + const relatedSimple = relation.map((t) => t[relationKey]); + Object.defineProperty(accum, `objective-${objectiveId}-${relationLabel}`, { + value: relatedSimple.join('\n'), + enumerable: true, + }); +} + /* * Create an object with goals and objectives. Used by transformGoalsAndObjectives * @param {Array} objectiveRecords @@ -243,7 +261,15 @@ function makeGoalsAndObjectivesObject(objectiveRecords) { return objectiveRecords.reduce((prevAccum, objective) => { const accum = { ...prevAccum }; const { - goal, title, status, ttaProvided, topics, files, resources, + goal, + title, + status, + ttaProvided, + topics, + files, + resources, + courses, + supportType, } = objective; const goalId = goal ? goal.id : null; const titleMd5 = md5(title); @@ -313,34 +339,52 @@ function makeGoalsAndObjectivesObject(objectiveRecords) { enumerable: true, }); - // Activity Report Objective: Topics. - const objTopics = topics.map((t) => t.name); - Object.defineProperty(accum, `objective-${objectiveId}-topics`, { - value: objTopics.join('\n'), - enumerable: true, - }); + updateObjectiveWithRelatedModelData( + topics, + 'topics', + 'name', + accum, + objectiveId, + ); + + updateObjectiveWithRelatedModelData( + courses, + 'courses', + 'name', + accum, + objectiveId, + ); + + updateObjectiveWithRelatedModelData( + resources, + 'resourcesLinks', + 'url', + accum, + objectiveId, + ); + + updateObjectiveWithRelatedModelData( + files, + 'nonResourceLinks', + 'originalFileName', + accum, + objectiveId, + ); - // Activity Report Objective: Resources Links. - const objResources = resources.map((r) => r.url); - Object.defineProperty(accum, `objective-${objectiveId}-resourcesLinks`, { - value: objResources.join('\n'), + Object.defineProperty(accum, `objective-${objectiveId}-ttaProvided`, { + value: convert(ttaProvided), enumerable: true, }); - // Activity Report Objective: Non-Resource Links (Files). - const objFiles = files.map((f) => f.originalFileName); - Object.defineProperty(accum, `objective-${objectiveId}-nonResourceLinks`, { - value: objFiles.join('\n'), + Object.defineProperty(accum, `objective-${objectiveId}-supportType`, { + value: supportType, enumerable: true, }); + Object.defineProperty(accum, `objective-${objectiveId}-status`, { value: status, enumerable: true, }); - Object.defineProperty(accum, `objective-${objectiveId}-ttaProvided`, { - value: convert(ttaProvided), - enumerable: true, - }); // Add this objective to the tracked list. processedObjectivesTitles.set(titleMd5, goalNum); @@ -396,6 +440,7 @@ const arTransformers = [ 'participants', 'topics', 'ttaType', + 'language', 'numberOfParticipants', 'deliveryMethod', 'duration', @@ -409,7 +454,9 @@ const arTransformers = [ transformRelatedModel('files', 'originalFileName'), transformGoalsAndObjectives, transformRelatedModel('recipientNextSteps', 'note'), + transformRelatedModel('recipientNextSteps', 'completeDate'), transformRelatedModel('specialistNextSteps', 'note'), + transformRelatedModel('specialistNextSteps', 'completeDate'), transformHTML('context'), transformHTML('additionalNotes'), 'lastSaved', From 444f98e94cdcfa31aaa0c83f23b22fdea1a7773a Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Mon, 17 Jun 2024 15:47:28 -0400 Subject: [PATCH 02/21] Updated CSV --- src/constants.js | 3 + src/lib/transform.js | 76 +++++++++++++++++++++++--- src/models/goal.js | 9 ++- src/routes/activityReports/handlers.js | 8 +++ src/services/activityReports.js | 22 +++++++- 5 files changed, 105 insertions(+), 13 deletions(-) diff --git a/src/constants.js b/src/constants.js index fcb1962904..5e75f215fd 100644 --- a/src/constants.js +++ b/src/constants.js @@ -261,6 +261,8 @@ const GOAL_CREATED_VIA = ['imported', 'activityReport', 'rtr', 'merge', 'admin', const CURRENT_GOAL_SIMILARITY_VERSION = 3; +const OHS_STANDARD_GOAL_TEMPLATE_IDS = []; + module.exports = { CURRENT_GOAL_SIMILARITY_VERSION, FILE_STATUSES, @@ -297,4 +299,5 @@ module.exports = { MAINTENANCE_TYPE, MAINTENANCE_CATEGORY, FEATURE_FLAGS, + OHS_STANDARD_GOAL_TEMPLATE_IDS, }; diff --git a/src/lib/transform.js b/src/lib/transform.js index ea27fda03e..736189a550 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -80,7 +80,7 @@ function transformRelatedModel(field, prop) { } // we sort the values const value = records.map((r) => (r[prop] || '')).sort().join('\n'); - Object.defineProperty(obj, field, { + Object.defineProperty(obj, `${field}`, { value, enumerable: true, }); @@ -90,6 +90,37 @@ function transformRelatedModel(field, prop) { return transformer; } +/** + * + * @param {string} field + * @param {Array} fieldDefs expected [{ subfield: string, label: string }] + * @param {*} prop + * @returns () => ({}) + */ +function transformRelatedModelWithMultiFields(field, fieldDefs) { + function transformer(instance) { + const obj = {}; + fieldDefs.forEach((fieldDef) => { + let records = instance[field]; + if (records) { + if (!Array.isArray(records)) { + records = [records]; + } + const value = records.map((r) => r[fieldDef.subfield]).join('\n'); + console.log({ value }); + console.log({ label: fieldDef.label }); + Object.defineProperty(obj, fieldDef.label, { + value, + enumerable: true, + }); + } + }); + return obj; + } + + return transformer; +} + function transformCollaborators(joinTable, field, fieldName) { function transformer(instance) { const obj = {}; @@ -210,7 +241,6 @@ function makeGoalsObjectFromActivityReportGoals(goalRecords) { name = null, status = null, createdVia = null, - goalTemplateId = null, source = null, } = goal || {}; const goalNameIndex = Object.values(goals).findIndex((n) => n === name); @@ -220,7 +250,6 @@ function makeGoalsObjectFromActivityReportGoals(goalRecords) { goals[`goal-${goalCsvRecordNumber}-status`] = status; goals[`goal-${goalCsvRecordNumber}-created-from`] = createdVia; goals[`goal-${goalCsvRecordNumber}-source`] = source; - goals[`goal-${goalCsvRecordNumber}-goal-template-id`] = goalTemplateId; goalCsvRecordNumber += 1; return; } @@ -301,6 +330,21 @@ function makeGoalsAndObjectivesObject(objectiveRecords) { enumerable: true, }); + Object.defineProperty(accum, `goal-${goalNum}-source`, { + value: goal.source, + enumerable: true, + }); + + Object.defineProperty(accum, `goal-${goalNum}-standard-ohs-goal`, { + value: goal.isCurated ? 'Yes' : 'No', + enumerable: true, + }); + + Object.defineProperty(accum, `goal-${goalNum}-fei-root-causes`, { + value: goal.responses.map((response) => response.response).join('\n'), + enumerable: true, + }); + // Created From. Object.defineProperty(accum, `goal-${goalNum}-created-from`, { value: goal.createdVia, @@ -312,6 +356,11 @@ function makeGoalsAndObjectivesObject(objectiveRecords) { // Make sure its not another objective for the same goal. if (goalIds[goalName] && !goalIds[goalName].includes(goalId)) { accum[`goal-${existingObjectiveTitle}-id`] = `${accum[`goal-${existingObjectiveTitle}-id`]}\n${goalId}`; + accum[`goal-${goalNum}-source`] = `${accum[`goal-${goalNum}-source`]}\n${goal.source}`; + if (goal.isCurated) { + accum[`goal-${goalNum}-fei-root-causes`] = `${accum[`goal-${goalNum}-fei-root-causes`]}\n${goal.responses + .map((response) => response.response).join('\n')}`; + } goalIds[goalName].push(goalId); } return accum; @@ -322,8 +371,6 @@ function makeGoalsAndObjectivesObject(objectiveRecords) { goalNum = 1; } - // same with objective num - /** * this will start other entity objectives at 1.1, which will prevent the creation * of columns that don't fit the current schema (for example, objective-1.0) @@ -412,6 +459,7 @@ function transformGoalsAndObjectives(report) { topics: aro.topics, files: aro.files, resources: aro.resources, + courses: aro.courses, } )); if (objectiveRecords) { @@ -453,10 +501,20 @@ const arTransformers = [ 'nonECLKCResourcesUsed', transformRelatedModel('files', 'originalFileName'), transformGoalsAndObjectives, - transformRelatedModel('recipientNextSteps', 'note'), - transformRelatedModel('recipientNextSteps', 'completeDate'), - transformRelatedModel('specialistNextSteps', 'note'), - transformRelatedModel('specialistNextSteps', 'completeDate'), + transformRelatedModelWithMultiFields('recipientNextSteps', [{ + subfield: 'note', + label: 'recipientNextSteps', + }, { + subfield: 'completeDate', + label: 'recipientNextStepsCompleteDate', + }]), + transformRelatedModelWithMultiFields('specialistNextSteps', [{ + subfield: 'note', + label: 'specialistNextSteps', + }, { + subfield: 'completeDate', + label: 'specialistNextStepsCompleteDate', + }]), transformHTML('context'), transformHTML('additionalNotes'), 'lastSaved', diff --git a/src/models/goal.js b/src/models/goal.js index 92620b56cc..f3cae275b4 100644 --- a/src/models/goal.js +++ b/src/models/goal.js @@ -8,7 +8,7 @@ const { afterUpdate, afterDestroy, } = require('./hooks/goal'); -const { GOAL_CREATED_VIA } = require('../constants'); +const { GOAL_CREATED_VIA, CREATION_METHOD } = require('../constants'); export const RTTAPA_ENUM = ['Yes', 'No']; @@ -79,6 +79,13 @@ export default (sequelize, DataTypes) => { return `G-${id}`; }, }, + isCurated: { + type: DataTypes.VIRTUAL, + get() { + const { goalTemplate } = this; + return goalTemplate?.creationMethod === CREATION_METHOD.CURATED; + }, + }, grantId: { type: DataTypes.INTEGER, allowNull: false, diff --git a/src/routes/activityReports/handlers.js b/src/routes/activityReports/handlers.js index 35f66e021e..e7c2ad3c3b 100644 --- a/src/routes/activityReports/handlers.js +++ b/src/routes/activityReports/handlers.js @@ -172,10 +172,18 @@ async function sendActivityReportCSV(reports, res) { key: 'specialistNextSteps', header: 'Specialist next steps', }, + { + key: 'specialistNextStepsCompleteDate', + header: 'Specialist next steps anticipated completion date', + }, { key: 'recipientNextSteps', header: 'Recipient next steps', }, + { + key: 'recipientNextStepsCompleteDate', + header: 'Recipient next steps anticipated completion date', + }, { key: 'createdAt', header: 'Created date', diff --git a/src/services/activityReports.js b/src/services/activityReports.js index c0c0346903..1e2dcfe398 100644 --- a/src/services/activityReports.js +++ b/src/services/activityReports.js @@ -20,6 +20,8 @@ import { Recipient, OtherEntity, Goal, + GoalTemplate, + GoalFieldResponse, User, NextStep, Objective, @@ -31,6 +33,7 @@ import { Topic, CollaboratorRole, Role, + Course, } from '../models'; import { removeUnusedGoalsObjectivesFromReport, @@ -1163,7 +1166,7 @@ async function getDownloadableActivityReports(where, separate = true) { { model: ActivityReportObjective, as: 'activityReportObjectives', - attributes: ['ttaProvided', 'status'], + attributes: ['ttaProvided', 'status', 'supportType'], order: [['objective', 'goal', 'id'], ['objective', 'id']], separate, include: [{ @@ -1173,6 +1176,15 @@ async function getDownloadableActivityReports(where, separate = true) { model: Goal, as: 'goal', required: false, + include: [{ + model: GoalFieldResponse, + as: 'responses', + attributes: ['response'], + }, { + model: GoalTemplate, + as: 'goalTemplate', + attributes: ['creationMethod'], + }], }, ], attributes: ['id', 'title', 'status'], @@ -1190,6 +1202,10 @@ async function getDownloadableActivityReports(where, separate = true) { model: File, as: 'files', }, + { + model: Course, + as: 'courses', + }, ], }, { @@ -1259,14 +1275,14 @@ async function getDownloadableActivityReports(where, separate = true) { }, { model: NextStep, - attributes: ['note', 'id'], + attributes: ['note', 'id', 'completeDate'], as: 'specialistNextSteps', separate, required: false, }, { model: NextStep, - attributes: ['note', 'id'], + attributes: ['note', 'id', 'completeDate'], as: 'recipientNextSteps', separate, required: false, From 22a3b55ae36c1d6f5bcfee3ca80e3713257fdbe1 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 08:50:30 -0400 Subject: [PATCH 03/21] Remove console.logs --- src/lib/transform.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/transform.js b/src/lib/transform.js index 736189a550..a3eba17d08 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -107,8 +107,6 @@ function transformRelatedModelWithMultiFields(field, fieldDefs) { records = [records]; } const value = records.map((r) => r[fieldDef.subfield]).join('\n'); - console.log({ value }); - console.log({ label: fieldDef.label }); Object.defineProperty(obj, fieldDef.label, { value, enumerable: true, From d3949c1ede9b12c56047d931ab4ad90bec520d9d Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 09:13:17 -0400 Subject: [PATCH 04/21] Update unit tests --- src/lib/transform.js | 5 +- src/lib/transform.test.js | 104 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/lib/transform.js b/src/lib/transform.js index a3eba17d08..7310287e70 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -1,5 +1,6 @@ import moment from 'moment'; import md5 from 'md5'; +import { uniq } from 'lodash'; import { convert } from 'html-to-text'; import { DATE_FORMAT } from '../constants'; @@ -266,7 +267,7 @@ function updateObjectiveWithRelatedModelData( accum, objectiveId, ) { - const relatedSimple = relation.map((t) => t[relationKey]); + const relatedSimple = (relation || []).map((t) => t[relationKey]); Object.defineProperty(accum, `objective-${objectiveId}-${relationLabel}`, { value: relatedSimple.join('\n'), enumerable: true, @@ -354,7 +355,7 @@ function makeGoalsAndObjectivesObject(objectiveRecords) { // Make sure its not another objective for the same goal. if (goalIds[goalName] && !goalIds[goalName].includes(goalId)) { accum[`goal-${existingObjectiveTitle}-id`] = `${accum[`goal-${existingObjectiveTitle}-id`]}\n${goalId}`; - accum[`goal-${goalNum}-source`] = `${accum[`goal-${goalNum}-source`]}\n${goal.source}`; + accum[`goal-${goalNum}-source`] = uniq([...accum[`goal-${goalNum}-source`].split('\n'), goal.source]).join('\n'); if (goal.isCurated) { accum[`goal-${goalNum}-fei-root-causes`] = `${accum[`goal-${goalNum}-fei-root-causes`]}\n${goal.responses .map((response) => response.response).join('\n')}`; diff --git a/src/lib/transform.test.js b/src/lib/transform.test.js index d8f4ade78b..fe33ab473f 100644 --- a/src/lib/transform.test.js +++ b/src/lib/transform.test.js @@ -1,5 +1,5 @@ import { REPORT_STATUSES } from '@ttahub/common'; -import { OBJECTIVE_STATUS } from '../constants'; +import { CREATION_METHOD, OBJECTIVE_STATUS } from '../constants'; import { ActivityReport, User, @@ -8,10 +8,13 @@ import { ActivityReportGoal, Grant, Goal, + GoalFieldResponse, + GoalTemplate, Recipient, ActivityReportCollaborator, ActivityReportObjective, Resource, + Course, Topic, Objective, File, @@ -59,6 +62,12 @@ describe('activityReportToCsvRecord', () => { grantId: 1, timeframe: 'None', createdVia: 'activityReport', + goalTemplate: { + creationMethod: CREATION_METHOD.AUTOMATIC, + id: 20_800, + }, + source: 'RTTAPA development', + responses: [], }, { name: 'Goal 2', @@ -67,6 +76,12 @@ describe('activityReportToCsvRecord', () => { grantId: 1, timeframe: 'None', createdVia: 'rtr', + goalTemplate: { + creationMethod: CREATION_METHOD.AUTOMATIC, + id: 20_801, + }, + source: 'RTTAPA development', + responses: [], }, { name: 'Goal 3', @@ -75,6 +90,12 @@ describe('activityReportToCsvRecord', () => { grantId: 1, timeframe: 'None', createdVia: 'imported', + goalTemplate: { + creationMethod: CREATION_METHOD.AUTOMATIC, + id: 20_802, + }, + source: 'RTTAPA development', + responses: [], }, { name: 'Goal 3', @@ -83,6 +104,12 @@ describe('activityReportToCsvRecord', () => { grantId: 2, timeframe: 'None', createdVia: 'activityReport', + goalTemplate: { + creationMethod: CREATION_METHOD.AUTOMATIC, + id: 20_803, + }, + source: 'RTTAPA development', + responses: [], }, { name: 'Goal 4', @@ -91,6 +118,12 @@ describe('activityReportToCsvRecord', () => { grantId: 3, timeframe: 'None', createdVia: 'activityReport', + goalTemplate: { + creationMethod: CREATION_METHOD.AUTOMATIC, + id: 20_804, + }, + source: 'RTTAPA development', + responses: [], }, // Same goal different recipient. { @@ -100,6 +133,12 @@ describe('activityReportToCsvRecord', () => { grantId: 4, timeframe: 'None', createdVia: 'activityReport', + goalTemplate: { + creationMethod: CREATION_METHOD.AUTOMATIC, + id: 20_805, + }, + source: 'RTTAPA development', + responses: [], }, ]; @@ -110,6 +149,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[0], + supportType: 'Maintaining', }, { id: 12, @@ -117,6 +157,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[0], + supportType: 'Maintaining', }, { id: 13, @@ -124,6 +165,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[1], + supportType: 'Maintaining', }, { id: 14, @@ -131,6 +173,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[1], + supportType: 'Maintaining', }, { id: 15, @@ -138,6 +181,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[1], + supportType: 'Maintaining', }, { id: 16, @@ -145,6 +189,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[2], + supportType: 'Maintaining', }, { id: 17, @@ -152,6 +197,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[2], + supportType: 'Maintaining', }, // Duplicate Objective name for goal 4. { @@ -160,6 +206,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[4], + supportType: 'Maintaining', }, { id: 19, @@ -167,6 +214,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[4], + supportType: 'Maintaining', }, // Same as goal 1 different recipient. { @@ -175,6 +223,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[5], + supportType: 'Maintaining', }, { id: 21, @@ -182,6 +231,7 @@ describe('activityReportToCsvRecord', () => { ttaProvided: 'Training', status: OBJECTIVE_STATUS.COMPLETE, goal: mockGoals[5], + supportType: 'Maintaining', }, ]; @@ -192,7 +242,6 @@ describe('activityReportToCsvRecord', () => { userId: 3, user: { name: 'Test Approver 1', - }, }, { @@ -415,7 +464,6 @@ describe('activityReportToCsvRecord', () => { as: 'approvers', include: [{ model: User, as: 'user' }], }, - { model: ActivityReportObjective, as: 'activityReportObjectives', @@ -423,6 +471,22 @@ describe('activityReportToCsvRecord', () => { { model: Objective, as: 'objective', + include: [ + { + model: Goal, + as: 'goal', + include: [ + { + model: GoalTemplate, + as: 'goalTemplate', + }, + { + model: GoalFieldResponse, + as: 'responses', + }, + ], + }, + ], }, { model: Resource, @@ -436,10 +500,15 @@ describe('activityReportToCsvRecord', () => { model: File, as: 'files', }, + { + model: Course, + as: 'courses', + }, ], }, ], }); + const output = await activityReportToCsvRecord(report.toJSON()); const { creatorName, @@ -471,6 +540,7 @@ describe('activityReportToCsvRecord', () => { topics: [{ name: 'Topic 1' }], resources: [{ url: 'https://test.gov' }], files: [{ originalFileName: 'TestFile.docx' }], + courses: [{ name: 'Other' }], })); const output = makeGoalsAndObjectivesObject(objectives); @@ -479,10 +549,15 @@ describe('activityReportToCsvRecord', () => { 'goal-1': 'Goal 1', 'goal-1-status': 'Not Started', 'goal-1-created-from': 'activityReport', + 'goal-1-fei-root-causes': '', + 'goal-1-source': 'RTTAPA development', + 'goal-1-standard-ohs-goal': 'No', 'objective-1.1': 'Objective 1.1', 'objective-1.1-topics': 'Topic 1', 'objective-1.1-resourcesLinks': 'https://test.gov', 'objective-1.1-nonResourceLinks': 'TestFile.docx', + 'objective-1.1-courses': 'Other', + 'objective-1.1-supportType': 'Maintaining', 'objective-1.1-ttaProvided': 'Training', 'objective-1.1-status': 'Complete', 'objective-1.2': 'Objective 1.2', @@ -491,54 +566,77 @@ describe('activityReportToCsvRecord', () => { 'objective-1.2-nonResourceLinks': 'TestFile.docx', 'objective-1.2-ttaProvided': 'Training', 'objective-1.2-status': 'Complete', + 'objective-1.2-courses': 'Other', + 'objective-1.2-supportType': 'Maintaining', 'goal-2-id': '2081', 'goal-2': 'Goal 2', 'goal-2-status': 'Not Started', 'goal-2-created-from': 'rtr', + 'goal-2-fei-root-causes': '', + 'goal-2-source': 'RTTAPA development', + 'goal-2-standard-ohs-goal': 'No', 'objective-2.1': 'Objective 2.1', 'objective-2.1-topics': 'Topic 1', 'objective-2.1-resourcesLinks': 'https://test.gov', 'objective-2.1-nonResourceLinks': 'TestFile.docx', 'objective-2.1-ttaProvided': 'Training', 'objective-2.1-status': 'Complete', + 'objective-2.1-courses': 'Other', + 'objective-2.1-supportType': 'Maintaining', 'objective-2.2': 'Objective 2.2', 'objective-2.2-topics': 'Topic 1', 'objective-2.2-resourcesLinks': 'https://test.gov', 'objective-2.2-nonResourceLinks': 'TestFile.docx', 'objective-2.2-ttaProvided': 'Training', 'objective-2.2-status': 'Complete', + 'objective-2.2-courses': 'Other', + 'objective-2.2-supportType': 'Maintaining', 'objective-2.3': 'Objective 2.3', 'objective-2.3-topics': 'Topic 1', 'objective-2.3-resourcesLinks': 'https://test.gov', 'objective-2.3-nonResourceLinks': 'TestFile.docx', 'objective-2.3-ttaProvided': 'Training', 'objective-2.3-status': 'Complete', + 'objective-2.3-courses': 'Other', + 'objective-2.3-supportType': 'Maintaining', 'goal-3-id': '2082', 'goal-3': 'Goal 3', 'goal-3-status': 'Not Started', 'goal-3-created-from': 'imported', + 'goal-3-fei-root-causes': '', + 'goal-3-source': 'RTTAPA development', + 'goal-3-standard-ohs-goal': 'No', 'objective-3.1': 'Objective 3.1', 'objective-3.1-topics': 'Topic 1', 'objective-3.1-resourcesLinks': 'https://test.gov', 'objective-3.1-nonResourceLinks': 'TestFile.docx', 'objective-3.1-ttaProvided': 'Training', 'objective-3.1-status': 'Complete', + 'objective-3.1-courses': 'Other', + 'objective-3.1-supportType': 'Maintaining', 'goal-4-id': '2084', 'goal-4': 'Goal 4', 'goal-4-status': 'Not Started', 'goal-4-created-from': 'activityReport', + 'goal-4-fei-root-causes': '', + 'goal-4-source': 'RTTAPA development', + 'goal-4-standard-ohs-goal': 'No', 'objective-4.1': 'Objective 3.1', 'objective-4.1-topics': 'Topic 1', 'objective-4.1-resourcesLinks': 'https://test.gov', 'objective-4.1-nonResourceLinks': 'TestFile.docx', 'objective-4.1-ttaProvided': 'Training', 'objective-4.1-status': 'Complete', + 'objective-4.1-courses': 'Other', + 'objective-4.1-supportType': 'Maintaining', 'objective-4.2': 'Objective 4.2', 'objective-4.2-topics': 'Topic 1', 'objective-4.2-resourcesLinks': 'https://test.gov', 'objective-4.2-nonResourceLinks': 'TestFile.docx', 'objective-4.2-ttaProvided': 'Training', 'objective-4.2-status': 'Complete', + 'objective-4.2-courses': 'Other', + 'objective-4.2-supportType': 'Maintaining', }); }); From 9ea2b8dcab41e8948805bfbd155a7a7e5574ce92 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 11:39:01 -0400 Subject: [PATCH 05/21] Remove unused array --- src/constants.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/constants.js b/src/constants.js index 5e75f215fd..fcb1962904 100644 --- a/src/constants.js +++ b/src/constants.js @@ -261,8 +261,6 @@ const GOAL_CREATED_VIA = ['imported', 'activityReport', 'rtr', 'merge', 'admin', const CURRENT_GOAL_SIMILARITY_VERSION = 3; -const OHS_STANDARD_GOAL_TEMPLATE_IDS = []; - module.exports = { CURRENT_GOAL_SIMILARITY_VERSION, FILE_STATUSES, @@ -299,5 +297,4 @@ module.exports = { MAINTENANCE_TYPE, MAINTENANCE_CATEGORY, FEATURE_FLAGS, - OHS_STANDARD_GOAL_TEMPLATE_IDS, }; From b924fef0b782ef2caf45fc9c658cc357a92d5088 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 11:41:23 -0400 Subject: [PATCH 06/21] Add uniq to fei root causes as well --- src/lib/transform.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/transform.js b/src/lib/transform.js index 7310287e70..a2ea7b9c26 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -357,8 +357,7 @@ function makeGoalsAndObjectivesObject(objectiveRecords) { accum[`goal-${existingObjectiveTitle}-id`] = `${accum[`goal-${existingObjectiveTitle}-id`]}\n${goalId}`; accum[`goal-${goalNum}-source`] = uniq([...accum[`goal-${goalNum}-source`].split('\n'), goal.source]).join('\n'); if (goal.isCurated) { - accum[`goal-${goalNum}-fei-root-causes`] = `${accum[`goal-${goalNum}-fei-root-causes`]}\n${goal.responses - .map((response) => response.response).join('\n')}`; + accum[`goal-${goalNum}-fei-root-causes`] = uniq([...accum[`goal-${goalNum}-fei-root-causes`].split('\n'), ...goal.responses.map((response) => response.response)]).join('\n'); } goalIds[goalName].push(goalId); } From 85a35573a42ade23b1c5d9d55772d08630a91286 Mon Sep 17 00:00:00 2001 From: nvms Date: Fri, 21 Jun 2024 10:13:01 -0400 Subject: [PATCH 07/21] initial stages --- .../src/pages/ResourcesDashboard/index.js | 1 + .../src/widgets/ResourcesDashboardOverview.js | 40 ++++++++++++++++++- src/services/dashboards/resource.js | 3 ++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/ResourcesDashboard/index.js b/frontend/src/pages/ResourcesDashboard/index.js index ab0ffe09a7..1a7941f80e 100644 --- a/frontend/src/pages/ResourcesDashboard/index.js +++ b/frontend/src/pages/ResourcesDashboard/index.js @@ -307,6 +307,7 @@ export default function ResourcesDashboard() { 'ECLKC Resources', 'Recipients reached', 'Participants reached', + 'Reports citing iPD courses', ]} showTooltips /> diff --git a/frontend/src/widgets/ResourcesDashboardOverview.js b/frontend/src/widgets/ResourcesDashboardOverview.js index 87e0984286..8f1d3eecb2 100644 --- a/frontend/src/widgets/ResourcesDashboardOverview.js +++ b/frontend/src/widgets/ResourcesDashboardOverview.js @@ -1,9 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Grid } from '@trussworks/react-uswds'; +import { Link } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - faLink, faCube, faUser, faUserFriends, + faLink, + faCube, + faUser, + faUserFriends, + faFolder, } from '@fortawesome/free-solid-svg-icons'; import './ResourcesDashboardOverview.css'; @@ -14,6 +19,7 @@ import colors from '../colors'; export function Field({ label1, label2, + route, data, icon, iconColor, @@ -39,6 +45,11 @@ export function Field({ /> ) : label1} {label2} + {route && ( + + {route.label} + + )} ); @@ -58,12 +69,17 @@ Field.propTypes = { backgroundColor: PropTypes.string.isRequired, tooltipText: PropTypes.string, showTooltip: PropTypes.bool, + route: PropTypes.shape({ + to: PropTypes.string, + label: PropTypes.string, + }), }; Field.defaultProps = { tooltipText: '', showTooltip: false, label2: '', + route: null, }; const DASHBOARD_FIELDS = { 'Reports with resources': { @@ -124,6 +140,24 @@ const DASHBOARD_FIELDS = { /> ), }, + 'Reports citing iPD courses': { + render: (data) => ( + + ), + }, }; export function ResourcesDashboardOverviewWidget({ @@ -180,6 +214,9 @@ ResourcesDashboardOverviewWidget.defaultProps = { participant: { numParticipants: '0', }, + ipdCourses: { + percentReports: '0%', + }, }, loading: false, showTooltips: false, @@ -188,6 +225,7 @@ ResourcesDashboardOverviewWidget.defaultProps = { 'ECLKC Resources', 'Recipients reached', 'Participants reached', + 'Reports citing iPD courses', ], }; diff --git a/src/services/dashboards/resource.js b/src/services/dashboards/resource.js index dc887099ac..c9144bb9eb 100644 --- a/src/services/dashboards/resource.js +++ b/src/services/dashboards/resource.js @@ -2102,6 +2102,9 @@ export function restructureOverview(data) { num: formatNumber(data.overView.pctOfECKLKCResources[0].allCount), percentEclkc: `${formatNumber(data.overView.pctOfECKLKCResources[0].eclkcPct, 2)}%`, }, + ipdCourses: { + percentReports: `${formatNumber(0)}%`, + }, }; } From 5408e13e153db080274f30347d9968fc5d6d55c3 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Fri, 21 Jun 2024 11:29:11 -0400 Subject: [PATCH 08/21] Fix bad created via and remove path --- src/goalServices/goals.js | 5 +--- .../20240621152352-reset-bad-created-vias.js | 23 +++++++++++++++++++ src/models/goal.js | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 src/migrations/20240621152352-reset-bad-created-vias.js diff --git a/src/goalServices/goals.js b/src/goalServices/goals.js index ae6aece909..7142516a2e 100644 --- a/src/goalServices/goals.js +++ b/src/goalServices/goals.js @@ -484,6 +484,7 @@ export async function createOrUpdateGoals(goals) { status: 'Draft', // if we are creating a goal for the first time, it should be set to 'Draft' isFromSmartsheetTtaPlan: false, rtrOrder: rtrOrder + 1, + createdVia: 'rtr', }); } @@ -504,10 +505,6 @@ export async function createOrUpdateGoals(goals) { if (newGoal.status !== status) { newGoal.set({ status }); } - - if (!newGoal.createdVia || newGoal.createdVia !== createdVia) { - newGoal.set({ createdVia: createdVia || (newGoal.isFromSmartsheetTtaPlan ? 'imported' : 'rtr') }); - } } // end date and source can be updated if the goal is not closed diff --git a/src/migrations/20240621152352-reset-bad-created-vias.js b/src/migrations/20240621152352-reset-bad-created-vias.js new file mode 100644 index 0000000000..63fef0f001 --- /dev/null +++ b/src/migrations/20240621152352-reset-bad-created-vias.js @@ -0,0 +1,23 @@ +const { + prepMigration, +} = require('../lib/migration'); + +module.exports = { + up: async (queryInterface) => queryInterface.sequelize.transaction( + async (transaction) => { + await prepMigration(queryInterface, transaction, __filename); + await queryInterface.sequelize.query(/* sql */` + UPDATE "Goals" + SET "createdVia" = 'merge' + WHERE id IN (69403, 78365) AND "createdVia" = 'imported'; -- Nathan helpfully provided me with these IDs based on the audit log + `, { transaction }); + }, + ), + + down: async (queryInterface) => queryInterface.sequelize.transaction( + async (transaction) => { + await prepMigration(queryInterface, transaction, __filename); + // No need to put back bad data + }, + ), +}; diff --git a/src/models/goal.js b/src/models/goal.js index 92620b56cc..c4983ce11c 100644 --- a/src/models/goal.js +++ b/src/models/goal.js @@ -1,5 +1,5 @@ const { Model } = require('sequelize'); -const { CLOSE_SUSPEND_REASONS, GOAL_SOURCES } = require('@ttahub/common'); +const { GOAL_SOURCES } = require('@ttahub/common'); const { formatDate } = require('../lib/modelHelpers'); const { beforeValidate, From 8982f1d1ebb6df238182e36200f527d331dbcb35 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Fri, 21 Jun 2024 11:55:51 -0400 Subject: [PATCH 09/21] Fix bugs and add additional tests --- src/lib/transform.js | 7 +++- src/lib/transform.test.js | 52 ++++++++++++++++++++++++++ src/routes/activityReports/handlers.js | 4 ++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/lib/transform.js b/src/lib/transform.js index a2ea7b9c26..500a656213 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -32,6 +32,7 @@ function transformSimpleValue(instance, field) { value = value.sort().join('\n'); } const obj = {}; + Object.defineProperty(obj, field, { value, enumerable: true, @@ -355,7 +356,11 @@ function makeGoalsAndObjectivesObject(objectiveRecords) { // Make sure its not another objective for the same goal. if (goalIds[goalName] && !goalIds[goalName].includes(goalId)) { accum[`goal-${existingObjectiveTitle}-id`] = `${accum[`goal-${existingObjectiveTitle}-id`]}\n${goalId}`; - accum[`goal-${goalNum}-source`] = uniq([...accum[`goal-${goalNum}-source`].split('\n'), goal.source]).join('\n'); + if (accum[`goal-${goalNum}-source`]) { + accum[`goal-${goalNum}-source`] = uniq([...(accum[`goal-${goalNum}-source`]).split('\n'), goal.source]).join('\n'); + } else { + accum[`goal-${goalNum}-source`] = goal.source; + } if (goal.isCurated) { accum[`goal-${goalNum}-fei-root-causes`] = uniq([...accum[`goal-${goalNum}-fei-root-causes`].split('\n'), ...goal.responses.map((response) => response.response)]).join('\n'); } diff --git a/src/lib/transform.test.js b/src/lib/transform.test.js index fe33ab473f..eaebd42596 100644 --- a/src/lib/transform.test.js +++ b/src/lib/transform.test.js @@ -640,6 +640,58 @@ describe('activityReportToCsvRecord', () => { }); }); + it('handles a null goal source', () => { + const objectives = mockObjectives.map((mo, i) => { + if (i === 0) { + return { + ...mo, + title: 'same title', + goal: { + ...mo.goal, + source: null, + name: 'same name', + }, + topics: [{ name: 'Topic 1' }], + resources: [{ url: 'https://test.gov' }], + files: [{ originalFileName: 'TestFile.docx' }], + courses: [{ name: 'Other' }], + }; + } + + return { + ...mo, + title: 'same title', + goal: { + ...mo.goal, + name: 'same name', + }, + topics: [{ name: 'Topic 1' }], + resources: [{ url: 'https://test.gov' }], + files: [{ originalFileName: 'TestFile.docx' }], + courses: [{ name: 'Other' }], + }; + }); + + const output = makeGoalsAndObjectivesObject(objectives); + expect(output).toEqual({ + 'goal-1-id': '2080\n2081\n2082\n2084\n2085', + 'goal-1': 'same name', + 'goal-1-status': 'Not Started', + 'goal-1-created-from': 'activityReport', + 'goal-1-fei-root-causes': '', + 'goal-1-source': 'RTTAPA development', + 'goal-1-standard-ohs-goal': 'No', + 'objective-1.1': 'same title', + 'objective-1.1-topics': 'Topic 1', + 'objective-1.1-resourcesLinks': 'https://test.gov', + 'objective-1.1-nonResourceLinks': 'TestFile.docx', + 'objective-1.1-courses': 'Other', + 'objective-1.1-supportType': 'Maintaining', + 'objective-1.1-ttaProvided': 'Training', + 'objective-1.1-status': 'Complete', + }); + }); + it('return a list of all keys that are a goal or objective and in the proper order', () => { const csvData = [ { diff --git a/src/routes/activityReports/handlers.js b/src/routes/activityReports/handlers.js index e7c2ad3c3b..a9b6ae6233 100644 --- a/src/routes/activityReports/handlers.js +++ b/src/routes/activityReports/handlers.js @@ -134,6 +134,10 @@ async function sendActivityReportCSV(reports, res) { key: 'ttaType', header: 'TTA type', }, + { + key: 'language', + header: 'Language', + }, { key: 'deliveryMethod', header: 'Delivery method', From 660e0eed1e8fcf193efb5246d7c4987a6bf48111 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Fri, 21 Jun 2024 12:03:50 -0400 Subject: [PATCH 10/21] Fix missing data in test --- src/goalServices/createOrUpdateGoals.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/goalServices/createOrUpdateGoals.test.js b/src/goalServices/createOrUpdateGoals.test.js index f58d47849e..99c5fede35 100644 --- a/src/goalServices/createOrUpdateGoals.test.js +++ b/src/goalServices/createOrUpdateGoals.test.js @@ -48,6 +48,7 @@ describe('createOrUpdateGoals', () => { status: 'Draft', grantId: grants[0].id, source: GOAL_SOURCES[0], + createdVia: 'activityReport', }); objective = await Objective.create({ @@ -113,7 +114,6 @@ describe('createOrUpdateGoals', () => { grantId: goal.grantId, status: 'Draft', }; - newGoals = await createOrUpdateGoals([ { ...basicGoal, From 8a38fe3d7f675ed009596994b738a8f42c2a5b47 Mon Sep 17 00:00:00 2001 From: nvms Date: Sun, 23 Jun 2024 21:07:24 -0400 Subject: [PATCH 11/21] sql for course count --- src/services/dashboards/resource.js | 45 +++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/services/dashboards/resource.js b/src/services/dashboards/resource.js index c9144bb9eb..ded3d6edc6 100644 --- a/src/services/dashboards/resource.js +++ b/src/services/dashboards/resource.js @@ -418,7 +418,23 @@ async function GenerateFlatTempTables(reportIds, tblNames) { JOIN ${tblNames.createdResourcesTempTableName} arorr ON aror."resourceId" = arorr.id; - -- 7.) Create date headers. + -- 7.) Create flat reports with courses temp table. + DROP TABLE IF EXISTS ${tblNames.createdAroCoursesTempTableName}; + SELECT + DISTINCT + ar.id AS "activityReportId", + c.id AS "courseId", + c.name + INTO TEMP ${tblNames.createdAroCoursesTempTableName} + FROM ${tblNames.createdArTempTableName} ar + JOIN "ActivityReportObjectives" aro + ON ar."id" = aro."activityReportId" + JOIN "ActivityReportObjectiveCourses" aroc + ON aro.id = aroc."activityReportObjectiveId" + JOIN "Courses" c + ON aroc."courseId" = c.id; + + -- 8.) Create date headers. DROP TABLE IF EXISTS ${tblNames.createdFlatResourceHeadersTempTableName}; SELECT generate_series( @@ -591,10 +607,27 @@ function getOverview(tblNames, totalReportCount) { END AS "resourcesPct" FROM ${tblNames.createdAroResourcesTempTableName}; `; + const pctOfReportsWithResources = sequelize.query(pctOfResourcesSql, { type: QueryTypes.SELECT, }); + const pctOfReportsWithCoursesSql = /* sql */` + SELECT + count(DISTINCT "activityReportId")::decimal AS "reportsWithCoursesCount", + ${totalReportCount}::decimal AS "totalReportsCount", + CASE WHEN ${totalReportCount} = 0 THEN + 0 + ELSE + (count(DISTINCT "activityReportId") / ${totalReportCount}::decimal * 100)::decimal(5,2) + END AS "coursesPct" + FROM ${tblNames.createdAroCoursesTempTableName}; + `; + + const pctOfReportsWithCourses = sequelize.query(pctOfReportsWithCoursesSql, { + type: QueryTypes.SELECT, + }); + // - Number of Reports with ECLKC Resources Pct - const pctOfECKLKCResources = sequelize.query(/* sql */` WITH eclkc AS ( @@ -633,6 +666,7 @@ function getOverview(tblNames, totalReportCount) { numberOfParticipants, numberOfRecipients, pctOfReportsWithResources, + pctOfReportsWithCourses, pctOfECKLKCResources, dateHeaders, }; @@ -674,6 +708,7 @@ export async function resourceFlatData(scopes) { const uuid = uuidv4().replaceAll('-', '_'); const createdArTempTableName = `Z_temp_resdb_ar__${uuid}`; const createdAroResourcesTempTableName = `Z_temp_resdb_aror__${uuid}`; + const createdAroCoursesTempTableName = `Z_temp_resdb_aroc__${uuid}`; const createdResourcesTempTableName = `Z_temp_resdb_res__${uuid}`; const createdAroTopicsTempTableName = `Z_temp_resdb_arot__${uuid}`; const createdTopicsTempTableName = `Z_temp_resdb_topics__${uuid}`; @@ -683,6 +718,7 @@ export async function resourceFlatData(scopes) { const tempTableNames = { createdArTempTableName, createdAroResourcesTempTableName, + createdAroCoursesTempTableName, createdResourcesTempTableName, createdAroTopicsTempTableName, createdTopicsTempTableName, @@ -706,6 +742,7 @@ export async function resourceFlatData(scopes) { numberOfParticipants, numberOfRecipients, pctOfReportsWithResources, + pctOfReportsWithCourses, pctOfECKLKCResources, dateHeaders, } = getOverview(tempTableNames, totalReportCount); @@ -717,6 +754,7 @@ export async function resourceFlatData(scopes) { numberOfParticipants, numberOfRecipients, pctOfReportsWithResources, + pctOfReportsWithCourses, pctOfECKLKCResources, dateHeaders, ] = await Promise.all( @@ -726,6 +764,7 @@ export async function resourceFlatData(scopes) { numberOfParticipants, numberOfRecipients, pctOfReportsWithResources, + pctOfReportsWithCourses, pctOfECKLKCResources, dateHeaders, ], @@ -733,7 +772,7 @@ export async function resourceFlatData(scopes) { // 5.) Restructure Overview. const overView = { - numberOfParticipants, numberOfRecipients, pctOfReportsWithResources, pctOfECKLKCResources, + numberOfParticipants, numberOfRecipients, pctOfReportsWithResources, pctOfECKLKCResources, pctOfReportsWithCourses, }; // 6.) Return the data. @@ -2103,7 +2142,7 @@ export function restructureOverview(data) { percentEclkc: `${formatNumber(data.overView.pctOfECKLKCResources[0].eclkcPct, 2)}%`, }, ipdCourses: { - percentReports: `${formatNumber(0)}%`, + percentReports: `${formatNumber(data.overView.pctOfReportsWithCourses[0].coursesPct)}%`, }, }; } From 5a419966b6ed24727c589fd3c3689295691c3951 Mon Sep 17 00:00:00 2001 From: nvms Date: Mon, 24 Jun 2024 10:27:11 -0400 Subject: [PATCH 12/21] format number correctly --- src/services/dashboards/resource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/dashboards/resource.js b/src/services/dashboards/resource.js index ded3d6edc6..18e15436a2 100644 --- a/src/services/dashboards/resource.js +++ b/src/services/dashboards/resource.js @@ -2142,7 +2142,7 @@ export function restructureOverview(data) { percentEclkc: `${formatNumber(data.overView.pctOfECKLKCResources[0].eclkcPct, 2)}%`, }, ipdCourses: { - percentReports: `${formatNumber(data.overView.pctOfReportsWithCourses[0].coursesPct)}%`, + percentReports: `${formatNumber(data.overView.pctOfReportsWithCourses[0].coursesPct, 2)}%`, }, }; } From ceb6672a03c698a25f5128f053adf52d24cefac1 Mon Sep 17 00:00:00 2001 From: nvms Date: Mon, 24 Jun 2024 10:36:42 -0400 Subject: [PATCH 13/21] update tests --- .../src/pages/ResourcesDashboard/__tests__/index.js | 13 +++++++++++++ src/services/dashboards/resourceFlat.test.js | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/frontend/src/pages/ResourcesDashboard/__tests__/index.js b/frontend/src/pages/ResourcesDashboard/__tests__/index.js index 19346a05d2..703f7eb563 100644 --- a/frontend/src/pages/ResourcesDashboard/__tests__/index.js +++ b/frontend/src/pages/ResourcesDashboard/__tests__/index.js @@ -49,6 +49,9 @@ const resourcesDefault = { participant: { numParticipants: '765', }, + ipdCourses: { + percentReports: '4.65%', + }, }, resourcesUse: { headers: ['Jan-22'], @@ -116,6 +119,9 @@ const resourcesRegion1 = { participant: { numParticipants: '665', }, + ipdCourses: { + percentReports: '4.65%', + }, }, resourcesUse: { headers: ['Jan-22'], @@ -183,6 +189,9 @@ const resourcesRegion2 = { participant: { numParticipants: '565', }, + ipdCourses: { + percentReports: '4.65%', + }, }, resourcesUse: { headers: ['Jan-22'], @@ -442,6 +451,10 @@ describe('Resource Dashboard page', () => { expect(screen.getAllByText(/^[ \t]*reports with resources[ \t]*$/i)[0]).toBeInTheDocument(); expect(screen.getByText(/6,135 of 17,914/i)).toBeInTheDocument(); + // iPD courses + expect(screen.getByText(/4.65%/i)).toBeInTheDocument(); + expect(screen.getAllByText(/^[ \t]*reports citing ipd courses[ \t]*$/i)[0]).toBeInTheDocument(); + expect(screen.getByText(/.66%/i)).toBeInTheDocument(); expect(screen.getAllByText(/^[ \t]*eclkc resources[ \t]*$/i)[0]).toBeInTheDocument(); expect(screen.getByText(/818 of 365/i)).toBeInTheDocument(); diff --git a/src/services/dashboards/resourceFlat.test.js b/src/services/dashboards/resourceFlat.test.js index 6ce38b7d9a..8fff6c6c51 100644 --- a/src/services/dashboards/resourceFlat.test.js +++ b/src/services/dashboards/resourceFlat.test.js @@ -829,6 +829,7 @@ describe('Resources dashboard', () => { numberOfParticipants: [{ participants: '44' }], numberOfRecipients: [{ recipients: '1' }], pctOfECKLKCResources: [{ eclkcCount: '2', allCount: '3', eclkcPct: '66.6667' }], + pctOfReportsWithCourses: [{ coursesPct: '80.0000', reportsWithCoursesCount: '4', totalReportsCount: '5' }], }, }; @@ -851,6 +852,9 @@ describe('Resources dashboard', () => { num: '3', percentEclkc: '66.67%', }, + ipdCourses: { + percentReports: '80.00%', + }, }); }); }); From 20ef7a76c5f77e16cd997bcfb6d2ef4c0b5546b5 Mon Sep 17 00:00:00 2001 From: nvms Date: Mon, 24 Jun 2024 10:37:55 -0400 Subject: [PATCH 14/21] push to sandbox --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 268ceb57c3..6f6c32f6b1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -427,7 +427,7 @@ parameters: default: "mb/TTAHUB-3007/no-disallowed-urls" type: string sandbox_git_branch: # change to feature branch to test deployment - default: "al-ttahub-add-fei-root-cause-to-review" + default: "jp/3005/ipd-courses-widget" type: string prod_new_relic_app_id: default: "877570491" From eb18db7e8827fdc9abeb64d7239399aa2f50b54d Mon Sep 17 00:00:00 2001 From: nvms Date: Mon, 24 Jun 2024 10:54:20 -0400 Subject: [PATCH 15/21] fix tests --- .../__tests__/ResourcesDashboardOverview.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js b/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js index 43e0522dd8..3370f3cd8f 100644 --- a/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js +++ b/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js @@ -1,12 +1,20 @@ /* eslint-disable jest/no-disabled-tests */ import '@testing-library/jest-dom'; import React from 'react'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history/cjs/history.min'; import { render, screen } from '@testing-library/react'; import { ResourcesDashboardOverviewWidget } from '../ResourcesDashboardOverview'; -const renderResourcesDashboardOverview = (props) => ( - render() -); +const renderResourcesDashboardOverview = (props) => { + const history = createMemoryHistory(); + + render( + + + , + ); +}; describe('Resource Dashboard Overview Widget', () => { it('handles undefined data', async () => { @@ -16,6 +24,7 @@ describe('Resource Dashboard Overview Widget', () => { expect(screen.getByText(/eclkc resources/i)).toBeInTheDocument(); expect(screen.getByText(/recipients reached/i)).toBeInTheDocument(); expect(screen.getByText(/participants reached/i)).toBeInTheDocument(); + expect(screen.getByText(/reports citing ipd courses/i)).toBeInTheDocument(); }); it('shows the correct data', async () => { @@ -36,6 +45,9 @@ describe('Resource Dashboard Overview Widget', () => { participant: { numParticipants: '765', }, + ipdCourses: { + percentReports: '88.88%', + }, }; renderResourcesDashboardOverview({ data }); @@ -45,5 +57,7 @@ describe('Resource Dashboard Overview Widget', () => { expect(await screen.findByText(/recipients reached/i)).toBeVisible(); expect(await screen.findByText(/765/i)).toBeVisible(); expect(await screen.findByText(/participants reached/i)).toBeVisible(); + expect(await screen.findByText(/88.88%/i)).toBeVisible(); + expect(await screen.findByText(/reports citing ipd courses/i)).toBeVisible(); }); }); From 6d09238305d7242f07c1667cb5725bb41a4a5985 Mon Sep 17 00:00:00 2001 From: nvms Date: Mon, 24 Jun 2024 11:29:29 -0400 Subject: [PATCH 16/21] spacing --- frontend/src/widgets/ResourcesDashboardOverview.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/widgets/ResourcesDashboardOverview.js b/frontend/src/widgets/ResourcesDashboardOverview.js index 8f1d3eecb2..5d0bbead26 100644 --- a/frontend/src/widgets/ResourcesDashboardOverview.js +++ b/frontend/src/widgets/ResourcesDashboardOverview.js @@ -43,7 +43,9 @@ export function Field({ buttonLabel={`${tooltipText} click to visually reveal this information`} tooltipText={tooltipText} /> - ) : label1} + ) : ( + {label1} + )} {label2} {route && ( From 4eda3c15fbebd2ce2fcf4ff74b30ed90db31739b Mon Sep 17 00:00:00 2001 From: nvms Date: Mon, 24 Jun 2024 12:43:31 -0400 Subject: [PATCH 17/21] fix test --- .../widgets/__tests__/ResourcesDashboardOverview.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js b/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js index 3370f3cd8f..871d61cf8c 100644 --- a/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js +++ b/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js @@ -51,13 +51,13 @@ describe('Resource Dashboard Overview Widget', () => { }; renderResourcesDashboardOverview({ data }); - expect(await screen.findByText(/^[ \t]*reports with resources\r?\n?[ \t]*8,135 of 19,914/i)).toBeVisible(); - expect(await screen.findByText(/^[ \t]*eclkc resources\n?[ \t]*1,819 of 2,365/i)).toBeVisible(); - expect(await screen.findByText(/248/i)).toBeVisible(); + expect(await screen.findByText(/8,135 of 19,914/)).toBeVisible(); + expect(await screen.findByText(/1,819 of 2,365/)).toBeVisible(); + expect(await screen.findByText(/248/)).toBeVisible(); expect(await screen.findByText(/recipients reached/i)).toBeVisible(); - expect(await screen.findByText(/765/i)).toBeVisible(); + expect(await screen.findByText(/765/)).toBeVisible(); expect(await screen.findByText(/participants reached/i)).toBeVisible(); - expect(await screen.findByText(/88.88%/i)).toBeVisible(); + expect(await screen.findByText(/88.88%/)).toBeVisible(); expect(await screen.findByText(/reports citing ipd courses/i)).toBeVisible(); }); }); From 5223130921ccae6044211286a94f26d654cde7e7 Mon Sep 17 00:00:00 2001 From: nvms Date: Mon, 24 Jun 2024 12:44:21 -0400 Subject: [PATCH 18/21] update import --- frontend/src/widgets/__tests__/ResourcesDashboardOverview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js b/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js index 871d61cf8c..d4f38dcc7d 100644 --- a/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js +++ b/frontend/src/widgets/__tests__/ResourcesDashboardOverview.js @@ -2,7 +2,7 @@ import '@testing-library/jest-dom'; import React from 'react'; import { Router } from 'react-router-dom'; -import { createMemoryHistory } from 'history/cjs/history.min'; +import { createMemoryHistory } from 'history'; import { render, screen } from '@testing-library/react'; import { ResourcesDashboardOverviewWidget } from '../ResourcesDashboardOverview'; From 091362d10905eb981c8292dc26925b691768adc6 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Mon, 24 Jun 2024 13:51:28 -0400 Subject: [PATCH 19/21] Pull out the support type --- src/lib/transform.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/transform.js b/src/lib/transform.js index 500a656213..b2e7244fec 100644 --- a/src/lib/transform.js +++ b/src/lib/transform.js @@ -463,6 +463,7 @@ function transformGoalsAndObjectives(report) { files: aro.files, resources: aro.resources, courses: aro.courses, + supportType: aro.supportType, } )); if (objectiveRecords) { From c6e2ee98bc022d0fd88159721f69de3c908bac01 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 25 Jun 2024 09:53:10 -0400 Subject: [PATCH 20/21] Rename migration --- ...d-created-vias.js => 20240625135148-reset-bad-created-vias.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/migrations/{20240621152352-reset-bad-created-vias.js => 20240625135148-reset-bad-created-vias.js} (100%) diff --git a/src/migrations/20240621152352-reset-bad-created-vias.js b/src/migrations/20240625135148-reset-bad-created-vias.js similarity index 100% rename from src/migrations/20240621152352-reset-bad-created-vias.js rename to src/migrations/20240625135148-reset-bad-created-vias.js From 1e873dcf492a50019ed186c6247685fca2bfe0eb Mon Sep 17 00:00:00 2001 From: nvms Date: Tue, 25 Jun 2024 11:20:50 -0400 Subject: [PATCH 21/21] swap order --- src/routes/courses/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/courses/index.ts b/src/routes/courses/index.ts index f23f1ec1f3..cc849358fb 100644 --- a/src/routes/courses/index.ts +++ b/src/routes/courses/index.ts @@ -11,9 +11,9 @@ import transactionWrapper from '../transactionWrapper'; const router = express.Router(); router.get('/', transactionWrapper(allCourses)); +router.get('/dashboard', transactionWrapper(getCourseUrlWidgetDataWithCache)); router.get('/:id', transactionWrapper(getCourseById)); router.put('/:id', transactionWrapper(updateCourseById)); router.post('/', transactionWrapper(createCourseByName)); router.delete('/:id', transactionWrapper(deleteCourseById)); -router.get('/dashboard', transactionWrapper(getCourseUrlWidgetDataWithCache)); export default router;