@overmap-ai/core 1.0.48-bulk-form-submission.2 → 1.0.48-bulk-form-submission.4

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.
@@ -795,6 +795,19 @@ var __publicField = (obj, key, value) => {
795
795
  element.click();
796
796
  document.body.removeChild(element);
797
797
  }
798
+ const constructUploadedFilePayloads = async (files) => {
799
+ const filePayloads = {};
800
+ for (const file of files) {
801
+ const sha1 = await hashFile(file);
802
+ filePayloads[sha1] = {
803
+ sha1,
804
+ extension: file.name.split(".").pop() || "",
805
+ file_type: file.type,
806
+ size: file.size
807
+ };
808
+ }
809
+ return Object.values(filePayloads);
810
+ };
798
811
  const fileToBlob = async (dataUrl) => {
799
812
  return (await fetch(dataUrl)).blob();
800
813
  };
@@ -1528,6 +1541,15 @@ var __publicField = (obj, key, value) => {
1528
1541
  throw new Error(`Attachment ${action.payload.offline_id} does not exist.`);
1529
1542
  }
1530
1543
  }
1544
+ function updateAttachments(state, action) {
1545
+ for (const attachment of action.payload) {
1546
+ if (attachment.offline_id in state.attachments) {
1547
+ state.attachments[attachment.offline_id] = attachment;
1548
+ } else {
1549
+ throw new Error(`Attachment ${attachment.offline_id} does not exist.`);
1550
+ }
1551
+ }
1552
+ }
1531
1553
  function removeAttachment(state, action) {
1532
1554
  if (action.payload in state.attachments) {
1533
1555
  delete state.attachments[action.payload];
@@ -2121,6 +2143,7 @@ var __publicField = (obj, key, value) => {
2121
2143
  }
2122
2144
  },
2123
2145
  updateIssueAttachment: updateAttachment,
2146
+ updateIssueAttachments: updateAttachments,
2124
2147
  removeIssue: (state, action) => {
2125
2148
  if (action.payload in state.issues) {
2126
2149
  delete state.issues[action.payload];
@@ -2129,6 +2152,7 @@ var __publicField = (obj, key, value) => {
2129
2152
  }
2130
2153
  },
2131
2154
  removeIssueAttachment: removeAttachment,
2155
+ removeIssueAttachments: removeAttachments,
2132
2156
  removeIssueUpdate: (state, action) => {
2133
2157
  if (action.payload in state.updates) {
2134
2158
  delete state.updates[action.payload];
@@ -2238,6 +2262,7 @@ var __publicField = (obj, key, value) => {
2238
2262
  addToRecentIssues,
2239
2263
  cleanRecentIssues,
2240
2264
  removeIssueAttachment,
2265
+ removeIssueAttachments,
2241
2266
  removeAttachmentsOfIssue,
2242
2267
  removeIssue,
2243
2268
  removeIssueUpdate,
@@ -2251,6 +2276,7 @@ var __publicField = (obj, key, value) => {
2251
2276
  setVisibleStatuses,
2252
2277
  setVisibleUserIds,
2253
2278
  updateIssueAttachment,
2279
+ updateIssueAttachments,
2254
2280
  updateIssue,
2255
2281
  // Commments
2256
2282
  addIssueComment,
@@ -3814,6 +3840,16 @@ var __publicField = (obj, key, value) => {
3814
3840
  state.attachments[attachment.offline_id] = attachment;
3815
3841
  }
3816
3842
  },
3843
+ updateFormSubmissionAttachments: (state, action) => {
3844
+ for (const attachment of action.payload) {
3845
+ if (state.attachments[attachment.offline_id] === void 0) {
3846
+ throw new Error(`Attachment with offline_id ${attachment.offline_id} does not exist`);
3847
+ }
3848
+ }
3849
+ for (const attachment of action.payload) {
3850
+ state.attachments[attachment.offline_id] = attachment;
3851
+ }
3852
+ },
3817
3853
  // The delete actions for UserFormSubmissionAttachments are not used in the app, but are included for completeness
3818
3854
  // Could be used if editing a submission is ever supported, will be applicable for supporting tip tap content in submissions
3819
3855
  deleteFormSubmissionAttachment: (state, action) => {
@@ -3844,6 +3880,7 @@ var __publicField = (obj, key, value) => {
3844
3880
  addFormSubmissionAttachment,
3845
3881
  addFormSubmissionAttachments,
3846
3882
  setFormSubmissionAttachments,
3883
+ updateFormSubmissionAttachments,
3847
3884
  deleteFormSubmissionAttachment,
3848
3885
  deleteFormSubmissionAttachments
3849
3886
  } = formSubmissionSlice.actions;
@@ -4697,6 +4734,22 @@ var __publicField = (obj, key, value) => {
4697
4734
  }
4698
4735
  }
4699
4736
  class AttachmentService extends BaseApiService {
4737
+ processPresignedUrls(presignedUrls) {
4738
+ for (const [sha1, presignedUrl] of Object.entries(presignedUrls)) {
4739
+ void this.enqueueRequest({
4740
+ url: presignedUrl.url,
4741
+ description: "Upload file",
4742
+ method: HttpMethod.POST,
4743
+ isExternalUrl: true,
4744
+ isAuthNeeded: false,
4745
+ attachmentHash: sha1,
4746
+ // TODO: can we use the sha1 as the blocker?
4747
+ blockers: [`s3-${sha1}`],
4748
+ blocks: [sha1],
4749
+ s3url: presignedUrl
4750
+ });
4751
+ }
4752
+ }
4700
4753
  fetchAll(projectId) {
4701
4754
  const promise = this.enqueueRequest({
4702
4755
  description: "Fetch attachments",
@@ -4722,6 +4775,7 @@ var __publicField = (obj, key, value) => {
4722
4775
  }
4723
4776
  const offlineAttachment = {
4724
4777
  ...attachmentPayload,
4778
+ // TODO: just handle creating the objectURL in here, then the front end doesn't need to worry about it
4725
4779
  file: attachmentPayload.file.objectURL,
4726
4780
  file_name: attachmentPayload.file.name,
4727
4781
  file_type: attachmentPayload.file.type,
@@ -4755,6 +4809,7 @@ var __publicField = (obj, key, value) => {
4755
4809
  }
4756
4810
  const offlineAttachment = {
4757
4811
  ...attachmentPayload,
4812
+ // TODO: just handle creating the objectURL in here, then the front end doesn't need to worry about it
4758
4813
  file: attachmentPayload.file.objectURL,
4759
4814
  file_name: attachmentPayload.file.name,
4760
4815
  file_type: attachmentPayload.file.type,
@@ -4788,6 +4843,7 @@ var __publicField = (obj, key, value) => {
4788
4843
  }
4789
4844
  const offlineAttachment = {
4790
4845
  ...attachmentPayload,
4846
+ // TODO: just handle creating the objectURL in here, then the front end doesn't need to worry about it
4791
4847
  file: attachmentPayload.file.objectURL,
4792
4848
  file_name: attachmentPayload.file.name,
4793
4849
  file_type: attachmentPayload.file.type,
@@ -4840,7 +4896,7 @@ var __publicField = (obj, key, value) => {
4840
4896
  offline_id,
4841
4897
  project,
4842
4898
  description: description2 ?? "",
4843
- submitted_at: (/* @__PURE__ */ new Date()).getTime() / 1e3,
4899
+ submitted_at: (/* @__PURE__ */ new Date()).toISOString(),
4844
4900
  ...fileProps
4845
4901
  }
4846
4902
  });
@@ -4853,26 +4909,54 @@ var __publicField = (obj, key, value) => {
4853
4909
  /** the outer Promise is needed to await the hashing of each file, which is required before offline use. If wanting to
4854
4910
  * attach promise handlers to the request to add the attachment in the backend, apply it on the promise returned from the
4855
4911
  * OptimisticModelResult. */
4856
- attachFilesToIssue(filesToSubmit, issueId) {
4857
- return filesToSubmit.map((file) => {
4858
- if (!(file instanceof File)) {
4859
- throw new Error("Expected a File instance.");
4860
- }
4861
- const photoAttachmentPromise = async (file2) => {
4862
- const hash = await hashFile(file2);
4863
- const attachment = offline({
4864
- file: file2,
4865
- file_name: file2.name,
4866
- file_type: file2.type,
4867
- issue: issueId,
4868
- file_sha1: hash,
4869
- submitted_at: (/* @__PURE__ */ new Date()).toISOString(),
4870
- created_by: this.client.store.getState().userReducer.currentUser.id
4871
- });
4872
- return this.addIssueAttachment(attachment);
4873
- };
4874
- return photoAttachmentPromise(file);
4912
+ // note the method is only marked as async since files needs to be hashed
4913
+ async attachFilesToIssue(files, issueId) {
4914
+ const { store } = this.client;
4915
+ const offlineAttachments = [];
4916
+ const attachmentsPayload = [];
4917
+ const currentUser = store.getState().userReducer.currentUser;
4918
+ const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
4919
+ for (const file of files) {
4920
+ const attachment = offline({
4921
+ file: URL.createObjectURL(file),
4922
+ file_name: file.name,
4923
+ file_type: file.type,
4924
+ file_sha1: await hashFile(file),
4925
+ description: "",
4926
+ submitted_at: submittedAt,
4927
+ created_by: currentUser.id,
4928
+ issue: issueId
4929
+ });
4930
+ attachmentsPayload.push({
4931
+ offline_id: attachment.offline_id,
4932
+ name: attachment.file_name,
4933
+ sha1: attachment.file_sha1,
4934
+ description: attachment.description,
4935
+ created_by: attachment.created_by,
4936
+ issue_id: attachment.issue
4937
+ });
4938
+ offlineAttachments.push(attachment);
4939
+ }
4940
+ store.dispatch(addIssueAttachments(offlineAttachments));
4941
+ const promise = this.enqueueRequest({
4942
+ description: "Attach files to issue",
4943
+ method: HttpMethod.POST,
4944
+ url: `/issues/${issueId}/attach/`,
4945
+ payload: {
4946
+ submitted_at: submittedAt,
4947
+ attachments: attachmentsPayload,
4948
+ files: await constructUploadedFilePayloads(files)
4949
+ },
4950
+ blocks: offlineAttachments.map((attachment) => attachment.offline_id),
4951
+ blockers: offlineAttachments.map((attachment) => attachment.file_sha1)
4952
+ });
4953
+ promise.then(({ attachments, presigned_urls }) => {
4954
+ store.dispatch(updateIssueAttachments(attachments));
4955
+ this.processPresignedUrls(presigned_urls);
4956
+ }).catch(() => {
4957
+ store.dispatch(removeIssueAttachments(offlineAttachments.map((attachment) => attachment.offline_id)));
4875
4958
  });
4959
+ return [offlineAttachments, promise.then(({ attachments }) => attachments)];
4876
4960
  }
4877
4961
  attachFilesToComponent(filesToSubmit, componentId) {
4878
4962
  return filesToSubmit.map((file) => {
@@ -7148,17 +7232,18 @@ var __publicField = (obj, key, value) => {
7148
7232
  }
7149
7233
  // Note currently the bulkAdd method is specific to form submissions for components
7150
7234
  // TODO: adapt the support bulk adding to any model type
7151
- bulkAdd(args) {
7152
- const { form_revision, values: argsValues, component_offline_ids } = args;
7235
+ async bulkAdd(args) {
7236
+ const { form_revision, values: argsValues, componentOfflineIds } = args;
7153
7237
  const { store } = this.client;
7154
- const submissions = [];
7238
+ const offlineSubmissions = [];
7239
+ const offlineAttachments = [];
7155
7240
  const submissionOfflineIds = [];
7156
- const offline_ids_to_component_ids = [];
7157
- let attachFilesPromises = [];
7241
+ const submissionsPayload = [];
7242
+ const attachmentsPayload = [];
7158
7243
  const { values, files } = separateFilesFromValues(argsValues);
7159
7244
  const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
7160
7245
  const createdBy = store.getState().userReducer.currentUser.id;
7161
- for (const component_id of component_offline_ids) {
7246
+ for (const component_id of componentOfflineIds) {
7162
7247
  const submission = offline({
7163
7248
  form_revision,
7164
7249
  values,
@@ -7166,12 +7251,43 @@ var __publicField = (obj, key, value) => {
7166
7251
  submitted_at: submittedAt,
7167
7252
  component: component_id
7168
7253
  });
7169
- attachFilesPromises = attachFilesPromises.concat(this.getAttachFilesPromises(files, submission));
7170
7254
  submissionOfflineIds.push(submission.offline_id);
7171
- offline_ids_to_component_ids.push([submission.offline_id, component_id]);
7172
- submissions.push(submission);
7255
+ submissionsPayload.push({ offline_id: submission.offline_id, component_id });
7256
+ offlineSubmissions.push(submission);
7257
+ for (const [fieldIdentifier, fileArray] of Object.entries(files)) {
7258
+ for (const file of fileArray) {
7259
+ const sha1 = await hashFile(file);
7260
+ await this.client.files.addCache(file, sha1);
7261
+ const offlineAttachment = offline({
7262
+ file_name: file.name,
7263
+ file_sha1: sha1,
7264
+ file: URL.createObjectURL(file),
7265
+ submission: submission.offline_id,
7266
+ field_identifier: fieldIdentifier
7267
+ });
7268
+ offlineAttachments.push(offlineAttachment);
7269
+ attachmentsPayload.push({
7270
+ offline_id: offlineAttachment.offline_id,
7271
+ submission_id: submission.offline_id,
7272
+ sha1,
7273
+ name: file.name,
7274
+ field_identifier: fieldIdentifier
7275
+ });
7276
+ }
7277
+ }
7278
+ }
7279
+ const filesRecord = {};
7280
+ for (const file of Object.values(files).flat()) {
7281
+ const sha1 = await hashFile(file);
7282
+ filesRecord[sha1] = {
7283
+ sha1,
7284
+ extension: file.name.split(".").pop() || "",
7285
+ file_type: file.type,
7286
+ size: file.size
7287
+ };
7173
7288
  }
7174
- store.dispatch(addFormSubmissions(submissions));
7289
+ store.dispatch(addFormSubmissions(offlineSubmissions));
7290
+ store.dispatch(addFormSubmissionAttachments(offlineAttachments));
7175
7291
  const promise = this.enqueueRequest({
7176
7292
  description: "Bulk add form submissions",
7177
7293
  method: HttpMethod.POST,
@@ -7179,17 +7295,37 @@ var __publicField = (obj, key, value) => {
7179
7295
  payload: {
7180
7296
  form_data: values,
7181
7297
  submitted_at: submittedAt,
7182
- offline_ids_to_component_ids
7298
+ submissions: submissionsPayload,
7299
+ attachments: attachmentsPayload,
7300
+ files: Object.values(filesRecord)
7183
7301
  },
7184
- blockers: component_offline_ids,
7302
+ blockers: componentOfflineIds,
7185
7303
  blocks: submissionOfflineIds
7186
7304
  });
7187
- promise.then((createdSubmissions) => {
7188
- store.dispatch(updateFormSubmissions(createdSubmissions));
7305
+ promise.then(({ submissions, attachments, presigned_urls }) => {
7306
+ store.dispatch(updateFormSubmissions(submissions));
7307
+ store.dispatch(updateFormSubmissionAttachments(attachments));
7308
+ for (const [sha1, presigned_url] of Object.entries(presigned_urls)) {
7309
+ const file = filesRecord[sha1];
7310
+ if (!file)
7311
+ continue;
7312
+ void this.enqueueRequest({
7313
+ url: presigned_url.url,
7314
+ description: "Upload file",
7315
+ method: HttpMethod.POST,
7316
+ isExternalUrl: true,
7317
+ isAuthNeeded: false,
7318
+ attachmentHash: sha1,
7319
+ blockers: [`s3-${file.sha1}.${file.extension}`],
7320
+ blocks: [sha1],
7321
+ s3url: presigned_url
7322
+ });
7323
+ }
7189
7324
  }).catch(() => {
7190
7325
  store.dispatch(deleteFormSubmissions(submissionOfflineIds));
7326
+ store.dispatch(deleteFormSubmissionAttachments(offlineAttachments.map((x) => x.offline_id)));
7191
7327
  });
7192
- return [submissions, promise.then(() => Promise.all(attachFilesPromises).then(() => promise))];
7328
+ return [offlineSubmissions, promise.then(({ submissions }) => submissions)];
7193
7329
  }
7194
7330
  update(submission) {
7195
7331
  const { store } = this.client;
@@ -15821,6 +15957,7 @@ var __publicField = (obj, key, value) => {
15821
15957
  exports2.componentStageSlice = componentStageSlice;
15822
15958
  exports2.componentTypeReducer = componentTypeReducer;
15823
15959
  exports2.componentTypeSlice = componentTypeSlice;
15960
+ exports2.constructUploadedFilePayloads = constructUploadedFilePayloads;
15824
15961
  exports2.coordinatesAreEqual = coordinatesAreEqual;
15825
15962
  exports2.coordinatesToLiteral = coordinatesToLiteral;
15826
15963
  exports2.coordinatesToPointGeometry = coordinatesToPointGeometry;
@@ -15949,6 +16086,7 @@ var __publicField = (obj, key, value) => {
15949
16086
  exports2.removeFavouriteProjectId = removeFavouriteProjectId;
15950
16087
  exports2.removeIssue = removeIssue;
15951
16088
  exports2.removeIssueAttachment = removeIssueAttachment;
16089
+ exports2.removeIssueAttachments = removeIssueAttachments;
15952
16090
  exports2.removeIssueComment = removeIssueComment;
15953
16091
  exports2.removeIssueComments = removeIssueComments;
15954
16092
  exports2.removeIssueUpdate = removeIssueUpdate;
@@ -16213,9 +16351,11 @@ var __publicField = (obj, key, value) => {
16213
16351
  exports2.updateComponentTypeAttachment = updateComponentTypeAttachment;
16214
16352
  exports2.updateDocuments = updateDocuments;
16215
16353
  exports2.updateFormSubmission = updateFormSubmission;
16354
+ exports2.updateFormSubmissionAttachments = updateFormSubmissionAttachments;
16216
16355
  exports2.updateFormSubmissions = updateFormSubmissions;
16217
16356
  exports2.updateIssue = updateIssue;
16218
16357
  exports2.updateIssueAttachment = updateIssueAttachment;
16358
+ exports2.updateIssueAttachments = updateIssueAttachments;
16219
16359
  exports2.updateLicense = updateLicense;
16220
16360
  exports2.updateOrCreateProject = updateOrCreateProject;
16221
16361
  exports2.updateOrganizationAccess = updateOrganizationAccess;