From 59a7284b9303f0aa0c0336fff0de2ea6241b4500 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Thu, 15 Aug 2024 17:51:43 +0300 Subject: [PATCH 1/6] Version Conflict dialog fixes --- .../interceptors/entity-conflict.interceptor.ts | 15 ++++++++++++--- .../entity/entity-details-panel.component.ts | 11 ++++++++--- .../entity-conflict-dialog.component.html | 10 ++++++---- .../entity-conflict-dialog.component.scss | 2 +- .../entity-conflict-dialog.component.ts | 4 ++++ .../src/assets/locale/locale.constant-en_US.json | 5 ++--- 6 files changed, 33 insertions(+), 14 deletions(-) diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts index 57ee28c779..538839347b 100644 --- a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -20,6 +20,7 @@ import { HttpEvent, HttpHandler, HttpInterceptor, + HttpParams, HttpRequest, HttpStatusCode } from '@angular/common/http'; @@ -32,6 +33,8 @@ import { import { HasId } from '@shared/models/base-data'; import { HasVersion } from '@shared/models/entity.models'; import { getInterceptorConfig } from './interceptor.util'; +import { isDefined } from '@core/utils'; +import { InterceptorConfig } from '@core/interceptors/interceptor-config'; @Injectable() export class EntityConflictInterceptor implements HttpInterceptor { @@ -67,8 +70,12 @@ export class EntityConflictInterceptor implements HttpInterceptor { return this.openConflictDialog(request.body, error.error.message).pipe( switchMap(result => { - if (result) { - return next.handle(this.updateRequestVersion(request)); + if (isDefined(result)) { + if (result) { + return next.handle(this.updateRequestVersion(request)); + } + (request.params as HttpParams & { interceptorConfig: InterceptorConfig }).interceptorConfig.ignoreErrors = true; + return next.handle(request); } return of(null); }) @@ -82,7 +89,9 @@ export class EntityConflictInterceptor implements HttpInterceptor { private openConflictDialog(entity: unknown & HasId & HasVersion, message: string): Observable { const dialogRef = this.dialog.open(EntityConflictDialogComponent, { - data: { message, entity } + disableClose: true, + data: { message, entity }, + panelClass: ['tb-fullscreen-dialog'], }); return dialogRef.afterClosed(); diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index b0a4998a08..6a1e8f5f47 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -40,11 +40,11 @@ import { UntypedFormGroup } from '@angular/forms'; import { EntityComponent } from './entity.component'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { EntityAction } from '@home/models/entity/entity-component.models'; -import { Observable, ReplaySubject, Subscription } from 'rxjs'; +import { Observable, of, ReplaySubject, Subscription } from 'rxjs'; import { MatTab, MatTabGroup } from '@angular/material/tabs'; import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; import { deepClone, mergeDeep } from '@core/utils'; -import { entityIdEquals } from '@shared/models/id/entity-id'; +import { catchError, take } from 'rxjs/operators'; @Component({ selector: 'tb-entity-details-panel', @@ -288,7 +288,12 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV editingEntity.additionalInfo = mergeDeep((this.editingEntity as any).additionalInfo, this.entityComponent.entityFormValue()?.additionalInfo); } - this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity).subscribe( + this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity) + .pipe( + take(1), + catchError(() => of(this.entity)) + ) + .subscribe( (entity) => { this.entity = entity; this.entityComponent.entity = entity; diff --git a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html index 502bc3381c..ba09311895 100644 --- a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.html @@ -16,7 +16,9 @@ --> -

{{ 'entity.version-conflict.label' | translate }}

+

+ {{ data.message }} +

- +
-
+
{{ 'widgets-bundle.export-widgets-bundle-widgets-prompt' | translate }}
diff --git a/ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.ts b/ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.ts index 025a2ddf4c..f012dc77fe 100644 --- a/ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.ts +++ b/ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.ts @@ -27,6 +27,7 @@ import { isDefinedAndNotNull } from '@core/utils'; export interface ExportWidgetsBundleDialogData { widgetsBundle: WidgetsBundle; includeBundleWidgetsInExport: boolean; + ignoreLoading?: boolean; } export interface ExportWidgetsBundleDialogResult { @@ -44,6 +45,8 @@ export class ExportWidgetsBundleDialogComponent extends DialogComponent, @@ -52,6 +55,7 @@ export class ExportWidgetsBundleDialogComponent extends DialogComponent) { super(store, router, dialogRef); this.widgetsBundle = data.widgetsBundle; + this.ignoreLoading = data.ignoreLoading; if (isDefinedAndNotNull(data.includeBundleWidgetsInExport)) { this.exportWidgetsFormControl.patchValue(data.includeBundleWidgetsInExport, {emitEvent: false}); } diff --git a/ui-ngx/src/app/shared/import-export/import-export.service.ts b/ui-ngx/src/app/shared/import-export/import-export.service.ts index 6ca925e9e9..682b6122f0 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.service.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.service.ts @@ -351,28 +351,7 @@ export class ImportExportService { forkJoin(tasks).subscribe({ next: ({includeBundleWidgetsInExport, widgetsBundle}) => { - this.dialog.open(ExportWidgetsBundleDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - widgetsBundle, - includeBundleWidgetsInExport - } - }).afterClosed().subscribe( - (result) => { - if (result) { - if (includeBundleWidgetsInExport !== result.exportWidgets) { - this.store.dispatch(new ActionPreferencesPutUserSettings({includeBundleWidgetsInExport: result.exportWidgets})); - } - if (result.exportWidgets) { - this.exportWidgetsBundleWithWidgetTypes(widgetsBundle); - } else { - this.exportWidgetsBundleWithWidgetTypeFqns(widgetsBundle); - } - } - } - ); + this.handleExportWidgetsBundle(widgetsBundle, includeBundleWidgetsInExport); }, error: (e) => { this.handleExportError(e, 'widgets-bundle.export-failed-error'); @@ -400,6 +379,9 @@ export class ImportExportService { })) .subscribe(ruleChainData => this.exportToPc(ruleChainData, entityData.name)); return; + case EntityType.WIDGETS_BUNDLE: + this.exportSelectedWidgetsBundle(entityData as WidgetsBundle); + return; case EntityType.DASHBOARD: preparedData = this.prepareDashboardExport(entityData as Dashboard); break; @@ -409,6 +391,43 @@ export class ImportExportService { this.exportToPc(preparedData, entityData.name); } + private exportSelectedWidgetsBundle(widgetsBundle: WidgetsBundle): void { + this.store.pipe(select(selectUserSettingsProperty( 'includeBundleWidgetsInExport'))).pipe(take(1)).subscribe({ + next: (includeBundleWidgetsInExport) => { + this.handleExportWidgetsBundle(widgetsBundle, includeBundleWidgetsInExport, true); + }, + error: (e) => { + this.handleExportError(e, 'widgets-bundle.export-failed-error'); + } + }); + } + + private handleExportWidgetsBundle(widgetsBundle: WidgetsBundle, includeBundleWidgetsInExport: boolean, ignoreLoading?: boolean): void { + this.dialog.open(ExportWidgetsBundleDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + widgetsBundle, + includeBundleWidgetsInExport, + ignoreLoading + } + }).afterClosed().subscribe( + (result) => { + if (result) { + if (includeBundleWidgetsInExport !== result.exportWidgets) { + this.store.dispatch(new ActionPreferencesPutUserSettings({includeBundleWidgetsInExport: result.exportWidgets})); + } + if (result.exportWidgets) { + this.exportWidgetsBundleWithWidgetTypes(widgetsBundle); + } else { + this.exportWidgetsBundleWithWidgetTypeFqns(widgetsBundle); + } + } + } + ); + } + private exportWidgetsBundleWithWidgetTypes(widgetsBundle: WidgetsBundle) { this.widgetService.exportBundleWidgetTypesDetails(widgetsBundle.id.id).subscribe({ next: (widgetTypesDetails) => { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 977de00ce7..3b60cd727e 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -41,7 +41,7 @@ import { isNotEmptyStr, mergeDeepIgnoreArray } from '@core/utils'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { HasTenantId } from '@shared/models/entity.models'; +import { HasTenantId, HasVersion } from '@shared/models/entity.models'; import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; @@ -199,7 +199,7 @@ export interface WidgetControllerDescriptor { actionSources?: {[actionSourceId: string]: WidgetActionSource}; } -export interface BaseWidgetType extends BaseData, HasTenantId { +export interface BaseWidgetType extends BaseData, HasTenantId, HasVersion { tenantId: TenantId; fqn: string; name: string; From 858bdfeea51473acd763f9c8ecdb7f97031939f7 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 19 Aug 2024 15:10:23 +0300 Subject: [PATCH 4/6] fixed double request on discard --- ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts index f39716dc5e..e34aca7007 100644 --- a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -75,7 +75,7 @@ export class EntityConflictInterceptor implements HttpInterceptor { return next.handle(this.updateRequestVersion(request)); } (request.params as HttpParams & { interceptorConfig: InterceptorConfig }).interceptorConfig.ignoreErrors = true; - return next.handle(request); + return throwError(() => new Error(error.error.message)); } return of(null); }) From 49da787fc409b7de088599489cae29294343c851 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 19 Aug 2024 16:59:12 +0300 Subject: [PATCH 5/6] UI: Change entity version conflict error handling --- .../entity-conflict.interceptor.ts | 2 +- .../dashboard-page.component.ts | 29 +++++++++++++++---- .../entity/entity-details-panel.component.ts | 10 +++++-- .../widget/widget-component.service.ts | 2 +- .../pages/widget/widget-editor.component.ts | 13 +++++---- 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts index e34aca7007..e4db59ec66 100644 --- a/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts @@ -75,7 +75,7 @@ export class EntityConflictInterceptor implements HttpInterceptor { return next.handle(this.updateRequestVersion(request)); } (request.params as HttpParams & { interceptorConfig: InterceptorConfig }).interceptorConfig.ignoreErrors = true; - return throwError(() => new Error(error.error.message)); + return throwError(() => error); } return of(null); }) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index f7efca5d40..109d5f014b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -86,7 +86,7 @@ import { Authority } from '@shared/models/authority.enum'; import { DialogService } from '@core/services/dialog.service'; import { EntityService } from '@core/http/entity.service'; import { AliasController } from '@core/api/alias-controller'; -import { BehaviorSubject, Observable, of, Subject, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, of, Subject, Subscription, throwError } from 'rxjs'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardService } from '@core/http/dashboard.service'; import { @@ -156,6 +156,7 @@ import { MoveWidgetsDialogComponent, MoveWidgetsDialogResult } from '@home/components/dashboard-page/layout/move-widgets-dialog.component'; +import { HttpStatusCode } from '@angular/common/http'; // @dynamic @Component({ @@ -1203,15 +1204,31 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC data: widget }; this.window.parent.postMessage(JSON.stringify(message), '*'); + this.setEditMode(false, false); } else { + let reInitDashboard = false; this.dashboardService.saveDashboard(this.dashboard).pipe( - catchError(() => { - this.setEditMode(false, true); - return of(null); + catchError((err) => { + if (err.status === HttpStatusCode.Conflict) { + reInitDashboard = true; + return this.dashboardService.getDashboard(this.dashboard.id.id).pipe( + map(dashboard => this.dashboardUtils.validateAndUpdateDashboard(dashboard)) + ); + } + return throwError(() => err); }) - ).subscribe(() => { - this.dashboard.version = this.dashboard.version + 1; + ).subscribe((dashboard) => { this.setEditMode(false, false); + this.dashboard = dashboard; + if (reInitDashboard) { + const dashboardPageInitData: DashboardPageInitData = { + dashboard, + currentDashboardId: dashboard.id ? dashboard.id.id : null, + widgetEditMode: false, + singlePageMode: false + }; + this.init(dashboardPageInitData); + } }); } } diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index c283a57c4f..1ec56028af 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -40,11 +40,12 @@ import { UntypedFormGroup } from '@angular/forms'; import { EntityComponent } from './entity.component'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { EntityAction } from '@home/models/entity/entity-component.models'; -import { Observable, of, ReplaySubject, Subscription } from 'rxjs'; +import { Observable, ReplaySubject, Subscription, throwError } from 'rxjs'; import { MatTab, MatTabGroup } from '@angular/material/tabs'; import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; import { deepClone, mergeDeep } from '@core/utils'; import { catchError } from 'rxjs/operators'; +import { HttpStatusCode } from '@angular/common/http'; @Component({ selector: 'tb-entity-details-panel', @@ -290,7 +291,12 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV } this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity) .pipe( - catchError(() => of(this.entity)) + catchError((err) => { + if (err.status === HttpStatusCode.Conflict) { + return this.entitiesTableConfig.loadEntity(this.currentEntityId); + } + return throwError(() => err); + }) ) .subscribe( (entity) => { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index aa7f62e33b..508d07ea0d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -113,7 +113,7 @@ export class WidgetComponentService { hasBasicMode: this.utils.editWidgetInfo.hasBasicMode, basicModeDirective: this.utils.editWidgetInfo.basicModeDirective, defaultConfig: this.utils.editWidgetInfo.defaultConfig - }, new WidgetTypeId('1'), new TenantId( NULL_UUID ), undefined, null + }, new WidgetTypeId('1'), new TenantId( NULL_UUID ), undefined, undefined ); } const initSubject = new ReplaySubject(); diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts index 614787233d..3ca2036369 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts @@ -59,12 +59,13 @@ import { SaveWidgetTypeAsDialogComponent, SaveWidgetTypeAsDialogResult } from '@home/pages/widget/save-widget-type-as-dialog.component'; -import { forkJoin, mergeMap, of, Subscription } from 'rxjs'; +import { forkJoin, mergeMap, of, Subscription, throwError } from 'rxjs'; import { ResizeObserver } from '@juggle/resize-observer'; import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; import { Observable } from 'rxjs/internal/Observable'; import { catchError, map, tap } from 'rxjs/operators'; import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models'; +import { HttpStatusCode } from '@angular/common/http'; import Timeout = NodeJS.Timeout; // @dynamic @@ -583,9 +584,11 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe } return of(widgetTypeDetails); }), - catchError(() => { - this.undoWidget(); - return of(null); + catchError((err) => { + if (id && err.status === HttpStatusCode.Conflict) { + return this.widgetService.getWidgetTypeById(id.id); + } + return throwError(() => err); }), ).subscribe({ next: (widgetTypeDetails) => { @@ -619,7 +622,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe config.title = this.widget.widgetName; this.widget.defaultConfig = JSON.stringify(config); this.isDirty = false; - this.widgetService.saveWidgetTypeDetails(this.widget, undefined, undefined, null).pipe( + this.widgetService.saveWidgetTypeDetails(this.widget, undefined, undefined, undefined).pipe( mergeMap((widget) => { if (saveWidgetAsData.widgetBundleId) { return this.widgetService.addWidgetFqnToWidgetBundle(saveWidgetAsData.widgetBundleId, widget.fqn).pipe( From dc336c37526704c89ad8a0e0c38a1cc77430fdf7 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 19 Aug 2024 18:36:31 +0300 Subject: [PATCH 6/6] reload entity fix --- .../dashboard-page/dashboard-page.component.ts | 9 +++++---- .../home/components/entity/entities-table.component.ts | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 109d5f014b..5af1833794 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -1218,16 +1218,17 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC return throwError(() => err); }) ).subscribe((dashboard) => { - this.setEditMode(false, false); - this.dashboard = dashboard; if (reInitDashboard) { const dashboardPageInitData: DashboardPageInitData = { dashboard, currentDashboardId: dashboard.id ? dashboard.id.id : null, - widgetEditMode: false, - singlePageMode: false + widgetEditMode: this.widgetEditMode, + singlePageMode: this.singlePageMode }; this.init(dashboardPageInitData); + } else { + this.dashboard = dashboard; + this.setEditMode(false, false); } }); } diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index dd3e846d92..99b6275dfe 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -402,7 +402,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa this.cd.detectChanges(); } - updateData(closeDetails: boolean = true) { + updateData(closeDetails: boolean = true, reloadEntity: boolean = true) { if (closeDetails) { this.isDetailsOpen = false; } @@ -427,7 +427,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa timePageLink.endTime = interval.endTime; } this.dataSource.loadEntities(this.pageLink); - if (this.isDetailsOpen && this.entityDetailsPanel) { + if (reloadEntity && this.isDetailsOpen && this.entityDetailsPanel) { this.entityDetailsPanel.reloadEntity(); } } @@ -511,7 +511,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa } onEntityUpdated(entity: BaseData) { - this.updateData(false); + this.updateData(false, false); this.entitiesTableConfig.entityUpdated(entity); }