From 39837163b413dc2fa38a770be5360e0fc3dc31f4 Mon Sep 17 00:00:00 2001 From: Paul McCafferty <> Date: Mon, 19 Apr 2021 11:22:46 +0100 Subject: [PATCH] Updates to notifications and bug fixes --- .../datarequest/datarequest.controller.js | 25 +- .../dataset/datasetonboarding.controller.js | 676 +++--------------- src/resources/message/message.model.js | 7 +- .../publisher/publisher.controller.js | 8 +- src/resources/utilities/constants.util.js | 5 + .../utilities/emailGenerator.util.js | 148 +++- 6 files changed, 291 insertions(+), 578 deletions(-) diff --git a/src/resources/datarequest/datarequest.controller.js b/src/resources/datarequest/datarequest.controller.js index 82ea7fb6..9ff4c71a 100644 --- a/src/resources/datarequest/datarequest.controller.js +++ b/src/resources/datarequest/datarequest.controller.js @@ -235,7 +235,13 @@ module.exports = { data = { ...accessRecord.toObject() }; } // 7. Append question actions depending on user type and application status - data.jsonSchema = datarequestUtil.injectQuestionActions(data.jsonSchema, constants.userTypes.APPLICANT, data.applicationStatus, null, constants.userTypes.APPLICANT); + data.jsonSchema = datarequestUtil.injectQuestionActions( + data.jsonSchema, + constants.userTypes.APPLICANT, + data.applicationStatus, + null, + constants.userTypes.APPLICANT + ); // 8. Return payload return res.status(200).json({ status: 'success', @@ -342,7 +348,13 @@ module.exports = { data = { ...accessRecord.toObject() }; } // 8. Append question actions depending on user type and application status - data.jsonSchema = datarequestUtil.injectQuestionActions(data.jsonSchema, constants.userTypes.APPLICANT, data.applicationStatus, null, constants.userTypes.APPLICANT); + data.jsonSchema = datarequestUtil.injectQuestionActions( + data.jsonSchema, + constants.userTypes.APPLICANT, + data.applicationStatus, + null, + constants.userTypes.APPLICANT + ); // 9. Return payload return res.status(200).json({ status: 'success', @@ -1777,8 +1789,8 @@ module.exports = { // Save new record await DataRequestModel.create(clonedAccessRecord, saveCallBack); } else { - - let appToCloneInto = await DataRequestModel.findOne({ _id: appIdToCloneInto }).populate([ + let appToCloneInto = await DataRequestModel.findOne({ _id: appIdToCloneInto }) + .populate([ { path: 'datasets dataset authors', }, @@ -2091,7 +2103,10 @@ module.exports = { case constants.notificationTypes.SUBMITTED: // 1. Create notifications // Custodian notification - if (_.has(accessRecord.datasets[0].toObject(), 'publisher.team.users')) { + if ( + _.has(accessRecord.datasets[0].toObject(), 'publisher.team.users') && + accessRecord.datasets[0].publisher.allowAccessRequestManagement + ) { // Retrieve all custodian user Ids to generate notifications custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, constants.roleTypes.MANAGER); custodianUserIds = custodianManagers.map(user => user.id); diff --git a/src/resources/dataset/datasetonboarding.controller.js b/src/resources/dataset/datasetonboarding.controller.js index 0623bb03..d4021862 100644 --- a/src/resources/dataset/datasetonboarding.controller.js +++ b/src/resources/dataset/datasetonboarding.controller.js @@ -1,5 +1,8 @@ import { Data } from '../tool/data.model'; import { PublisherModel } from '../publisher/publisher.model'; +import { TeamModel } from '../team/team.model'; +import { UserModel } from '../user/user.model'; +import teamController from '../team/team.controller'; import { filtersService } from '../filters/dependency'; import notificationBuilder from '../utilities/notificationBuilder'; import emailGenerator from '../utilities/emailGenerator.util'; @@ -27,7 +30,11 @@ module.exports = { if (publisherID === 'admin') { // get all datasets in review for admin - datasetIds = await Data.find({ activeflag: 'inReview' }).sort({ 'timestamps.submitted': -1 }); + datasetIds = await Data.find({ + activeflag: 'inReview', + }) + .sort({ 'timestamps.updated': -1 }) + .distinct('pid'); } else { // get all pids for publisherID datasetIds = await Data.find({ @@ -576,20 +583,8 @@ module.exports = { { activeflag: constants.datatsetStatuses.INREVIEW, 'timestamps.updated': Date.now(), 'timestamps.submitted': Date.now() } ); - /* , err => { - if (err) return res.send(err); - return res.json({ success: true }); - }); */ - //emails / notifications - /* await module.exports.createNotifications( - accessRecord.submissionType === constants.submissionTypes.INITIAL - ? constants.notificationTypes.SUBMITTED - : constants.notificationTypes.RESUBMITTED, - {}, - accessRecord, - req.user - ); */ + await module.exports.createNotifications(constants.notificationTypes.DATASETSUBMITTED, updatedDataset); return res.status(200).json({ status: 'success' }); } catch (err) { @@ -665,7 +660,8 @@ module.exports = { const loginDetails = { username: process.env.MDC_Config_HDRUK_username || '', password: process.env.MDC_Config_HDRUK_password || '', - }; //Paul - move to env variables + }; + await axios .post(metadataCatalogueLink + '/api/authentication/login', loginDetails, { withCredentials: true, @@ -819,7 +815,7 @@ module.exports = { let technicalDetails = await module.exports.buildTechnicalDetails(dataset.structuralMetadata); let metadataQuality = await module.exports.buildMetadataQuality(datasetv2Object); - await Data.findOneAndUpdate( + let updatedDataset = await Data.findOneAndUpdate( { _id: id }, { datasetid: newDatasetVersionId, @@ -860,8 +856,14 @@ module.exports = { }, datasetv2: datasetv2Object, applicationStatusDesc: applicationStatusDesc, - } + }, + { new: true } ); + + filtersService.optimiseFilters('dataset'); + + //emails / notifications + await module.exports.createNotifications(constants.notificationTypes.DATASETAPPROVED, updatedDataset); }) .catch(err => { console.log('Error when trying to create new dataset on the MDC - ' + err.message); @@ -875,20 +877,22 @@ module.exports = { console.log('Error when trying to logout of the MDC - ' + err.message); }); - filtersService.optimiseFilters('dataset'); - return res.status(200).json({ status: 'success' }); } else if (applicationStatus === 'rejected') { - await Data.findOneAndUpdate( + let updatedDataset = await Data.findOneAndUpdate( { _id: id }, { activeflag: constants.datatsetStatuses.REJECTED, applicationStatusDesc: applicationStatusDesc, 'timestamps.rejected': Date.now(), 'timestamps.updated': Date.now(), - } + }, + { new: true } ); + //emails / notifications + await module.exports.createNotifications(constants.notificationTypes.DATASETREJECTED, updatedDataset); + return res.status(200).json({ status: 'success' }); } else if (applicationStatus === 'archived') { //await Data.findOneAndUpdate({ _id: id }, { activeflag: constants.datatsetStatuses.ARCHIVED }); @@ -1271,583 +1275,123 @@ module.exports = { }, dataset); }, - createNotifications: async (type, context, team) => { - const teamName = getTeamName(team); - let options = {}; - let html = ''; + createNotifications: async (type, context) => { + let options = {}, + html = '', + team, + teamMembers = [], + teamMembersDetails, + teamMembersIds = []; switch (type) { - case constants.notificationTypes.MEMBERREMOVED: + case constants.notificationTypes.DATASETSUBMITTED: // 1. Get user removed - const { removedUser } = context; + let adminTeam = await TeamModel.findOne({ type: 'admin' }).lean(); + + let adminMembers = []; + for (let member of adminTeam.members) { + adminMembers.push(member.memberid); + } + + let adminMembersDetails = await UserModel.find({ _id: { $in: adminMembers } }) + .populate('additionalInfo') + .lean(); + + let adminMembersIds = []; + for (let member of adminMembersDetails) { + adminMembersIds.push(member.id); + } + // 2. Create user notifications notificationBuilder.triggerNotificationMessage( - [removedUser.id], - `You have been removed from the team ${teamName}`, - 'team unlinked', - teamName + adminMembersIds, + context.datasetVersion !== '1.0.0' + ? `A new dataset version for "${context.name}" is available for review` + : `A new dataset "${context.name}" is available for review`, + 'dataset submitted', + context._id ); // 3. Create email options = { - teamName, + name: context.name, + publisher: context.datasetv2.summary.publisher.name, }; - html = emailGenerator.generateRemovedFromTeam(options); - emailGenerator.sendEmail([removedUser], constants.hdrukEmail, `You have been removed from the team ${teamName}`, html, false); + html = emailGenerator.generateMetadataOnboardingSumbitted(options); + emailGenerator.sendEmail(adminMembersDetails, constants.hdrukEmail, `Dataset version available for review`, html, false); break; - case constants.notificationTypes.MEMBERADDED: - // 1. Get users added - const { newUsers } = context; - const newUserIds = newUsers.map(user => user.id); - // 2. Create user notifications - notificationBuilder.triggerNotificationMessage( - newUserIds, - `You have been added to the team ${teamName} on the HDR UK Innovation Gateway`, - 'team', - teamName - ); - // 3. Create email for reviewers - options = { - teamName, - role: constants.roleTypes.REVIEWER, - }; - html = emailGenerator.generateAddedToTeam(options); - emailGenerator.sendEmail( - newUsers, - constants.hdrukEmail, - `You have been added as a reviewer to the team ${teamName} on the HDR UK Innovation Gateway`, - html, - false - ); - // 4. Create email for managers - options = { - teamName, - role: constants.roleTypes.MANAGER, - }; - html = emailGenerator.generateAddedToTeam(options); - emailGenerator.sendEmail( - newUsers, - constants.hdrukEmail, - `You have been added as a manager to the team ${teamName} on the HDR UK Innovation Gateway`, - html, - false - ); - break; - case constants.notificationTypes.MEMBERROLECHANGED: - break; - } - }, - - createNotifications: async (type, context, accessRecord, user) => { - // Project details from about application if 5 Safes - let { aboutApplication = {} } = accessRecord; - if (typeof aboutApplication === 'string') { - aboutApplication = JSON.parse(accessRecord.aboutApplication); - } - let { projectName = 'No project name set' } = aboutApplication; - let { projectId, _id, workflow = {}, dateSubmitted = '', jsonSchema, questionAnswers } = accessRecord; - if (_.isEmpty(projectId)) { - projectId = _id; - } - // Parse the schema - if (typeof jsonSchema === 'string') { - jsonSchema = JSON.parse(accessRecord.jsonSchema); - } - if (typeof questionAnswers === 'string') { - questionAnswers = JSON.parse(accessRecord.questionAnswers); - } - let { pages, questionPanels, questionSets: questions } = jsonSchema; - // Publisher details from single dataset - let { - datasetfields: { contactPoint, publisher }, - } = accessRecord.datasets[0]; - let datasetTitles = accessRecord.datasets.map(dataset => dataset.name).join(', '); - // Main applicant (user obj) - let { firstname: appFirstName, lastname: appLastName, email: appEmail } = accessRecord.mainApplicant; - // Requesting user - let { firstname, lastname } = user; - // Instantiate default params - let custodianManagers = [], - custodianUserIds = [], - managerUserIds = [], - emailRecipients = [], - options = {}, - html = '', - attachmentContent = '', - filename = '', - jsonContent = {}, - authors = [], - attachments = []; - let applicants = datarequestUtil.extractApplicantNames(questionAnswers).join(', '); - // Fall back for single applicant on short application form - if (_.isEmpty(applicants)) { - applicants = `${appFirstName} ${appLastName}`; - } - // Get authors/contributors (user obj) - if (!_.isEmpty(accessRecord.authors)) { - authors = accessRecord.authors.map(author => { - let { firstname, lastname, email, id } = author; - return { firstname, lastname, email, id }; - }); - } - // Deconstruct workflow context if passed - let { - workflowName = '', - stepName = '', - reviewerNames = '', - reviewSections = '', - nextStepName = '', - stepReviewers = [], - stepReviewerUserIds = [], - currentDeadline = '', - remainingReviewers = [], - remainingReviewerUserIds = [], - dateDeadline, - } = context; + case constants.notificationTypes.DATASETAPPROVED: + // 1. Get user removed + team = await TeamModel.findOne({ _id: context.datasetv2.summary.publisher.identifier }).lean(); - switch (type) { - case constants.notificationTypes.STATUSCHANGE: - // 1. Create notifications - // Custodian manager and current step reviewer notifications - if (_.has(accessRecord.datasets[0].toObject(), 'publisher.team.users')) { - // Retrieve all custodian manager user Ids and active step reviewers - custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, constants.roleTypes.MANAGER); - let activeStep = workflowController.getActiveWorkflowStep(workflow); - stepReviewers = workflowController.getStepReviewers(activeStep); - // Create custodian notification - let statusChangeUserIds = [...custodianManagers, ...stepReviewers].map(user => user.id); - await notificationBuilder.triggerNotificationMessage( - statusChangeUserIds, - `${appFirstName} ${appLastName}'s Data Access Request for ${datasetTitles} was ${context.applicationStatus} by ${firstname} ${lastname}`, - 'data access request', - accessRecord._id - ); + for (let member of team.members) { + teamMembers.push(member.memberid); } - // Create applicant notification - await notificationBuilder.triggerNotificationMessage( - [accessRecord.userId], - `Your Data Access Request for ${datasetTitles} was ${context.applicationStatus} by ${publisher}`, - 'data access request', - accessRecord._id - ); - // Create authors notification - if (!_.isEmpty(authors)) { - await notificationBuilder.triggerNotificationMessage( - authors.map(author => author.id), - `A Data Access Request you are contributing to for ${datasetTitles} was ${context.applicationStatus} by ${publisher}`, - 'data access request', - accessRecord._id - ); - } + teamMembersDetails = await UserModel.find({ _id: { $in: teamMembers } }) + .populate('additionalInfo') + .lean(); - // 2. Send emails to relevant users - // Aggregate objects for custodian and applicant - emailRecipients = [accessRecord.mainApplicant, ...custodianManagers, ...stepReviewers, ...accessRecord.authors]; - if (!dateSubmitted) ({ updatedAt: dateSubmitted } = accessRecord); - // Create object to pass through email data - options = { - id: accessRecord._id, - applicationStatus: context.applicationStatus, - applicationStatusDesc: context.applicationStatusDesc, - publisher, - projectId, - projectName, - datasetTitles, - dateSubmitted, - applicants, - }; - // Create email body content - html = emailGenerator.generateDARStatusChangedEmail(options); - // Send email - await emailGenerator.sendEmail( - emailRecipients, - constants.hdrukEmail, - `Data Access Request for ${datasetTitles} was ${context.applicationStatus} by ${publisher}`, - html, - false - ); - break; - case constants.notificationTypes.SUBMITTED: - // 1. Create notifications - // Custodian notification - if (_.has(accessRecord.datasets[0].toObject(), 'publisher.team.users')) { - // Retrieve all custodian user Ids to generate notifications - custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, constants.roleTypes.MANAGER); - custodianUserIds = custodianManagers.map(user => user.id); - await notificationBuilder.triggerNotificationMessage( - custodianUserIds, - `A Data Access Request has been submitted to ${publisher} for ${datasetTitles} by ${appFirstName} ${appLastName}`, - 'data access request', - accessRecord._id - ); - } else { - const dataCustodianEmail = process.env.DATA_CUSTODIAN_EMAIL || contactPoint; - custodianManagers = [{ email: dataCustodianEmail }]; + for (let member of teamMembersDetails) { + teamMembersIds.push(member.id); } - // Applicant notification - await notificationBuilder.triggerNotificationMessage( - [accessRecord.userId], - `Your Data Access Request for ${datasetTitles} was successfully submitted to ${publisher}`, - 'data access request', - accessRecord._id - ); - // Contributors/authors notification - if (!_.isEmpty(authors)) { - await notificationBuilder.triggerNotificationMessage( - accessRecord.authors.map(author => author.id), - `A Data Access Request you are contributing to for ${datasetTitles} was successfully submitted to ${publisher} by ${firstname} ${lastname}`, - 'data access request', - accessRecord._id - ); - } - // 2. Send emails to custodian and applicant - // Create object to pass to email generator - options = { - userType: '', - userEmail: appEmail, - publisher, - datasetTitles, - userName: `${appFirstName} ${appLastName}`, - }; - // Iterate through the recipient types - for (let emailRecipientType of constants.submissionEmailRecipientTypes) { - // Establish email context object - options = { - ...options, - userType: emailRecipientType, - submissionType: constants.submissionTypes.INITIAL, - }; - // Build email template - ({ html, jsonContent } = await emailGenerator.generateEmail(questions, pages, questionPanels, questionAnswers, options)); - // Send emails to custodian team members who have opted in to email notifications - if (emailRecipientType === 'dataCustodian') { - emailRecipients = [...custodianManagers]; - // Generate json attachment for external system integration - attachmentContent = Buffer.from(JSON.stringify({ id: accessRecord._id, ...jsonContent })).toString('base64'); - filename = `${helper.generateFriendlyId(accessRecord._id)} ${moment().format().toString()}.json`; - attachments = [await emailGenerator.generateAttachment(filename, attachmentContent, 'application/json')]; - } else { - // Send email to main applicant and contributors if they have opted in to email notifications - emailRecipients = [accessRecord.mainApplicant, ...accessRecord.authors]; - } - // Send email - if (!_.isEmpty(emailRecipients)) { - await emailGenerator.sendEmail( - emailRecipients, - constants.hdrukEmail, - `Data Access Request has been submitted to ${publisher} for ${datasetTitles}`, - html, - false, - attachments - ); - } - } - break; - case constants.notificationTypes.RESUBMITTED: - // 1. Create notifications - // Custodian notification - if (_.has(accessRecord.datasets[0], 'publisher.team.users')) { - // Retrieve all custodian user Ids to generate notifications - custodianManagers = teamController.getTeamMembersByRole(accessRecord.datasets[0].publisher.team, constants.roleTypes.MANAGER); - custodianUserIds = custodianManagers.map(user => user.id); - await notificationBuilder.triggerNotificationMessage( - custodianUserIds, - `A Data Access Request has been resubmitted with updates to ${publisher} for ${datasetTitles} by ${appFirstName} ${appLastName}`, - 'data access request', - accessRecord._id - ); - } else { - const dataCustodianEmail = process.env.DATA_CUSTODIAN_EMAIL || contactPoint; - custodianManagers = [{ email: dataCustodianEmail }]; - } - // Applicant notification - await notificationBuilder.triggerNotificationMessage( - [accessRecord.userId], - `Your Data Access Request for ${datasetTitles} was successfully resubmitted with updates to ${publisher}`, - 'data access request', - accessRecord._id - ); - // Contributors/authors notification - if (!_.isEmpty(authors)) { - await notificationBuilder.triggerNotificationMessage( - accessRecord.authors.map(author => author.id), - `A Data Access Request you are contributing to for ${datasetTitles} was successfully resubmitted with updates to ${publisher} by ${firstname} ${lastname}`, - 'data access request', - accessRecord._id - ); - } - // 2. Send emails to custodian and applicant - // Create object to pass to email generator - options = { - userType: '', - userEmail: appEmail, - publisher, - datasetTitles, - userName: `${appFirstName} ${appLastName}`, - }; - // Iterate through the recipient types - for (let emailRecipientType of constants.submissionEmailRecipientTypes) { - // Establish email context object - options = { - ...options, - userType: emailRecipientType, - submissionType: constants.submissionTypes.RESUBMISSION, - }; - // Build email template - ({ html, jsonContent } = await emailGenerator.generateEmail(questions, pages, questionPanels, questionAnswers, options)); - // Send emails to custodian team members who have opted in to email notifications - if (emailRecipientType === 'dataCustodian') { - emailRecipients = [...custodianManagers]; - // Generate json attachment for external system integration - attachmentContent = Buffer.from(JSON.stringify({ id: accessRecord._id, ...jsonContent })).toString('base64'); - filename = `${helper.generateFriendlyId(accessRecord._id)} ${moment().format().toString()}.json`; - attachments = [await emailGenerator.generateAttachment(filename, attachmentContent, 'application/json')]; - } else { - // Send email to main applicant and contributors if they have opted in to email notifications - emailRecipients = [accessRecord.mainApplicant, ...accessRecord.authors]; - } - // Send email - if (!_.isEmpty(emailRecipients)) { - await emailGenerator.sendEmail( - emailRecipients, - constants.hdrukEmail, - `Data Access Request to ${publisher} for ${datasetTitles} has been updated with updates`, - html, - false, - attachments - ); - } - } - break; - case constants.notificationTypes.CONTRIBUTORCHANGE: - // 1. Deconstruct authors array from context to compare with existing Mongo authors - const { newAuthors, currentAuthors } = context; - // 2. Determine authors who have been removed - let addedAuthors = [...newAuthors].filter(author => !currentAuthors.includes(author)); - // 3. Determine authors who have been added - let removedAuthors = [...currentAuthors].filter(author => !newAuthors.includes(author)); - // 4. Create emails and notifications for added/removed contributors - // Set required data for email generation - options = { - id: accessRecord._id, - projectName, - projectId, - datasetTitles, - userName: `${appFirstName} ${appLastName}`, - actioner: `${firstname} ${lastname}`, - applicants, - }; - // Notifications for added contributors - if (!_.isEmpty(addedAuthors)) { - options.change = 'added'; - html = emailGenerator.generateContributorEmail(options); - // Find related user objects and filter out users who have not opted in to email communications - let addedUsers = await UserModel.find({ - id: { $in: addedAuthors }, - }).populate('additionalInfo'); - - await notificationBuilder.triggerNotificationMessage( - addedUsers.map(user => user.id), - `You have been added as a contributor for a Data Access Request to ${publisher} by ${firstname} ${lastname}`, - 'data access request', - accessRecord._id - ); - await emailGenerator.sendEmail( - addedUsers, - constants.hdrukEmail, - `You have been added as a contributor for a Data Access Request to ${publisher} by ${firstname} ${lastname}`, - html, - false - ); - } - // Notifications for removed contributors - if (!_.isEmpty(removedAuthors)) { - options.change = 'removed'; - html = await emailGenerator.generateContributorEmail(options); - // Find related user objects and filter out users who have not opted in to email communications - let removedUsers = await UserModel.find({ - id: { $in: removedAuthors }, - }).populate('additionalInfo'); - - await notificationBuilder.triggerNotificationMessage( - removedUsers.map(user => user.id), - `You have been removed as a contributor from a Data Access Request to ${publisher} by ${firstname} ${lastname}`, - 'data access request unlinked', - accessRecord._id - ); - await emailGenerator.sendEmail( - removedUsers, - constants.hdrukEmail, - `You have been removed as a contributor from a Data Access Request to ${publisher} by ${firstname} ${lastname}`, - html, - false - ); - } - break; - case constants.notificationTypes.STEPOVERRIDE: - // 1. Create reviewer notifications - notificationBuilder.triggerNotificationMessage( - stepReviewerUserIds, - `${firstname} ${lastname} has approved a Data Access Request application phase that you were assigned to review`, - 'data access request', - accessRecord._id - ); - // 2. Create reviewer emails - options = { - id: accessRecord._id, - projectName, - projectId, - datasetTitles, - userName: `${appFirstName} ${appLastName}`, - actioner: `${firstname} ${lastname}`, - applicants, - dateSubmitted, - ...context, - }; - html = emailGenerator.generateStepOverrideEmail(options); - emailGenerator.sendEmail( - stepReviewers, - constants.hdrukEmail, - `${firstname} ${lastname} has approved a Data Access Request application phase that you were assigned to review`, - html, - false - ); - break; - case constants.notificationTypes.REVIEWSTEPSTART: - // 1. Create reviewer notifications + // 2. Create user notifications notificationBuilder.triggerNotificationMessage( - stepReviewerUserIds, - `You are required to review a new Data Access Request application for ${publisher} by ${moment(currentDeadline).format( - 'D MMM YYYY HH:mm' - )}`, - 'data access request', - accessRecord._id + teamMembersIds, + context.datasetVersion !== '1.0.0' + ? `Your dataset version for "${context.name}" has been approved and is now active` + : `A dataset "${context.name}" has been approved and is now active`, + 'dataset approved', + context.pid ); - // 2. Create reviewer emails + // 3. Create email options = { - id: accessRecord._id, - projectName, - projectId, - datasetTitles, - userName: `${appFirstName} ${appLastName}`, - actioner: `${firstname} ${lastname}`, - applicants, - dateSubmitted, - ...context, + name: context.name, + publisherId: context.datasetv2.summary.publisher.identifier, + comment: context.applicationStatusDesc, }; - html = emailGenerator.generateNewReviewPhaseEmail(options); + html = emailGenerator.generateMetadataOnboardingApproved(options); emailGenerator.sendEmail( - stepReviewers, + teamMembersDetails, constants.hdrukEmail, - `You are required to review a new Data Access Request application for ${publisher} by ${moment(currentDeadline).format( - 'D MMM YYYY HH:mm' - )}`, + `Your dataset version has been approved and is now active`, html, false ); break; - case constants.notificationTypes.FINALDECISIONREQUIRED: - // 1. Get managers for publisher - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleTypes.MANAGER); - managerUserIds = custodianManagers.map(user => user.id); + case constants.notificationTypes.DATASETREJECTED: + // 1. Get user removed + team = await TeamModel.findOne({ _id: context.datasetv2.summary.publisher.identifier }).lean(); + + for (let member of team.members) { + teamMembers.push(member.memberid); + } - // 2. Create manager notifications + teamMembersDetails = await UserModel.find({ _id: { $in: teamMembers } }) + .populate('additionalInfo') + .lean(); + for (let member of teamMembersDetails) { + teamMembersIds.push(member.id); + } + // 2. Create user notifications notificationBuilder.triggerNotificationMessage( - managerUserIds, - `Action is required as a Data Access Request application for ${publisher} is now awaiting a final decision`, - 'data access request', - accessRecord._id + teamMembersIds, + context.datasetVersion !== '1.0.0' + ? `Your dataset version for "${context.name}" has been reviewed and rejected` + : `A dataset "${context.name}" has been reviewed and rejected`, + 'dataset rejected', + context.pid ); - // 3. Create manager emails + // 3. Create email options = { - id: accessRecord._id, - projectName, - projectId, - datasetTitles, - userName: `${appFirstName} ${appLastName}`, - actioner: `${firstname} ${lastname}`, - applicants, - dateSubmitted, - ...context, + name: context.name, + publisherId: context.datasetv2.summary.publisher.identifier, + comment: context.applicationStatusDesc, }; - html = emailGenerator.generateFinalDecisionRequiredEmail(options); + html = emailGenerator.generateMetadataOnboardingRejected(options); emailGenerator.sendEmail( - custodianManagers, - constants.hdrukEmail, - `Action is required as a Data Access Request application for ${publisher} is now awaiting a final decision`, - html, - false - ); - break; - case constants.notificationTypes.DEADLINEWARNING: - // 1. Create reviewer notifications - await notificationBuilder.triggerNotificationMessage( - remainingReviewerUserIds, - `The deadline is approaching for a Data Access Request application you are reviewing`, - 'data access request', - accessRecord._id - ); - // 2. Create reviewer emails - options = { - id: accessRecord._id, - projectName, - projectId, - datasetTitles, - userName: `${appFirstName} ${appLastName}`, - actioner: `${firstname} ${lastname}`, - applicants, - workflowName, - stepName, - reviewSections, - reviewerNames, - nextStepName, - dateDeadline, - }; - html = await emailGenerator.generateReviewDeadlineWarning(options); - await emailGenerator.sendEmail( - remainingReviewers, - constants.hdrukEmail, - `The deadline is approaching for a Data Access Request application you are reviewing`, - html, - false - ); - break; - case constants.notificationTypes.DEADLINEPASSED: - // 1. Get all managers - custodianManagers = teamController.getTeamMembersByRole(accessRecord.publisherObj.team, constants.roleTypes.MANAGER); - managerUserIds = custodianManagers.map(user => user.id); - // 2. Combine managers and reviewers remaining - let deadlinePassedUserIds = [...remainingReviewerUserIds, ...managerUserIds]; - let deadlinePassedUsers = [...remainingReviewers, ...custodianManagers]; - - // 3. Create notifications - await notificationBuilder.triggerNotificationMessage( - deadlinePassedUserIds, - `The deadline for a Data Access Request review phase has now elapsed`, - 'data access request', - accessRecord._id - ); - // 4. Create emails - options = { - id: accessRecord._id, - projectName, - projectId, - datasetTitles, - userName: `${appFirstName} ${appLastName}`, - actioner: `${firstname} ${lastname}`, - applicants, - workflowName, - stepName, - reviewSections, - reviewerNames, - nextStepName, - dateDeadline, - }; - html = await emailGenerator.generateReviewDeadlinePassed(options); - await emailGenerator.sendEmail( - deadlinePassedUsers, + teamMembersDetails, constants.hdrukEmail, - `The deadline for a Data Access Request review phase has now elapsed`, + `Your dataset version has been reviewed and rejected`, html, false ); diff --git a/src/resources/message/message.model.js b/src/resources/message/message.model.js index 61502ec2..b88f171c 100644 --- a/src/resources/message/message.model.js +++ b/src/resources/message/message.model.js @@ -27,13 +27,16 @@ const MessageSchema = new Schema( 'team', 'team unlinked', 'edit', - 'workflow' + 'workflow', + 'dataset submitted', + 'dataset approved', + 'dataset rejected', ], }, publisherName: { type: String, default: '', - trim: true + trim: true, }, createdBy: { type: Schema.Types.ObjectId, diff --git a/src/resources/publisher/publisher.controller.js b/src/resources/publisher/publisher.controller.js index 52529268..99c5f3c6 100644 --- a/src/resources/publisher/publisher.controller.js +++ b/src/resources/publisher/publisher.controller.js @@ -1,4 +1,5 @@ import _ from 'lodash'; +import mongoose from 'mongoose'; import { PublisherModel } from './publisher.model'; import { Data } from '../tool/data.model'; import { DataRequestModel } from '../datarequest/datarequest.model'; @@ -13,7 +14,12 @@ module.exports = { getPublisherById: async (req, res) => { try { // 1. Get the publisher from the database - const publisher = await PublisherModel.findOne({ name: req.params.id }); + let publisher; + if (mongoose.Types.ObjectId.isValid(req.params.id)) { + publisher = await PublisherModel.findOne({ _id: req.params.id }); + } else { + publisher = await PublisherModel.findOne({ name: req.params.id }); + } if (!publisher) { return res.status(200).json({ success: true, diff --git a/src/resources/utilities/constants.util.js b/src/resources/utilities/constants.util.js index 27276ac3..66c5ed4d 100644 --- a/src/resources/utilities/constants.util.js +++ b/src/resources/utilities/constants.util.js @@ -297,6 +297,9 @@ const _notificationTypes = { INPROGRESS: 'InProgress', APPLICATIONCLONED: 'ApplicationCloned', APPLICATIONDELETED: 'ApplicationDeleted', + DATASETSUBMITTED: 'DatasetSubmitted', + DATASETAPPROVED: 'DatasetApproved', + DATASETREJECTED: 'DatasetRejected', }; const _applicationStatuses = { @@ -342,6 +345,8 @@ const _darPanelMapper = { const _roleTypes = { MANAGER: 'manager', REVIEWER: 'reviewer', + METADATA_EDITOR: 'metadata_editor', + ADMIN_DATASET: 'admin_dataset', }; // diff --git a/src/resources/utilities/emailGenerator.util.js b/src/resources/utilities/emailGenerator.util.js index ad7704d4..ca12a2d0 100644 --- a/src/resources/utilities/emailGenerator.util.js +++ b/src/resources/utilities/emailGenerator.util.js @@ -1,4 +1,4 @@ -import { isNil, isEmpty, capitalize, groupBy, forEach } from 'lodash'; +import _, { isNil, isEmpty, capitalize, groupBy, forEach } from 'lodash'; import moment from 'moment'; import { UserModel } from '../user/user.model'; import helper from '../utilities/helper.util'; @@ -1627,6 +1627,137 @@ const _generateAddedToTeam = options => { return body; }; +const _generateMetadataOnboardingSumbitted = options => { + let { name, publisher } = options; + + let body = `
+ + + + + + + + + + + + +
+ Dataset version available for review +
+ The dataset, ${name}, has been submitted to the Gateway by ${publisher}. You can review and approve or reject this dataset version from application view. +
+ View datasets pending approval +
+
`; + return body; +}; + +const _generateMetadataOnboardingApproved = options => { + let { name, publisherId, comment } = options; + + let commentHTML = ''; + + if (!_.isEmpty(comment)) { + commentHTML = ` + + Approval comment + + + + + ${comment} + + `; + } + + let body = `
+ + + + + + + + + ${commentHTML} + + + + +
+ Your dataset version has been approved and is now active +
+ The submitted version of ${name} has been reviewed and approved by the HDRUK admins. It is now active, searchable and available to request access to on the Innovation Gateway. You may view and create a new version of the dataset in your dataset dashboard. +
+ View dataset dashboard +
+
`; + return body; +}; + +const _generateMetadataOnboardingRejected = options => { + let { name, publisherId, comment } = options; + + let commentHTML = ''; + + if (!_.isEmpty(comment)) { + commentHTML = ` + + Reason for rejection + + + + + ${comment} + + `; + } + + let body = `
+ + + + + + + + + ${commentHTML} + + + + +
+ Your dataset version has been reviewed and rejected +
+ The submitted version of ${name} has been reviewed and rejected by the HDRUK admins. Please view and create a new version of this dataset and make the necessary changes if you would like to make another submission to the Gateway. +
+ View dataset dashboard +
+
`; + return body; +}; + /** * [_sendEmail] * @@ -1711,6 +1842,11 @@ const _generateAttachment = (filename, content, type) => { }; export default { + //General + sendEmail: _sendEmail, + generateEmailFooter: _generateEmailFooter, + generateAttachment: _generateAttachment, + //DAR generateEmail: _generateEmail, generateDARReturnedEmail: _generateDARReturnedEmail, generateDARStatusChangedEmail: _generateDARStatusChangedEmail, @@ -1724,9 +1860,13 @@ export default { generateFinalDecisionRequiredEmail: _generateFinalDecisionRequiredEmail, generateRemovedFromTeam: _generateRemovedFromTeam, generateAddedToTeam: _generateAddedToTeam, - sendEmail: _sendEmail, - generateEmailFooter: _generateEmailFooter, - generateAttachment: _generateAttachment, + //Workflows generateWorkflowAssigned: _generateWorkflowAssigned, generateWorkflowCreated: _generateWorkflowCreated, + //Metadata Onboarding + generateMetadataOnboardingSumbitted: _generateMetadataOnboardingSumbitted, + generateMetadataOnboardingApproved: _generateMetadataOnboardingApproved, + generateMetadataOnboardingRejected: _generateMetadataOnboardingRejected, + //generateMetadataOnboardingArchived: _generateMetadataOnboardingArchived, + //generateMetadataOnboardingUnArchived: _generateMetadataOnboardingUnArchived, };