From 956781dac82bb013720b4a1669e9cc3ad2107ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Thu, 18 Jan 2024 10:54:18 -0300 Subject: [PATCH 01/10] Front end almost done --- .../add-form-modal.component.html | 8 +- .../app/dashboard/dashboard-routing.module.ts | 7 + .../src/app/dashboard/dashboard.component.ts | 7 + .../conversion-fields-type-mapping.ts | 202 ++++++++++++++ .../conversion-fields.component.html | 141 ++++++++++ .../conversion-fields.component.scss | 0 .../conversion-fields.component.spec.ts | 22 ++ .../conversion-fields.component.ts | 163 +++++++++++ .../conversion/conversion-routing.module.ts | 22 ++ .../conversion/conversion.component.html | 102 +++++++ .../conversion/conversion.component.scss | 0 .../conversion/conversion.component.spec.ts | 22 ++ .../pages/conversion/conversion.component.ts | 255 ++++++++++++++++++ .../pages/conversion/conversion.module.ts | 47 ++++ .../pages/conversion/graphql/queries.ts | 82 ++++++ libs/shared/src/i18n/en.json | 28 ++ libs/shared/src/i18n/fr.json | 28 ++ libs/shared/src/i18n/test.json | 28 ++ 18 files changed, 1160 insertions(+), 4 deletions(-) create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.scss create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.spec.ts create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-routing.module.ts create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion.component.html create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion.component.scss create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion.component.spec.ts create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts diff --git a/apps/back-office/src/app/components/add-form-modal/add-form-modal.component.html b/apps/back-office/src/app/components/add-form-modal/add-form-modal.component.html index ee7d572aad..56cb63af2e 100644 --- a/apps/back-office/src/app/components/add-form-modal/add-form-modal.component.html +++ b/apps/back-office/src/app/components/add-form-modal/add-form-modal.component.html @@ -71,10 +71,12 @@

{{ 'models.form.new' | translate }}

{{ 'components.form.create.choice.template' | translate }} @@ -84,9 +86,7 @@

{{ 'models.form.new' | translate }}

- + import('@oort-front/shared').then((m) => m.ProfileViewModule), }, + { + path: 'conversion', + loadChildren: () => + import('./pages/conversion/conversion.module').then( + (m) => m.ConversionModule + ), + }, { path: 'referencedata', children: [ diff --git a/apps/back-office/src/app/dashboard/dashboard.component.ts b/apps/back-office/src/app/dashboard/dashboard.component.ts index 495336d34c..fe0ec05a53 100644 --- a/apps/back-office/src/app/dashboard/dashboard.component.ts +++ b/apps/back-office/src/app/dashboard/dashboard.component.ts @@ -78,6 +78,13 @@ export class DashboardComponent extends UnsubscribeComponent { icon: 'cloud_download', }); } + if (this.ability.can('read', 'Resource')) { + dataItems.push({ + name: this.translate.instant('common.conversion.few'), + path: '/conversion', + icon: 'change_circle', + }); + } if (dataItems.length > 0) { navGroups.push({ name: this.translate.instant('pages.formBuilder.title'), diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts new file mode 100644 index 0000000000..1ede263e2f --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts @@ -0,0 +1,202 @@ +interface ConversionMap { + [key: string]: { + displayName: string; + convertibleTypes: string[]; + confirmation: Object; + }; +} + +export const conversionMap: ConversionMap = { + text: { + displayName: 'Text/Long Text/Month/Password/Range/Week', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + 'resource', + 'owner', + 'users', + ], + confirmation: { checkId: ['owner', 'users'], selectResource: ['resource'] }, + }, + radiogroup: { + displayName: 'Radio Button Group', + convertibleTypes: ['checkbox', 'dropdown', 'boolean', 'tagbox', 'text'], + confirmation: {}, + }, + checkbox: { + displayName: 'Checkboxes', + convertibleTypes: ['radiogroup', 'dropdown', 'boolean', 'tagbox', 'text'], + confirmation: { popArray: ['radiogroup', 'dropdown', 'text'] }, + }, + dropdown: { + displayName: 'Dropdown', + convertibleTypes: ['radiogroup', 'checkbox', 'boolean', 'tagbox', 'text'], + confirmation: {}, + }, + boolean: { + displayName: 'Yes/No (Boolean)', + convertibleTypes: ['text'], + confirmation: {}, + }, + file: { + displayName: 'File Upload', + convertibleTypes: ['boolean'], + confirmation: {}, + }, + tagbox: { + displayName: 'Multi-Select Dropdown', + convertibleTypes: ['radiogroup', 'checkbox', 'dropdown', 'boolean', 'text'], + confirmation: { popArray: ['radiogroup', 'dropdown', 'text'] }, + }, + multipletext: { + displayName: 'Multiple Text', + convertibleTypes: ['boolean'], + confirmation: {}, + }, + matrix: { + displayName: 'Single-Select Matrix', + convertibleTypes: ['boolean'], + confirmation: {}, + }, + matrixdropdown: { + displayName: 'Multi-Select Matrix', + convertibleTypes: [], + confirmation: {}, + }, + matrixdynamic: { + displayName: 'Dynamic Matrix', + convertibleTypes: [], + confirmation: {}, + }, + expression: { + displayName: 'Expression (read-only)', + convertibleTypes: ['boolean'], + confirmation: {}, + }, + resource: { + displayName: 'Resource', + convertibleTypes: ['boolean', 'resources'], + confirmation: {}, + }, + resources: { + displayName: 'Resources', + convertibleTypes: ['boolean', 'resource'], + confirmation: { popArray: ['resource'] }, + }, + owner: { + displayName: 'Owner', + convertibleTypes: ['boolean'], + confirmation: {}, + }, + users: { + displayName: 'Users', + convertibleTypes: ['boolean'], + confirmation: {}, + }, + geospatial: { + displayName: 'Geospatial', + convertibleTypes: [], + confirmation: {}, + }, + html: { + displayName: 'HTML', + convertibleTypes: [], + confirmation: {}, + }, + image: { + displayName: 'Image', + convertibleTypes: [], + confirmation: {}, + }, + color: { + displayName: 'Color', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + ], + confirmation: {}, + }, + date: { + displayName: 'Date', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + ], + confirmation: {}, + }, + 'datetime-local': { + displayName: 'Date and Time', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + ], + confirmation: {}, + }, + email: { + displayName: 'Email', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + ], + confirmation: {}, + }, + numeric: { + displayName: 'Number', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + ], + confirmation: {}, + }, + tel: { + displayName: 'Phone Number', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + ], + confirmation: {}, + }, + time: { + displayName: 'Time', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + ], + confirmation: {}, + }, + url: { + displayName: 'URL', + convertibleTypes: [ + 'radiogroup', + 'checkbox', + 'dropdown', + 'boolean', + 'tagbox', + ], + confirmation: {}, + }, +}; diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html new file mode 100644 index 0000000000..181626f87a --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html @@ -0,0 +1,141 @@ + +
+
+
+ + + + {{ field }} + + +
+ +
+ + + + + {{ getDisplayName(type) }} + + +
+ +
+ + + + + + {{ getDisplayName(type) }} + + +
+ +
+ + + + + {{ action.action }} + + +
+ +
+ + + + + {{ action.action }} + + +
+ +
+ + + + + None + + + {{ resource.resource.name }} + + +
+
+
+ + {{ 'components.conversion.convertButton' | translate }} + +
+
diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.scss b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.spec.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.spec.ts new file mode 100644 index 0000000000..29ba61fed0 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConversionFieldsComponent } from './conversion-fields.component'; + +describe('ConversionFieldsComponent', () => { + let component: ConversionFieldsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ConversionFieldsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ConversionFieldsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts new file mode 100644 index 0000000000..1e424124af --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts @@ -0,0 +1,163 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Resource } from '@oort-front/shared'; +import { conversionMap } from './conversion-fields-type-mapping'; +import { + FormBuilder, + FormGroup, + FormControl, + Validators, + ValidationErrors, +} from '@angular/forms'; + +interface TableResourceElement { + resource: Resource; +} + +@Component({ + selector: 'app-conversion-fields', + templateUrl: './conversion-fields.component.html', + styleUrls: ['./conversion-fields.component.scss'], +}) +export class ConversionFieldsComponent { + @Input() resource!: Resource; + @Input() disabled = false; + @Input() resources!: Array; + + @Output() convertEvent = new EventEmitter(); + + public fields = new Array(); + + public conversionForm!: FormGroup; + + constructor(private fb: FormBuilder) {} + + public popArrayActions = [ + { value: 'first', action: 'Keep first element' }, + { value: 'last', action: 'Keep last element' }, + ]; + + public failedConversionActions = [ + { value: 'ignore', action: 'Ignore' }, + { value: 'delete', action: 'Delete and proceed' }, + { value: 'cancel', action: 'Cancel conversion' }, + ]; + + ngOnInit() { + this.fields = this.resource.fields.filter((field: any) => !field.generated); + this.conversionForm = this.createConversionForm(); + this.conversionForm.get('selectedType')?.valueChanges.subscribe((value) => { + if (this.conversionForm.get('selectedConvertibleType')?.value) { + this.conversionForm.get('selectedConvertibleType')?.reset(); + this.resetActions(); + } + }); + this.conversionForm + .get('selectedConvertibleType') + ?.valueChanges.subscribe((value) => { + this.resetActions(); + if (value) { + this.setActionsValidators( + 'popArray', + 'selectedPopArrayAction', + value + ); + this.setActionsValidators( + 'selectResource', + 'selectedResourceAction', + value + ); + this.checkIdRequirement(value); + } + }); + } + + public getFieldsNames(): string[] { + return this.fields.map((field: any) => field.name); + } + + public getAllTypes(): string[] { + return Object.keys(conversionMap); + } + + public getConvertibleTypes(): string[] { + if (!conversionMap[this.conversionForm.value.selectedType]) { + return []; + } + return conversionMap[this.conversionForm.value.selectedType] + .convertibleTypes; + } + + public getDisplayName(type: string): string { + if (!conversionMap[type]) { + return type; + } + return conversionMap[type].displayName; + } + + public onClick() { + this.convertEvent.emit(this.conversionForm.value); + } + + private createConversionForm() { + return this.fb.group({ + currentResource: new FormControl(this.resource.id), + selectedField: new FormControl('', Validators.required), + selectedType: new FormControl('', Validators.required), + selectedConvertibleType: new FormControl('', Validators.required), + selectedFailedConversionAction: new FormControl('', Validators.required), + selectedPopArrayAction: new FormControl(''), + selectedResourceAction: new FormControl(''), + checkIdControl: new FormControl(''), + }); + } + + public checkActionRequirement(action: string): boolean { + if ( + this.conversionForm.get('selectedConvertibleType')?.value && + this.conversionForm.get('selectedType')?.value + ) { + if ( + ( + conversionMap[this.conversionForm.get('selectedType')?.value] as any + ).confirmation[action]?.includes( + this.conversionForm.get('selectedConvertibleType')?.value + ) + ) { + return true; + } + } + return false; + } + + private resetActions() { + this.conversionForm.get('selectedPopArrayAction')?.clearValidators(); + this.conversionForm.get('selectedPopArrayAction')?.updateValueAndValidity(); + + this.conversionForm.get('selectedResourceAction')?.clearValidators(); + this.conversionForm.get('selectedResourceAction')?.updateValueAndValidity(); + } + + private setActionsValidators( + action: string, + formControl: string, + value: string + ) { + if ( + ( + conversionMap[this.conversionForm.get('selectedType')?.value] as any + ).confirmation[action]?.includes(value) + ) { + this.conversionForm.get(formControl)?.setValidators(Validators.required); + } + } + + private checkIdRequirement(value: string) { + if ( + ( + conversionMap[this.conversionForm.get('selectedType')?.value] as any + ).confirmation.checkId?.includes(value) + ) { + this.conversionForm.get('checkIdControl')?.setValue(value); + } + } +} diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-routing.module.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-routing.module.ts new file mode 100644 index 0000000000..456feeb709 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-routing.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ConversionComponent } from './conversion.component'; + +/** + * Routes of conversion module. + */ +const routes: Routes = [ + { + path: '', + component: ConversionComponent, + }, +]; + +/** + * Conversion routing module. + */ +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ConversionRoutingModule {} diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.html b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.html new file mode 100644 index 0000000000..37538af346 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.html @@ -0,0 +1,102 @@ + + +
+ +
+ + + + + + + + + + + + + +
+ {{ 'common.name' | translate }} + +
+ {{ element.resource.name }} + + + + + +
+
+
+ + + +
+
+ + +
+
+ + diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.scss b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.spec.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.spec.ts new file mode 100644 index 0000000000..1296f59f9b --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConversionComponent } from './conversion.component'; + +describe('ConversionComponent', () => { + let component: ConversionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ConversionComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ConversionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts new file mode 100644 index 0000000000..276a1876e4 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts @@ -0,0 +1,255 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { + ButtonModule, + DateModule, + FormWrapperModule, + IconModule, + PaginatorModule, + SelectMenuModule, + SnackbarService, + SpinnerModule, + TableModule, + TooltipModule, + UIPageChangeEvent, + handleTablePageEvent, +} from '@oort-front/ui'; +import { + ListFilterComponent, + Resource, + ResourceQueryResponse, + ResourcesQueryResponse, + SkeletonTableModule, + UnsubscribeComponent, +} from '@oort-front/shared'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FilterModule } from 'libs/shared/src/lib/components/filter/filter.module'; +import { + animate, + state, + style, + transition, + trigger, +} from '@angular/animations'; +import { Apollo, QueryRef } from 'apollo-angular'; +import { takeUntil } from 'rxjs'; +import { GET_RESOURCE, GET_RESOURCES } from './graphql/queries'; +import { updateQueryUniqueValues } from '../../../utils/update-queries'; +import { ConversionModule } from './conversion.module'; + +/** Default page size */ +const DEFAULT_PAGE_SIZE = 10; + +/** Interface of table elements */ +interface TableResourceElement { + resource: Resource; +} + +@Component({ + selector: 'app-conversion', + templateUrl: './conversion.component.html', + styleUrls: ['./conversion.component.scss'], + animations: [ + trigger('detailExpand', [ + state('collapsed', style({ height: '0px', minHeight: '0' })), + state('expanded', style({ height: '*' })), + transition( + 'expanded <=> collapsed', + animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)') + ), + ]), + ], +}) +export class ConversionComponent extends UnsubscribeComponent { + // === PAGINATION === + public loading = true; // First load && pagination + public pageInfo = { + pageIndex: 0, + pageSize: DEFAULT_PAGE_SIZE, + length: 0, + endCursor: '', + }; + + // === SINGLE ELEMENT === + public updating = false; // Update of resource + public openedResource?: Resource; + + // === FILTERING === + public filter: any; + public filterLoading = false; + + // === TABLE ELEMENTS === + private resourcesQuery!: QueryRef; + public displayedColumns: string[] = ['name']; + public resources = new Array(); + public cachedResources: Resource[] = []; + + private conversionFormValues: Object = {}; + + /** + * Resource tab of Role Summary component. + * + * @param apollo Apollo client service + * @param snackBar shared snackbar service + */ + constructor(private apollo: Apollo, private snackBar: SnackbarService) { + super(); + } + + /** Load the resources. */ + ngOnInit(): void { + this.resourcesQuery = this.apollo.watchQuery({ + query: GET_RESOURCES, + variables: { + first: DEFAULT_PAGE_SIZE, + sortField: 'name', + sortOrder: 'asc', + }, + }); + + this.resourcesQuery.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(({ data, loading }) => { + this.updateValues(data, loading); + }); + } + + /** + * Custom TrackByFunction to compute the identity of items in an iterable, so when + * updating fields the scroll don't get back to the beginning of the table. + * + * @param index index of the item in the table + * @param item item table + * @returns unique value for all unique inputs + */ + public getUniqueIdentifier(index: number, item: any): any { + return item.resource.id; + } + + /** + * Update resources query. + * + * @param refetch erase previous query results + */ + private fetchResources(refetch?: boolean): void { + this.updating = true; + if (refetch) { + this.cachedResources = []; + this.pageInfo.pageIndex = 0; + this.resourcesQuery.refetch({ + first: this.pageInfo.pageSize, + filter: this.filter, + afterCursor: null, + }); + } else { + this.loading = true; + this.resourcesQuery + .fetchMore({ + variables: { + first: this.pageInfo.pageSize, + filter: this.filter, + afterCursor: this.pageInfo.endCursor, + }, + }) + .then((results) => this.updateValues(results.data, results.loading)); + } + } + + /** + * Handles page event. + * + * @param e page event. + */ + onPage(e: UIPageChangeEvent): void { + const cachedData = handleTablePageEvent( + e, + this.pageInfo, + this.cachedResources + ); + if (cachedData && cachedData.length === this.pageInfo.pageSize) { + //this.resources = this.resources = this.setTableElements(cachedData); + } else { + this.fetchResources(); + } + } + + /** + * Toggles the accordion for the clicked resource and fetches its forms + * + * @param resource The resource element for the resource to be toggled + */ + toggleResource(resource: Resource): void { + if (resource.id === this.openedResource?.id) { + this.openedResource = undefined; + } else { + this.updating = true; + this.apollo + .query({ + query: GET_RESOURCE, + variables: { + id: resource.id, + }, + }) + .pipe(takeUntil(this.destroy$)) + .subscribe(({ data }) => { + if (data.resource) { + this.openedResource = data.resource; + } + this.updating = false; + }); + } + } + + /** + * Serialize single table element from resource + * + * @param resource resource to serialize + * @returns serialized element + */ + private setTableElement(resource: Resource): TableResourceElement { + return { + resource, + }; + } + + /** + * Serialize list of table elements from resource + * + * @param resources resources to serialize + * @returns serialized elements + */ + private setTableElements(resources: Resource[]): TableResourceElement[] { + return resources.map((x: Resource) => this.setTableElement(x)); + } + + /** + * Update resource data value + * + * @param data query response data + * @param loading loading status + */ + private updateValues(data: ResourcesQueryResponse, loading: boolean) { + const mappedValues = data.resources?.edges?.map((x) => x.node); + this.cachedResources = updateQueryUniqueValues( + this.cachedResources, + mappedValues + ); + this.resources = this.setTableElements( + this.cachedResources.slice( + this.pageInfo.pageSize * this.pageInfo.pageIndex, + this.pageInfo.pageSize * (this.pageInfo.pageIndex + 1) + ) + ); + this.pageInfo.length = data.resources.totalCount; + this.pageInfo.endCursor = data.resources.pageInfo.endCursor; + this.loading = loading; + this.updating = loading; + this.filterLoading = false; + } + + getConversionFormValues(conversionFormValues: Object) { + this.conversionFormValues = conversionFormValues; + console.log(this.conversionFormValues); + } +} diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts new file mode 100644 index 0000000000..f673482b3f --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts @@ -0,0 +1,47 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ConversionComponent } from './conversion.component'; +import { + ButtonModule, + DateModule, + FormWrapperModule, + IconModule, + PaginatorModule, + SelectMenuModule, + SelectOptionModule, + SpinnerModule, + TableModule, + TooltipModule, +} from '@oort-front/ui'; +import { ListFilterComponent, SkeletonTableModule } from '@oort-front/shared'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FilterModule } from 'libs/shared/src/lib/components/filter/filter.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { ConversionRoutingModule } from './conversion-routing.module'; +import { ConversionFieldsComponent } from './conversion-fields/conversion-fields.component'; + +@NgModule({ + declarations: [ConversionComponent, ConversionFieldsComponent], + imports: [ + CommonModule, + TooltipModule, + PaginatorModule, + TranslateModule, + DateModule, + SkeletonTableModule, + FormsModule, + ReactiveFormsModule, + SpinnerModule, + FormWrapperModule, + IconModule, + FilterModule, + SelectMenuModule, + ButtonModule, + TableModule, + DateModule, + ListFilterComponent, + ConversionRoutingModule, + SelectOptionModule, + ], +}) +export class ConversionModule {} diff --git a/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts b/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts new file mode 100644 index 0000000000..529c551a0e --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts @@ -0,0 +1,82 @@ +import { gql } from 'apollo-angular'; + +/** GraphQL resource fields for role summary */ +export const RESOURCE_FIELDS = gql` + fragment ResourceFields on Resource { + id + name + metadata { + name + type + editor + filter + multiSelect + options + fields { + name + type + editor + filter + multiSelect + options + } + usedIn + } + fields + } +`; + +/** GraphQL short resource fields for role summary */ +export const SHORT_RESOURCE_FIELDS = gql` + fragment ShortResourceFields on Resource { + id + name + fields + } +`; + +/** Graphql query for getting resources with a filter and more data */ +export const GET_RESOURCES = gql` + query GetResources( + $first: Int + $afterCursor: ID + $filter: JSON + $sortField: String + $sortOrder: String + ) { + resources( + first: $first + afterCursor: $afterCursor + filter: $filter + sortField: $sortField + sortOrder: $sortOrder + ) { + edges { + node { + ...ShortResourceFields + } + cursor + } + totalCount + pageInfo { + hasNextPage + endCursor + } + } + } + ${SHORT_RESOURCE_FIELDS} +`; + +/** GraphQL query to get a single resource */ +export const GET_RESOURCE = gql` + query GetResources($id: ID!) { + resource(id: $id) { + forms { + id + name + } + ...ResourceFields + } + } + ${RESOURCE_FIELDS} +`; diff --git a/libs/shared/src/i18n/en.json b/libs/shared/src/i18n/en.json index 0e0cab75d0..1c3baa45cb 100644 --- a/libs/shared/src/i18n/en.json +++ b/libs/shared/src/i18n/en.json @@ -77,6 +77,11 @@ "one": "Column" }, "comma": "comma", + "conversion": { + "few": "Data Conversion", + "none": "No conversion", + "one": "Conversion" + }, "copy": "Copy", "create": "Create", "createdOn": "Created on", @@ -649,6 +654,29 @@ "delete": "Delete", "title": "Confirm" }, + "conversion": { + "convertButton": "Convert", + "failedAction": { + "name": "Action for records that fail to convert", + "tooltip": "Select what will be done to records that fail to convert." + }, + "initialType": { + "name": "Initial field type", + "tooltip": "Initial type of the field to convert the records from." + }, + "newType": { + "name": "New field type", + "tooltip": "New type of the field to convert the records to. Select an initial type to list its possible conversions." + }, + "popArray": { + "name": "Action for popArray", + "tooltip": "This conversion transforms a field that is stored as an array to a field that is not. Select which element of the array will be kept." + }, + "selectResource": { + "name": "Action for selectResource", + "tooltip": "Select a resource to enforce the record to be related to or None to get the resource from the record automatically." + } + }, "customNotifications": { "edit": { "dataset": "Dataset", diff --git a/libs/shared/src/i18n/fr.json b/libs/shared/src/i18n/fr.json index e1187afe47..ccf721c609 100644 --- a/libs/shared/src/i18n/fr.json +++ b/libs/shared/src/i18n/fr.json @@ -77,6 +77,11 @@ "one": "Colonne" }, "comma": "virgule", + "conversion": { + "few": "Data Conversion", + "none": "No conversion", + "one": "Conversion" + }, "copy": "Copier", "create": "Créer", "createdOn": "Date de création", @@ -649,6 +654,29 @@ "delete": "Supprimer", "title": "Confirmer" }, + "conversion": { + "convertButton": "Convert", + "failedAction": { + "name": "Action for records that fail to convert", + "tooltip": "Select what will be done to records that fail to convert." + }, + "initialType": { + "name": "Initial field type", + "tooltip": "Initial type of the field to convert the records from." + }, + "newType": { + "name": "New field type", + "tooltip": "New type of the field to convert the records to. Select an initial type to list its possible conversions." + }, + "popArray": { + "name": "Action for popArray", + "tooltip": "This conversion transforms a field that is stored as an array to a field that is not. Select which element of the array will be kept." + }, + "selectResource": { + "name": "Action for selectResource", + "tooltip": "Select a resource to enforce the record to be related to or None to get the resource from the record automatically." + } + }, "customNotifications": { "edit": { "dataset": "Jeu de données", diff --git a/libs/shared/src/i18n/test.json b/libs/shared/src/i18n/test.json index f7a0dc3f79..793d835127 100644 --- a/libs/shared/src/i18n/test.json +++ b/libs/shared/src/i18n/test.json @@ -77,6 +77,11 @@ "one": "******" }, "comma": "******", + "conversion": { + "few": "******", + "none": "******", + "one": "******" + }, "copy": "******", "create": "******", "createdOn": "******", @@ -649,6 +654,29 @@ "delete": "******", "title": "******" }, + "conversion": { + "convertButton": "******", + "failedAction": { + "name": "******", + "tooltip": "******" + }, + "initialType": { + "name": "******", + "tooltip": "******" + }, + "newType": { + "name": "******", + "tooltip": "******" + }, + "popArray": { + "name": "******", + "tooltip": "******" + }, + "selectResource": { + "name": "******", + "tooltip": "******" + } + }, "customNotifications": { "edit": { "dataset": "******", From 7c6dc675093e8a92d5405cef6487e7673ca7665e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:13:17 -0300 Subject: [PATCH 02/10] Started coding conversions --- .../conversion-fields.component.ts | 5 +- .../pages/conversion/conversion.component.ts | 62 +++++++++++++++++-- .../pages/conversion/graphql/mutations.ts | 28 +++++++++ .../pages/conversion/graphql/queries.ts | 28 +++++++++ 4 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 apps/back-office/src/app/dashboard/pages/conversion/graphql/mutations.ts diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts index 1e424124af..4934b4f1f8 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts @@ -34,11 +34,12 @@ export class ConversionFieldsComponent { public popArrayActions = [ { value: 'first', action: 'Keep first element' }, { value: 'last', action: 'Keep last element' }, + { value: 'all', action: 'Keep all elements, split by comma' }, ]; public failedConversionActions = [ - { value: 'ignore', action: 'Ignore' }, - { value: 'delete', action: 'Delete and proceed' }, + { value: 'ignore', action: 'Ignore Record' }, + { value: 'delete', action: 'Delete value and proceed' }, { value: 'cancel', action: 'Cancel conversion' }, ]; diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts index 276a1876e4..378c4aca38 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts @@ -22,6 +22,9 @@ import { ResourcesQueryResponse, SkeletonTableModule, UnsubscribeComponent, + Record, + ResourceRecordsNodesQueryResponse, + RecordQueryResponse, } from '@oort-front/shared'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FilterModule } from 'libs/shared/src/lib/components/filter/filter.module'; @@ -33,8 +36,13 @@ import { trigger, } from '@angular/animations'; import { Apollo, QueryRef } from 'apollo-angular'; -import { takeUntil } from 'rxjs'; -import { GET_RESOURCE, GET_RESOURCES } from './graphql/queries'; +import { takeUntil, firstValueFrom } from 'rxjs'; +import { + GET_RESOURCE, + GET_RESOURCES, + GET_RESOURCE_RECORDS, +} from './graphql/queries'; +import { CONVERT_RESOURCE_RECORDS } from './graphql/mutations'; import { updateQueryUniqueValues } from '../../../utils/update-queries'; import { ConversionModule } from './conversion.module'; @@ -46,6 +54,16 @@ interface TableResourceElement { resource: Resource; } +interface ConversionFormValues { + id: string; + initialType: string; + newType: string; + field: string; + selectedResources: string; + popArray: string; + failedAction: string; +} + @Component({ selector: 'app-conversion', templateUrl: './conversion.component.html', @@ -85,7 +103,15 @@ export class ConversionComponent extends UnsubscribeComponent { public resources = new Array(); public cachedResources: Resource[] = []; - private conversionFormValues: Object = {}; + private conversionFormValues: ConversionFormValues = { + id: '', + initialType: '', + newType: '', + field: '', + selectedResources: '', + popArray: '', + failedAction: '', + }; /** * Resource tab of Role Summary component. @@ -248,8 +274,34 @@ export class ConversionComponent extends UnsubscribeComponent { this.filterLoading = false; } - getConversionFormValues(conversionFormValues: Object) { + public getConversionFormValues(conversionFormValues: any) { this.conversionFormValues = conversionFormValues; - console.log(this.conversionFormValues); + this.onConvert(this.openedResource as Resource, this.conversionFormValues); + } + + public async onConvert( + resource: Resource, + conversionForm: any + ): Promise { + const promises: Promise[] = []; + promises.push( + firstValueFrom( + this.apollo.mutate({ + mutation: CONVERT_RESOURCE_RECORDS, + variables: { + id: resource.id, + initialType: conversionForm['selectedType'], + newType: conversionForm['selectedConvertibleType'], + field: conversionForm['selectedField'], + selectedResource: conversionForm['selectedResources'], + popArray: conversionForm['selectedPopArrayAction'], + failedAction: conversionForm['selectedFailedConversionAction'], + }, + }) + ) + ); + Promise.all(promises).then((results) => { + console.log('results', results); + }); } } diff --git a/apps/back-office/src/app/dashboard/pages/conversion/graphql/mutations.ts b/apps/back-office/src/app/dashboard/pages/conversion/graphql/mutations.ts new file mode 100644 index 0000000000..b0cdb49134 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/conversion/graphql/mutations.ts @@ -0,0 +1,28 @@ +import { gql } from 'apollo-angular'; + +/** Graphql query for converting records of a resource */ +export const CONVERT_RESOURCE_RECORDS = gql` + mutation convertRecords( + $id: ID! + $initialType: String! + $newType: String! + $field: String! + $selectedResource: String + $popArray: String + $failedAction: String! + ) { + convertRecords( + id: $id + initialType: $initialType + newType: $newType + field: $field + selectedResource: $selectedResource + popArray: $popArray + failedAction: $failedAction + ) { + id + createdAt + modifiedAt + } + } +`; diff --git a/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts b/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts index 529c551a0e..612739cc95 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts @@ -80,3 +80,31 @@ export const GET_RESOURCE = gql` } ${RESOURCE_FIELDS} `; + +/** Graphql query for getting records of a resource */ +export const GET_RESOURCE_RECORDS = gql` + query GetResourceRecords( + $id: ID! + $afterCursor: ID + $first: Int + $filter: JSON + ) { + resource(id: $id) { + records(first: $first, afterCursor: $afterCursor, filter: $filter) { + edges { + node { + id + incrementalId + data + } + cursor + } + totalCount + pageInfo { + hasNextPage + endCursor + } + } + } + } +`; From 34aa415c02797f45f0da7ae5d6a50eee991653ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:50:52 -0300 Subject: [PATCH 03/10] Corrected conversionForm mistake --- .../src/app/dashboard/pages/conversion/conversion.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts index 378c4aca38..13fa697e60 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts @@ -283,6 +283,7 @@ export class ConversionComponent extends UnsubscribeComponent { resource: Resource, conversionForm: any ): Promise { + console.log('conversionForm', conversionForm); const promises: Promise[] = []; promises.push( firstValueFrom( @@ -293,7 +294,7 @@ export class ConversionComponent extends UnsubscribeComponent { initialType: conversionForm['selectedType'], newType: conversionForm['selectedConvertibleType'], field: conversionForm['selectedField'], - selectedResource: conversionForm['selectedResources'], + selectedResource: conversionForm['selectedResourceAction'], popArray: conversionForm['selectedPopArrayAction'], failedAction: conversionForm['selectedFailedConversionAction'], }, From f2e3c8524887b50731172a3ae9f464c4d61f9188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:37:49 -0300 Subject: [PATCH 04/10] removed selectResource --- .../conversion-fields-type-mapping.ts | 1 - .../conversion-fields.component.html | 28 ------------------- .../conversion-fields.component.ts | 23 +-------------- .../pages/conversion/conversion.component.ts | 9 +----- .../pages/conversion/graphql/mutations.ts | 2 -- .../pages/conversion/graphql/queries.ts | 28 ------------------- 6 files changed, 2 insertions(+), 89 deletions(-) diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts index 1ede263e2f..691051bb4f 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts @@ -15,7 +15,6 @@ export const conversionMap: ConversionMap = { 'dropdown', 'boolean', 'tagbox', - 'resource', 'owner', 'users', ], diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html index 181626f87a..332e2bd0bb 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html @@ -98,34 +98,6 @@ - -
- - - - - None - - - {{ resource.resource.name }} - - -
Date: Mon, 29 Jan 2024 11:28:40 -0300 Subject: [PATCH 05/10] Linted component --- .../conversion-fields-type-mapping.ts | 2 +- .../conversion-fields.component.ts | 97 ++++++++++++++++--- .../pages/conversion/conversion.component.ts | 49 +++++----- .../pages/conversion/conversion.module.ts | 2 - libs/shared/src/i18n/en.json | 8 ++ libs/shared/src/i18n/fr.json | 30 +++--- libs/shared/src/i18n/test.json | 8 ++ 7 files changed, 142 insertions(+), 54 deletions(-) diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts index 691051bb4f..8e563cbe8b 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts @@ -2,7 +2,7 @@ interface ConversionMap { [key: string]: { displayName: string; convertibleTypes: string[]; - confirmation: Object; + confirmation: object; }; } diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts index de8532af55..1cf642a2d1 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Resource } from '@oort-front/shared'; import { conversionMap } from './conversion-fields-type-mapping'; import { @@ -6,47 +6,73 @@ import { FormGroup, FormControl, Validators, - ValidationErrors, } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +/** Interface for the table element */ interface TableResourceElement { resource: Resource; } +/** Conversion fields component */ @Component({ selector: 'app-conversion-fields', templateUrl: './conversion-fields.component.html', styleUrls: ['./conversion-fields.component.scss'], }) -export class ConversionFieldsComponent { +export class ConversionFieldsComponent implements OnInit { @Input() resource!: Resource; @Input() disabled = false; - @Input() resources!: Array; + @Input() resources!: TableResourceElement[]; - @Output() convertEvent = new EventEmitter(); + @Output() convertEvent = new EventEmitter(); public fields = new Array(); public conversionForm!: FormGroup; - constructor(private fb: FormBuilder) {} + /** + * Constructor for the conversion fields component + * + * @param fb The form builder + * @param translate The translate service + */ + constructor(private fb: FormBuilder, private translate: TranslateService) {} public popArrayActions = [ - { value: 'first', action: 'Keep first element' }, - { value: 'last', action: 'Keep last element' }, - { value: 'all', action: 'Keep all elements, split by comma' }, + { + value: 'first', + action: this.translate.instant('components.conversion.actions.first'), + }, + { + value: 'last', + action: this.translate.instant('components.conversion.actions.last'), + }, + { + value: 'all', + action: this.translate.instant('components.conversion.actions.all'), + }, ]; public failedConversionActions = [ - { value: 'ignore', action: 'Ignore record and proceed' }, - { value: 'delete', action: 'Delete value and proceed' }, - { value: 'cancel', action: 'Cancel conversion' }, + { + value: 'ignore', + action: this.translate.instant('components.conversion.actions.ignore'), + }, + { + value: 'delete', + action: this.translate.instant('components.conversion.actions.delete'), + }, + { + value: 'cancel', + action: this.translate.instant('components.conversion.actions.cancel'), + }, ]; ngOnInit() { this.fields = this.resource.fields.filter((field: any) => !field.generated); this.conversionForm = this.createConversionForm(); - this.conversionForm.get('selectedType')?.valueChanges.subscribe((value) => { + this.conversionForm.get('selectedType')?.valueChanges.subscribe(() => { if (this.conversionForm.get('selectedConvertibleType')?.value) { this.conversionForm.get('selectedConvertibleType')?.reset(); this.resetActions(); @@ -66,14 +92,29 @@ export class ConversionFieldsComponent { }); } + /** + * Get the fields names + * + * @returns the fields names + */ public getFieldsNames(): string[] { return this.fields.map((field: any) => field.name); } + /** + * Get all the types + * + * @returns the types + */ public getAllTypes(): string[] { return Object.keys(conversionMap); } + /** + * Get the convertible types + * + * @returns the convertible types + */ public getConvertibleTypes(): string[] { if (!conversionMap[this.conversionForm.value.selectedType]) { return []; @@ -82,6 +123,12 @@ export class ConversionFieldsComponent { .convertibleTypes; } + /** + * Get the display name in the conversion map + * + * @param type The type + * @returns The type display name + */ public getDisplayName(type: string): string { if (!conversionMap[type]) { return type; @@ -89,10 +136,18 @@ export class ConversionFieldsComponent { return conversionMap[type].displayName; } + /** + * onClick event for the button + */ public onClick() { this.convertEvent.emit(this.conversionForm.value); } + /** + * Create the conversion form + * + * @returns the conversion form + */ private createConversionForm() { return this.fb.group({ currentResource: new FormControl(this.resource.id), @@ -104,6 +159,12 @@ export class ConversionFieldsComponent { }); } + /** + * Check if the action is required + * + * @param action The action + * @returns True or false accordingly + */ public checkActionRequirement(action: string): boolean { if ( this.conversionForm.get('selectedConvertibleType')?.value && @@ -122,11 +183,21 @@ export class ConversionFieldsComponent { return false; } + /** + * Reset the actions validators + */ private resetActions() { this.conversionForm.get('selectedPopArrayAction')?.clearValidators(); this.conversionForm.get('selectedPopArrayAction')?.updateValueAndValidity(); } + /** + * Set the actions validators when they are needed + * + * @param action Action to validate + * @param formControl The form control + * @param value The value + */ private setActionsValidators( action: string, formControl: string, diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts index 9f69708c25..1453a59d55 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts @@ -1,33 +1,12 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { TranslateModule } from '@ngx-translate/core'; +import { Component, OnInit } from '@angular/core'; +import { UIPageChangeEvent, handleTablePageEvent } from '@oort-front/ui'; import { - ButtonModule, - DateModule, - FormWrapperModule, - IconModule, - PaginatorModule, - SelectMenuModule, - SnackbarService, - SpinnerModule, - TableModule, - TooltipModule, - UIPageChangeEvent, - handleTablePageEvent, -} from '@oort-front/ui'; -import { - ListFilterComponent, Resource, ResourceQueryResponse, ResourcesQueryResponse, - SkeletonTableModule, UnsubscribeComponent, - Record, ResourceRecordsNodesQueryResponse, - RecordQueryResponse, } from '@oort-front/shared'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { FilterModule } from 'libs/shared/src/lib/components/filter/filter.module'; import { animate, state, @@ -40,7 +19,6 @@ import { takeUntil, firstValueFrom } from 'rxjs'; import { GET_RESOURCE, GET_RESOURCES } from './graphql/queries'; import { CONVERT_RESOURCE_RECORDS } from './graphql/mutations'; import { updateQueryUniqueValues } from '../../../utils/update-queries'; -import { ConversionModule } from './conversion.module'; /** Default page size */ const DEFAULT_PAGE_SIZE = 10; @@ -50,6 +28,7 @@ interface TableResourceElement { resource: Resource; } +/** Interface of conversion form */ interface ConversionFormValues { id: string; initialType: string; @@ -59,6 +38,9 @@ interface ConversionFormValues { failedAction: string; } +/** + * Data conversion component + */ @Component({ selector: 'app-conversion', templateUrl: './conversion.component.html', @@ -74,7 +56,10 @@ interface ConversionFormValues { ]), ], }) -export class ConversionComponent extends UnsubscribeComponent { +export class ConversionComponent + extends UnsubscribeComponent + implements OnInit +{ // === PAGINATION === public loading = true; // First load && pagination public pageInfo = { @@ -111,9 +96,8 @@ export class ConversionComponent extends UnsubscribeComponent { * Resource tab of Role Summary component. * * @param apollo Apollo client service - * @param snackBar shared snackbar service */ - constructor(private apollo: Apollo, private snackBar: SnackbarService) { + constructor(private apollo: Apollo) { super(); } @@ -268,11 +252,22 @@ export class ConversionComponent extends UnsubscribeComponent { this.filterLoading = false; } + /** + * Get the values from the conversion form + * + * @param conversionFormValues conversion form values + */ public getConversionFormValues(conversionFormValues: any) { this.conversionFormValues = conversionFormValues; this.onConvert(this.openedResource as Resource, this.conversionFormValues); } + /** + * Convert the resource records + * + * @param resource resource the records are from + * @param conversionForm conversion form + */ public async onConvert( resource: Resource, conversionForm: any diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts index f673482b3f..d5d9647139 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts +++ b/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts @@ -15,7 +15,6 @@ import { } from '@oort-front/ui'; import { ListFilterComponent, SkeletonTableModule } from '@oort-front/shared'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { FilterModule } from 'libs/shared/src/lib/components/filter/filter.module'; import { TranslateModule } from '@ngx-translate/core'; import { ConversionRoutingModule } from './conversion-routing.module'; import { ConversionFieldsComponent } from './conversion-fields/conversion-fields.component'; @@ -34,7 +33,6 @@ import { ConversionFieldsComponent } from './conversion-fields/conversion-fields SpinnerModule, FormWrapperModule, IconModule, - FilterModule, SelectMenuModule, ButtonModule, TableModule, diff --git a/libs/shared/src/i18n/en.json b/libs/shared/src/i18n/en.json index 1c3baa45cb..17bc97e612 100644 --- a/libs/shared/src/i18n/en.json +++ b/libs/shared/src/i18n/en.json @@ -655,6 +655,14 @@ "title": "Confirm" }, "conversion": { + "actions": { + "all": "Keep all elements, split by comma", + "cancel": "Cancel conversion", + "delete": "Delete value and proceed", + "first": "Keep first element", + "ignore": "Ignore record and proceed", + "last": "Keep last element" + }, "convertButton": "Convert", "failedAction": { "name": "Action for records that fail to convert", diff --git a/libs/shared/src/i18n/fr.json b/libs/shared/src/i18n/fr.json index ccf721c609..ab142813ba 100644 --- a/libs/shared/src/i18n/fr.json +++ b/libs/shared/src/i18n/fr.json @@ -655,26 +655,34 @@ "title": "Confirmer" }, "conversion": { - "convertButton": "Convert", + "actions": { + "all": "Conserver tous les éléments, séparés par une virgule", + "cancel": "Annuler la conversion", + "delete": "Supprimer la valeur et continuer", + "first": "Conserver le premier élément", + "ignore": "Ignorer l'enregistrement et poursuivre", + "last": "Conserver le dernier élément" + }, + "convertButton": "Convertir", "failedAction": { - "name": "Action for records that fail to convert", - "tooltip": "Select what will be done to records that fail to convert." + "name": "Mesures à prendre en cas d'échec de la conversion", + "tooltip": "Sélectionnez ce qui sera fait pour les enregistrements qui ne sont pas convertis." }, "initialType": { - "name": "Initial field type", - "tooltip": "Initial type of the field to convert the records from." + "name": "Type de champ initial", + "tooltip": "Type initial du champ à partir duquel les enregistrements doivent être convertis." }, "newType": { - "name": "New field type", - "tooltip": "New type of the field to convert the records to. Select an initial type to list its possible conversions." + "name": "Nouveau type de champ", + "tooltip": "Nouveau type de champ vers lequel convertir les enregistrements. Sélectionnez un type initial pour obtenir la liste des conversions possibles." }, "popArray": { - "name": "Action for popArray", - "tooltip": "This conversion transforms a field that is stored as an array to a field that is not. Select which element of the array will be kept." + "name": "Action pour popArray", + "tooltip": "Cette conversion transforme un champ stocké sous forme de tableau en un champ qui ne l'est pas. Sélectionnez l'élément du tableau qui sera conservé." }, "selectResource": { - "name": "Action for selectResource", - "tooltip": "Select a resource to enforce the record to be related to or None to get the resource from the record automatically." + "name": "Action pour selectResource", + "tooltip": "Sélectionnez une ressource à laquelle l'enregistrement doit être lié ou Aucune pour obtenir automatiquement la ressource à partir de l'enregistrement." } }, "customNotifications": { diff --git a/libs/shared/src/i18n/test.json b/libs/shared/src/i18n/test.json index 793d835127..d473709f0e 100644 --- a/libs/shared/src/i18n/test.json +++ b/libs/shared/src/i18n/test.json @@ -655,6 +655,14 @@ "title": "******" }, "conversion": { + "actions": { + "all": "******", + "cancel": "******", + "delete": "******", + "first": "******", + "ignore": "******", + "last": "******" + }, "convertButton": "******", "failedAction": { "name": "******", From 24f73ea853afbff62df46a0c92f28fde3eaa0dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:12:36 -0300 Subject: [PATCH 06/10] front-end first working version --- .../app/dashboard/dashboard-routing.module.ts | 6 +- .../src/app/dashboard/dashboard.component.ts | 6 +- .../conversion-fields-type-mapping.ts | 201 ------------ .../conversion-fields.component.html | 113 ------- .../conversion-fields.component.spec.ts | 22 -- .../conversion-fields.component.ts | 214 ------------- .../conversion/conversion.component.html | 102 ------ .../conversion/conversion.component.scss | 0 .../pages/conversion/conversion.component.ts | 296 ------------------ .../data-generation-fields-type-mapping.ts | 125 ++++++++ .../data-generation-fields.component.html | 138 ++++++++ .../data-generation-fields.component.scss | 7 + .../data-generation-fields.component.spec.ts | 22 ++ .../data-generation-fields.component.ts | 281 +++++++++++++++++ .../graphql/mutations.ts | 12 + .../data-generation-fields/graphql/queries.ts | 58 ++++ .../data-studio-routing.module.ts} | 8 +- .../data-studio/data-studio.component.html | 23 ++ .../data-studio.component.scss} | 0 .../data-studio.component.spec.ts} | 12 +- .../data-studio/data-studio.component.ts | 171 ++++++++++ .../data-studio.module.ts} | 26 +- .../graphql/mutations.ts | 0 .../graphql/queries.ts | 30 ++ libs/shared/src/lib/models/record.model.ts | 5 + .../expansion-panel.component.ts | 3 + 26 files changed, 911 insertions(+), 970 deletions(-) delete mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts delete mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html delete mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.spec.ts delete mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts delete mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion.component.html delete mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion.component.scss delete mode 100644 apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.scss create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.spec.ts create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/mutations.ts create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/queries.ts rename apps/back-office/src/app/dashboard/pages/{conversion/conversion-routing.module.ts => data-studio/data-studio-routing.module.ts} (62%) create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.html rename apps/back-office/src/app/dashboard/pages/{conversion/conversion-fields/conversion-fields.component.scss => data-studio/data-studio.component.scss} (100%) rename apps/back-office/src/app/dashboard/pages/{conversion/conversion.component.spec.ts => data-studio/data-studio.component.spec.ts} (52%) create mode 100644 apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.ts rename apps/back-office/src/app/dashboard/pages/{conversion/conversion.module.ts => data-studio/data-studio.module.ts} (56%) rename apps/back-office/src/app/dashboard/pages/{conversion => data-studio}/graphql/mutations.ts (100%) rename apps/back-office/src/app/dashboard/pages/{conversion => data-studio}/graphql/queries.ts (74%) diff --git a/apps/back-office/src/app/dashboard/dashboard-routing.module.ts b/apps/back-office/src/app/dashboard/dashboard-routing.module.ts index 804298d217..5143ae8421 100644 --- a/apps/back-office/src/app/dashboard/dashboard-routing.module.ts +++ b/apps/back-office/src/app/dashboard/dashboard-routing.module.ts @@ -26,10 +26,10 @@ export const routes: Routes = [ import('@oort-front/shared').then((m) => m.ProfileViewModule), }, { - path: 'conversion', + path: 'datastudio', loadChildren: () => - import('./pages/conversion/conversion.module').then( - (m) => m.ConversionModule + import('./pages/data-studio/data-studio.module').then( + (m) => m.DataStudioModule ), }, { diff --git a/apps/back-office/src/app/dashboard/dashboard.component.ts b/apps/back-office/src/app/dashboard/dashboard.component.ts index fe0ec05a53..f8e7134c1a 100644 --- a/apps/back-office/src/app/dashboard/dashboard.component.ts +++ b/apps/back-office/src/app/dashboard/dashboard.component.ts @@ -80,9 +80,9 @@ export class DashboardComponent extends UnsubscribeComponent { } if (this.ability.can('read', 'Resource')) { dataItems.push({ - name: this.translate.instant('common.conversion.few'), - path: '/conversion', - icon: 'change_circle', + name: this.translate.instant('common.datastudio.few'), + path: '/datastudio', + icon: 'build_circle', }); } if (dataItems.length > 0) { diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts deleted file mode 100644 index 8e563cbe8b..0000000000 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields-type-mapping.ts +++ /dev/null @@ -1,201 +0,0 @@ -interface ConversionMap { - [key: string]: { - displayName: string; - convertibleTypes: string[]; - confirmation: object; - }; -} - -export const conversionMap: ConversionMap = { - text: { - displayName: 'Text/Long Text/Month/Password/Range/Week', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - 'owner', - 'users', - ], - confirmation: { checkId: ['owner', 'users'], selectResource: ['resource'] }, - }, - radiogroup: { - displayName: 'Radio Button Group', - convertibleTypes: ['checkbox', 'dropdown', 'boolean', 'tagbox', 'text'], - confirmation: {}, - }, - checkbox: { - displayName: 'Checkboxes', - convertibleTypes: ['radiogroup', 'dropdown', 'boolean', 'tagbox', 'text'], - confirmation: { popArray: ['radiogroup', 'dropdown', 'text'] }, - }, - dropdown: { - displayName: 'Dropdown', - convertibleTypes: ['radiogroup', 'checkbox', 'boolean', 'tagbox', 'text'], - confirmation: {}, - }, - boolean: { - displayName: 'Yes/No (Boolean)', - convertibleTypes: ['text'], - confirmation: {}, - }, - file: { - displayName: 'File Upload', - convertibleTypes: ['boolean'], - confirmation: {}, - }, - tagbox: { - displayName: 'Multi-Select Dropdown', - convertibleTypes: ['radiogroup', 'checkbox', 'dropdown', 'boolean', 'text'], - confirmation: { popArray: ['radiogroup', 'dropdown', 'text'] }, - }, - multipletext: { - displayName: 'Multiple Text', - convertibleTypes: ['boolean'], - confirmation: {}, - }, - matrix: { - displayName: 'Single-Select Matrix', - convertibleTypes: ['boolean'], - confirmation: {}, - }, - matrixdropdown: { - displayName: 'Multi-Select Matrix', - convertibleTypes: [], - confirmation: {}, - }, - matrixdynamic: { - displayName: 'Dynamic Matrix', - convertibleTypes: [], - confirmation: {}, - }, - expression: { - displayName: 'Expression (read-only)', - convertibleTypes: ['boolean'], - confirmation: {}, - }, - resource: { - displayName: 'Resource', - convertibleTypes: ['boolean', 'resources'], - confirmation: {}, - }, - resources: { - displayName: 'Resources', - convertibleTypes: ['boolean', 'resource'], - confirmation: { popArray: ['resource'] }, - }, - owner: { - displayName: 'Owner', - convertibleTypes: ['boolean'], - confirmation: {}, - }, - users: { - displayName: 'Users', - convertibleTypes: ['boolean'], - confirmation: {}, - }, - geospatial: { - displayName: 'Geospatial', - convertibleTypes: [], - confirmation: {}, - }, - html: { - displayName: 'HTML', - convertibleTypes: [], - confirmation: {}, - }, - image: { - displayName: 'Image', - convertibleTypes: [], - confirmation: {}, - }, - color: { - displayName: 'Color', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - ], - confirmation: {}, - }, - date: { - displayName: 'Date', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - ], - confirmation: {}, - }, - 'datetime-local': { - displayName: 'Date and Time', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - ], - confirmation: {}, - }, - email: { - displayName: 'Email', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - ], - confirmation: {}, - }, - numeric: { - displayName: 'Number', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - ], - confirmation: {}, - }, - tel: { - displayName: 'Phone Number', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - ], - confirmation: {}, - }, - time: { - displayName: 'Time', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - ], - confirmation: {}, - }, - url: { - displayName: 'URL', - convertibleTypes: [ - 'radiogroup', - 'checkbox', - 'dropdown', - 'boolean', - 'tagbox', - ], - confirmation: {}, - }, -}; diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html deleted file mode 100644 index 332e2bd0bb..0000000000 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.html +++ /dev/null @@ -1,113 +0,0 @@ - -
-
-
- - - - {{ field }} - - -
- -
- - - - - {{ getDisplayName(type) }} - - -
- -
- - - - - - {{ getDisplayName(type) }} - - -
- -
- - - - - {{ action.action }} - - -
- -
- - - - - {{ action.action }} - - -
-
-
- - {{ 'components.conversion.convertButton' | translate }} - -
-
diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.spec.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.spec.ts deleted file mode 100644 index 29ba61fed0..0000000000 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ConversionFieldsComponent } from './conversion-fields.component'; - -describe('ConversionFieldsComponent', () => { - let component: ConversionFieldsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ConversionFieldsComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(ConversionFieldsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts deleted file mode 100644 index 1cf642a2d1..0000000000 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Resource } from '@oort-front/shared'; -import { conversionMap } from './conversion-fields-type-mapping'; -import { - FormBuilder, - FormGroup, - FormControl, - Validators, -} from '@angular/forms'; -import { TranslateService } from '@ngx-translate/core'; - -/** Interface for the table element */ -interface TableResourceElement { - resource: Resource; -} - -/** Conversion fields component */ -@Component({ - selector: 'app-conversion-fields', - templateUrl: './conversion-fields.component.html', - styleUrls: ['./conversion-fields.component.scss'], -}) -export class ConversionFieldsComponent implements OnInit { - @Input() resource!: Resource; - @Input() disabled = false; - @Input() resources!: TableResourceElement[]; - - @Output() convertEvent = new EventEmitter(); - - public fields = new Array(); - - public conversionForm!: FormGroup; - - /** - * Constructor for the conversion fields component - * - * @param fb The form builder - * @param translate The translate service - */ - constructor(private fb: FormBuilder, private translate: TranslateService) {} - - public popArrayActions = [ - { - value: 'first', - action: this.translate.instant('components.conversion.actions.first'), - }, - { - value: 'last', - action: this.translate.instant('components.conversion.actions.last'), - }, - { - value: 'all', - action: this.translate.instant('components.conversion.actions.all'), - }, - ]; - - public failedConversionActions = [ - { - value: 'ignore', - action: this.translate.instant('components.conversion.actions.ignore'), - }, - { - value: 'delete', - action: this.translate.instant('components.conversion.actions.delete'), - }, - { - value: 'cancel', - action: this.translate.instant('components.conversion.actions.cancel'), - }, - ]; - - ngOnInit() { - this.fields = this.resource.fields.filter((field: any) => !field.generated); - this.conversionForm = this.createConversionForm(); - this.conversionForm.get('selectedType')?.valueChanges.subscribe(() => { - if (this.conversionForm.get('selectedConvertibleType')?.value) { - this.conversionForm.get('selectedConvertibleType')?.reset(); - this.resetActions(); - } - }); - this.conversionForm - .get('selectedConvertibleType') - ?.valueChanges.subscribe((value) => { - this.resetActions(); - if (value) { - this.setActionsValidators( - 'popArray', - 'selectedPopArrayAction', - value - ); - } - }); - } - - /** - * Get the fields names - * - * @returns the fields names - */ - public getFieldsNames(): string[] { - return this.fields.map((field: any) => field.name); - } - - /** - * Get all the types - * - * @returns the types - */ - public getAllTypes(): string[] { - return Object.keys(conversionMap); - } - - /** - * Get the convertible types - * - * @returns the convertible types - */ - public getConvertibleTypes(): string[] { - if (!conversionMap[this.conversionForm.value.selectedType]) { - return []; - } - return conversionMap[this.conversionForm.value.selectedType] - .convertibleTypes; - } - - /** - * Get the display name in the conversion map - * - * @param type The type - * @returns The type display name - */ - public getDisplayName(type: string): string { - if (!conversionMap[type]) { - return type; - } - return conversionMap[type].displayName; - } - - /** - * onClick event for the button - */ - public onClick() { - this.convertEvent.emit(this.conversionForm.value); - } - - /** - * Create the conversion form - * - * @returns the conversion form - */ - private createConversionForm() { - return this.fb.group({ - currentResource: new FormControl(this.resource.id), - selectedField: new FormControl('', Validators.required), - selectedType: new FormControl('', Validators.required), - selectedConvertibleType: new FormControl('', Validators.required), - selectedFailedConversionAction: new FormControl('', Validators.required), - selectedPopArrayAction: new FormControl(''), - }); - } - - /** - * Check if the action is required - * - * @param action The action - * @returns True or false accordingly - */ - public checkActionRequirement(action: string): boolean { - if ( - this.conversionForm.get('selectedConvertibleType')?.value && - this.conversionForm.get('selectedType')?.value - ) { - if ( - ( - conversionMap[this.conversionForm.get('selectedType')?.value] as any - ).confirmation[action]?.includes( - this.conversionForm.get('selectedConvertibleType')?.value - ) - ) { - return true; - } - } - return false; - } - - /** - * Reset the actions validators - */ - private resetActions() { - this.conversionForm.get('selectedPopArrayAction')?.clearValidators(); - this.conversionForm.get('selectedPopArrayAction')?.updateValueAndValidity(); - } - - /** - * Set the actions validators when they are needed - * - * @param action Action to validate - * @param formControl The form control - * @param value The value - */ - private setActionsValidators( - action: string, - formControl: string, - value: string - ) { - if ( - ( - conversionMap[this.conversionForm.get('selectedType')?.value] as any - ).confirmation[action]?.includes(value) - ) { - this.conversionForm.get(formControl)?.setValidators(Validators.required); - } - } -} diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.html b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.html deleted file mode 100644 index 37538af346..0000000000 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.html +++ /dev/null @@ -1,102 +0,0 @@ - - -
- -
- - - - - - - - - - - - - -
- {{ 'common.name' | translate }} - -
- {{ element.resource.name }} - - - - - -
-
-
- - - -
-
- - -
-
- - diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.scss b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts b/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts deleted file mode 100644 index 1453a59d55..0000000000 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { UIPageChangeEvent, handleTablePageEvent } from '@oort-front/ui'; -import { - Resource, - ResourceQueryResponse, - ResourcesQueryResponse, - UnsubscribeComponent, - ResourceRecordsNodesQueryResponse, -} from '@oort-front/shared'; -import { - animate, - state, - style, - transition, - trigger, -} from '@angular/animations'; -import { Apollo, QueryRef } from 'apollo-angular'; -import { takeUntil, firstValueFrom } from 'rxjs'; -import { GET_RESOURCE, GET_RESOURCES } from './graphql/queries'; -import { CONVERT_RESOURCE_RECORDS } from './graphql/mutations'; -import { updateQueryUniqueValues } from '../../../utils/update-queries'; - -/** Default page size */ -const DEFAULT_PAGE_SIZE = 10; - -/** Interface of table elements */ -interface TableResourceElement { - resource: Resource; -} - -/** Interface of conversion form */ -interface ConversionFormValues { - id: string; - initialType: string; - newType: string; - field: string; - popArray: string; - failedAction: string; -} - -/** - * Data conversion component - */ -@Component({ - selector: 'app-conversion', - templateUrl: './conversion.component.html', - styleUrls: ['./conversion.component.scss'], - animations: [ - trigger('detailExpand', [ - state('collapsed', style({ height: '0px', minHeight: '0' })), - state('expanded', style({ height: '*' })), - transition( - 'expanded <=> collapsed', - animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)') - ), - ]), - ], -}) -export class ConversionComponent - extends UnsubscribeComponent - implements OnInit -{ - // === PAGINATION === - public loading = true; // First load && pagination - public pageInfo = { - pageIndex: 0, - pageSize: DEFAULT_PAGE_SIZE, - length: 0, - endCursor: '', - }; - - // === SINGLE ELEMENT === - public updating = false; // Update of resource - public openedResource?: Resource; - - // === FILTERING === - public filter: any; - public filterLoading = false; - - // === TABLE ELEMENTS === - private resourcesQuery!: QueryRef; - public displayedColumns: string[] = ['name']; - public resources = new Array(); - public cachedResources: Resource[] = []; - - private conversionFormValues: ConversionFormValues = { - id: '', - initialType: '', - newType: '', - field: '', - popArray: '', - failedAction: '', - }; - - /** - * Resource tab of Role Summary component. - * - * @param apollo Apollo client service - */ - constructor(private apollo: Apollo) { - super(); - } - - /** Load the resources. */ - ngOnInit(): void { - this.resourcesQuery = this.apollo.watchQuery({ - query: GET_RESOURCES, - variables: { - first: DEFAULT_PAGE_SIZE, - sortField: 'name', - sortOrder: 'asc', - }, - }); - - this.resourcesQuery.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(({ data, loading }) => { - this.updateValues(data, loading); - }); - } - - /** - * Custom TrackByFunction to compute the identity of items in an iterable, so when - * updating fields the scroll don't get back to the beginning of the table. - * - * @param index index of the item in the table - * @param item item table - * @returns unique value for all unique inputs - */ - public getUniqueIdentifier(index: number, item: any): any { - return item.resource.id; - } - - /** - * Update resources query. - * - * @param refetch erase previous query results - */ - private fetchResources(refetch?: boolean): void { - this.updating = true; - if (refetch) { - this.cachedResources = []; - this.pageInfo.pageIndex = 0; - this.resourcesQuery.refetch({ - first: this.pageInfo.pageSize, - filter: this.filter, - afterCursor: null, - }); - } else { - this.loading = true; - this.resourcesQuery - .fetchMore({ - variables: { - first: this.pageInfo.pageSize, - filter: this.filter, - afterCursor: this.pageInfo.endCursor, - }, - }) - .then((results) => this.updateValues(results.data, results.loading)); - } - } - - /** - * Handles page event. - * - * @param e page event. - */ - onPage(e: UIPageChangeEvent): void { - const cachedData = handleTablePageEvent( - e, - this.pageInfo, - this.cachedResources - ); - if (cachedData && cachedData.length === this.pageInfo.pageSize) { - //this.resources = this.resources = this.setTableElements(cachedData); - } else { - this.fetchResources(); - } - } - - /** - * Toggles the accordion for the clicked resource and fetches its forms - * - * @param resource The resource element for the resource to be toggled - */ - toggleResource(resource: Resource): void { - if (resource.id === this.openedResource?.id) { - this.openedResource = undefined; - } else { - this.updating = true; - this.apollo - .query({ - query: GET_RESOURCE, - variables: { - id: resource.id, - }, - }) - .pipe(takeUntil(this.destroy$)) - .subscribe(({ data }) => { - if (data.resource) { - this.openedResource = data.resource; - } - this.updating = false; - }); - } - } - - /** - * Serialize single table element from resource - * - * @param resource resource to serialize - * @returns serialized element - */ - private setTableElement(resource: Resource): TableResourceElement { - return { - resource, - }; - } - - /** - * Serialize list of table elements from resource - * - * @param resources resources to serialize - * @returns serialized elements - */ - private setTableElements(resources: Resource[]): TableResourceElement[] { - return resources.map((x: Resource) => this.setTableElement(x)); - } - - /** - * Update resource data value - * - * @param data query response data - * @param loading loading status - */ - private updateValues(data: ResourcesQueryResponse, loading: boolean) { - const mappedValues = data.resources?.edges?.map((x) => x.node); - this.cachedResources = updateQueryUniqueValues( - this.cachedResources, - mappedValues - ); - this.resources = this.setTableElements( - this.cachedResources.slice( - this.pageInfo.pageSize * this.pageInfo.pageIndex, - this.pageInfo.pageSize * (this.pageInfo.pageIndex + 1) - ) - ); - this.pageInfo.length = data.resources.totalCount; - this.pageInfo.endCursor = data.resources.pageInfo.endCursor; - this.loading = loading; - this.updating = loading; - this.filterLoading = false; - } - - /** - * Get the values from the conversion form - * - * @param conversionFormValues conversion form values - */ - public getConversionFormValues(conversionFormValues: any) { - this.conversionFormValues = conversionFormValues; - this.onConvert(this.openedResource as Resource, this.conversionFormValues); - } - - /** - * Convert the resource records - * - * @param resource resource the records are from - * @param conversionForm conversion form - */ - public async onConvert( - resource: Resource, - conversionForm: any - ): Promise { - console.log('conversionForm', conversionForm); - const promises: Promise[] = []; - promises.push( - firstValueFrom( - this.apollo.mutate({ - mutation: CONVERT_RESOURCE_RECORDS, - variables: { - id: resource.id, - initialType: conversionForm['selectedType'], - newType: conversionForm['selectedConvertibleType'], - field: conversionForm['selectedField'], - popArray: conversionForm['selectedPopArrayAction'], - failedAction: conversionForm['selectedFailedConversionAction'], - }, - }) - ) - ); - Promise.all(promises).then((results) => { - console.log('results', results); - }); - } -} diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts new file mode 100644 index 0000000000..0c51d0d04a --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts @@ -0,0 +1,125 @@ +interface DataGenerationMap { + [key: string]: { + displayName: string; + source: string; + options?: any[] | null; + }; +} + +export const dataGenerationMap: DataGenerationMap = { + text: { + displayName: 'Text/Long Text/Month/Password/Range/Week', + source: '', + options: [ + { action: 'sentence', desc: 'Random sentence' }, + { action: 'month', desc: 'Random month' }, + { action: 'password', desc: 'Random password' }, + { action: 'range', desc: 'Random range (0 to 100)' }, + { action: 'week', desc: 'Random week' }, + ], + }, + radiogroup: { + displayName: 'Radio Button Group', + source: 'Random choice of question choices', + }, + checkbox: { + displayName: 'Checkboxes', + source: 'Random choice of question choices', + }, + dropdown: { + displayName: 'Dropdown', + source: 'Random choice of question choices', + }, + boolean: { + displayName: 'Yes/No (Boolean)', + source: 'Randomly true or false', + }, + file: { + displayName: 'File Upload', + source: '', + }, + tagbox: { + displayName: 'Multi-Select Dropdown', + source: 'Random choice of question choices', + }, + multipletext: { + displayName: 'Multiple Text', + source: 'Random line for each text field', + }, + matrix: { + displayName: 'Single-Select Matrix', + source: 'Random selection for each row', + }, + matrixdropdown: { + displayName: 'Multi-Select Matrix', + source: 'Random generation for each column type for each row', + }, + matrixdynamic: { + displayName: 'Dynamic Matrix', + source: 'Random generation for each column type for each row', + }, + expression: { + displayName: 'Expression (read-only)', + source: '', + }, + resource: { + displayName: 'Resource', + source: 'Random record from the resource', + }, + resources: { + displayName: 'Resources', + source: 'Random records from the resource', + }, + owner: { + displayName: 'Owner', + source: 'Random roles', + }, + users: { + displayName: 'Users', + source: 'Random users', + }, + geospatial: { + displayName: 'Geospatial', + source: 'Random location', + }, + html: { + displayName: 'HTML', + source: '', + }, + image: { + displayName: 'Image', + source: '', + }, + color: { + displayName: 'Color', + source: 'Random color', + }, + date: { + displayName: 'Date', + source: 'Random date', + }, + 'datetime-local': { + displayName: 'Date and Time', + source: 'Random date and time', + }, + email: { + displayName: 'Email', + source: 'Random email', + }, + numeric: { + displayName: 'Number', + source: 'Random number', + }, + tel: { + displayName: 'Phone Number', + source: 'Random phone number', + }, + time: { + displayName: 'Time', + source: 'Random time', + }, + url: { + displayName: 'URL', + source: 'Random URL', + }, +}; diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html new file mode 100644 index 0000000000..2dfd2b6d32 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html @@ -0,0 +1,138 @@ + +
+
+

Fields

+ + + + Select all + + +
+ + +
+
+ + + {{ field.name }} + + + +
+
+
+ Field type: {{ getDisplayName(field.type) }} +
+
+ Generation source: +
+ {{ getGenerationSource(field.type) }} +
+ + + {{ option.desc }} + + +
+
+ +
+ + Set default + + +
+
+ +
+
+ +
+ Field type: {{ getDisplayName(field.type) }} +
+
+ Values for this field type cannot be generated. +
+
+
+
+
+
+ +
+ + + Generate + +
+
+
+
{{ dataGenerationForm.value | json }}
+
diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.scss b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.scss new file mode 100644 index 0000000000..39a4668069 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.scss @@ -0,0 +1,7 @@ +::ng-deep .accordion-item span { + @apply flex w-full; +} + +.field-type { + border-right: 1px solid #e0e0e0; +} diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.spec.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.spec.ts new file mode 100644 index 0000000000..d0e75a24ed --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DataGenerationFieldsComponent } from './data-generation-fields.component'; + +describe('DataGenerationFieldsComponent', () => { + let component: DataGenerationFieldsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DataGenerationFieldsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(DataGenerationFieldsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts new file mode 100644 index 0000000000..de03d2efff --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts @@ -0,0 +1,281 @@ +import { + AfterViewInit, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, +} from '@angular/core'; +import { + Form, + FormQueryResponse, + UnsubscribeComponent, +} from '@oort-front/shared'; +import { dataGenerationMap } from './data-generation-fields-type-mapping'; +import { + FormBuilder, + FormGroup, + FormControl, + Validators, + FormArray, +} from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { Apollo, QueryRef } from 'apollo-angular'; +import { GET_FORM_STRUCTURE } from './graphql/queries'; +import { GENERATE_RECORDS } from './graphql/mutations'; +import { Model, SurveyModel } from 'survey-core'; +import { FormBuilderService } from './../../../../../../../../libs/shared/src/lib/services/form-builder/form-builder.service'; + +import { takeUntil, firstValueFrom } from 'rxjs'; +import { get, indexOf, set } from 'lodash'; +import { F, V } from '@angular/cdk/keycodes'; +import { GenerateRecordsMutationResponse } from './../../../../../../../../libs/shared/src/lib/models/record.model'; +import { setTime } from '@progress/kendo-angular-dateinputs/util'; +import { SnackbarService } from '@oort-front/ui'; + +/** Conversion fields component */ +@Component({ + selector: 'app-data-generation-fields', + templateUrl: './data-generation-fields.component.html', + styleUrls: ['./data-generation-fields.component.scss'], +}) +export class DataGenerationFieldsComponent + extends UnsubscribeComponent + implements AfterViewInit, OnChanges +{ + @Input() formId!: string; + @Input() disabled = false; + + @Output() generateEvent = new EventEmitter(); + + public fields: any[] = []; + + private formStructureQuery!: QueryRef; + private formStructure: any = {}; + + public dataGenerationForm!: FormGroup; + + public survey: Model = new Model(); + + public accordionItemExpanded: number = -1; + + public loading = false; + + public isChecked = false; + + /** Emit changes applied to the settings */ + // eslint-disable-next-line @angular-eslint/no-output-native + @Output() change: EventEmitter = new EventEmitter(); + + /** + * Data studio component + * + * @param apollo Apollo client service + */ + constructor( + private apollo: Apollo, + private fb: FormBuilder, + private translate: TranslateService, + private formBuilderService: FormBuilderService, + private snackBar: SnackbarService + ) { + super(); + } + + ngAfterViewInit() { + this.loading = false; + } + + ngOnChanges() { + this.loading = true; + if (this.formId) { + this.formStructureQuery = this.apollo.watchQuery({ + query: GET_FORM_STRUCTURE, + variables: { + id: this.formId, + }, + }); + this.formStructureQuery.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(({ data, loading }) => { + this.dataGenerationForm = this.createDataGenerationForm(); + this.isChecked = false; + this.fields = + data.form.fields?.filter((field: any) => !field.generated) ?? []; + this.fields.forEach((field: any) => { + this.fieldsForm.push(this.createFieldForm()); + this.fieldsForm.controls[indexOf(this.fields, field)].patchValue({ + field: field.name, + setDefault: false, + include: false, + option: dataGenerationMap[field.type].options?.[0].action, + }); + this.formStructure = data.form.structure; + }); + this.loading = loading; + }); + } else { + this.loading = false; + } + } + + /** + * Get the display name in the conversion map + * + * @param type The type + * @returns The type display name + */ + public getDisplayName(type: string): string { + if (!dataGenerationMap[type]) { + return type; + } + return dataGenerationMap[type].displayName; + } + + /** + * Get the display name in the conversion map + * + * @param type The type + * @returns The type display name + */ + public getGenerationSource(type: string): string { + if (!dataGenerationMap[type]) { + return ''; + } + return dataGenerationMap[type].source; + } + + /** + * Get the display name in the conversion map + * + * @param type The type + * @returns The type display name + */ + public getGenerationOptions(type: string): any { + if (!dataGenerationMap[type]) { + return ''; + } + return dataGenerationMap[type].options; + } + + /** + * onClick event for the button + */ + public onClick() { + this.generateData(); + } + + public onAccordionItemOpen(index: number) { + this.survey = this.formBuilderService.createSurvey( + this.getSingleFieldSurveyStructure(this.fields[index].name) + ); + // Resetting the "default" value on survey in case its closed and opened again + if (this.fieldsForm.controls[index].get('setDefault')?.value) { + this.survey.setValue( + this.fields[index].name, + this.fieldsForm.controls[index].get('default')?.value + ); + } + // Subscribe the fieldsForm "default" control to the survey value change + this.survey.onValueChanged.add((sender, options) => { + this.fieldsForm.controls[index].patchValue({ + default: options.value, + }); + }); + } + + /** + * Create the dataGeneration form + * + * @returns the dataGeneration form + */ + private createDataGenerationForm() { + return this.fb.group({ + fieldsForm: this.fb.array([]), + recordsNumber: new FormControl(null, Validators.required), + }); + } + + // Helper function to create a field FormGroup + private createFieldForm() { + return this.fb.group({ + field: new FormControl(null, Validators.required), + include: new FormControl(false, Validators.required), + setDefault: new FormControl(false, Validators.required), + default: new FormControl(), + option: new FormControl(), + }); + } + + private getSingleFieldSurveyStructure(fieldName: string): any { + let resultStructure: any = {}; + + function traverse(structure: any) { + if (structure && typeof structure === 'object') { + if (structure.hasOwnProperty('name') && structure.name === fieldName) { + resultStructure = structure; + } + + for (const key in structure) { + if (structure.hasOwnProperty(key)) { + traverse(structure[key]); + } + } + } + } + + traverse(JSON.parse(this.formStructure ?? '')); + + return { + pages: [ + { + name: 'page1', + elements: [resultStructure], + }, + ], + showQuestionNumbers: 'off', + }; + } + + private async generateData(): Promise { + this.loading = true; + const promises: Promise[] = []; + promises.push( + firstValueFrom( + this.apollo.mutate({ + mutation: GENERATE_RECORDS, + variables: { + form: this.formId, + data: this.dataGenerationForm.value, + }, + }) + ) + ); + Promise.all(promises).then(() => { + this.loading = false; + this.snackBar.openSnackBar( + this.dataGenerationForm.value.recordsNumber + + ' ' + + this.translate.instant('common.notifications.dataGenerated') + ); + }); + } + + get fieldsForm() { + return this.dataGenerationForm.get('fieldsForm') as FormArray; + } + + public selectAll() { + if (this.isChecked) { + this.fieldsForm.controls.forEach((control) => { + control.patchValue({ include: true }); + }); + } else { + this.fieldsForm.controls.forEach((control) => { + control.patchValue({ include: false }); + }); + } + } +} diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/mutations.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/mutations.ts new file mode 100644 index 0000000000..046b59fe6d --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/mutations.ts @@ -0,0 +1,12 @@ +import { gql } from 'apollo-angular'; + +/** Graphql query for generating records */ +export const GENERATE_RECORDS = gql` + mutation generateRecords($form: ID!, $data: JSON!) { + generateRecords(form: $form, data: $data) { + id + createdAt + modifiedAt + } + } +`; diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/queries.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/queries.ts new file mode 100644 index 0000000000..5134b0f4fa --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/queries.ts @@ -0,0 +1,58 @@ +import { gql } from 'apollo-angular'; + +// === GET RECORD BY ID === +/** Graphql request for getting a record by its id */ +export const GET_RECORD_BY_ID = gql` + query GetRecordById($id: ID!) { + record(id: $id) { + id + incrementalId + createdAt + modifiedAt + createdBy { + name + } + modifiedBy { + name + } + data + form { + id + name + queryName + structure + fields + metadata { + name + automated + canSee + canUpdate + } + } + } + } +`; + +// === GET FORM STRUCTURE === + +/** Graphql request for getting the structure of a form by its id */ +export const GET_FORM_STRUCTURE = gql` + query GetFormStructure($id: ID!) { + form(id: $id) { + id + name + structure + fields + metadata { + name + automated + canSee + canUpdate + } + resource { + id + name + } + } + } +`; diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-routing.module.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio-routing.module.ts similarity index 62% rename from apps/back-office/src/app/dashboard/pages/conversion/conversion-routing.module.ts rename to apps/back-office/src/app/dashboard/pages/data-studio/data-studio-routing.module.ts index 456feeb709..37f1c98185 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion-routing.module.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio-routing.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { ConversionComponent } from './conversion.component'; +import { DataStudioComponent } from './data-studio.component'; /** * Routes of conversion module. @@ -8,15 +8,15 @@ import { ConversionComponent } from './conversion.component'; const routes: Routes = [ { path: '', - component: ConversionComponent, + component: DataStudioComponent, }, ]; /** - * Conversion routing module. + * DataStudio routing module. */ @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) -export class ConversionRoutingModule {} +export class DataStudioRoutingModule {} diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.html b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.html new file mode 100644 index 0000000000..e86b739ef1 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.html @@ -0,0 +1,23 @@ +

Data Studio

+ + + Data Generation +
+

Select a form:

+
+
+ +
+
+
+ +
+
diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.scss b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.scss similarity index 100% rename from apps/back-office/src/app/dashboard/pages/conversion/conversion-fields/conversion-fields.component.scss rename to apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.scss diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.spec.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.spec.ts similarity index 52% rename from apps/back-office/src/app/dashboard/pages/conversion/conversion.component.spec.ts rename to apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.spec.ts index 1296f59f9b..b249dc09af 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion.component.spec.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.spec.ts @@ -1,17 +1,17 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ConversionComponent } from './conversion.component'; +import { DataStudioComponent } from './data-studio.component'; -describe('ConversionComponent', () => { - let component: ConversionComponent; - let fixture: ComponentFixture; +describe('DataStudioComponent', () => { + let component: DataStudioComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ConversionComponent], + imports: [DataStudioComponent], }).compileComponents(); - fixture = TestBed.createComponent(ConversionComponent); + fixture = TestBed.createComponent(DataStudioComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.ts new file mode 100644 index 0000000000..98f75625d5 --- /dev/null +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.ts @@ -0,0 +1,171 @@ +import { Component, OnInit } from '@angular/core'; +import { UIPageChangeEvent, handleTablePageEvent } from '@oort-front/ui'; +import { + Resource, + ResourceQueryResponse, + ResourcesQueryResponse, + UnsubscribeComponent, + ResourceRecordsNodesQueryResponse, + Form, + FormsQueryResponse, +} from '@oort-front/shared'; +import { + animate, + state, + style, + transition, + trigger, +} from '@angular/animations'; +import { Apollo, QueryRef } from 'apollo-angular'; +import { takeUntil, firstValueFrom } from 'rxjs'; +import { GET_RESOURCE, GET_RESOURCES, GET_FORM_NAMES } from './graphql/queries'; +import { CONVERT_RESOURCE_RECORDS } from './graphql/mutations'; +import { updateQueryUniqueValues } from '../../../utils/update-queries'; +import { FormBuilder } from '@angular/forms'; + +/** Items per page */ +const ITEMS_PER_PAGE = 10; + +/** Default page size */ +const DEFAULT_PAGE_SIZE = 10; + +/** Interface of table elements */ +interface TableResourceElement { + resource: Resource; +} + +/** Interface of table elements */ +interface TableFormElement { + form: Form; +} + +/** Interface of dataGeneration form */ +interface DataGenerationFormValues { + id: string; + initialType: string; + newType: string; + field: string; + popArray: string; + failedAction: string; +} + +/** + * Data Studio component + */ +@Component({ + selector: 'app-data-studio', + templateUrl: './data-studio.component.html', + styleUrls: ['./data-studio.component.scss'], + animations: [ + trigger('detailExpand', [ + state('collapsed', style({ height: '0px', minHeight: '0' })), + state('expanded', style({ height: '*' })), + transition( + 'expanded <=> collapsed', + animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)') + ), + ]), + ], +}) +export class DataStudioComponent + extends UnsubscribeComponent + implements OnInit +{ + // === PAGINATION === + public loading = true; // First load && pagination + public pageInfo = { + pageIndex: 0, + pageSize: DEFAULT_PAGE_SIZE, + length: 0, + endCursor: '', + }; + + // === SINGLE ELEMENT === + public updating = false; // Update of resource + public formId = ''; + + // === FILTERING === + public filter: any; + public filterLoading = false; + + // === TABLE ELEMENTS === + // private resourcesQuery!: QueryRef; + public formsQuery!: QueryRef; + public displayedColumns: string[] = ['name']; + // public resources = new Array(); + public forms = new Array(); + // public cachedResources: Resource[] = []; + public cachedForms: Form[] = []; + + private dataGenerationFormValues: DataGenerationFormValues = { + id: '', + initialType: '', + newType: '', + field: '', + popArray: '', + failedAction: '', + }; + + public selectedForm = this.fb.group({ + form: [''], + }); + + /** + * Data studio component + * + * @param apollo Apollo client service + */ + constructor(private apollo: Apollo, private fb: FormBuilder) { + super(); + } + + /** OnInit Hook. */ + ngOnInit(): void { + this.formsQuery = this.apollo.watchQuery({ + query: GET_FORM_NAMES, + variables: { + first: ITEMS_PER_PAGE, + sortField: 'name', + }, + }); + } + + /** + * Update query based on text search. + * + * @param search Search text from the graphql select + */ + onSearchChange(search: string): void { + const variables = this.formsQuery.variables; + this.formsQuery.refetch({ + ...variables, + filter: { + logic: 'and', + filters: [ + { + field: 'name', + operator: 'contains', + value: search, + }, + ], + }, + }); + } + + onSelectionChange(event: any): void { + this.formId = event; + } + + /** + * Get the values from the dataGeneration form + * + * @param dataGenerationFormValues dataGeneration form values + */ + public getDataGenerationFormValues(dataGenerationFormValues: any) { + this.dataGenerationFormValues = dataGenerationFormValues; + // this.onConvert( + // this.openedForm as Form, + // this.dataGenerationFormValues + // ); + } +} diff --git a/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.module.ts similarity index 56% rename from apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts rename to apps/back-office/src/app/dashboard/pages/data-studio/data-studio.module.ts index d5d9647139..a017d1ddf7 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/conversion.module.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.module.ts @@ -1,8 +1,9 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ConversionComponent } from './conversion.component'; +import { DataStudioComponent } from '../data-studio/data-studio.component'; import { ButtonModule, + CheckboxModule, DateModule, FormWrapperModule, IconModule, @@ -11,16 +12,22 @@ import { SelectOptionModule, SpinnerModule, TableModule, + TabsModule, TooltipModule, + ExpansionPanelModule, + GraphQLSelectModule, + RadioModule, + ToggleModule, } from '@oort-front/ui'; import { ListFilterComponent, SkeletonTableModule } from '@oort-front/shared'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { ConversionRoutingModule } from './conversion-routing.module'; -import { ConversionFieldsComponent } from './conversion-fields/conversion-fields.component'; +import { DataStudioRoutingModule } from './data-studio-routing.module'; +import { DataGenerationFieldsComponent } from './data-generation-fields/data-generation-fields.component'; +import { SurveyModule } from 'survey-angular-ui'; @NgModule({ - declarations: [ConversionComponent, ConversionFieldsComponent], + declarations: [DataStudioComponent, DataGenerationFieldsComponent], imports: [ CommonModule, TooltipModule, @@ -38,8 +45,15 @@ import { ConversionFieldsComponent } from './conversion-fields/conversion-fields TableModule, DateModule, ListFilterComponent, - ConversionRoutingModule, + DataStudioRoutingModule, SelectOptionModule, + TabsModule, + CheckboxModule, + ExpansionPanelModule, + GraphQLSelectModule, + RadioModule, + ToggleModule, + SurveyModule, ], }) -export class ConversionModule {} +export class DataStudioModule {} diff --git a/apps/back-office/src/app/dashboard/pages/conversion/graphql/mutations.ts b/apps/back-office/src/app/dashboard/pages/data-studio/graphql/mutations.ts similarity index 100% rename from apps/back-office/src/app/dashboard/pages/conversion/graphql/mutations.ts rename to apps/back-office/src/app/dashboard/pages/data-studio/graphql/mutations.ts diff --git a/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts b/apps/back-office/src/app/dashboard/pages/data-studio/graphql/queries.ts similarity index 74% rename from apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts rename to apps/back-office/src/app/dashboard/pages/data-studio/graphql/queries.ts index 529c551a0e..97d329fd4f 100644 --- a/apps/back-office/src/app/dashboard/pages/conversion/graphql/queries.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/graphql/queries.ts @@ -80,3 +80,33 @@ export const GET_RESOURCE = gql` } ${RESOURCE_FIELDS} `; + +/** Graphql query for getting form names */ +export const GET_FORM_NAMES = gql` + query GetFormNames( + $first: Int + $afterCursor: ID + $sortField: String + $filter: JSON + ) { + forms( + first: $first + afterCursor: $afterCursor + sortField: $sortField + filter: $filter + ) { + edges { + node { + id + name + } + cursor + } + totalCount + pageInfo { + hasNextPage + endCursor + } + } + } +`; diff --git a/libs/shared/src/lib/models/record.model.ts b/libs/shared/src/lib/models/record.model.ts index 2cdd9186a8..5408c73ad0 100644 --- a/libs/shared/src/lib/models/record.model.ts +++ b/libs/shared/src/lib/models/record.model.ts @@ -36,6 +36,11 @@ export interface AddRecordMutationResponse { addRecord: Record; } +/** Model for generate records graphql mutation response */ +export interface GenerateRecordsMutationResponse { + generateRecords: Record[]; +} + /** Model for edit record graphql mutation response */ export interface EditRecordMutationResponse { editRecord: Record; diff --git a/libs/ui/src/lib/expansion-panel/expansion-panel.component.ts b/libs/ui/src/lib/expansion-panel/expansion-panel.component.ts index 4a7f694426..ce2086ecfe 100644 --- a/libs/ui/src/lib/expansion-panel/expansion-panel.component.ts +++ b/libs/ui/src/lib/expansion-panel/expansion-panel.component.ts @@ -55,6 +55,8 @@ export class ExpansionPanelComponent implements AfterViewInit { @Input() index = 0; /** Event emitter for closing the panel. */ @Output() closePanel = new EventEmitter(); + /** Event emitter for opening the panel. */ + @Output() openPanel = new EventEmitter(); /** Reference to the accordion item. */ @ViewChild('accordionItem') accordionItem!: CdkAccordionItem; /** Reference to the content container. */ @@ -90,5 +92,6 @@ export class ExpansionPanelComponent implements AfterViewInit { onOpened() { this.renderer.removeClass(this.contentContainer.nativeElement, 'hidden'); this.renderer.addClass(this.contentContainer.nativeElement, 'block'); + this.openPanel.emit(true); } } From 8ccaf136b115e61c6e6681eb824dd59d2fe39bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:03:42 -0300 Subject: [PATCH 07/10] second working version, everything seems to be working --- .../add-form-modal.component.html | 4 +- .../src/app/dashboard/dashboard.component.ts | 2 +- .../data-generation-fields-type-mapping.ts | 72 +++++----- .../data-generation-fields.component.html | 49 ++++--- .../data-generation-fields.component.ts | 123 ++++++++---------- .../data-studio/data-studio.component.html | 10 +- .../data-studio/data-studio.component.ts | 85 ++---------- libs/shared/src/i18n/en.json | 83 +++++++----- libs/shared/src/i18n/fr.json | 83 +++++++----- libs/shared/src/i18n/test.json | 83 +++++++----- 10 files changed, 283 insertions(+), 311 deletions(-) diff --git a/apps/back-office/src/app/components/add-form-modal/add-form-modal.component.html b/apps/back-office/src/app/components/add-form-modal/add-form-modal.component.html index 56cb63af2e..c33f6b7c66 100644 --- a/apps/back-office/src/app/components/add-form-modal/add-form-modal.component.html +++ b/apps/back-office/src/app/components/add-form-modal/add-form-modal.component.html @@ -71,12 +71,10 @@

{{ 'models.form.new' | translate }}

{{ 'components.form.create.choice.template' | translate }} diff --git a/apps/back-office/src/app/dashboard/dashboard.component.ts b/apps/back-office/src/app/dashboard/dashboard.component.ts index f8e7134c1a..2ae04a537d 100644 --- a/apps/back-office/src/app/dashboard/dashboard.component.ts +++ b/apps/back-office/src/app/dashboard/dashboard.component.ts @@ -80,7 +80,7 @@ export class DashboardComponent extends UnsubscribeComponent { } if (this.ability.can('read', 'Resource')) { dataItems.push({ - name: this.translate.instant('common.datastudio.few'), + name: this.translate.instant('common.dataStudio.few'), path: '/datastudio', icon: 'build_circle', }); diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts index 0c51d0d04a..03701ca358 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts @@ -1,3 +1,4 @@ +/** Interface for the data generation map */ interface DataGenerationMap { [key: string]: { displayName: string; @@ -6,120 +7,127 @@ interface DataGenerationMap { }; } +/** Data generation map */ export const dataGenerationMap: DataGenerationMap = { text: { displayName: 'Text/Long Text/Month/Password/Range/Week', - source: '', + source: ' ', options: [ - { action: 'sentence', desc: 'Random sentence' }, - { action: 'month', desc: 'Random month' }, - { action: 'password', desc: 'Random password' }, - { action: 'range', desc: 'Random range (0 to 100)' }, - { action: 'week', desc: 'Random week' }, + { + action: 'sentence', + desc: 'common.dataStudio.dataGeneration.map.sentence', + }, + { action: 'month', desc: 'common.dataStudio.dataGeneration.map.month' }, + { + action: 'password', + desc: 'common.dataStudio.dataGeneration.map.password', + }, + { action: 'range', desc: 'common.dataStudio.dataGeneration.map.range' }, + { action: 'week', desc: 'common.dataStudio.dataGeneration.map.week' }, ], }, radiogroup: { displayName: 'Radio Button Group', - source: 'Random choice of question choices', + source: 'common.dataStudio.dataGeneration.map.radiogroup', }, checkbox: { displayName: 'Checkboxes', - source: 'Random choice of question choices', + source: 'common.dataStudio.dataGeneration.map.checkbox', }, dropdown: { displayName: 'Dropdown', - source: 'Random choice of question choices', + source: 'common.dataStudio.dataGeneration.map.dropdown', }, boolean: { displayName: 'Yes/No (Boolean)', - source: 'Randomly true or false', + source: 'common.dataStudio.dataGeneration.map.boolean', }, file: { displayName: 'File Upload', - source: '', + source: ' ', }, tagbox: { displayName: 'Multi-Select Dropdown', - source: 'Random choice of question choices', + source: 'common.dataStudio.dataGeneration.map.tagbox', }, multipletext: { displayName: 'Multiple Text', - source: 'Random line for each text field', + source: 'common.dataStudio.dataGeneration.map.multipletext', }, matrix: { displayName: 'Single-Select Matrix', - source: 'Random selection for each row', + source: 'common.dataStudio.dataGeneration.map.matrix', }, matrixdropdown: { displayName: 'Multi-Select Matrix', - source: 'Random generation for each column type for each row', + source: 'common.dataStudio.dataGeneration.map.matrixdropdown', }, matrixdynamic: { displayName: 'Dynamic Matrix', - source: 'Random generation for each column type for each row', + source: 'common.dataStudio.dataGeneration.map.matrixdynamic', }, expression: { displayName: 'Expression (read-only)', - source: '', + source: 'common.dataStudio.dataGeneration.map.expression', }, resource: { displayName: 'Resource', - source: 'Random record from the resource', + source: 'common.dataStudio.dataGeneration.map.resource', }, resources: { displayName: 'Resources', - source: 'Random records from the resource', + source: 'common.dataStudio.dataGeneration.map.resources', }, owner: { displayName: 'Owner', - source: 'Random roles', + source: 'common.dataStudio.dataGeneration.map.owner', }, users: { displayName: 'Users', - source: 'Random users', + source: 'common.dataStudio.dataGeneration.map.users', }, geospatial: { displayName: 'Geospatial', - source: 'Random location', + source: 'common.dataStudio.dataGeneration.map.geospatial', }, html: { displayName: 'HTML', - source: '', + source: ' ', }, image: { displayName: 'Image', - source: '', + source: ' ', }, color: { displayName: 'Color', - source: 'Random color', + source: 'common.dataStudio.dataGeneration.map.color', }, date: { displayName: 'Date', - source: 'Random date', + source: 'common.dataStudio.dataGeneration.map.date', }, 'datetime-local': { displayName: 'Date and Time', - source: 'Random date and time', + source: 'common.dataStudio.dataGeneration.map.datetimelocal', }, email: { displayName: 'Email', - source: 'Random email', + source: 'common.dataStudio.dataGeneration.map.email', }, numeric: { displayName: 'Number', - source: 'Random number', + source: 'common.dataStudio.dataGeneration.map.numeric', }, tel: { displayName: 'Phone Number', - source: 'Random phone number', + source: 'common.dataStudio.dataGeneration.map.tel', }, time: { displayName: 'Time', - source: 'Random time', + source: 'common.dataStudio.dataGeneration.map.time', }, url: { displayName: 'URL', - source: 'Random URL', + source: 'common.dataStudio.dataGeneration.map.url', }, }; diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html index 2dfd2b6d32..de508cd919 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html @@ -1,14 +1,12 @@
-

Fields

+

{{ 'common.field.few' | translate }}

Fields (change)="selectAll()" > - Select all + {{ 'common.dataStudio.dataGeneration.selectAll' | translate }}
@@ -54,10 +52,16 @@

Fields

>
- Field type: {{ getDisplayName(field.type) }} + {{ + 'common.dataStudio.dataGeneration.fieldType' | translate + }}{{ getDisplayName(field.type) }}
- Generation source: + {{ + 'common.dataStudio.dataGeneration.generationSource' + | translate + }}
{{ getGenerationSource(field.type) }}
@@ -67,10 +71,10 @@

Fields

formControlName="option" > - {{ option.desc }} + {{ option.desc | translate }}
@@ -78,14 +82,19 @@

Fields

- Set default + + {{ + 'common.dataStudio.dataGeneration.setDefault' | translate + }} + @@ -102,22 +111,30 @@

Fields

- Field type: {{ getDisplayName(field.type) }} + {{ + 'common.dataStudio.dataGeneration.fieldType' | translate + }}{{ getDisplayName(field.type) }}
- Values for this field type cannot be generated. + {{ + 'common.dataStudio.dataGeneration.generationNotPossible' + | translate + }}
-
+
@@ -129,7 +146,7 @@

Fields

[disabled]="!dataGenerationForm.valid || loading" class="mx-4" > - Generate + {{ 'common.dataStudio.dataGeneration.generate' | translate }}
diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts index de03d2efff..d463d35759 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts @@ -1,18 +1,5 @@ -import { - AfterViewInit, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnChanges, - OnInit, - Output, -} from '@angular/core'; -import { - Form, - FormQueryResponse, - UnsubscribeComponent, -} from '@oort-front/shared'; +import { Component, Input, OnChanges } from '@angular/core'; +import { FormQueryResponse, UnsubscribeComponent } from '@oort-front/shared'; import { dataGenerationMap } from './data-generation-fields-type-mapping'; import { FormBuilder, @@ -25,14 +12,11 @@ import { TranslateService } from '@ngx-translate/core'; import { Apollo, QueryRef } from 'apollo-angular'; import { GET_FORM_STRUCTURE } from './graphql/queries'; import { GENERATE_RECORDS } from './graphql/mutations'; -import { Model, SurveyModel } from 'survey-core'; +import { Model } from 'survey-core'; import { FormBuilderService } from './../../../../../../../../libs/shared/src/lib/services/form-builder/form-builder.service'; - import { takeUntil, firstValueFrom } from 'rxjs'; -import { get, indexOf, set } from 'lodash'; -import { F, V } from '@angular/cdk/keycodes'; +import { indexOf } from 'lodash'; import { GenerateRecordsMutationResponse } from './../../../../../../../../libs/shared/src/lib/models/record.model'; -import { setTime } from '@progress/kendo-angular-dateinputs/util'; import { SnackbarService } from '@oort-front/ui'; /** Conversion fields component */ @@ -41,36 +25,30 @@ import { SnackbarService } from '@oort-front/ui'; templateUrl: './data-generation-fields.component.html', styleUrls: ['./data-generation-fields.component.scss'], }) +/** Data generation class component */ export class DataGenerationFieldsComponent extends UnsubscribeComponent - implements AfterViewInit, OnChanges + implements OnChanges { + /** Form id input */ @Input() formId!: string; - @Input() disabled = false; - - @Output() generateEvent = new EventEmitter(); - - public fields: any[] = []; + /** Form */ private formStructureQuery!: QueryRef; private formStructure: any = {}; - public dataGenerationForm!: FormGroup; - public survey: Model = new Model(); - public accordionItemExpanded: number = -1; + /** Form fields */ + public fields: any[] = []; + /** Flags */ public loading = false; - public isChecked = false; - - /** Emit changes applied to the settings */ - // eslint-disable-next-line @angular-eslint/no-output-native - @Output() change: EventEmitter = new EventEmitter(); + public accordionItemExpanded: number = -1; /** - * Data studio component + * Data generation component constructor * * @param apollo Apollo client service */ @@ -84,10 +62,7 @@ export class DataGenerationFieldsComponent super(); } - ngAfterViewInit() { - this.loading = false; - } - + /** On changes hook */ ngOnChanges() { this.loading = true; if (this.formId) { @@ -112,8 +87,8 @@ export class DataGenerationFieldsComponent include: false, option: dataGenerationMap[field.type].options?.[0].action, }); - this.formStructure = data.form.structure; }); + this.formStructure = data.form.fields; this.loading = loading; }); } else { @@ -124,7 +99,7 @@ export class DataGenerationFieldsComponent /** * Get the display name in the conversion map * - * @param type The type + * @param type Field type * @returns The type display name */ public getDisplayName(type: string): string { @@ -135,29 +110,26 @@ export class DataGenerationFieldsComponent } /** - * Get the display name in the conversion map + * Get generation method to display * - * @param type The type - * @returns The type display name + * @param type Field type + * @returns Generation source for the type */ public getGenerationSource(type: string): string { if (!dataGenerationMap[type]) { return ''; } - return dataGenerationMap[type].source; + return this.translate.instant(dataGenerationMap[type].source ?? ' '); } /** - * Get the display name in the conversion map + * Get options for text type fields * - * @param type The type - * @returns The type display name + * @param type Field type + * @returns Options */ - public getGenerationOptions(type: string): any { - if (!dataGenerationMap[type]) { - return ''; - } - return dataGenerationMap[type].options; + public getTextGenerationOptions(): any { + return dataGenerationMap['text'].options; } /** @@ -167,6 +139,11 @@ export class DataGenerationFieldsComponent this.generateData(); } + /** + * Accordion item open handler + * + * @param index Item index + */ public onAccordionItemOpen(index: number) { this.survey = this.formBuilderService.createSurvey( this.getSingleFieldSurveyStructure(this.fields[index].name) @@ -198,7 +175,11 @@ export class DataGenerationFieldsComponent }); } - // Helper function to create a field FormGroup + /** + * Create field form which is going to be a formArray + * + * @returns the fieldForm + */ private createFieldForm() { return this.fb.group({ field: new FormControl(null, Validators.required), @@ -209,25 +190,16 @@ export class DataGenerationFieldsComponent }); } + /** + * Function to get a survey structure with 1 field given it's name + * + * @param fieldName Field name + * @returns The survey structure + */ private getSingleFieldSurveyStructure(fieldName: string): any { - let resultStructure: any = {}; - - function traverse(structure: any) { - if (structure && typeof structure === 'object') { - if (structure.hasOwnProperty('name') && structure.name === fieldName) { - resultStructure = structure; - } - - for (const key in structure) { - if (structure.hasOwnProperty(key)) { - traverse(structure[key]); - } - } - } - } - - traverse(JSON.parse(this.formStructure ?? '')); - + const resultStructure = this.formStructure.find( + (obj: any) => obj.name === fieldName + ); return { pages: [ { @@ -239,6 +211,9 @@ export class DataGenerationFieldsComponent }; } + /** + * Generate new record data + */ private async generateData(): Promise { this.loading = true; const promises: Promise[] = []; @@ -263,10 +238,14 @@ export class DataGenerationFieldsComponent }); } + /** Getter for the fieldsForm */ get fieldsForm() { return this.dataGenerationForm.get('fieldsForm') as FormArray; } + /** + * Function to handle Select all checkbox + */ public selectAll() { if (this.isChecked) { this.fieldsForm.controls.forEach((control) => { diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.html b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.html index e86b739ef1..ea78922d87 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.html +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.component.html @@ -1,9 +1,13 @@ -

Data Studio

+

{{ 'common.dataStudio.few' | translate }}

- Data Generation + {{ + 'common.dataStudio.dataGeneration.few' | translate + }}
-

Select a form:

+

+ {{ 'models.form.select' | translate }}: +

; + /** Form handling */ public formsQuery!: QueryRef; - public displayedColumns: string[] = ['name']; - // public resources = new Array(); - public forms = new Array(); - // public cachedResources: Resource[] = []; public cachedForms: Form[] = []; - - private dataGenerationFormValues: DataGenerationFormValues = { - id: '', - initialType: '', - newType: '', - field: '', - popArray: '', - failedAction: '', - }; - public selectedForm = this.fb.group({ form: [''], }); /** - * Data studio component + * Data studio component constructor * * @param apollo Apollo client service */ @@ -152,20 +93,12 @@ export class DataStudioComponent }); } - onSelectionChange(event: any): void { - this.formId = event; - } - /** - * Get the values from the dataGeneration form + * Form selection change handler * - * @param dataGenerationFormValues dataGeneration form values + * @param event */ - public getDataGenerationFormValues(dataGenerationFormValues: any) { - this.dataGenerationFormValues = dataGenerationFormValues; - // this.onConvert( - // this.openedForm as Form, - // this.dataGenerationFormValues - // ); + onSelectionChange(event: any): void { + this.formId = event; } } diff --git a/libs/shared/src/i18n/en.json b/libs/shared/src/i18n/en.json index 17bc97e612..3e11e53389 100644 --- a/libs/shared/src/i18n/en.json +++ b/libs/shared/src/i18n/en.json @@ -77,11 +77,6 @@ "one": "Column" }, "comma": "comma", - "conversion": { - "few": "Data Conversion", - "none": "No conversion", - "one": "Conversion" - }, "copy": "Copy", "create": "Create", "createdOn": "Created on", @@ -157,6 +152,51 @@ "none": "No dashboard", "one": "Dashboard" }, + "dataStudio": { + "dataGeneration": { + "few": "Data Generation", + "fieldType": "Field type: ", + "fieldsTooltip": "List of the fields from the form to generate data for. Select the fields you want to include by marking the checkboxes. You can also see the field type and the generation source and choose between multiple generation sources in some cases.", + "generate": "Generate", + "generationNotPossible": "Values for this field type cannot be generated.", + "generationSource": "Generation source: ", + "map": { + "boolean": "Randomly true or false", + "checkbox": "Random choices of question choices", + "color": "Random color", + "date": "Random date", + "datetimelocal": "Random date and time", + "dropdown": "Random choice of question choices", + "email": "Random email", + "expression": "Solved expression value", + "geospatial": "Random location", + "matrix": "Random selection for each row", + "matrixdropdown": "Random generation for each column type for each row", + "matrixdynamic": "Random generation for each column type for each row", + "month": "Random month", + "multipletext": "Random line for each text field", + "numeric": "Random number", + "owner": "Random roles from the preselected application", + "password": "Random password", + "radiogroup": "Random choice of question choices", + "range": "Random range (0 to 100)", + "resource": "Random record from the resource", + "resources": "Random records from the resource", + "sentence": "Random sentence", + "tagbox": "Random choices of question choices", + "tel": "Random phone number", + "time": "Random time", + "url": "Random URL", + "users": "Random users", + "week": "Random week" + }, + "recordsNumber": "Number of records (max: 50)", + "selectAll": "Select All", + "setDefault": "Set default", + "setDefaultTooltip": "Leave this option off to generate random data for the field. If you want to set a default value for the field, turn this option on and fill in the survey." + }, + "few": "Data Studio" + }, "delete": "Delete", "deleteObject": "Delete {{name}}", "deleted": "Deleted", @@ -234,6 +274,7 @@ "accessNotProvided": "No access provided to this {{type}}. {{error}}", "alreadyExists": "The {{type}} {{value}} already exists on this application.", "copiedToClipboard": "Dashboard link copied!", + "dataGenerated": "new record(s) successfully generated.", "dataNotRecovered": "Failure on data recovery", "dataRecovered": "The data has been recovered", "email": { @@ -530,6 +571,7 @@ }, "customStyling": "Custom Styling", "dashboard": { + "clearFields": "Clear fields", "editFilter": "Edit filter", "empty": "No content available. Wait for your administrator to build some.", "filter": { @@ -654,37 +696,6 @@ "delete": "Delete", "title": "Confirm" }, - "conversion": { - "actions": { - "all": "Keep all elements, split by comma", - "cancel": "Cancel conversion", - "delete": "Delete value and proceed", - "first": "Keep first element", - "ignore": "Ignore record and proceed", - "last": "Keep last element" - }, - "convertButton": "Convert", - "failedAction": { - "name": "Action for records that fail to convert", - "tooltip": "Select what will be done to records that fail to convert." - }, - "initialType": { - "name": "Initial field type", - "tooltip": "Initial type of the field to convert the records from." - }, - "newType": { - "name": "New field type", - "tooltip": "New type of the field to convert the records to. Select an initial type to list its possible conversions." - }, - "popArray": { - "name": "Action for popArray", - "tooltip": "This conversion transforms a field that is stored as an array to a field that is not. Select which element of the array will be kept." - }, - "selectResource": { - "name": "Action for selectResource", - "tooltip": "Select a resource to enforce the record to be related to or None to get the resource from the record automatically." - } - }, "customNotifications": { "edit": { "dataset": "Dataset", diff --git a/libs/shared/src/i18n/fr.json b/libs/shared/src/i18n/fr.json index ab142813ba..111de98c8c 100644 --- a/libs/shared/src/i18n/fr.json +++ b/libs/shared/src/i18n/fr.json @@ -77,11 +77,6 @@ "one": "Colonne" }, "comma": "virgule", - "conversion": { - "few": "Data Conversion", - "none": "No conversion", - "one": "Conversion" - }, "copy": "Copier", "create": "Créer", "createdOn": "Date de création", @@ -157,6 +152,51 @@ "none": "Aucun tableau de bord", "one": "Tableau de bord" }, + "dataStudio": { + "dataGeneration": { + "few": "Génération de données", + "fieldType": "Type de champ : ", + "fieldsTooltip": "Liste des champs du formulaire pour lesquels des données doivent être générées. Sélectionnez les champs que vous souhaitez inclure en cochant les cases correspondantes. Vous pouvez également voir le type de champ et la source de génération et choisir entre plusieurs sources de génération dans certains cas.", + "generate": "Générer", + "generationNotPossible": "Les valeurs de ce type de champ ne peuvent pas être générées.", + "generationSource": "Source de génération : ", + "map": { + "boolean": "Vrai ou faux au hasard", + "checkbox": "Choix aléatoire des questions", + "color": "Couleur aléatoire", + "date": "Date aléatoire", + "datetimelocal": "Date et heure aléatoires", + "dropdown": "Choix aléatoire des questions", + "email": "Courriel aléatoire", + "expression": "Valeur de l'expression résolue", + "geospatial": "Emplacement aléatoire", + "matrix": "Sélection aléatoire pour chaque ligne", + "matrixdropdown": "Génération aléatoire pour chaque type de colonne pour chaque ligne", + "matrixdynamic": "Génération aléatoire pour chaque type de colonne pour chaque ligne", + "month": "Mois aléatoire", + "multipletext": "Ligne aléatoire pour chaque champ de texte", + "numeric": "Nombre aléatoire", + "owner": "Rôles aléatoires à partir de l'application présélectionnée", + "password": "Mot de passe aléatoire", + "radiogroup": "Choix aléatoire des questions", + "range": "Plage aléatoire (0 à 100)", + "resource": "Enregistrement aléatoire de la ressource", + "resources": "Enregistrements aléatoires de la ressource", + "sentence": "Phrase aléatoire", + "tagbox": "Choix aléatoire des questions", + "tel": "Numéro de téléphone aléatoire", + "time": "Temps aléatoire", + "url": "URL aléatoire", + "users": "Utilisateurs aléatoires", + "week": "Semaine aléatoire" + }, + "recordsNumber": "Nombre d'enregistrements (max : 50)", + "selectAll": "Sélectionner tout", + "setDefault": "Définir la valeur par défaut", + "setDefaultTooltip": "Laissez cette option désactivée pour générer des données aléatoires pour le champ. Si vous souhaitez définir une valeur par défaut pour le champ, activez cette option et remplissez le questionnaire." + }, + "few": "Data Studio" + }, "delete": "Supprimer", "deleteObject": "Supprimer {{name}}", "deleted": "Supprimé", @@ -234,6 +274,7 @@ "accessNotProvided": "Pas d'accès fourni à cet objet de type {{type}}. {{error}}", "alreadyExists": "Cet objet {{type}} {{value}} existe déjà dans cette application.", "copiedToClipboard": "Lien du tableau de bord copié !", + "dataGenerated": "le(s) nouvel(aux) enregistrement(s) a (ont) été généré(s) avec succès.", "dataNotRecovered": "Échec de la récupération des données", "dataRecovered": "Les données ont été récupérées !", "email": { @@ -530,6 +571,7 @@ }, "customStyling": "Style personnalisé", "dashboard": { + "clearFields": "Supprimer les champs", "editFilter": "Modifier le filtre", "empty": "Aucun contenu n'est disponible. Veuillez attendre que votre administrateur en crée.", "filter": { @@ -654,37 +696,6 @@ "delete": "Supprimer", "title": "Confirmer" }, - "conversion": { - "actions": { - "all": "Conserver tous les éléments, séparés par une virgule", - "cancel": "Annuler la conversion", - "delete": "Supprimer la valeur et continuer", - "first": "Conserver le premier élément", - "ignore": "Ignorer l'enregistrement et poursuivre", - "last": "Conserver le dernier élément" - }, - "convertButton": "Convertir", - "failedAction": { - "name": "Mesures à prendre en cas d'échec de la conversion", - "tooltip": "Sélectionnez ce qui sera fait pour les enregistrements qui ne sont pas convertis." - }, - "initialType": { - "name": "Type de champ initial", - "tooltip": "Type initial du champ à partir duquel les enregistrements doivent être convertis." - }, - "newType": { - "name": "Nouveau type de champ", - "tooltip": "Nouveau type de champ vers lequel convertir les enregistrements. Sélectionnez un type initial pour obtenir la liste des conversions possibles." - }, - "popArray": { - "name": "Action pour popArray", - "tooltip": "Cette conversion transforme un champ stocké sous forme de tableau en un champ qui ne l'est pas. Sélectionnez l'élément du tableau qui sera conservé." - }, - "selectResource": { - "name": "Action pour selectResource", - "tooltip": "Sélectionnez une ressource à laquelle l'enregistrement doit être lié ou Aucune pour obtenir automatiquement la ressource à partir de l'enregistrement." - } - }, "customNotifications": { "edit": { "dataset": "Jeu de données", diff --git a/libs/shared/src/i18n/test.json b/libs/shared/src/i18n/test.json index d473709f0e..8d5a3456eb 100644 --- a/libs/shared/src/i18n/test.json +++ b/libs/shared/src/i18n/test.json @@ -77,11 +77,6 @@ "one": "******" }, "comma": "******", - "conversion": { - "few": "******", - "none": "******", - "one": "******" - }, "copy": "******", "create": "******", "createdOn": "******", @@ -157,6 +152,51 @@ "none": "******", "one": "******" }, + "dataStudio": { + "dataGeneration": { + "few": "******", + "fieldType": "******", + "fieldsTooltip": "******", + "generate": "******", + "generationNotPossible": "******", + "generationSource": "******", + "map": { + "boolean": "******", + "checkbox": "******", + "color": "******", + "date": "******", + "datetimelocal": "******", + "dropdown": "******", + "email": "******", + "expression": "******", + "geospatial": "******", + "matrix": "******", + "matrixdropdown": "******", + "matrixdynamic": "******", + "month": "******", + "multipletext": "******", + "numeric": "******", + "owner": "******", + "password": "******", + "radiogroup": "******", + "range": "******", + "resource": "******", + "resources": "******", + "sentence": "******", + "tagbox": "******", + "tel": "******", + "time": "******", + "url": "******", + "users": "******", + "week": "******" + }, + "recordsNumber": "******", + "selectAll": "******", + "setDefault": "******", + "setDefaultTooltip": "******" + }, + "few": "******" + }, "delete": "******", "deleteObject": "****** {{name}}", "deleted": "******", @@ -234,6 +274,7 @@ "accessNotProvided": "****** {{type}} ****** {{error}}", "alreadyExists": "****** {{type}} ****** {{value}} ******", "copiedToClipboard": "******", + "dataGenerated": "******", "dataNotRecovered": "******", "dataRecovered": "******", "email": { @@ -530,6 +571,7 @@ }, "customStyling": "******", "dashboard": { + "clearFields": "******", "editFilter": "******", "empty": "******", "filter": { @@ -654,37 +696,6 @@ "delete": "******", "title": "******" }, - "conversion": { - "actions": { - "all": "******", - "cancel": "******", - "delete": "******", - "first": "******", - "ignore": "******", - "last": "******" - }, - "convertButton": "******", - "failedAction": { - "name": "******", - "tooltip": "******" - }, - "initialType": { - "name": "******", - "tooltip": "******" - }, - "newType": { - "name": "******", - "tooltip": "******" - }, - "popArray": { - "name": "******", - "tooltip": "******" - }, - "selectResource": { - "name": "******", - "tooltip": "******" - } - }, "customNotifications": { "edit": { "dataset": "******", From 8970f4d8395187b8452b1d62cdaff1c4d303c399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:54:49 -0300 Subject: [PATCH 08/10] Switch from using form.fields to using form.structure --- .../data-generation-fields-type-mapping.ts | 43 +++++++----- .../data-generation-fields.component.html | 16 +---- .../data-generation-fields.component.ts | 67 +++++++++++-------- libs/shared/src/i18n/en.json | 2 + libs/shared/src/i18n/fr.json | 2 + libs/shared/src/i18n/test.json | 2 + 6 files changed, 74 insertions(+), 58 deletions(-) diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts index 03701ca358..a81d8a640e 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts @@ -10,21 +10,28 @@ interface DataGenerationMap { /** Data generation map */ export const dataGenerationMap: DataGenerationMap = { text: { - displayName: 'Text/Long Text/Month/Password/Range/Week', - source: ' ', - options: [ - { - action: 'sentence', - desc: 'common.dataStudio.dataGeneration.map.sentence', - }, - { action: 'month', desc: 'common.dataStudio.dataGeneration.map.month' }, - { - action: 'password', - desc: 'common.dataStudio.dataGeneration.map.password', - }, - { action: 'range', desc: 'common.dataStudio.dataGeneration.map.range' }, - { action: 'week', desc: 'common.dataStudio.dataGeneration.map.week' }, - ], + displayName: 'Text', + source: 'common.dataStudio.dataGeneration.map.text', + }, + comment: { + displayName: 'Comment', + source: 'common.dataStudio.dataGeneration.map.text', + }, + month: { + displayName: 'Month', + source: 'common.dataStudio.dataGeneration.map.month', + }, + password: { + displayName: 'Password', + source: 'common.dataStudio.dataGeneration.map.password', + }, + range: { + displayName: 'Range', + source: 'common.dataStudio.dataGeneration.map.range', + }, + week: { + displayName: 'Week', + source: 'common.dataStudio.dataGeneration.map.week', }, radiogroup: { displayName: 'Radio Button Group', @@ -114,7 +121,7 @@ export const dataGenerationMap: DataGenerationMap = { displayName: 'Email', source: 'common.dataStudio.dataGeneration.map.email', }, - numeric: { + number: { displayName: 'Number', source: 'common.dataStudio.dataGeneration.map.numeric', }, @@ -130,4 +137,8 @@ export const dataGenerationMap: DataGenerationMap = { displayName: 'URL', source: 'common.dataStudio.dataGeneration.map.url', }, + paneldynamic: { + displayName: 'Panel Dynamic', + source: 'common.dataStudio.dataGeneration.map.paneldynamic', + }, }; diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html index de508cd919..b2ba2d6f24 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html @@ -55,7 +55,7 @@

{{ 'common.field.few' | translate }}

{{ 'common.dataStudio.dataGeneration.fieldType' | translate }}{{ getDisplayName(field.type) }} + >{{ getDisplayName(field) }}
{{ @@ -63,20 +63,8 @@

{{ 'common.field.few' | translate }}

| translate }}
- {{ getGenerationSource(field.type) }} + {{ getGenerationSource(field) }}
- - - {{ option.desc | translate }} - -
diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts index d463d35759..f642b62ef2 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts @@ -18,7 +18,6 @@ import { takeUntil, firstValueFrom } from 'rxjs'; import { indexOf } from 'lodash'; import { GenerateRecordsMutationResponse } from './../../../../../../../../libs/shared/src/lib/models/record.model'; import { SnackbarService } from '@oort-front/ui'; - /** Conversion fields component */ @Component({ selector: 'app-data-generation-fields', @@ -35,7 +34,7 @@ export class DataGenerationFieldsComponent /** Form */ private formStructureQuery!: QueryRef; - private formStructure: any = {}; + private form: any = {}; public dataGenerationForm!: FormGroup; public survey: Model = new Model(); @@ -77,18 +76,29 @@ export class DataGenerationFieldsComponent .subscribe(({ data, loading }) => { this.dataGenerationForm = this.createDataGenerationForm(); this.isChecked = false; + console.log( + JSON.parse(data.form.structure ?? '').pages.reduce( + (acc: any, page: any) => acc.concat(page.elements), + [] + ) + ); this.fields = - data.form.fields?.filter((field: any) => !field.generated) ?? []; + JSON.parse(data.form.structure ?? '') + .pages.reduce( + (acc: any, page: any) => acc.concat(page.elements), + [] + ) + ?.filter((field: any) => !field.generated) ?? []; this.fields.forEach((field: any) => { this.fieldsForm.push(this.createFieldForm()); this.fieldsForm.controls[indexOf(this.fields, field)].patchValue({ field: field.name, setDefault: false, include: false, - option: dataGenerationMap[field.type].options?.[0].action, + //option: dataGenerationMap[field.type].options?.[0].action, }); }); - this.formStructure = data.form.fields; + this.form = data.form; this.loading = loading; }); } else { @@ -99,37 +109,35 @@ export class DataGenerationFieldsComponent /** * Get the display name in the conversion map * - * @param type Field type + * @param field the Field * @returns The type display name */ - public getDisplayName(type: string): string { - if (!dataGenerationMap[type]) { - return type; + public getDisplayName(field: any): string { + if (!dataGenerationMap[field.type] && !dataGenerationMap[field.inputType]) { + return field.type; + } + if (dataGenerationMap[field.inputType]) { + return dataGenerationMap[field.inputType].displayName; } - return dataGenerationMap[type].displayName; + return dataGenerationMap[field.type].displayName; } /** * Get generation method to display * - * @param type Field type + * @param field the Field * @returns Generation source for the type */ - public getGenerationSource(type: string): string { - if (!dataGenerationMap[type]) { + public getGenerationSource(field: any): string { + if (!dataGenerationMap[field.type] && !dataGenerationMap[field.inputType]) { return ''; } - return this.translate.instant(dataGenerationMap[type].source ?? ' '); - } - - /** - * Get options for text type fields - * - * @param type Field type - * @returns Options - */ - public getTextGenerationOptions(): any { - return dataGenerationMap['text'].options; + if (dataGenerationMap[field.inputType]) { + return ( + this.translate.instant(dataGenerationMap[field.inputType].source) ?? '' + ); + } + return this.translate.instant(dataGenerationMap[field.type].source) ?? ''; } /** @@ -191,15 +199,18 @@ export class DataGenerationFieldsComponent } /** - * Function to get a survey structure with 1 field given it's name + * Recursive function to get a field structure from a nested JSON and return a single field survey structure * * @param fieldName Field name * @returns The survey structure */ private getSingleFieldSurveyStructure(fieldName: string): any { - const resultStructure = this.formStructure.find( - (obj: any) => obj.name === fieldName - ); + // Parses the structure, concatenates all fields from "elements" from all pages and finds the field by name + const resultStructure = JSON.parse(this.form.structure) + .pages.reduce((acc: any, page: any) => acc.concat(page.elements), []) + .find((obj: any) => obj.name === fieldName); + + // Returns a simple survey structure with one field return { pages: [ { diff --git a/libs/shared/src/i18n/en.json b/libs/shared/src/i18n/en.json index 3e11e53389..62d1272025 100644 --- a/libs/shared/src/i18n/en.json +++ b/libs/shared/src/i18n/en.json @@ -177,6 +177,7 @@ "multipletext": "Random line for each text field", "numeric": "Random number", "owner": "Random roles from the preselected application", + "paneldynamic": "Generate 1-5 entries for each panel", "password": "Random password", "radiogroup": "Random choice of question choices", "range": "Random range (0 to 100)", @@ -185,6 +186,7 @@ "sentence": "Random sentence", "tagbox": "Random choices of question choices", "tel": "Random phone number", + "text": "Random text", "time": "Random time", "url": "Random URL", "users": "Random users", diff --git a/libs/shared/src/i18n/fr.json b/libs/shared/src/i18n/fr.json index 111de98c8c..09a68504df 100644 --- a/libs/shared/src/i18n/fr.json +++ b/libs/shared/src/i18n/fr.json @@ -177,6 +177,7 @@ "multipletext": "Ligne aléatoire pour chaque champ de texte", "numeric": "Nombre aléatoire", "owner": "Rôles aléatoires à partir de l'application présélectionnée", + "paneldynamic": "Générez 1 à 5 entrées pour chaque panneau", "password": "Mot de passe aléatoire", "radiogroup": "Choix aléatoire des questions", "range": "Plage aléatoire (0 à 100)", @@ -185,6 +186,7 @@ "sentence": "Phrase aléatoire", "tagbox": "Choix aléatoire des questions", "tel": "Numéro de téléphone aléatoire", + "text": "Texte aléatoire", "time": "Temps aléatoire", "url": "URL aléatoire", "users": "Utilisateurs aléatoires", diff --git a/libs/shared/src/i18n/test.json b/libs/shared/src/i18n/test.json index 8d5a3456eb..74ceb981e5 100644 --- a/libs/shared/src/i18n/test.json +++ b/libs/shared/src/i18n/test.json @@ -177,6 +177,7 @@ "multipletext": "******", "numeric": "******", "owner": "******", + "paneldynamic": "******", "password": "******", "radiogroup": "******", "range": "******", @@ -185,6 +186,7 @@ "sentence": "******", "tagbox": "******", "tel": "******", + "text": "******", "time": "******", "url": "******", "users": "******", From 9034c01ead5e34f5bd89cf636970e40023b3d4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:09:00 -0300 Subject: [PATCH 09/10] Expressions run ok --- .../data-generation-fields-type-mapping.ts | 158 +++++++++--------- .../data-generation-fields.component.html | 76 ++++++++- .../data-generation-fields.component.ts | 59 ++++--- .../graphql/mutations.ts | 21 +++ .../pages/data-studio/data-studio.module.ts | 11 +- libs/shared/src/i18n/en.json | 6 + libs/shared/src/i18n/fr.json | 6 + libs/shared/src/i18n/test.json | 6 + 8 files changed, 241 insertions(+), 102 deletions(-) diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts index a81d8a640e..e423734cfa 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields-type-mapping.ts @@ -9,126 +9,122 @@ interface DataGenerationMap { /** Data generation map */ export const dataGenerationMap: DataGenerationMap = { - text: { - displayName: 'Text', - source: 'common.dataStudio.dataGeneration.map.text', + boolean: { + displayName: 'Yes/No (Boolean)', + source: 'common.dataStudio.dataGeneration.map.boolean', + }, + checkbox: { + displayName: 'Checkboxes', + source: 'common.dataStudio.dataGeneration.map.checkbox', + }, + color: { + displayName: 'Color', + source: 'common.dataStudio.dataGeneration.map.color', }, comment: { displayName: 'Comment', source: 'common.dataStudio.dataGeneration.map.text', }, - month: { - displayName: 'Month', - source: 'common.dataStudio.dataGeneration.map.month', - }, - password: { - displayName: 'Password', - source: 'common.dataStudio.dataGeneration.map.password', - }, - range: { - displayName: 'Range', - source: 'common.dataStudio.dataGeneration.map.range', - }, - week: { - displayName: 'Week', - source: 'common.dataStudio.dataGeneration.map.week', - }, - radiogroup: { - displayName: 'Radio Button Group', - source: 'common.dataStudio.dataGeneration.map.radiogroup', + date: { + displayName: 'Date', + source: 'common.dataStudio.dataGeneration.map.date', }, - checkbox: { - displayName: 'Checkboxes', - source: 'common.dataStudio.dataGeneration.map.checkbox', + 'datetime-local': { + displayName: 'Date and Time', + source: 'common.dataStudio.dataGeneration.map.datetimelocal', }, dropdown: { displayName: 'Dropdown', source: 'common.dataStudio.dataGeneration.map.dropdown', }, - boolean: { - displayName: 'Yes/No (Boolean)', - source: 'common.dataStudio.dataGeneration.map.boolean', + email: { + displayName: 'Email', + source: 'common.dataStudio.dataGeneration.map.email', + }, + expression: { + displayName: 'Expression (read-only)', + source: 'common.dataStudio.dataGeneration.map.expression', }, file: { displayName: 'File Upload', source: ' ', }, - tagbox: { - displayName: 'Multi-Select Dropdown', - source: 'common.dataStudio.dataGeneration.map.tagbox', + geospatial: { + displayName: 'Geospatial', + source: 'common.dataStudio.dataGeneration.map.geospatial', }, - multipletext: { - displayName: 'Multiple Text', - source: 'common.dataStudio.dataGeneration.map.multipletext', + html: { + displayName: 'HTML', + source: ' ', + }, + image: { + displayName: 'Image', + source: ' ', }, matrix: { displayName: 'Single-Select Matrix', source: 'common.dataStudio.dataGeneration.map.matrix', }, - matrixdropdown: { - displayName: 'Multi-Select Matrix', - source: 'common.dataStudio.dataGeneration.map.matrixdropdown', - }, matrixdynamic: { displayName: 'Dynamic Matrix', source: 'common.dataStudio.dataGeneration.map.matrixdynamic', }, - expression: { - displayName: 'Expression (read-only)', - source: 'common.dataStudio.dataGeneration.map.expression', + matrixdropdown: { + displayName: 'Multi-Select Matrix', + source: 'common.dataStudio.dataGeneration.map.matrixdropdown', }, - resource: { - displayName: 'Resource', - source: 'common.dataStudio.dataGeneration.map.resource', + month: { + displayName: 'Month', + source: 'common.dataStudio.dataGeneration.map.month', }, - resources: { - displayName: 'Resources', - source: 'common.dataStudio.dataGeneration.map.resources', + multipletext: { + displayName: 'Multiple Text', + source: 'common.dataStudio.dataGeneration.map.multipletext', + }, + number: { + displayName: 'Number', + source: 'common.dataStudio.dataGeneration.map.numeric', }, owner: { displayName: 'Owner', source: 'common.dataStudio.dataGeneration.map.owner', }, - users: { - displayName: 'Users', - source: 'common.dataStudio.dataGeneration.map.users', - }, - geospatial: { - displayName: 'Geospatial', - source: 'common.dataStudio.dataGeneration.map.geospatial', - }, - html: { - displayName: 'HTML', - source: ' ', + paneldynamic: { + displayName: 'Panel Dynamic', + source: 'common.dataStudio.dataGeneration.map.paneldynamic', }, - image: { - displayName: 'Image', - source: ' ', + password: { + displayName: 'Password', + source: 'common.dataStudio.dataGeneration.map.password', }, - color: { - displayName: 'Color', - source: 'common.dataStudio.dataGeneration.map.color', + radiogroup: { + displayName: 'Radio Button Group', + source: 'common.dataStudio.dataGeneration.map.radiogroup', }, - date: { - displayName: 'Date', - source: 'common.dataStudio.dataGeneration.map.date', + range: { + displayName: 'Range', + source: 'common.dataStudio.dataGeneration.map.range', }, - 'datetime-local': { - displayName: 'Date and Time', - source: 'common.dataStudio.dataGeneration.map.datetimelocal', + resource: { + displayName: 'Resource', + source: 'common.dataStudio.dataGeneration.map.resource', }, - email: { - displayName: 'Email', - source: 'common.dataStudio.dataGeneration.map.email', + resources: { + displayName: 'Resources', + source: 'common.dataStudio.dataGeneration.map.resources', }, - number: { - displayName: 'Number', - source: 'common.dataStudio.dataGeneration.map.numeric', + tagbox: { + displayName: 'Multi-Select Dropdown', + source: 'common.dataStudio.dataGeneration.map.tagbox', }, tel: { displayName: 'Phone Number', source: 'common.dataStudio.dataGeneration.map.tel', }, + text: { + displayName: 'Text', + source: 'common.dataStudio.dataGeneration.map.text', + }, time: { displayName: 'Time', source: 'common.dataStudio.dataGeneration.map.time', @@ -137,8 +133,12 @@ export const dataGenerationMap: DataGenerationMap = { displayName: 'URL', source: 'common.dataStudio.dataGeneration.map.url', }, - paneldynamic: { - displayName: 'Panel Dynamic', - source: 'common.dataStudio.dataGeneration.map.paneldynamic', + users: { + displayName: 'Users', + source: 'common.dataStudio.dataGeneration.map.users', + }, + week: { + displayName: 'Week', + source: 'common.dataStudio.dataGeneration.map.week', }, }; diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html index b2ba2d6f24..b2720c8dd0 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.html @@ -86,6 +86,81 @@

{{ 'common.field.few' | translate }}

" class="m-1" > +
+ + + +
+
+ + +
+
+
+ +
+
+ +
+
{{ 'common.field.few' | translate }}
-
{{ dataGenerationForm.value | json }}
diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts index f642b62ef2..d697752f4d 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts @@ -11,13 +11,17 @@ import { import { TranslateService } from '@ngx-translate/core'; import { Apollo, QueryRef } from 'apollo-angular'; import { GET_FORM_STRUCTURE } from './graphql/queries'; -import { GENERATE_RECORDS } from './graphql/mutations'; +import { GENERATE_RECORDS, EDIT_RECORD } from './graphql/mutations'; import { Model } from 'survey-core'; import { FormBuilderService } from './../../../../../../../../libs/shared/src/lib/services/form-builder/form-builder.service'; import { takeUntil, firstValueFrom } from 'rxjs'; import { indexOf } from 'lodash'; -import { GenerateRecordsMutationResponse } from './../../../../../../../../libs/shared/src/lib/models/record.model'; +import { + EditRecordMutationResponse, + GenerateRecordsMutationResponse, +} from './../../../../../../../../libs/shared/src/lib/models/record.model'; import { SnackbarService } from '@oort-front/ui'; +const TIMEOUT_DURATION = 1000; /** Conversion fields component */ @Component({ selector: 'app-data-generation-fields', @@ -194,7 +198,12 @@ export class DataGenerationFieldsComponent include: new FormControl(false, Validators.required), setDefault: new FormControl(false, Validators.required), default: new FormControl(), - option: new FormControl(), + minDate: new FormControl(), + maxDate: new FormControl(), + minNumber: new FormControl(), + maxNumber: new FormControl(), + minTime: new FormControl(), + maxTime: new FormControl(), }); } @@ -227,26 +236,38 @@ export class DataGenerationFieldsComponent */ private async generateData(): Promise { this.loading = true; - const promises: Promise[] = []; - promises.push( - firstValueFrom( - this.apollo.mutate({ - mutation: GENERATE_RECORDS, + const res = await firstValueFrom( + this.apollo.mutate({ + mutation: GENERATE_RECORDS, + variables: { + form: this.formId, + data: this.dataGenerationForm.value, + }, + }) + ); + const expressionSurvey = this.formBuilderService.createSurvey( + this.form.structure + ); + for (const record of res.data?.generateRecords ?? []) { + expressionSurvey.data = record.data; + await new Promise((resolve) => setTimeout(resolve, TIMEOUT_DURATION)); + await firstValueFrom( + this.apollo.mutate({ + mutation: EDIT_RECORD, variables: { - form: this.formId, - data: this.dataGenerationForm.value, + id: record.id, + data: expressionSurvey.data, }, }) - ) - ); - Promise.all(promises).then(() => { - this.loading = false; - this.snackBar.openSnackBar( - this.dataGenerationForm.value.recordsNumber + - ' ' + - this.translate.instant('common.notifications.dataGenerated') ); - }); + console.log(expressionSurvey.data); + } + this.loading = false; + this.snackBar.openSnackBar( + this.dataGenerationForm.value.recordsNumber + + ' ' + + this.translate.instant('common.notifications.dataGenerated') + ); } /** Getter for the fieldsForm */ diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/mutations.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/mutations.ts index 046b59fe6d..a26be18b3c 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/mutations.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/graphql/mutations.ts @@ -7,6 +7,27 @@ export const GENERATE_RECORDS = gql` id createdAt modifiedAt + data + } + } +`; + +export const EDIT_RECORD = gql` + mutation editRecord( + $id: ID! + $data: JSON + $version: ID + $template: ID + $display: Boolean + ) { + editRecord(id: $id, data: $data, version: $version, template: $template) { + id + data(display: $display) + createdAt + modifiedAt + createdBy { + name + } } } `; diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.module.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.module.ts index a017d1ddf7..93284b05a3 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.module.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-studio.module.ts @@ -4,7 +4,7 @@ import { DataStudioComponent } from '../data-studio/data-studio.component'; import { ButtonModule, CheckboxModule, - DateModule, + DateModule as UiDateModule, FormWrapperModule, IconModule, PaginatorModule, @@ -25,7 +25,9 @@ import { TranslateModule } from '@ngx-translate/core'; import { DataStudioRoutingModule } from './data-studio-routing.module'; import { DataGenerationFieldsComponent } from './data-generation-fields/data-generation-fields.component'; import { SurveyModule } from 'survey-angular-ui'; - +import { DateModule } from './../../../../../../../libs/shared/src/lib/pipes/date/date.module'; +import { InputsModule } from '@progress/kendo-angular-inputs'; +import { DateInputsModule } from '@progress/kendo-angular-dateinputs'; @NgModule({ declarations: [DataStudioComponent, DataGenerationFieldsComponent], imports: [ @@ -33,7 +35,7 @@ import { SurveyModule } from 'survey-angular-ui'; TooltipModule, PaginatorModule, TranslateModule, - DateModule, + UiDateModule, SkeletonTableModule, FormsModule, ReactiveFormsModule, @@ -54,6 +56,9 @@ import { SurveyModule } from 'survey-angular-ui'; RadioModule, ToggleModule, SurveyModule, + DateModule, + InputsModule, + DateInputsModule, ], }) export class DataStudioModule {} diff --git a/libs/shared/src/i18n/en.json b/libs/shared/src/i18n/en.json index 62d1272025..111cbea0e9 100644 --- a/libs/shared/src/i18n/en.json +++ b/libs/shared/src/i18n/en.json @@ -192,6 +192,12 @@ "users": "Random users", "week": "Random week" }, + "maxDate": "Max date (optional)", + "maxNumber": "Max number (optional)", + "maxTime": "Max time (optional)", + "minDate": "Min date (optional)", + "minNumber": "Min number (optional)", + "minTime": "Min time (optional)", "recordsNumber": "Number of records (max: 50)", "selectAll": "Select All", "setDefault": "Set default", diff --git a/libs/shared/src/i18n/fr.json b/libs/shared/src/i18n/fr.json index 09a68504df..3979a59e7a 100644 --- a/libs/shared/src/i18n/fr.json +++ b/libs/shared/src/i18n/fr.json @@ -192,6 +192,12 @@ "users": "Utilisateurs aléatoires", "week": "Semaine aléatoire" }, + "maxDate": "Date maximale (facultatif)", + "maxNumber": "Nombre maximal (facultatif)", + "maxTime": "Heure maximale (facultatif)", + "minDate": "Date minimale (facultatif)", + "minNumber": "Nombre minimal (facultatif)", + "minTime": "Heure minimale (facultatif)", "recordsNumber": "Nombre d'enregistrements (max : 50)", "selectAll": "Sélectionner tout", "setDefault": "Définir la valeur par défaut", diff --git a/libs/shared/src/i18n/test.json b/libs/shared/src/i18n/test.json index 74ceb981e5..e0617fb50f 100644 --- a/libs/shared/src/i18n/test.json +++ b/libs/shared/src/i18n/test.json @@ -192,6 +192,12 @@ "users": "******", "week": "******" }, + "maxDate": "******", + "maxNumber": "******", + "maxTime": "******", + "minDate": "******", + "minNumber": "******", + "minTime": "******", "recordsNumber": "******", "selectAll": "******", "setDefault": "******", From 2bdb985d5c7d316a40cc64f9beaafe650748a5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Tann=C3=BAs?= <39497117+brunotannus@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:15:14 -0300 Subject: [PATCH 10/10] Removed logs. Linting not done yet. --- .../data-generation-fields.component.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts index d697752f4d..03b5461544 100644 --- a/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts +++ b/apps/back-office/src/app/dashboard/pages/data-studio/data-generation-fields/data-generation-fields.component.ts @@ -80,12 +80,6 @@ export class DataGenerationFieldsComponent .subscribe(({ data, loading }) => { this.dataGenerationForm = this.createDataGenerationForm(); this.isChecked = false; - console.log( - JSON.parse(data.form.structure ?? '').pages.reduce( - (acc: any, page: any) => acc.concat(page.elements), - [] - ) - ); this.fields = JSON.parse(data.form.structure ?? '') .pages.reduce( @@ -260,7 +254,6 @@ export class DataGenerationFieldsComponent }, }) ); - console.log(expressionSurvey.data); } this.loading = false; this.snackBar.openSnackBar(