From db10be93b9976c74bb4747c84c078bd2a2243ced Mon Sep 17 00:00:00 2001 From: khalifan-kfan Date: Fri, 15 Mar 2024 15:08:59 +0300 Subject: [PATCH 1/6] feat: enable transformation of xls to enketo --- .../monitoring/monitoring-forms.module.ts | 5 + .../modules/monitoring/monitoring.service.ts | 32 ++++- .../update-monitoring-forms.component.html | 17 +++ .../update-monitoring-forms.component.scss | 28 +++++ .../update-monitoring-forms.component.spec.ts | 21 ++++ .../update-monitoring-forms.component.ts | 112 ++++++++++++++++++ .../view/view-monitoring-forms.component.html | 3 + .../view/view-monitoring-forms.component.scss | 6 +- 8 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html create mode 100644 apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.scss create mode 100644 apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.spec.ts create mode 100644 apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts index 3b7c1090f..d14c4c42f 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts @@ -4,6 +4,7 @@ import { RouterModule } from '@angular/router'; import { FormSubmissionsComponent } from './pages/form-submissions/form-submissions.component'; import { MonitoringPageComponent } from './pages/home/monitoring.page'; +import { UpdateMonitoringFormsComponent } from './pages/update/update-monitoring-forms.component'; import { ViewMonitoringFormsComponent } from './pages/view/view-monitoring-forms.component'; @NgModule({ @@ -23,6 +24,10 @@ import { ViewMonitoringFormsComponent } from './pages/view/view-monitoring-forms path: ':id/submissions', component: FormSubmissionsComponent, }, + { + path: ':id/update', + component: UpdateMonitoringFormsComponent, + } ]), ], }) diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts index 1e3d9daca..217b18b80 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts @@ -1,3 +1,4 @@ +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; // eslint-disable-next-line @nx/enforce-module-boundaries import { Database } from '@picsa/server-types'; @@ -21,7 +22,7 @@ export class MonitoringFormsDashboardService extends PicsaAsyncService { return this.supabaseService.db.table(this.TABLE_NAME); } - constructor(private supabaseService: SupabaseService) { + constructor(private supabaseService: SupabaseService, private http: HttpClient) { super(); } @@ -57,4 +58,33 @@ export class MonitoringFormsDashboardService extends PicsaAsyncService { } return { data, error }; } + + public async updateFormById(id: string, updatedForm: Partial): Promise { + const { data, error } = await this.supabaseService.db + .table(this.TABLE_NAME) + .update(updatedForm) + .eq('id', id) + .single(); + if (error) { + throw error; + } + return data; + } + + submitFormToConvertXlsToXForm(formData: FormData) { + const url = 'http://localhost:5262/api/convert_xls_to_xml'; + //should alow the browser to set this header so it can automatically with the boundary + + // const headers = new HttpHeaders({ + // 'Content-Type': 'multipart/form-data' + // }); + + return this.http.post(url, formData); + } + submitFormToConvertXFormToEnketo(formData: FormData) { + const url = 'http://localhost:5261/api/xlsform-to-enketo'; + + return this.http.post(url, formData); + } + } diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html new file mode 100644 index 000000000..bc7adfe58 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html @@ -0,0 +1,17 @@ +
+
+

Update Form

+
+ @if(form){ +
+

Upload new Form excel file

+ + +
+ } + @if(updateFeedbackMessage) { +
{{ updateFeedbackMessage }}
+ } +
\ No newline at end of file diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.scss b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.scss new file mode 100644 index 000000000..a5617fc10 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.scss @@ -0,0 +1,28 @@ +.form-content{ + display: flex; + flex-direction: column; + gap: 1.4rem; +} +.submitButton{ + width: 7rem; + margin-bottom: 1rem; +} +.form-data{ + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.data-container{ + margin-left: 2rem; + max-height: 25rem; + overflow-y: auto; +} +label{ + font-weight: 700; +} +.action-button-section{ + display: flex; + flex-direction: row; + gap:5px +} \ No newline at end of file diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.spec.ts new file mode 100644 index 000000000..9322b3b12 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UpdateMonitoringFormsComponent } from './update-monitoring-forms.component' + +describe('UpdateMonitoringFormsComponent', () => { + let component: UpdateMonitoringFormsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UpdateMonitoringFormsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(UpdateMonitoringFormsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts new file mode 100644 index 000000000..7d8016a82 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts @@ -0,0 +1,112 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnInit } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +// eslint-disable-next-line @nx/enforce-module-boundaries +import type { Database } from '@picsa/server-types'; +import { + IUploadResult, + SupabaseStoragePickerDirective, + SupabaseUploadComponent, +} from '@picsa/shared/services/core/supabase'; +import { NgxJsonViewerModule } from 'ngx-json-viewer'; + +import { DashboardMaterialModule } from '../../../../material.module'; +import { MonitoringFormsDashboardService } from '../../monitoring.service'; + +export type IMonitoringFormsRow = Database['public']['Tables']['monitoring_forms']['Row']; + +@Component({ + selector: 'dashboard-monitoring-update', + standalone: true, + imports: [ + CommonModule, + DashboardMaterialModule, + FormsModule, + ReactiveFormsModule, + RouterModule, + NgxJsonViewerModule, + SupabaseUploadComponent, + SupabaseStoragePickerDirective, + ], + templateUrl: './update-monitoring-forms.component.html', + styleUrls: ['./update-monitoring-forms.component.scss'], +}) +export class UpdateMonitoringFormsComponent implements OnInit { + public form: IMonitoringFormsRow; + public updateFeedbackMessage = ''; + public allowedFileTypes = ['xlsx', 'xls'].map((ext) => `.${ext}`); + constructor(private service: MonitoringFormsDashboardService, private route: ActivatedRoute) {} + async ngOnInit() { + await this.service.ready(); + this.route.params.subscribe(async (params) => { + const id = params['id']; + this.service + .getFormById(id) + .then((data) => { + this.form = data; + }) + .catch((error) => { + console.error('Error fetching Form:', error); + }); + }); + } + + public async handleUploadComplete(res: IUploadResult[], controlName: 'submission_forms') { + if (res.length === 0) { + return; + } + const [{ entry }] = res; + const [{ data }] = res; + console.log(entry) + const formData = new FormData(); + formData.append('files', data, data instanceof File ? data.name : 'blob_storage_file'); + this.service.submitFormToConvertXlsToXForm(formData).subscribe({ + next: (response) => { + console.log('POST request successful!', response); + const xFormResponse = response['xml_file']; + const blob = new Blob([xFormResponse], { type: 'text/xml' }); + + const xmlFile = new File([blob], 'form.xml', { type: 'text/xml' }); + + const formData = new FormData(); + formData.append('files', xmlFile); + this.service.submitFormToConvertXFormToEnketo(formData).subscribe({ + next: async (response) => { + const convertedContent = response['convertedFiles'][0]['content']; + //since path is not returned and it is what the relation requires + //SUGGESTION: we could make this reference the entry's id instead + if(entry){ + this.form.form_xlsx = `resources/${controlName}/${entry.name}` + } + if (this.form.enketo_definition) { + this.form.enketo_definition['theme'] = convertedContent.theme; + this.form.enketo_definition['languageMap'] = convertedContent.languageMap; + } + + this.form.enketo_form = convertedContent.form; + this.form.enketo_model = convertedContent.model; + this.service + .updateFormById(this.form.id, this.form) + .then((data) => { + this.form = data; + this.updateFeedbackMessage = 'Form updated successfully!'; + }) + .catch((error) => { + console.error('Failed to update form in database:', error); + this.updateFeedbackMessage = 'Failed to update form. Please try again.'; + }); + }, + error: (error) => { + console.error('Error occurred while converting to enketo:', error); + this.updateFeedbackMessage = 'Failed to convert form to Enketo. Please try again.'; + }, + }); + }, + error: (error) => { + console.error('Error occurred while converting to xform:', error); + this.updateFeedbackMessage = 'Failed to convert form to XForm. Please try again.'; + }, + }); + } +} diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html index 00e7f4b91..ca4bb45a2 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html @@ -2,7 +2,10 @@

Monitoring Form View

@if(form){ +
+ +
}
@if(form){ diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.scss b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.scss index 5e710ebdf..a5617fc10 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.scss +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.scss @@ -20,5 +20,9 @@ } label{ font-weight: 700; - +} +.action-button-section{ + display: flex; + flex-direction: row; + gap:5px } \ No newline at end of file From 49a5fb0a83382496ad260e85586f78ba0ae25ff2 Mon Sep 17 00:00:00 2001 From: khalifan-kfan Date: Wed, 27 Mar 2024 12:06:37 +0300 Subject: [PATCH 2/6] chore: add form parser variable --- .../modules/monitoring/monitoring.service.ts | 17 +++++------------ .../update-monitoring-forms.component.html | 3 +++ .../update/update-monitoring-forms.component.ts | 14 ++++++++------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts index 217b18b80..bfe72c856 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts @@ -1,4 +1,4 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient,HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; // eslint-disable-next-line @nx/enforce-module-boundaries import { Database } from '@picsa/server-types'; @@ -71,19 +71,12 @@ export class MonitoringFormsDashboardService extends PicsaAsyncService { return data; } - submitFormToConvertXlsToXForm(formData: FormData) { - const url = 'http://localhost:5262/api/convert_xls_to_xml'; - //should alow the browser to set this header so it can automatically with the boundary - - // const headers = new HttpHeaders({ - // 'Content-Type': 'multipart/form-data' - // }); - - return this.http.post(url, formData); + submitFormToConvertXlsToXForm(file: any) { + const url = 'https://xform-converter.picsa.app/api/v1/convert'; + return this.http.post(url, file); } submitFormToConvertXFormToEnketo(formData: FormData) { - const url = 'http://localhost:5261/api/xlsform-to-enketo'; - + const url = 'https://enketo-converter.picsa.app/api/xlsform-to-enketo'; return this.http.post(url, formData); } diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html index bc7adfe58..e2fa127a7 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html @@ -14,4 +14,7 @@

Upload new Form excel file

@if(updateFeedbackMessage) {
{{ updateFeedbackMessage }}
} + @if(uploading==true) { +
Uploading form...
+ } \ No newline at end of file diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts index 7d8016a82..f71a730e9 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts @@ -35,6 +35,7 @@ export type IMonitoringFormsRow = Database['public']['Tables']['monitoring_forms export class UpdateMonitoringFormsComponent implements OnInit { public form: IMonitoringFormsRow; public updateFeedbackMessage = ''; + public uploading = false; public allowedFileTypes = ['xlsx', 'xls'].map((ext) => `.${ext}`); constructor(private service: MonitoringFormsDashboardService, private route: ActivatedRoute) {} async ngOnInit() { @@ -56,15 +57,12 @@ export class UpdateMonitoringFormsComponent implements OnInit { if (res.length === 0) { return; } + this.uploading = true const [{ entry }] = res; const [{ data }] = res; - console.log(entry) - const formData = new FormData(); - formData.append('files', data, data instanceof File ? data.name : 'blob_storage_file'); - this.service.submitFormToConvertXlsToXForm(formData).subscribe({ + this.service.submitFormToConvertXlsToXForm(data).subscribe({ next: (response) => { - console.log('POST request successful!', response); - const xFormResponse = response['xml_file']; + const xFormResponse = response['result']; const blob = new Blob([xFormResponse], { type: 'text/xml' }); const xmlFile = new File([blob], 'form.xml', { type: 'text/xml' }); @@ -91,21 +89,25 @@ export class UpdateMonitoringFormsComponent implements OnInit { .then((data) => { this.form = data; this.updateFeedbackMessage = 'Form updated successfully!'; + this.uploading = false; }) .catch((error) => { console.error('Failed to update form in database:', error); this.updateFeedbackMessage = 'Failed to update form. Please try again.'; + this.uploading = false; }); }, error: (error) => { console.error('Error occurred while converting to enketo:', error); this.updateFeedbackMessage = 'Failed to convert form to Enketo. Please try again.'; + this.uploading = false; }, }); }, error: (error) => { console.error('Error occurred while converting to xform:', error); this.updateFeedbackMessage = 'Failed to convert form to XForm. Please try again.'; + this.uploading = false; }, }); } From de3ba44a4bf46f220f1d31b891db59437148601b Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Wed, 27 Mar 2024 16:05:44 -0700 Subject: [PATCH 3/6] chore: auto-intent --- .../monitoring/monitoring-forms.module.ts | 2 +- .../modules/monitoring/monitoring.service.ts | 9 ++-- .../update-monitoring-forms.component.html | 22 +++++----- .../update-monitoring-forms.component.scss | 44 +++++++++---------- .../update-monitoring-forms.component.spec.ts | 3 +- .../update-monitoring-forms.component.ts | 6 +-- .../view/view-monitoring-forms.component.html | 4 +- .../view/view-monitoring-forms.component.scss | 44 +++++++++---------- 8 files changed, 68 insertions(+), 66 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts index d14c4c42f..d16ba7f1b 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts @@ -27,7 +27,7 @@ import { ViewMonitoringFormsComponent } from './pages/view/view-monitoring-forms { path: ':id/update', component: UpdateMonitoringFormsComponent, - } + }, ]), ], }) diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts index bfe72c856..8f22d55c8 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts @@ -1,4 +1,4 @@ -import { HttpClient,HttpHeaders } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; // eslint-disable-next-line @nx/enforce-module-boundaries import { Database } from '@picsa/server-types'; @@ -71,13 +71,12 @@ export class MonitoringFormsDashboardService extends PicsaAsyncService { return data; } - submitFormToConvertXlsToXForm(file: any) { - const url = 'https://xform-converter.picsa.app/api/v1/convert'; + submitFormToConvertXlsToXForm(file: any) { + const url = 'https://xform-converter.picsa.app/api/v1/convert'; return this.http.post(url, file); } submitFormToConvertXFormToEnketo(formData: FormData) { - const url = 'https://enketo-converter.picsa.app/api/xlsform-to-enketo'; + const url = 'https://enketo-converter.picsa.app/api/xlsform-to-enketo'; return this.http.post(url, formData); } - } diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html index e2fa127a7..d8e9a7780 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html @@ -1,20 +1,22 @@
-

Update Form

+

Update Form

@if(form){

Upload new Form excel file

- + - } - @if(updateFeedbackMessage) { + } @if(updateFeedbackMessage) {
{{ updateFeedbackMessage }}
- } - @if(uploading==true) { -
Uploading form...
+ } @if(uploading==true) { +
Uploading form...
} -
\ No newline at end of file + diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.scss b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.scss index a5617fc10..ae8bc9dc2 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.scss +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.scss @@ -1,28 +1,28 @@ -.form-content{ - display: flex; - flex-direction: column; - gap: 1.4rem; +.form-content { + display: flex; + flex-direction: column; + gap: 1.4rem; } -.submitButton{ - width: 7rem; - margin-bottom: 1rem; +.submitButton { + width: 7rem; + margin-bottom: 1rem; } -.form-data{ - display: flex; - flex-direction: column; - gap: 0.5rem; +.form-data { + display: flex; + flex-direction: column; + gap: 0.5rem; } -.data-container{ - margin-left: 2rem; - max-height: 25rem; - overflow-y: auto; +.data-container { + margin-left: 2rem; + max-height: 25rem; + overflow-y: auto; } -label{ - font-weight: 700; +label { + font-weight: 700; +} +.action-button-section { + display: flex; + flex-direction: row; + gap: 5px; } -.action-button-section{ - display: flex; - flex-direction: row; - gap:5px -} \ No newline at end of file diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.spec.ts index 9322b3b12..9039c82d0 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.spec.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.spec.ts @@ -1,5 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { UpdateMonitoringFormsComponent } from './update-monitoring-forms.component' + +import { UpdateMonitoringFormsComponent } from './update-monitoring-forms.component'; describe('UpdateMonitoringFormsComponent', () => { let component: UpdateMonitoringFormsComponent; diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts index f71a730e9..94074d76e 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts @@ -57,7 +57,7 @@ export class UpdateMonitoringFormsComponent implements OnInit { if (res.length === 0) { return; } - this.uploading = true + this.uploading = true; const [{ entry }] = res; const [{ data }] = res; this.service.submitFormToConvertXlsToXForm(data).subscribe({ @@ -74,8 +74,8 @@ export class UpdateMonitoringFormsComponent implements OnInit { const convertedContent = response['convertedFiles'][0]['content']; //since path is not returned and it is what the relation requires //SUGGESTION: we could make this reference the entry's id instead - if(entry){ - this.form.form_xlsx = `resources/${controlName}/${entry.name}` + if (entry) { + this.form.form_xlsx = `resources/${controlName}/${entry.name}`; } if (this.form.enketo_definition) { this.form.enketo_definition['theme'] = convertedContent.theme; diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html index ca4bb45a2..16b2599ab 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html @@ -3,8 +3,8 @@

Monitoring Form View

@if(form){
- - + +
} diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.scss b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.scss index a5617fc10..ae8bc9dc2 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.scss +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.scss @@ -1,28 +1,28 @@ -.form-content{ - display: flex; - flex-direction: column; - gap: 1.4rem; +.form-content { + display: flex; + flex-direction: column; + gap: 1.4rem; } -.submitButton{ - width: 7rem; - margin-bottom: 1rem; +.submitButton { + width: 7rem; + margin-bottom: 1rem; } -.form-data{ - display: flex; - flex-direction: column; - gap: 0.5rem; +.form-data { + display: flex; + flex-direction: column; + gap: 0.5rem; } -.data-container{ - margin-left: 2rem; - max-height: 25rem; - overflow-y: auto; +.data-container { + margin-left: 2rem; + max-height: 25rem; + overflow-y: auto; } -label{ - font-weight: 700; +label { + font-weight: 700; +} +.action-button-section { + display: flex; + flex-direction: row; + gap: 5px; } -.action-button-section{ - display: flex; - flex-direction: row; - gap:5px -} \ No newline at end of file From 96a7eef49e10664b58f1cc5fa1eefdee785120f0 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Wed, 27 Mar 2024 19:32:03 -0700 Subject: [PATCH 4/6] refactor: converter request promise and types --- .../modules/monitoring/monitoring.service.ts | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts index 8f22d55c8..3e92dbd8f 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring.service.ts @@ -3,8 +3,10 @@ import { Injectable } from '@angular/core'; // eslint-disable-next-line @nx/enforce-module-boundaries import { Database } from '@picsa/server-types'; import { PicsaAsyncService } from '@picsa/shared/services/asyncService.service'; +import { PicsaNotificationService } from '@picsa/shared/services/core/notification.service'; import { SupabaseService } from '@picsa/shared/services/core/supabase'; import { IStorageEntry } from '@picsa/shared/services/core/supabase/services/supabase-storage.service'; +import { firstValueFrom, Observable } from 'rxjs'; export type IMonitoringFormsRow = Database['public']['Tables']['monitoring_forms']['Row']; @@ -22,7 +24,11 @@ export class MonitoringFormsDashboardService extends PicsaAsyncService { return this.supabaseService.db.table(this.TABLE_NAME); } - constructor(private supabaseService: SupabaseService, private http: HttpClient) { + constructor( + private supabaseService: SupabaseService, + private http: HttpClient, + private notificationService: PicsaNotificationService + ) { super(); } @@ -64,6 +70,7 @@ export class MonitoringFormsDashboardService extends PicsaAsyncService { .table(this.TABLE_NAME) .update(updatedForm) .eq('id', id) + .select() .single(); if (error) { throw error; @@ -71,12 +78,60 @@ export class MonitoringFormsDashboardService extends PicsaAsyncService { return data; } - submitFormToConvertXlsToXForm(file: any) { + /** + * Convert an xls form to xml-xform standard + * @param file xls file representation + * @returns xml string of converted form + */ + async submitFormToConvertXlsToXForm(file: File) { const url = 'https://xform-converter.picsa.app/api/v1/convert'; - return this.http.post(url, file); + try { + const { result } = await firstValueFrom(this.http.post(url, file) as Observable); + return result; + } catch (error: any) { + console.error(error); + this.notificationService.showUserNotification({ matIcon: 'error', message: error?.message || error }); + return null; + } } - submitFormToConvertXFormToEnketo(formData: FormData) { + /** + * Convert + * @param formData formData object with 'files' property that includes xml xform read as a File + * @returns enketo entry of converted xmlform + */ + async submitFormToConvertXFormToEnketo(formData: FormData) { const url = 'https://enketo-converter.picsa.app/api/xlsform-to-enketo'; - return this.http.post(url, formData); + try { + const { convertedFiles } = await firstValueFrom(this.http.post(url, formData) as Observable); + return convertedFiles[0]?.content; + } catch (error: any) { + console.error(error); + this.notificationService.showUserNotification({ matIcon: 'error', message: error?.message || error }); + return null; + } } } +/** Response model returned from xform-converter */ +interface XFormConvertRes { + /** http error if thrown */ + error: any; + /** xml string of converted */ + result: string; + /** https status code, 200 indicates success */ + status: number; +} +/** Response model returned from enketo-converter */ +interface IEnketoConvertRes { + convertedFiles: { + content: IEnketoConvertContent; + filename: string; + }[]; + message: string; +} +interface IEnketoConvertContent { + form: string; + languageMap: any; + model: string; + theme: string; + transformerVersion: string; +} From 7572c4ce5bb3acd87388eaafea8b1320678f5fdc Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Wed, 27 Mar 2024 19:33:00 -0700 Subject: [PATCH 5/6] refactor: failed conversion delete and tidying --- .../update-monitoring-forms.component.html | 6 +- .../update-monitoring-forms.component.ts | 87 ++++++++----------- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html index d8e9a7780..438f79c56 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.html @@ -8,9 +8,9 @@

Upload new Form excel file

diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts index 94074d76e..39527da1f 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/update/update-monitoring-forms.component.ts @@ -9,6 +9,7 @@ import { SupabaseStoragePickerDirective, SupabaseUploadComponent, } from '@picsa/shared/services/core/supabase'; +import { SupabaseStorageService } from '@picsa/shared/services/core/supabase/services/supabase-storage.service'; import { NgxJsonViewerModule } from 'ngx-json-viewer'; import { DashboardMaterialModule } from '../../../../material.module'; @@ -37,7 +38,13 @@ export class UpdateMonitoringFormsComponent implements OnInit { public updateFeedbackMessage = ''; public uploading = false; public allowedFileTypes = ['xlsx', 'xls'].map((ext) => `.${ext}`); - constructor(private service: MonitoringFormsDashboardService, private route: ActivatedRoute) {} + public storageBucketName = 'global'; + public storageFolderPath = 'monitoring/forms'; + constructor( + private service: MonitoringFormsDashboardService, + private route: ActivatedRoute, + private storageService: SupabaseStorageService + ) {} async ngOnInit() { await this.service.ready(); this.route.params.subscribe(async (params) => { @@ -53,62 +60,44 @@ export class UpdateMonitoringFormsComponent implements OnInit { }); } - public async handleUploadComplete(res: IUploadResult[], controlName: 'submission_forms') { + public async handleUploadComplete(res: IUploadResult[]) { if (res.length === 0) { return; } + // As conversion is a 2-step process (xls file -> xml form -> enketo form) track progress + // so that uploaded file can be removed if not successful + let xformConversionSuccess = false; this.uploading = true; - const [{ entry }] = res; - const [{ data }] = res; - this.service.submitFormToConvertXlsToXForm(data).subscribe({ - next: (response) => { - const xFormResponse = response['result']; - const blob = new Blob([xFormResponse], { type: 'text/xml' }); + const [{ data, entry }] = res; - const xmlFile = new File([blob], 'form.xml', { type: 'text/xml' }); + const xform = await this.service.submitFormToConvertXlsToXForm(data as File); - const formData = new FormData(); - formData.append('files', xmlFile); - this.service.submitFormToConvertXFormToEnketo(formData).subscribe({ - next: async (response) => { - const convertedContent = response['convertedFiles'][0]['content']; - //since path is not returned and it is what the relation requires - //SUGGESTION: we could make this reference the entry's id instead - if (entry) { - this.form.form_xlsx = `resources/${controlName}/${entry.name}`; - } - if (this.form.enketo_definition) { - this.form.enketo_definition['theme'] = convertedContent.theme; - this.form.enketo_definition['languageMap'] = convertedContent.languageMap; - } + if (xform) { + const blob = new Blob([xform], { type: 'text/xml' }); + const xmlFile = new File([blob], 'form.xml', { type: 'text/xml' }); + const formData = new FormData(); + formData.append('files', xmlFile); - this.form.enketo_form = convertedContent.form; - this.form.enketo_model = convertedContent.model; - this.service - .updateFormById(this.form.id, this.form) - .then((data) => { - this.form = data; - this.updateFeedbackMessage = 'Form updated successfully!'; - this.uploading = false; - }) - .catch((error) => { - console.error('Failed to update form in database:', error); - this.updateFeedbackMessage = 'Failed to update form. Please try again.'; - this.uploading = false; - }); - }, - error: (error) => { - console.error('Error occurred while converting to enketo:', error); - this.updateFeedbackMessage = 'Failed to convert form to Enketo. Please try again.'; - this.uploading = false; - }, + const enketoContent = await this.service.submitFormToConvertXFormToEnketo(formData); + if (enketoContent) { + const { form, languageMap, model, theme } = enketoContent; + // Update db entry with form_xlsx + this.form = await this.service.updateFormById(this.form.id, { + form_xlsx: `${this.storageBucketName}/${this.storageFolderPath}/${entry.name}`, + enketo_form: form, + enketo_model: model, + enketo_definition: { ...(this.form.enketo_definition as any), languageMap, theme }, }); - }, - error: (error) => { - console.error('Error occurred while converting to xform:', error); - this.updateFeedbackMessage = 'Failed to convert form to XForm. Please try again.'; + this.updateFeedbackMessage = 'Form updated successfully!'; this.uploading = false; - }, - }); + xformConversionSuccess = true; + } + } + // If conversion not successful delete file from storage + if (!xformConversionSuccess) { + const storagePath = `${this.storageFolderPath}/${entry.name}`; + const { error } = await this.storageService.deleteFile(this.storageBucketName, storagePath); + if (error) throw error; + } } } From ce10297c2e3eaa23b1e40e8882c5aed659a454ce Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Wed, 27 Mar 2024 19:33:22 -0700 Subject: [PATCH 6/6] chore: code tidying --- .../src/app/modules/monitoring/monitoring-forms.module.ts | 2 +- .../pages/view/view-monitoring-forms.component.html | 2 +- .../supabase/components/upload/supabase-upload.component.ts | 2 +- .../core/supabase/services/supabase-storage.service.ts | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts index d16ba7f1b..17c00ba3a 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/monitoring-forms.module.ts @@ -25,7 +25,7 @@ import { ViewMonitoringFormsComponent } from './pages/view/view-monitoring-forms component: FormSubmissionsComponent, }, { - path: ':id/update', + path: ':id/edit', component: UpdateMonitoringFormsComponent, }, ]), diff --git a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html index 16b2599ab..b75f10c03 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/monitoring/pages/view/view-monitoring-forms.component.html @@ -3,7 +3,7 @@

Monitoring Form View

@if(form){
- +
} diff --git a/libs/shared/src/services/core/supabase/components/upload/supabase-upload.component.ts b/libs/shared/src/services/core/supabase/components/upload/supabase-upload.component.ts index 25928c919..4044bec88 100644 --- a/libs/shared/src/services/core/supabase/components/upload/supabase-upload.component.ts +++ b/libs/shared/src/services/core/supabase/components/upload/supabase-upload.component.ts @@ -205,7 +205,7 @@ export class SupabaseUploadComponent { private async checkDuplicateUpload(file: UppyFile) { const storageFile = await this.storageService.getFile({ - bucketId: 'resources', + bucketId: this.storageBucketName, filename: file.name, folderPath: this.storageFolderPath || '', }); diff --git a/libs/shared/src/services/core/supabase/services/supabase-storage.service.ts b/libs/shared/src/services/core/supabase/services/supabase-storage.service.ts index aecc75557..7e8703b04 100644 --- a/libs/shared/src/services/core/supabase/services/supabase-storage.service.ts +++ b/libs/shared/src/services/core/supabase/services/supabase-storage.service.ts @@ -95,6 +95,10 @@ export class SupabaseStorageService { return data?.[0] || null; } + public async deleteFile(bucketId: string, filePath: string) { + return this.storage.from(bucketId).remove([filePath]); + } + /** Return the link to a file in a public bucket */ public getPublicLink(bucketId: string, objectPath: string) { return this.storage.from(bucketId).getPublicUrl(objectPath).data.publicUrl;