From bcf28c94e9f6d116975f2282eb11b81b038d5342 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Fri, 16 Aug 2024 16:21:16 +0300 Subject: [PATCH] added implementation for dashboards and widget edit; added widget bundle export --- ui-ngx/src/app/core/http/widget.service.ts | 3 +- .../dashboard-page.component.ts | 13 +++- .../entity/entity-details-panel.component.ts | 3 +- .../widget/widget-component.service.ts | 2 +- .../home/models/widget-component.models.ts | 7 ++- .../pages/widget/widget-editor.component.ts | 15 +++-- .../entity-conflict-dialog.component.scss | 2 +- ...xport-widgets-bundle-dialog.component.html | 8 +-- .../export-widgets-bundle-dialog.component.ts | 4 ++ .../import-export/import-export.service.ts | 63 ++++++++++++------- ui-ngx/src/app/shared/models/widget.models.ts | 4 +- 11 files changed, 81 insertions(+), 43 deletions(-) diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index b172b61a1d..8b57feb488 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -185,8 +185,9 @@ export class WidgetService { public saveWidgetTypeDetails(widgetInfo: WidgetInfo, id: WidgetTypeId, createdTime: number, + version: number, config?: RequestConfig): Observable { - const widgetTypeDetails = toWidgetTypeDetails(widgetInfo, id, undefined, createdTime); + const widgetTypeDetails = toWidgetTypeDetails(widgetInfo, id, undefined, createdTime, version); return this.http.post('/api/widgetType', widgetTypeDetails, defaultHttpOptionsFromConfig(config)).pipe( tap((savedWidgetType) => { 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 d16e8f360f..2609744cfc 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 @@ -146,7 +146,7 @@ import { IAliasController } from '@core/api/widget-api.models'; import { MatButton } from '@angular/material/button'; import { VersionControlComponent } from '@home/components/vc/version-control.component'; import { TbPopoverService } from '@shared/components/popover.service'; -import { map, tap } from 'rxjs/operators'; +import { catchError, map, tap } from 'rxjs/operators'; import { LayoutFixedSize, LayoutWidthType } from '@home/components/dashboard-page/layout/layout.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { ResizeObserver } from '@juggle/resize-observer'; @@ -1027,7 +1027,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC public saveDashboard() { this.translatedDashboardTitle = this.getTranslatedDashboardTitle(); - this.setEditMode(false, false); this.notifyDashboardUpdated(); } @@ -1146,7 +1145,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC }; this.window.parent.postMessage(JSON.stringify(message), '*'); } else { - this.dashboardService.saveDashboard(this.dashboard).subscribe(); + this.dashboardService.saveDashboard(this.dashboard).pipe( + catchError(() => { + this.setEditMode(false, true); + return of(null); + }) + ).subscribe(() => { + this.dashboard.version = this.dashboard.version + 1; + this.setEditMode(false, false); + }); } } 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 6a1e8f5f47..c283a57c4f 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 @@ -44,7 +44,7 @@ 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 { catchError, take } from 'rxjs/operators'; +import { catchError } from 'rxjs/operators'; @Component({ selector: 'tb-entity-details-panel', @@ -290,7 +290,6 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV } this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity) .pipe( - take(1), catchError(() => of(this.entity)) ) .subscribe( 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 23d0403a6e..aa7f62e33b 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 + }, new WidgetTypeId('1'), new TenantId( NULL_UUID ), undefined, null ); } const initSubject = new ReplaySubject(); diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 1441bd4e92..85b10124e8 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -665,7 +665,7 @@ export const detailsToWidgetInfo = (widgetTypeDetailsEntity: WidgetTypeDetails): }; export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, - createdTime: number): WidgetType => { + createdTime: number, version: number): WidgetType => { const descriptor: WidgetTypeDescriptor = { type: widgetInfo.type, sizeX: widgetInfo.sizeX, @@ -688,6 +688,7 @@ export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: id, tenantId, createdTime, + version, fqn: widgetTypeFqn(widgetInfo.fullFqn), name: widgetInfo.widgetName, deprecated: widgetInfo.deprecated, @@ -697,8 +698,8 @@ export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: }; export const toWidgetTypeDetails = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, - createdTime: number): WidgetTypeDetails => { - const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, createdTime); + createdTime: number, version: number): WidgetTypeDetails => { + const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, createdTime, version); return { ...widgetTypeEntity, description: widgetInfo.description, 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 4e470d03da..614787233d 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 @@ -63,7 +63,7 @@ import { forkJoin, mergeMap, of, Subscription } from 'rxjs'; import { ResizeObserver } from '@juggle/resize-observer'; import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; import { Observable } from 'rxjs/internal/Observable'; -import { map, tap } from 'rxjs/operators'; +import { catchError, map, tap } from 'rxjs/operators'; import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models'; import Timeout = NodeJS.Timeout; @@ -569,9 +569,12 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe private commitSaveWidget() { const id = (this.widgetTypeDetails && this.widgetTypeDetails.id) ? this.widgetTypeDetails.id : undefined; + const version = this.widgetTypeDetails?.version ?? null; const createdTime = (this.widgetTypeDetails && this.widgetTypeDetails.createdTime) ? this.widgetTypeDetails.createdTime : undefined; - this.widgetService.saveWidgetTypeDetails(this.widget, id, createdTime).pipe( + this.saveWidgetPending = false; + this.widgetService.saveWidgetTypeDetails(this.widget, id, createdTime, version).pipe( mergeMap((widgetTypeDetails) => { + this.saveWidgetPending = true; const widgetsBundleId = this.route.snapshot.params.widgetsBundleId as string; if (widgetsBundleId && !id) { return this.widgetService.addWidgetFqnToWidgetBundle(widgetsBundleId, widgetTypeDetails.fqn).pipe( @@ -579,7 +582,11 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe ); } return of(widgetTypeDetails); - }) + }), + catchError(() => { + this.undoWidget(); + return of(null); + }), ).subscribe({ next: (widgetTypeDetails) => { this.saveWidgetPending = false; @@ -612,7 +619,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).pipe( + this.widgetService.saveWidgetTypeDetails(this.widget, undefined, undefined, null).pipe( mergeMap((widget) => { if (saveWidgetAsData.widgetBundleId) { return this.widgetService.addWidgetFqnToWidgetBundle(saveWidgetAsData.widgetBundleId, widget.fqn).pipe( diff --git a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss index cb1540c341..02c2e6bd00 100644 --- a/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss +++ b/ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.scss @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -$conflict-dialog-width: 520px; +$conflict-dialog-width: 530px; :host { .main-label { diff --git a/ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.html b/ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.html index f8ddefc2ca..27aaeec20b 100644 --- a/ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.html +++ b/ui-ngx/src/app/shared/import-export/export-widgets-bundle-dialog.component.html @@ -24,23 +24,23 @@ close - +
-
+
{{ '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;