@plusscommunities/pluss-maintenance-aws-forms 2.1.32 → 2.1.34-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/feature.config.js CHANGED
@@ -155,6 +155,14 @@ exports.serverless = {
155
155
  timeout: 300,
156
156
  table: values.tableKeyMaintenance,
157
157
  },
158
+ {
159
+ name: "jobTypesChanged",
160
+ file: "jobTypesChanged",
161
+ function: "jobTypesChanged",
162
+ memorySize: 512,
163
+ timeout: 60,
164
+ table: values.tableKeyJobTypes,
165
+ },
158
166
  ],
159
167
  schedules: [
160
168
  {
@@ -45,6 +45,12 @@ class IntegrationStrategy {
45
45
  onCompleteRequest = async (request) => {
46
46
  return null;
47
47
  };
48
+
49
+ // TODO: Remove once all existing records have ActiveEntityType (PC-1382)
50
+ // backfills ActiveEntityType on existing externalentities records for the sparse GSI migration
51
+ backfillActiveEntityType = async (timeout) => {
52
+ return { backfillCount: 0, complete: true };
53
+ };
48
54
  }
49
55
 
50
56
  module.exports = IntegrationStrategy;
@@ -23,6 +23,7 @@ class ArchibusStrategy extends IntegrationStrategy {
23
23
  this.host = url.host;
24
24
 
25
25
  this.statusMap = config.StatusMap;
26
+ this.completedStatuses = config.CompletedStatuses ?? [];
26
27
  this.siteMap = config.SiteMap ?? {};
27
28
  this.buildingCodes = [];
28
29
  }
@@ -51,7 +52,7 @@ class ArchibusStrategy extends IntegrationStrategy {
51
52
  * @returns {Number} The refresh interval in milliseconds
52
53
  */
53
54
  getRefreshInterval = () => {
54
- return 15 * 60 * 1000; // 15 minutes
55
+ return 60 * 60 * 1000; // 60 minutes
55
56
  };
56
57
 
57
58
  /**
@@ -125,6 +126,7 @@ class ArchibusStrategy extends IntegrationStrategy {
125
126
  RowId: `${this.getEntityType()}_${response.data.wrId}`,
126
127
  LastUpdated: moment().valueOf(),
127
128
  EntityType: this.getEntityType(),
129
+ ActiveEntityType: this.getEntityType(),
128
130
  InternalId: request.id,
129
131
  ExternalId: response.data.wrId,
130
132
  TrackedData: {},
@@ -304,6 +306,17 @@ class ArchibusStrategy extends IntegrationStrategy {
304
306
  }
305
307
  );
306
308
 
309
+ // If the external status is terminal, remove ActiveEntityType to exclude from sparse GSI
310
+ if (this.completedStatuses.includes(request.status)) {
311
+ log("Archibus:RefreshFromSource", "TerminalStatus", request.status, logId);
312
+ const entityRowId = `${this.getEntityType()}_${externalId}`;
313
+ const entity = await getRef("externalentities", "RowId", entityRowId);
314
+ if (entity && entity.ActiveEntityType) {
315
+ delete entity.ActiveEntityType;
316
+ await updateRef("externalentities", entity);
317
+ }
318
+ }
319
+
307
320
  log("Archibus:RefreshFromSource", "Result", true, logId);
308
321
  return true;
309
322
  };
@@ -630,6 +643,48 @@ class ArchibusStrategy extends IntegrationStrategy {
630
643
  onCompleteRequest = async (request) => {
631
644
  return true;
632
645
  };
646
+
647
+ // TODO: Remove once all existing records have ActiveEntityType (PC-1382)
648
+ // Backfills ActiveEntityType on existing externalentities records for the sparse GSI migration.
649
+ // Runs as part of the cron job after normal processing, respecting the given timeout.
650
+ backfillActiveEntityType = async (timeout) => {
651
+ const logId = log("Archibus:Backfill", "Start", true);
652
+ const startTime = moment().valueOf();
653
+ let backfillCount = 0;
654
+ let lastEvaluatedKey = null;
655
+
656
+ do {
657
+ const query = {
658
+ IndexName: "EntityTypeIndex",
659
+ KeyConditionExpression: "EntityType = :entityType",
660
+ ExpressionAttributeValues: {
661
+ ":entityType": this.getEntityType(),
662
+ },
663
+ Limit: 25,
664
+ };
665
+ if (lastEvaluatedKey) query.ExclusiveStartKey = lastEvaluatedKey;
666
+
667
+ const result = await indexQuery("externalentities", query);
668
+ lastEvaluatedKey = result.LastEvaluatedKey;
669
+
670
+ for (const item of result.Items) {
671
+ if (!item.ActiveEntityType) {
672
+ // Skip records already in a terminal status — they should stay out of the sparse GSI
673
+ const trackedStatus = item.TrackedData && item.TrackedData.status;
674
+ if (trackedStatus && this.completedStatuses.includes(trackedStatus)) {
675
+ continue;
676
+ }
677
+ item.ActiveEntityType = item.EntityType;
678
+ await updateRef("externalentities", item);
679
+ backfillCount++;
680
+ }
681
+ }
682
+ } while (lastEvaluatedKey && moment().valueOf() < startTime + timeout);
683
+
684
+ const complete = !lastEvaluatedKey;
685
+ log("Archibus:Backfill", "End", { backfillCount, complete }, logId);
686
+ return { backfillCount, complete };
687
+ };
633
688
  }
634
689
 
635
690
  module.exports = ArchibusStrategy;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * DynamoDB Stream Handler for JobTypes changes
3
+ *
4
+ * Republishes any templates using this site as source.
5
+ * Only notifies siteConfigs for variants with enableSiteConfigsPropagation: true
6
+ * (maintenance and maintenanceForms). Other variants (feedback, enquiry) are excluded.
7
+ */
8
+
9
+ const { Marshaller } = require("@aws/dynamodb-auto-marshaller");
10
+ const marshaller = new Marshaller();
11
+ const { log } = require("@plusscommunities/pluss-core-aws/helper");
12
+ const notifySiteConfigs = require("@plusscommunities/pluss-core-aws/helper/notifySiteConfigs");
13
+ const { values } = require("./values.config");
14
+
15
+ module.exports.jobTypesChanged = async (event, context, callback) => {
16
+ const logId = log("jobTypesChanged", "Start", { recordCount: event.Records.length, serviceKey: values.serviceKey });
17
+
18
+ // Check if this variant supports siteConfigs propagation
19
+ if (!values.enableSiteConfigsPropagation) {
20
+ log("jobTypesChanged", "SkipPropagation", { serviceKey: values.serviceKey }, logId);
21
+ return;
22
+ }
23
+
24
+ const promises = [];
25
+
26
+ event.Records.forEach((record) => {
27
+ log("jobTypesChanged", "Record", { eventName: record.eventName }, logId);
28
+
29
+ if (record.eventName === "INSERT") {
30
+ const data = marshaller.unmarshallItem(record.dynamodb.NewImage);
31
+ log("jobTypesChanged", "Insert", { id: data.id, site: data.site }, logId);
32
+
33
+ if (data.site) {
34
+ promises.push(notifySiteConfigs(data.site, logId));
35
+ }
36
+ } else if (record.eventName === "MODIFY") {
37
+ const data = marshaller.unmarshallItem(record.dynamodb.NewImage);
38
+ log("jobTypesChanged", "Modify", { id: data.id, site: data.site }, logId);
39
+
40
+ if (data.site) {
41
+ promises.push(notifySiteConfigs(data.site, logId));
42
+ }
43
+ } else if (record.eventName === "REMOVE") {
44
+ const previousData = marshaller.unmarshallItem(record.dynamodb.OldImage);
45
+ log("jobTypesChanged", "Remove", { id: previousData.id, site: previousData.site }, logId);
46
+
47
+ if (previousData.site) {
48
+ promises.push(notifySiteConfigs(previousData.site, logId));
49
+ }
50
+ }
51
+ });
52
+
53
+ await Promise.all(promises);
54
+ log("jobTypesChanged", "Complete", { promiseCount: promises.length }, logId);
55
+ };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-maintenance-aws-forms",
3
- "version": "2.1.32",
3
+ "version": "2.1.34-beta.0",
4
4
  "description": "Extension package to enable maintenance on Pluss Communities Platform",
5
5
  "scripts": {
6
6
  "gc": "node ../../tools/gc ./",
7
- "gs": "node ../../tools/gs ./ ../../activity/serverless.yml",
7
+ "gs": "node ../../tools/gs ./ ../../strings/serverless.yml",
8
8
  "betapatch": "npm version prepatch --preid=beta",
9
9
  "patch": "npm version patch",
10
10
  "deploy": "npm run gc && npm run gs && serverless deploy",
@@ -24,7 +24,7 @@
24
24
  "license": "ISC",
25
25
  "dependencies": {
26
26
  "@aws/dynamodb-auto-marshaller": "^0.7.1",
27
- "@plusscommunities/pluss-core-aws": "2.0.23",
27
+ "@plusscommunities/pluss-core-aws": "2.0.24-beta.0",
28
28
  "amazon-cognito-identity-js": "^2.0.19",
29
29
  "aws-sdk": "^2.1591.0",
30
30
  "axios": "^1.6.8",
@@ -62,6 +62,7 @@ module.exports = async (event, data) => {
62
62
  const externalEntity = await updateRef("externalentities", {
63
63
  RowId: rowId,
64
64
  EntityType: entityType,
65
+ ActiveEntityType: entityType,
65
66
  InternalId: job.id,
66
67
  ExternalId: data.externalId,
67
68
  LastUpdated: moment().valueOf(),
@@ -29,11 +29,11 @@ const processBatch = async (strategy, startTime) => {
29
29
 
30
30
  // use index query to fetch IDs of requests
31
31
  const query = {
32
- IndexName: "EntityTypeIndex",
32
+ IndexName: "ActiveEntityTypeIndex",
33
33
  KeyConditionExpression:
34
- "EntityType = :entityType AND LastUpdated < :lastUpdate", // only fetch items that haven't been updated in this scan
34
+ "ActiveEntityType = :activeEntityType AND LastUpdated < :lastUpdate", // only fetch items that haven't been updated in this scan
35
35
  ExpressionAttributeValues: {
36
- ":entityType": strategy.getEntityType(),
36
+ ":activeEntityType": strategy.getEntityType(),
37
37
  ":lastUpdate": startTime - strategy.getRefreshInterval(),
38
38
  },
39
39
  Limit: 10,
@@ -77,5 +77,11 @@ module.exports.scheduleJobImport = async (event, context, callback) => {
77
77
  }
78
78
  log("scheduleJobImport", "EndLoop", moment().valueOf(), logId);
79
79
  }
80
+ // TODO: Remove this call once all existing records have ActiveEntityType (PC-1382)
81
+ const remainingTime = (startTime + timeout) - moment().valueOf();
82
+ if (remainingTime > 0) {
83
+ await integrationStrategy.backfillActiveEntityType(remainingTime);
84
+ }
85
+
80
86
  log("scheduleJobImport", "End", moment().valueOf(), logId);
81
87
  };
@@ -23,5 +23,6 @@ const values = {
23
23
  activityEditMaintenanceNote: "EditMaintenanceNoteA",
24
24
  textJobEmailTitle: "Service Request",
25
25
  triggerMaintenanceStatusChanged: "MaintenanceStatusChangedA",
26
+ enableSiteConfigsPropagation: false,
26
27
  };
27
28
  exports.values = values;
@@ -26,5 +26,6 @@ const values = {
26
26
  allowGeneralType: true,
27
27
  defaultJobTypes: [],
28
28
  triggerMaintenanceStatusChanged: "MaintenanceStatusChanged",
29
+ enableSiteConfigsPropagation: true,
29
30
  };
30
31
  exports.values = values;
@@ -219,5 +219,6 @@ const values = {
219
219
  },
220
220
  ],
221
221
  triggerMaintenanceStatusChanged: "MaintenanceStatusChangedEnquiry",
222
+ enableSiteConfigsPropagation: false,
222
223
  };
223
224
  exports.values = values;
@@ -191,5 +191,6 @@ const values = {
191
191
  },
192
192
  ],
193
193
  triggerMaintenanceStatusChanged: "MaintenanceStatusChangedFeedback",
194
+ enableSiteConfigsPropagation: false,
194
195
  };
195
196
  exports.values = values;
@@ -26,5 +26,6 @@ const values = {
26
26
  allowGeneralType: false,
27
27
  defaultJobTypes: [],
28
28
  triggerMaintenanceStatusChanged: "MaintenanceStatusChangedFood",
29
+ enableSiteConfigsPropagation: false,
29
30
  };
30
31
  exports.values = values;
@@ -26,5 +26,6 @@ const values = {
26
26
  allowGeneralType: false,
27
27
  defaultJobTypes: [],
28
28
  triggerMaintenanceStatusChanged: "MaintenanceStatusChangedForms",
29
+ enableSiteConfigsPropagation: true,
29
30
  };
30
31
  exports.values = values;
package/values.config.js CHANGED
@@ -26,5 +26,6 @@ const values = {
26
26
  allowGeneralType: false,
27
27
  defaultJobTypes: [],
28
28
  triggerMaintenanceStatusChanged: "MaintenanceStatusChangedForms",
29
+ enableSiteConfigsPropagation: true,
29
30
  };
30
31
  exports.values = values;