From 3fe7679c94243a025cc6c9de9d86ebdf11650235 Mon Sep 17 00:00:00 2001 From: vincent Date: Tue, 14 Dec 2021 18:13:44 +0800 Subject: [PATCH 1/7] add(actions-dialog): add action parameter selection dialog --- .../home/details/actions/actions.page.ts | 57 +++++-------- .../actions-dialog.component.html | 20 +++++ .../actions-dialog.component.scss | 0 .../actions-dialog.component.spec.ts | 30 +++++++ .../actions-dialog.component.ts | 82 +++++++++++++++++++ .../{ => service}/actions.service.spec.ts | 2 +- .../actions/{ => service}/actions.service.ts | 4 +- src/app/shared/shared.module.ts | 8 ++ 8 files changed, 162 insertions(+), 41 deletions(-) create mode 100644 src/app/shared/actions/actions-dialog/actions-dialog.component.html create mode 100644 src/app/shared/actions/actions-dialog/actions-dialog.component.scss create mode 100644 src/app/shared/actions/actions-dialog/actions-dialog.component.spec.ts create mode 100644 src/app/shared/actions/actions-dialog/actions-dialog.component.ts rename src/app/shared/actions/{ => service}/actions.service.spec.ts (85%) rename src/app/shared/actions/{ => service}/actions.service.ts (93%) diff --git a/src/app/features/home/details/actions/actions.page.ts b/src/app/features/home/details/actions/actions.page.ts index 5a5a29fef..bfc76625b 100644 --- a/src/app/features/home/details/actions/actions.page.ts +++ b/src/app/features/home/details/actions/actions.page.ts @@ -1,16 +1,16 @@ import { Component } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { ActivatedRoute } from '@angular/router'; -import { AlertController } from '@ionic/angular'; -import { AlertInput } from '@ionic/core'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { combineLatest } from 'rxjs'; import { catchError, concatMap, map, tap } from 'rxjs/operators'; +import { ActionsDialogComponent } from '../../../../shared/actions/actions-dialog/actions-dialog.component'; import { Action, ActionsService, -} from '../../../../shared/actions/actions.service'; +} from '../../../../shared/actions/service/actions.service'; import { BlockingActionService } from '../../../../shared/blocking-action/blocking-action.service'; import { DiaBackendAuthService } from '../../../../shared/dia-backend/auth/dia-backend-auth.service'; import { ErrorService } from '../../../../shared/error/error.service'; @@ -33,12 +33,12 @@ export class ActionsPage { constructor( private readonly actionsService: ActionsService, private readonly errorService: ErrorService, - private readonly alertController: AlertController, private readonly translocoService: TranslocoService, private readonly blockingActionService: BlockingActionService, private readonly route: ActivatedRoute, private readonly authService: DiaBackendAuthService, - private readonly snackBar: MatSnackBar + private readonly snackBar: MatSnackBar, + private readonly dialog: MatDialog ) {} openAction(action: Action) { @@ -51,39 +51,20 @@ export class ActionsPage { concatMap( ([params, token, id]) => new Promise(resolve => { - this.alertController - .create({ - header: action.title_text, - message: action.description_text, - inputs: params.map( - param => - ({ - name: param.name_text, - label: param.display_text_text, - type: param.type_text, - placeholder: param.placeholder_text, - value: param.default_values_list_text[0], - disabled: !param.user_input_boolean, - } as AlertInput) - ), - buttons: [ - { - text: this.translocoService.translate('cancel'), - role: 'cancel', - }, - { - text: this.translocoService.translate('ok'), - handler: value => { - const body = { ...value, token: token, cid: id }; - return this.sendAction(action, body); - }, - }, - ], - }) - .then(alert => { - alert.present(); - resolve(); - }); + const dialogRef = this.dialog.open(ActionsDialogComponent, { + disableClose: true, + data: { + action: action, + params: params, + }, + }); + dialogRef.afterClosed().subscribe(data => { + if (data !== undefined) { + const body = { ...data, token: token, cid: id }; + return this.sendAction(action, body); + } + }); + resolve(); }) ), untilDestroyed(this) diff --git a/src/app/shared/actions/actions-dialog/actions-dialog.component.html b/src/app/shared/actions/actions-dialog/actions-dialog.component.html new file mode 100644 index 000000000..59d38230a --- /dev/null +++ b/src/app/shared/actions/actions-dialog/actions-dialog.component.html @@ -0,0 +1,20 @@ +
+

{{ title }}

+

{{ description }}

+ +
+ + + + + +
+
diff --git a/src/app/shared/actions/actions-dialog/actions-dialog.component.scss b/src/app/shared/actions/actions-dialog/actions-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/shared/actions/actions-dialog/actions-dialog.component.spec.ts b/src/app/shared/actions/actions-dialog/actions-dialog.component.spec.ts new file mode 100644 index 000000000..a9bb64695 --- /dev/null +++ b/src/app/shared/actions/actions-dialog/actions-dialog.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { SharedTestingModule } from '../../shared-testing.module'; +import { ActionsDialogComponent } from './actions-dialog.component'; + +describe('ActionsDialogComponent', () => { + let component: ActionsDialogComponent; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ActionsDialogComponent], + imports: [SharedTestingModule], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ActionsDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }) + ); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/actions/actions-dialog/actions-dialog.component.ts b/src/app/shared/actions/actions-dialog/actions-dialog.component.ts new file mode 100644 index 000000000..928979ea2 --- /dev/null +++ b/src/app/shared/actions/actions-dialog/actions-dialog.component.ts @@ -0,0 +1,82 @@ +import { Component, Inject } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { FormlyFieldConfig } from '@ngx-formly/core'; +import { Action, Param } from '../service/actions.service'; + +@Component({ + selector: 'app-actions-dialog', + templateUrl: './actions-dialog.component.html', + styleUrls: ['./actions-dialog.component.scss'], +}) +export class ActionsDialogComponent { + readonly form = new FormGroup({}); + readonly fields: FormlyFieldConfig[] = []; + readonly model: any = {}; + + readonly title: string = ''; + readonly description: string = ''; + readonly params: Param[] = []; + + constructor( + private readonly dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: MatDialogData + ) { + if (data.action !== undefined && data.params !== undefined) { + this.title = data.action.title_text; + this.description = data.action.description_text; + this.params = data.params; + this.createFormModel(); + this.createFormFields(); + } + } + + private createFormModel() { + for (const param of this.params) + this.model[param.name_text] = param.default_values_list_text[0] || ''; + } + + private createFormFields() { + for (const param of this.params) { + if (param.type_text === 'dropdown') + this.fields.push({ + key: param.name_text, + type: 'select', + templateOptions: { + options: param.default_values_list_text.map(value => ({ + label: value, + value: value, + })), + placeholder: param.placeholder_text, + disabled: !param.user_input_boolean, + required: true, + }, + }); + else + this.fields.push({ + key: param.name_text, + type: 'input', + templateOptions: { + type: param.type_text, + label: param.display_text_text, + placeholder: param.placeholder_text, + disabled: !param.user_input_boolean, + required: true, + }, + }); + } + } + + send() { + this.dialogRef.close(this.model); + } + + cancel() { + this.dialogRef.close(); + } +} + +interface MatDialogData { + action: Action | undefined; + params: Param[] | undefined; +} diff --git a/src/app/shared/actions/actions.service.spec.ts b/src/app/shared/actions/service/actions.service.spec.ts similarity index 85% rename from src/app/shared/actions/actions.service.spec.ts rename to src/app/shared/actions/service/actions.service.spec.ts index 780bd7756..556419230 100644 --- a/src/app/shared/actions/actions.service.spec.ts +++ b/src/app/shared/actions/service/actions.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@angular/core/testing'; -import { SharedTestingModule } from '../shared-testing.module'; +import { SharedTestingModule } from '../../shared-testing.module'; import { ActionsService } from './actions.service'; describe('ActionsService', () => { diff --git a/src/app/shared/actions/actions.service.ts b/src/app/shared/actions/service/actions.service.ts similarity index 93% rename from src/app/shared/actions/actions.service.ts rename to src/app/shared/actions/service/actions.service.ts index 6ce81e3c8..95e668337 100644 --- a/src/app/shared/actions/actions.service.ts +++ b/src/app/shared/actions/service/actions.service.ts @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { defer, forkJoin } from 'rxjs'; import { map } from 'rxjs/operators'; -import { BUBBLE_DB_URL } from '../dia-backend/secret'; +import { BUBBLE_DB_URL } from '../../dia-backend/secret'; @Injectable({ providedIn: 'root', @@ -50,7 +50,7 @@ export interface Param { readonly display_text_text: string; readonly name_text: string; readonly placeholder_text: string; - readonly type_text: string; + readonly type_text: 'number' | 'text' | 'dropdown'; readonly user_input_boolean: boolean; } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 6233e57bd..86818d1a4 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -2,9 +2,13 @@ import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatDialogModule } from '@angular/material/dialog'; import { IonicModule } from '@ionic/angular'; import { TranslocoModule } from '@ngneat/transloco'; import { ReactiveComponentModule } from '@ngrx/component'; +import { FormlyModule } from '@ngx-formly/core'; +import { FormlyMaterialModule } from '@ngx-formly/material'; +import { ActionsDialogComponent } from './actions/actions-dialog/actions-dialog.component'; import { AvatarComponent } from './avatar/avatar.component'; import { CapacitorPluginsModule } from './capacitor-plugins/capacitor-plugins.module'; import { ContactSelectionDialogComponent } from './contact-selection-dialog/contact-selection-dialog.component'; @@ -16,6 +20,7 @@ import { StartsWithPipe } from './pipes/starts-with/starts-with.pipe'; const declarations = [ MigratingDialogComponent, + ActionsDialogComponent, AvatarComponent, MediaComponent, StartsWithPipe, @@ -33,6 +38,9 @@ const imports = [ MaterialModule, CapacitorPluginsModule, ReactiveComponentModule, + MatDialogModule, + FormlyModule, + FormlyMaterialModule, ]; @NgModule({ From 8fb4d79ef1a9f785bbf73c16f6b67eb116463069 Mon Sep 17 00:00:00 2001 From: vincent Date: Tue, 14 Dec 2021 19:10:01 +0800 Subject: [PATCH 2/7] fix(actions-dialog): fix cancel not working --- .../actions-dialog.component.html | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/shared/actions/actions-dialog/actions-dialog.component.html b/src/app/shared/actions/actions-dialog/actions-dialog.component.html index 59d38230a..babfde028 100644 --- a/src/app/shared/actions/actions-dialog/actions-dialog.component.html +++ b/src/app/shared/actions/actions-dialog/actions-dialog.component.html @@ -2,19 +2,19 @@

{{ title }}

{{ description }}

-
+ - - - -
+ + + + From 27edf0c0ae23c5851b762353c50be065b8364868 Mon Sep 17 00:00:00 2001 From: vincent Date: Tue, 14 Dec 2021 19:10:54 +0800 Subject: [PATCH 3/7] change(actions-dialog): add range validation for number input --- .../actions-dialog/actions-dialog.component.ts | 16 +++++++++++++++- .../shared/actions/service/actions.service.ts | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/app/shared/actions/actions-dialog/actions-dialog.component.ts b/src/app/shared/actions/actions-dialog/actions-dialog.component.ts index 928979ea2..3b0e4b57d 100644 --- a/src/app/shared/actions/actions-dialog/actions-dialog.component.ts +++ b/src/app/shared/actions/actions-dialog/actions-dialog.component.ts @@ -52,12 +52,26 @@ export class ActionsDialogComponent { required: true, }, }); + else if (param.type_text === 'number') + this.fields.push({ + key: param.name_text, + type: 'input', + templateOptions: { + type: 'number', + label: param.display_text_text, + placeholder: param.placeholder_text, + disabled: !param.user_input_boolean, + max: param.max_number, + min: param.min_number, + required: true, + }, + }); else this.fields.push({ key: param.name_text, type: 'input', templateOptions: { - type: param.type_text, + type: 'text', label: param.display_text_text, placeholder: param.placeholder_text, disabled: !param.user_input_boolean, diff --git a/src/app/shared/actions/service/actions.service.ts b/src/app/shared/actions/service/actions.service.ts index 95e668337..d358cb6ad 100644 --- a/src/app/shared/actions/service/actions.service.ts +++ b/src/app/shared/actions/service/actions.service.ts @@ -52,6 +52,8 @@ export interface Param { readonly placeholder_text: string; readonly type_text: 'number' | 'text' | 'dropdown'; readonly user_input_boolean: boolean; + readonly max_number: number; + readonly min_number: number; } export interface GetActionsResponse { From e7b6c884d4b442fedf869c58910701f96952bece Mon Sep 17 00:00:00 2001 From: vincent Date: Wed, 15 Dec 2021 11:57:38 +0800 Subject: [PATCH 4/7] change(actinos.page.ts): revise to prevent anti-pattern and improve coding style --- .../home/details/actions/actions.page.ts | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/app/features/home/details/actions/actions.page.ts b/src/app/features/home/details/actions/actions.page.ts index bfc76625b..6b7bd7849 100644 --- a/src/app/features/home/details/actions/actions.page.ts +++ b/src/app/features/home/details/actions/actions.page.ts @@ -5,7 +5,7 @@ import { ActivatedRoute } from '@angular/router'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { combineLatest } from 'rxjs'; -import { catchError, concatMap, map, tap } from 'rxjs/operators'; +import { catchError, concatMap, first, map, tap } from 'rxjs/operators'; import { ActionsDialogComponent } from '../../../../shared/actions/actions-dialog/actions-dialog.component'; import { Action, @@ -14,6 +14,7 @@ import { import { BlockingActionService } from '../../../../shared/blocking-action/blocking-action.service'; import { DiaBackendAuthService } from '../../../../shared/dia-backend/auth/dia-backend-auth.service'; import { ErrorService } from '../../../../shared/error/error.service'; +import { isNonNullable } from '../../../../utils/rx-operators/rx-operators'; @UntilDestroy() @Component({ @@ -48,25 +49,25 @@ export class ActionsPage { this.id$, ]) .pipe( - concatMap( - ([params, token, id]) => - new Promise(resolve => { - const dialogRef = this.dialog.open(ActionsDialogComponent, { - disableClose: true, - data: { - action: action, - params: params, - }, - }); - dialogRef.afterClosed().subscribe(data => { - if (data !== undefined) { - const body = { ...data, token: token, cid: id }; - return this.sendAction(action, body); - } - }); - resolve(); - }) - ), + first(), + concatMap(([params, token, id]) => { + const dialogRef = this.dialog.open( + ActionsDialogComponent, + { + disableClose: true, + data: { + action: action, + params: params, + }, + } + ); + return dialogRef.afterClosed().pipe( + isNonNullable(), + tap(data => + this.sendAction(action, { ...data, token: token, cid: id }) + ) + ); + }), untilDestroyed(this) ) .subscribe(); From 0cc870bd3e661d7d9c1112223d2f914de997602d Mon Sep 17 00:00:00 2001 From: vincent Date: Wed, 15 Dec 2021 13:24:27 +0800 Subject: [PATCH 5/7] add(dia-backend-workflow.service): add workflow service to retrieve workflow status --- .../dia-backend-workflow.service.spec.ts | 18 +++++++++ .../workflow/dia-backend-workflow.service.ts | 38 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/app/shared/dia-backend/workflow/dia-backend-workflow.service.spec.ts create mode 100644 src/app/shared/dia-backend/workflow/dia-backend-workflow.service.ts diff --git a/src/app/shared/dia-backend/workflow/dia-backend-workflow.service.spec.ts b/src/app/shared/dia-backend/workflow/dia-backend-workflow.service.spec.ts new file mode 100644 index 000000000..2325d6723 --- /dev/null +++ b/src/app/shared/dia-backend/workflow/dia-backend-workflow.service.spec.ts @@ -0,0 +1,18 @@ +import { TestBed } from '@angular/core/testing'; +import { SharedTestingModule } from '../../shared-testing.module'; +import { DiaBackendWorkflowService } from './dia-backend-workflow.service'; + +describe('DiaBackendWorkflowService', () => { + let service: DiaBackendWorkflowService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [SharedTestingModule], + }); + service = TestBed.inject(DiaBackendWorkflowService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/dia-backend/workflow/dia-backend-workflow.service.ts b/src/app/shared/dia-backend/workflow/dia-backend-workflow.service.ts new file mode 100644 index 000000000..6628d693c --- /dev/null +++ b/src/app/shared/dia-backend/workflow/dia-backend-workflow.service.ts @@ -0,0 +1,38 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { defer } from 'rxjs'; +import { concatMap } from 'rxjs/operators'; +import { DiaBackendAuthService } from '../auth/dia-backend-auth.service'; +import { BASE_URL } from '../secret'; + +@Injectable({ + providedIn: 'root', +}) +export class DiaBackendWorkflowService { + constructor( + private readonly httpClient: HttpClient, + private readonly authService: DiaBackendAuthService + ) {} + + getWorkflowById$(id: string) { + return defer(() => this.authService.getAuthHeaders()).pipe( + concatMap(headers => { + return this.httpClient.get( + `${BASE_URL}/api/v3/workflows/${id}/`, + { headers } + ); + }) + ); + } +} + +export interface DiaBackendWorkflow { + readonly id: string; + readonly name: string; + readonly status: 'success' | 'failure' | 'pending' | 'unknown'; + readonly description: string; + readonly created_at: string; + readonly updated_at: string; + readonly last_executed_at: string; + readonly completed_at: string | null; +} From c692acdbc7ca2c1ae6664d65179e24e986634015 Mon Sep 17 00:00:00 2001 From: vincent Date: Wed, 15 Dec 2021 13:25:21 +0800 Subject: [PATCH 6/7] change(details.page): do not show capture options when workflow isn't completed --- .../features/home/details/details.page.html | 5 ++- src/app/features/home/details/details.page.ts | 24 +++++++++++--- .../session/information-session.service.ts | 32 +++++++++++++++++-- .../dia-backend-asset-repository.service.ts | 2 ++ 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/app/features/home/details/details.page.html b/src/app/features/home/details/details.page.html index e4ac1a847..f9af46669 100644 --- a/src/app/features/home/details/details.page.html +++ b/src/app/features/home/details/details.page.html @@ -13,7 +13,10 @@