From 44debf57d14ac0d8374849c6adb86903f4c93509 Mon Sep 17 00:00:00 2001 From: jimmyPasta <39963238+jimmyPasta@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:07:24 -0800 Subject: [PATCH 1/3] fix: FORMS-1025 ab#23188 fix chefs event service (#1295) * bugfix/AB#23188 CHEFs event service * bugfix/AB#23188 CHEFs event service * bugfix/AB#23188 CHEFs event service * bugfix/AB#23188 CHEFs event service * bugfix/AB#23188 CHEFs event service remove logs * bugfix/AB#23188 CHEFs event service remove logs * bugfix/AB#23188 CHEFs event service remove return char * bugfix/AB#23188 CHEFs small fix for initializing subscripton to blank on form * bugfix/AB#23188 CHEFs small fix for initial publish event * bugfix/AB#23188 CHEFs small fix for initial publish event remove unused subscription event --- app/frontend/src/store/form.js | 2 + app/src/forms/event/eventService.js | 139 ++++++++++++++++++++++++++++ app/src/forms/form/service.js | 69 ++------------ app/src/forms/submission/service.js | 3 +- 4 files changed, 150 insertions(+), 63 deletions(-) create mode 100644 app/src/forms/event/eventService.js diff --git a/app/frontend/src/store/form.js b/app/frontend/src/store/form.js index cbe93e100..19f728224 100644 --- a/app/frontend/src/store/form.js +++ b/app/frontend/src/store/form.js @@ -839,6 +839,8 @@ export const useFormStore = defineStore('form', { const { data } = await formService.readFormSubscriptionData(formId); if (data) { this.subscriptionData = data; + } else { + this.subscriptionData = genInitialSubscribeDetails(); } } catch (error) { const notificationStore = useNotificationStore(); diff --git a/app/src/forms/event/eventService.js b/app/src/forms/event/eventService.js new file mode 100644 index 000000000..eeea7d144 --- /dev/null +++ b/app/src/forms/event/eventService.js @@ -0,0 +1,139 @@ +const log = require('../../components/log')(module.filename); +const { SubscriptionEvent } = require('../common/constants'); +const { FormVersion, Form, FormSubscription } = require('../common/models'); +const axios = require('axios'); +const { queryUtils } = require('../common/utils'); + +const service = { + /** + * @function formSubmissionEventReceived + * Completing submission of a form + * @param {string} formId + * @param {string} formVersionId + * @param {string} submissionId + * @param {string} data + * @returns The callout to the submission event endpoint + */ + formSubmissionEventReceived: async (formId, formVersionId, submissionId, data) => { + try { + const { subscribe } = await service.readForm(formId, {}); + if (subscribe && subscribe.enabled && data.submission.data.submit) { + const formVersion = await service.readVersion(formVersionId); + const subscribeConfig = await service.readFormSubscriptionDetails(formId); + const config = Object.assign({}, subscribe, subscribeConfig); + service.postSubscriptionEvent(config, formVersion, submissionId, SubscriptionEvent.FORM_SUBMITTED); + } + } catch (e) { + log.error(e.message, { + function: 'formSubmissionEventReceived', + formId: formId, + formVersionId: formVersionId, + submissionId: submissionId, + data: data, + }); + throw e; + } + }, + + /** + * @function postSubscriptionEvent + * Post the subscription event + * @param {string} subscribe settings + * @param {string} formVersion form version json object + * @param {string} submissionId submission id + * @param {string} subscriptionEvent string value of the event + * @returns The FormVersion + */ + postSubscriptionEvent: async (subscribe, formVersion, submissionId, subscriptionEvent) => { + try { + // Check if there are endpoints subscribed for form submission event + if (subscribe && subscribe.endpointUrl) { + const axiosOptions = { timeout: 10000 }; + const axiosInstance = axios.create(axiosOptions); + const jsonData = { formId: formVersion.formId, formVersion: formVersion.id, subscriptionEvent: subscriptionEvent }; + if (submissionId != null) { + jsonData['submissionId'] = submissionId; + } + + axiosInstance.interceptors.request.use( + (cfg) => { + cfg.headers = { [subscribe.key]: `${subscribe.endpointToken}` }; + return Promise.resolve(cfg); + }, + (error) => { + return Promise.reject(error); + } + ); + axiosInstance.post(subscribe.endpointUrl, jsonData); + } + } catch (err) { + log.error(err.message, err, { + function: 'postSubscriptionEvent', + }); + } + }, + + /** + * @function publishFormEvent + * Publish Event for form + * @param {string} formId the form id + * @param {string} formVersionId form version id + * @param {boolean} publish bool true or false - Published or Unpublished + */ + publishFormEvent: async (formId, formVersionId, publish) => { + const { subscribe } = await service.readForm(formId); + + if (subscribe && subscribe.enabled) { + const subscribeConfig = await service.readFormSubscriptionDetails(formId); + const config = Object.assign({}, subscribe, subscribeConfig); + const formVersion = new FormVersion(); + formVersion.id = formVersionId; + formVersion.formId = formId; + + if (publish) { + service.postSubscriptionEvent(config, formVersion, null, SubscriptionEvent.FORM_PUBLISHED); + } else { + service.postSubscriptionEvent(config, formVersion, null, SubscriptionEvent.FORM_UNPUBLISHED); + } + } + }, + + /** + * @function readFormSubscriptionDetails + * @param {string} formId + * Get the current subscription settings for a form + * @returns The subscription settings for a form + */ + readFormSubscriptionDetails: (formId) => { + return FormSubscription.query().modify('filterFormId', formId).first(); + }, + + /** + * @function readVersion + * Get the current FormVersion + * @param {string} formVersionId + * @returns The FormVersion + */ + readVersion: (formVersionId) => { + return FormVersion.query().findById(formVersionId).throwIfNotFound(); + }, + /** + * @function readForm + * Read Form + * @param {string} formId form id + * @param {object} params form query params + * @returns The Form object + */ + readForm: (formId, params = {}) => { + params = queryUtils.defaultActiveOnly(params); + return Form.query() + .findById(formId) + .modify('filterActive', params.active) + .allowGraph('[identityProviders,versions]') + .withGraphFetched('identityProviders(orderDefault)') + .withGraphFetched('versions(selectWithoutSchema, orderVersionDescending)') + .throwIfNotFound(); + }, +}; + +module.exports = service; diff --git a/app/src/forms/form/service.js b/app/src/forms/form/service.js index 01ad71249..f262a76a2 100644 --- a/app/src/forms/form/service.js +++ b/app/src/forms/form/service.js @@ -1,9 +1,8 @@ const Problem = require('api-problem'); const { ref } = require('objection'); const { v4: uuidv4 } = require('uuid'); -const { EmailTypes, SubscriptionEvent } = require('../common/constants'); -const axios = require('axios'); -const log = require('../../components/log')(module.filename); +const { EmailTypes } = require('../common/constants'); +const eventService = require('../event/eventService'); const moment = require('moment'); const { FileStorage, @@ -383,21 +382,7 @@ const service = { }); await trx.commit(); - - const { subscribe } = await service.readForm(formId); - if (subscribe && subscribe.enabled) { - const subscribeConfig = await service.readFormSubscriptionDetails(formId); - const config = Object.assign({}, subscribe, subscribeConfig); - const formVersion = new FormVersion(); - formVersion.id = formVersionId; - formVersion.formId = formId; - - if (publish) { - service.postSubscriptionEvent(config, formVersion, null, SubscriptionEvent.FORM_PUBLISHED); - } else { - service.postSubscriptionEvent(config, formVersion, null, SubscriptionEvent.FORM_UNPUBLISHED); - } - } + eventService.publishFormEvent(formId, formVersionId, publish); // return the published form/version... return await service.readPublishedForm(formId); @@ -445,7 +430,7 @@ const service = { let trx; try { const formVersion = await service.readVersion(formVersionId); - const { identityProviders, subscribe } = await service.readForm(formVersion.formId); + const { identityProviders } = await service.readForm(formVersion.formId); trx = await FormSubmission.startTransaction(); @@ -498,13 +483,10 @@ const service = { }; await FormSubmissionStatus.query(trx).insert(stObj); - if (subscribe && subscribe.enabled) { - const subscribeConfig = await service.readFormSubscriptionDetails(formVersion.formId); - const config = Object.assign({}, subscribe, subscribeConfig); - service.postSubscriptionEvent(config, formVersion, submissionId, SubscriptionEvent.FORM_SUBMITTED); - } } + eventService.formSubmissionEventReceived(formVersion.formId, formVersion.id, submissionId, data); + // does this submission contain any file uploads? // if so, we need to update the file storage records. // use the schema to determine if there are uploads, fetch the ids from the submission data... @@ -685,15 +667,7 @@ const service = { await FormVersionDraft.query().deleteById(formVersionDraftId); await trx.commit(); - const { subscribe } = await service.readForm(formId); - if (subscribe && subscribe.enabled) { - const subscribeConfig = await service.readFormSubscriptionDetails(formId); - const config = Object.assign({}, subscribe, subscribeConfig); - const formVersion = new FormVersion(); - formVersion.id = version.id; - formVersion.formId = formId; - service.postSubscriptionEvent(config, formVersion, null, SubscriptionEvent.FORM_DRAFT_PUBLISHED); - } + eventService.publishFormEvent(formId, version.id, version.published); // return the published version... return await service.readVersion(version.id); @@ -796,35 +770,6 @@ const service = { return { url: imageUrl }; }, - postSubscriptionEvent: async (subscribe, formVersion, submissionId, subscriptionEvent) => { - try { - // Check if there are endpoints subscribed for form submission event - if (subscribe && subscribe.endpointUrl) { - const axiosOptions = { timeout: 10000 }; - const axiosInstance = axios.create(axiosOptions); - const jsonData = { formId: formVersion.formId, formVersion: formVersion.id, subscriptionEvent: subscriptionEvent }; - if (submissionId != null) { - jsonData['submissionId'] = submissionId; - } - - axiosInstance.interceptors.request.use( - (cfg) => { - cfg.headers = { [subscribe.key]: `${subscribe.endpointToken}` }; - return Promise.resolve(cfg); - }, - (error) => { - return Promise.reject(error); - } - ); - axiosInstance.post(subscribe.endpointUrl, jsonData); - } - } catch (err) { - log.error(err.message, err, { - function: 'postSubscriptionEvent', - }); - } - }, - /** * @function listFormComponentsProactiveHelp * Search for all form components help information diff --git a/app/src/forms/submission/service.js b/app/src/forms/submission/service.js index 8c8923928..e8da1fc15 100644 --- a/app/src/forms/submission/service.js +++ b/app/src/forms/submission/service.js @@ -1,8 +1,8 @@ const { v4: uuidv4 } = require('uuid'); - const { Statuses } = require('../common/constants'); const { Form, FormVersion, FormSubmission, FormSubmissionStatus, Note, SubmissionAudit, SubmissionMetadata } = require('../common/models'); const emailService = require('../email/emailService'); +const eventService = require('../event/eventService'); const formService = require('../form/service'); const permissionService = require('../permission/service'); @@ -72,6 +72,7 @@ const service = { // If finalizing submission, send the submission email (quiet fail if anything goes wrong) const submissionMetaData = await SubmissionMetadata.query().where('submissionId', formSubmissionId).first(); emailService.submissionReceived(submissionMetaData.formId, formSubmissionId, data, referrer).catch(() => {}); + eventService.formSubmissionEventReceived(submissionMetaData.formId, submissionMetaData.formVersionId, formSubmissionId, data); } } else { if (statuses && statuses.length > 0 && (statuses[0].code === Statuses.SUBMITTED || statuses[0].code === Statuses.COMPLETED)) { From 9da22b112e76946a0299102402339bbc1422c640 Mon Sep 17 00:00:00 2001 From: Walter Moar Date: Wed, 6 Mar 2024 13:01:25 -0800 Subject: [PATCH 2/3] perf: FORMS-1034 increase the number of pods in production (#1303) * perf: FORMS-1034 increase the number of pods in production * need to quote ints * tweak for int32 --- openshift/app.dc.yaml | 6 +++++- openshift/app.prod.param | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/openshift/app.dc.yaml b/openshift/app.dc.yaml index 0f77451da..20b88307d 100644 --- a/openshift/app.dc.yaml +++ b/openshift/app.dc.yaml @@ -59,7 +59,7 @@ objects: app.openshift.io/connects-to: '[{"apiVersion":"apps/v1","kind":"StatefulSet","name":"patroni-${JOB_NAME}"}]' name: "${APP_NAME}-app-${JOB_NAME}" spec: - replicas: 2 + replicas: ${{REPLICAS}} revisionHistoryLimit: 10 selector: app: "${APP_NAME}-${JOB_NAME}" @@ -359,3 +359,7 @@ parameters: description: Requested Memory per pod (in gigabytes Gi or megabytes Mi ex. 500Mi) displayName: Memory Request value: 256Mi + - name: REPLICAS + description: The number of replicas to run + displayName: Replicas + value: "2" diff --git a/openshift/app.prod.param b/openshift/app.prod.param index 12f76a453..5e18472f0 100644 --- a/openshift/app.prod.param +++ b/openshift/app.prod.param @@ -1,3 +1,4 @@ CPU_LIMIT=1500m CPU_REQUEST=250m MEMORY_LIMIT=2Gi +REPLICAS=3 From 5381e7380ae6060f6644b0d04c64f51ee82c27ec Mon Sep 17 00:00:00 2001 From: Walter Moar Date: Thu, 7 Mar 2024 08:07:28 -0800 Subject: [PATCH 3/3] fix: FORMS-1037 handle missing submission schedule (#1304) --- app/src/forms/email/reminderService.js | 2 ++ app/tests/unit/forms/email/reminderService.spec.js | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/app/src/forms/email/reminderService.js b/app/src/forms/email/reminderService.js index ab6d70d68..eabe6950d 100644 --- a/app/src/forms/email/reminderService.js +++ b/app/src/forms/email/reminderService.js @@ -124,6 +124,8 @@ const service = { }), ]; } + + return []; }, _getGraceDate: (schedule) => { let substartDate = moment(schedule.openSubmissionDateTime); diff --git a/app/tests/unit/forms/email/reminderService.spec.js b/app/tests/unit/forms/email/reminderService.spec.js index a43356239..61696ac15 100644 --- a/app/tests/unit/forms/email/reminderService.spec.js +++ b/app/tests/unit/forms/email/reminderService.spec.js @@ -27,6 +27,16 @@ const schedule = { closeSubmissionDateTime: null, }; +describe('_init', () => { + it('should handle reminder enabled with empty schedule', async () => { + reminderService._getForms = jest.fn().mockReturnValue([{ schedule: {} }]); + + const result = await reminderService._init(); + + expect(result.errors).not.toBe([]); + }); +}); + describe('getDifference', () => { it('should return the difference between 2 arrays of object', () => { let obj1 = [