| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| import { |
| Component, |
| ElementRef, |
| inject, |
| OnDestroy, |
| OnInit, |
| ViewChild, |
| } from '@angular/core'; |
| import { |
| ChartService, |
| DataExplorerWidgetModel, |
| DataLakeMeasure, |
| EventPropertyUnion, |
| FieldConfig, |
| LinkageData, |
| PermissionsService, |
| TimeSelectionConstants, |
| SpQueryResult, |
| TimeSettings, |
| } from '@streampipes/platform-services'; |
| import { |
| ActivatedRoute, |
| ActivatedRouteSnapshot, |
| Router, |
| RouterStateSnapshot, |
| } from '@angular/router'; |
| import { |
| AssetSaveService, |
| ConfirmDialogAction, |
| ConfirmDialogComponent, |
| CurrentUserService, |
| DialogService, |
| KeyboardShortcutService, |
| ObjectManageDialogComponent, |
| ObjectManageDialogResourceConfig, |
| ObjectManageDialogResult, |
| PanelType, |
| ShortcutRegistration, |
| SidebarResizeComponent, |
| SpAlertBannerComponent, |
| SpBasicViewComponent, |
| TimeSelectionService, |
| } from '@streampipes/shared-ui'; |
| import { ChartRoutingService } from '../../../chart-shared/services/chart-routing.service'; |
| import { ChartSharedService } from '../../../chart-shared/services/chart-shared.service'; |
| import { ChartDetectChangesService } from '../../services/chart-detect-changes.service'; |
| import { SupportsUnsavedChangeDialog } from '../../../chart-shared/models/dataview-dashboard.model'; |
| import { firstValueFrom, Observable, of, Subscription } from 'rxjs'; |
| import { MatDialog } from '@angular/material/dialog'; |
| import { catchError, map, switchMap, tap } from 'rxjs/operators'; |
| import { TranslatePipe, TranslateService } from '@ngx-translate/core'; |
| import { ResizeEchartsService } from '../../../chart-shared/services/resize-echarts.service'; |
| import { ResizeService } from '../../../chart-shared/services/resize.service'; |
| import { AssetDialogComponent } from '../../dialog/asset-dialog.component'; |
| import { AuthService } from '../../../services/auth.service'; |
| import { UserRole } from '../../../core/auth/user-role.enum'; |
| import { ChartFieldProviderService } from '../../../chart-shared/services/chart-field-provider.service'; |
| import { Tuple2 } from '../../../core-model/base/Tuple2'; |
| import { |
| FlexDirective, |
| LayoutAlignDirective, |
| LayoutDirective, |
| } from '@ngbracket/ngx-layout/flex'; |
| import { ChartViewToolbarComponent } from './toolbar/chart-view-toolbar.component'; |
| import { |
| MatDrawer, |
| MatDrawerContainer, |
| MatDrawerContent, |
| } from '@angular/material/sidenav'; |
| import { ChartDesignerPanelComponent } from './designer-panel/chart-designer-panel.component'; |
| import { ChartContainerComponent } from '../../../chart-shared/components/chart-container/chart-container.component'; |
| import { ChartDataPreviewComponent } from './query-result-preview/chart-data-preview.component'; |
| |
| type ManageableChart = DataExplorerWidgetModel & { |
| name: string; |
| description: string; |
| }; |
| |
| @Component({ |
| selector: 'sp-chart-data-view', |
| templateUrl: './chart-view.component.html', |
| styleUrls: ['./chart-view.component.scss'], |
| imports: [ |
| SpBasicViewComponent, |
| SpAlertBannerComponent, |
| FlexDirective, |
| LayoutAlignDirective, |
| LayoutDirective, |
| ChartViewToolbarComponent, |
| MatDrawerContainer, |
| MatDrawer, |
| SidebarResizeComponent, |
| ChartDesignerPanelComponent, |
| MatDrawerContent, |
| ChartContainerComponent, |
| ChartDataPreviewComponent, |
| TranslatePipe, |
| ], |
| }) |
| export class ChartViewComponent |
| implements OnInit, OnDestroy, SupportsUnsavedChangeDialog |
| { |
| dataViewLoaded = false; |
| timeSettings: TimeSettings; |
| |
| editMode = true; |
| dataView: DataExplorerWidgetModel; |
| originalDataView: DataExplorerWidgetModel; |
| dataLakeMeasure: DataLakeMeasure; |
| drawerWidth = 450; |
| |
| selectedAssets = []; |
| deselectedAssets = []; |
| originalAssets = []; |
| |
| resizeEchartsService = inject(ResizeEchartsService); |
| private shortcutReg: ShortcutRegistration; |
| resizeService = inject(ResizeService); |
| |
| private shortcutService = inject(KeyboardShortcutService); |
| private dataExplorerSharedService = inject(ChartSharedService); |
| private detectChangesService = inject(ChartDetectChangesService); |
| private route = inject(ActivatedRoute); |
| private router = inject(Router); |
| private dialog = inject(MatDialog); |
| private routingService = inject(ChartRoutingService); |
| private dataViewService = inject(ChartService); |
| private timeSelectionService = inject(TimeSelectionService); |
| private translateService = inject(TranslateService); |
| private dialogService = inject(DialogService); |
| private currentUserService = inject(CurrentUserService); |
| private authService = inject(AuthService); |
| private fieldProvider = inject(ChartFieldProviderService); |
| private assetSaveService = inject(AssetSaveService); |
| private permissionsService = inject(PermissionsService); |
| |
| currentUser$: Subscription; |
| queryParams$: Subscription; |
| |
| chartNotFound = false; |
| legacyMultiSourceChart = false; |
| createMode = false; |
| hasDataExplorerWritePrivileges = false; |
| latestQueryResults: SpQueryResult[] = []; |
| |
| observableGenerator = |
| this.dataExplorerSharedService.defaultObservableGenerator(); |
| |
| @ViewChild('panel', { static: false }) outerPanel: ElementRef; |
| |
| private pendingManageChartResult?: ObjectManageDialogResult<ManageableChart>; |
| |
| ngOnInit() { |
| this.shortcutReg = this.shortcutService.register('chart-view', [ |
| { |
| key: 's', |
| ctrl: true, |
| action: () => this.onShortcutSave(), |
| allowInDialog: true, |
| }, |
| ]); |
| |
| const dataViewId = this.route.snapshot.params.id; |
| |
| this.currentUser$ = this.currentUserService.user$.subscribe(() => { |
| this.hasDataExplorerWritePrivileges = this.authService.hasRole( |
| UserRole.ROLE_DATA_EXPLORER_ADMIN, |
| ); |
| this.editMode = this.shouldEnableEditMode(); |
| }); |
| |
| this.createMode = !dataViewId; |
| |
| if (dataViewId) { |
| this.loadDataView(dataViewId); |
| } else { |
| this.createWidget(); |
| this.timeSettings = |
| this.getTimeSettingsFromQueryParams() ?? |
| this.makeDefaultTimeSettings(); |
| this.dataView.timeSettings = this.timeSettings; |
| this.afterDataViewLoaded(); |
| } |
| |
| this.queryParams$ = this.route.queryParams.subscribe(queryParams => { |
| this.applyTimeSettingsFromQueryParams(queryParams); |
| }); |
| } |
| |
| onAddWidget(_event: Tuple2<DataLakeMeasure, DataExplorerWidgetModel>) { |
| if (!this.originalDataView?.visualizationConfig) { |
| this.setDefaultValuesOnOriginalDataViewForNewCharts(); |
| } |
| } |
| |
| setDefaultValuesOnOriginalDataViewForNewCharts() { |
| //Change original Data View if default Config does not exist |
| |
| //Reset name as widget generation sets name to datalakename - chart |
| this.dataView.baseAppearanceConfig.widgetTitle = |
| this.translateService.instant('New chart'); |
| this.originalDataView = JSON.parse(JSON.stringify(this.dataView)); |
| this.originalDataView.elementId = undefined; |
| this.originalDataView.rev = undefined; |
| this.originalDataView.widgetId = undefined; |
| //Set default |
| this.originalDataView.dataConfig.sourceConfigs[0].queryConfig.order ??= |
| 'DESC'; |
| this.addAllFields(); |
| } |
| |
| addAllFields() { |
| this.originalDataView.dataConfig.sourceConfigs[0].measure.eventSchema.eventProperties.forEach( |
| property => { |
| if (this.fieldProvider.isDimensionProperty(property)) { |
| this.addField(property); |
| } |
| }, |
| ); |
| } |
| |
| addField(property: EventPropertyUnion) { |
| const selection: FieldConfig = { |
| runtimeName: property.runtimeName, |
| selected: false, |
| numeric: this.fieldProvider.isNumber(property), |
| }; |
| this.originalDataView.dataConfig.sourceConfigs[0].queryConfig.groupBy.push( |
| selection, |
| ); |
| } |
| |
| loadDataView(dataViewId: string): void { |
| this.dataViewLoaded = false; |
| this.latestQueryResults = []; |
| this.dataViewService |
| .getChart(dataViewId) |
| .pipe( |
| catchError(() => { |
| this.chartNotFound = true; |
| return of(null); |
| }), |
| ) |
| .subscribe(res => { |
| if (!res) { |
| this.dataViewLoaded = true; |
| return; |
| } else { |
| this.dataView = res; |
| this.originalDataView = JSON.parse( |
| JSON.stringify(this.dataView), |
| ); |
| this.legacyMultiSourceChart = this.hasMultipleSourceConfigs( |
| this.dataView, |
| ); |
| this.editMode = this.shouldEnableEditMode(); |
| this.timeSettings = |
| this.dataExplorerSharedService.makeChartTimeSettings( |
| this.dataView, |
| ); |
| this.timeSettings = |
| this.getTimeSettingsFromQueryParams() ?? |
| this.timeSettings; |
| this.afterDataViewLoaded(); |
| } |
| }); |
| } |
| |
| private applyTimeSettingsFromQueryParams(queryParams: { |
| [key: string]: any; |
| }): void { |
| if (!this.timeSettings) { |
| return; |
| } |
| |
| const startDate = Number(queryParams.startDate); |
| const endDate = Number(queryParams.endDate); |
| if ( |
| !Number.isFinite(startDate) || |
| !Number.isFinite(endDate) || |
| startDate >= endDate |
| ) { |
| return; |
| } |
| |
| if ( |
| this.timeSettings.startTime === startDate && |
| this.timeSettings.endTime === endDate |
| ) { |
| return; |
| } |
| |
| this.timeSettings = { |
| ...this.timeSettings, |
| startTime: startDate, |
| endTime: endDate, |
| timeSelectionId: TimeSelectionConstants.CUSTOM, |
| }; |
| this.timeSelectionService.notify(this.timeSettings); |
| } |
| |
| private getTimeSettingsFromQueryParams(): TimeSettings | undefined { |
| const startDate = Number(this.route.snapshot.queryParams.startDate); |
| const endDate = Number(this.route.snapshot.queryParams.endDate); |
| |
| if ( |
| !Number.isFinite(startDate) || |
| !Number.isFinite(endDate) || |
| startDate >= endDate |
| ) { |
| return undefined; |
| } |
| |
| return { |
| startTime: startDate, |
| endTime: endDate, |
| dynamicSelection: -1, |
| timeSelectionId: TimeSelectionConstants.CUSTOM, |
| }; |
| } |
| |
| afterDataViewLoaded(): void { |
| this.dataViewLoaded = true; |
| setTimeout(() => { |
| this.timeSelectionService.notify(this.timeSettings); |
| this.updateQueryParams(this.timeSettings); |
| }); |
| } |
| |
| editDataView(): void { |
| this.routingService.navigateToChart(true, this.dataView.elementId); |
| } |
| |
| onQueryResultsChanged(results: SpQueryResult[]): void { |
| this.latestQueryResults = results ?? []; |
| } |
| |
| get showRequiresAttentionWarning(): boolean { |
| return this.dataView?.healthStatus === 'REQUIRES_ATTENTION'; |
| } |
| |
| get chartSchemaUpdateMessages(): string[] { |
| return this.dataView?.affectedSchemaUpdateFields ?? []; |
| } |
| |
| get requiresAttentionDescription(): string { |
| if (this.chartSchemaUpdateMessages.length > 0) { |
| return `${this.translateService.instant('The following fields used by this chart no longer exist in the dataset:')} |
| ${this.chartSchemaUpdateMessages.join(', ')}`; |
| } |
| return this.translateService.instant( |
| 'Some fields used by this chart no longer exist in the dataset.', |
| ); |
| } |
| |
| makeDefaultTimeSettings(): TimeSettings { |
| return this.timeSelectionService.getDefaultTimeSettings(); |
| } |
| |
| setShouldShowConfirm(): boolean { |
| let originalTimeSettings: TimeSettings; |
| if (!this.dataView) { |
| return false; |
| } |
| if (this.originalDataView?.timeSettings) { |
| originalTimeSettings = this.originalDataView |
| .timeSettings as TimeSettings; |
| } else { |
| originalTimeSettings = this.dataView.timeSettings as TimeSettings; |
| } |
| const currentTimeSettings = this.dataView.timeSettings as TimeSettings; |
| return ( |
| this.pendingManageChartResult !== undefined || |
| this.detectChangesService.shouldShowConfirm( |
| this.originalDataView, |
| this.dataView, |
| originalTimeSettings, |
| currentTimeSettings, |
| model => { |
| model.timeSettings = undefined; |
| }, |
| ) |
| ); |
| } |
| |
| createWidget() { |
| this.dataView = new DataExplorerWidgetModel(); |
| this.dataView['@class'] = |
| 'org.apache.streampipes.model.datalake.DataExplorerWidgetModel'; |
| this.dataView.baseAppearanceConfig = {}; |
| this.dataView.baseAppearanceConfig.widgetTitle = |
| this.translateService.instant('New chart'); |
| this.dataView.dataConfig = {}; |
| this.dataView.dataConfig.ignoreMissingValues = false; |
| this.dataView.baseAppearanceConfig.backgroundColor = |
| 'var(--color-bg-0)'; |
| this.dataView.baseAppearanceConfig.textColor = |
| 'var(--color-default-text)'; |
| this.dataView.metadata = { |
| createdAtEpochMs: Date.now(), |
| lastModifiedEpochMs: Date.now(), |
| }; |
| |
| this.dataView = { ...this.dataView }; |
| } |
| |
| saveDataView(): void { |
| if (this.legacyMultiSourceChart) { |
| return; |
| } |
| if (this.createMode) { |
| this.openCreateChartDialog(); |
| return; |
| } |
| |
| this.prepareChartForSave(this.dataView); |
| const observable = |
| this.dataView.elementId !== undefined |
| ? this.dataViewService.updateChart(this.dataView) |
| : this.dataViewService.saveChart(this.dataView); |
| observable.subscribe(data => { |
| if ( |
| this.selectedAssets.length > 0 || |
| this.deselectedAssets.length > 0 || |
| this.originalAssets.length > 0 |
| ) { |
| this.saveToAssets(data); |
| } |
| |
| this.savePendingManageChartChanges().then(() => { |
| this.routingService.navigateToDataViewOverview(true); |
| }); |
| }); |
| } |
| |
| private openCreateChartDialog(): void { |
| const resource = this.makeManageableChart(this.dataView); |
| const resourceConfig: ObjectManageDialogResourceConfig<ManageableChart> = |
| { |
| resourceLabel: 'Chart', |
| nameLabel: 'Chart title', |
| descriptionLabel: 'Chart description', |
| idProperty: 'elementId', |
| nameProperty: 'name', |
| assetLinkType: 'chart', |
| assetLinkCheckboxLabel: |
| 'Add the current chart to an existing asset', |
| saveResource: resource => { |
| const chartResource = this.makeChartResource(resource); |
| this.prepareChartForSave(chartResource); |
| return this.dataViewService.saveChart(chartResource).pipe( |
| tap((savedChart: DataExplorerWidgetModel) => { |
| Object.assign(this.dataView, savedChart); |
| Object.assign( |
| resource, |
| this.makeManageableChart(savedChart), |
| ); |
| }), |
| ); |
| }, |
| }; |
| const dialogRef = this.dialogService.open(ObjectManageDialogComponent, { |
| panelType: PanelType.SLIDE_IN_PANEL, |
| title: this.translateService.instant('New chart'), |
| width: '50vw', |
| data: { |
| createMode: true, |
| resource, |
| saveMode: 'immediate', |
| resourceConfig, |
| headerTitle: this.translateService.instant('New chart'), |
| }, |
| }); |
| |
| dialogRef.afterClosed().subscribe(refresh => { |
| if (refresh) { |
| this.createMode = false; |
| this.originalDataView = JSON.parse( |
| JSON.stringify(this.dataView), |
| ); |
| this.routingService.navigateToDataViewOverview(true); |
| } |
| }); |
| } |
| |
| manageChart(): void { |
| const resource = this.makeManageableChart(this.dataView); |
| const resourceConfig: ObjectManageDialogResourceConfig<ManageableChart> = |
| { |
| resourceLabel: 'Chart', |
| nameLabel: 'Chart title', |
| descriptionLabel: 'Chart description', |
| nameProperty: 'name', |
| assetLinkType: 'chart', |
| assetLinkCheckboxLabel: |
| 'Add the current chart to an existing asset', |
| }; |
| const dialogRef = this.dialogService.open(ObjectManageDialogComponent, { |
| panelType: PanelType.SLIDE_IN_PANEL, |
| title: this.translateService.instant('Manage'), |
| width: '50vw', |
| data: { |
| objectInstanceId: resource.elementId, |
| resource, |
| saveMode: 'deferred', |
| resourceConfig, |
| headerTitle: |
| this.translateService.instant('Manage Chart ') + |
| resource.name, |
| }, |
| }); |
| dialogRef.afterClosed().subscribe(result => { |
| if (result && typeof result !== 'boolean') { |
| this.pendingManageChartResult = result; |
| Object.assign( |
| this.dataView, |
| this.makeChartResource(result.resource), |
| ); |
| } |
| }); |
| } |
| |
| deleteChart(): void { |
| const dialogRef = this.dialog.open(ConfirmDialogComponent, { |
| width: '600px', |
| data: { |
| title: this.translateService.instant( |
| 'Are you sure you want to delete chart "{{chartTitle}}"?', |
| { |
| chartTitle: |
| this.dataView.baseAppearanceConfig.widgetTitle ?? |
| '', |
| }, |
| ), |
| subtitle: this.translateService.instant( |
| 'The chart will be removed from all dashboards as well. This action cannot be undone!', |
| ), |
| cancelTitle: this.translateService.instant('Cancel'), |
| confirmTitle: this.translateService.instant('Delete chart'), |
| }, |
| }); |
| dialogRef.afterClosed().subscribe(result => { |
| if (result === 'confirm') { |
| this.dataViewService |
| .deleteChart(this.dataView.elementId) |
| .subscribe(() => { |
| this.routingService.navigateToDataViewOverview(true); |
| }); |
| } |
| }); |
| } |
| |
| addAssetDialog(): void { |
| const dialogRef = this.dialogService.open(AssetDialogComponent, { |
| panelType: PanelType.STANDARD_PANEL, |
| width: '500px', |
| title: this.translateService.instant( |
| 'Do you want to link the chart to an Asset?', |
| ), |
| data: { |
| subtitle: this.translateService.instant( |
| 'Update asset links or close.', |
| ), |
| cancelTitle: this.translateService.instant('Close'), |
| confirmTitle: this.translateService.instant('Update'), |
| editMode: this.editMode, |
| selectedAssets: this.selectedAssets, |
| deselectedAssets: this.deselectedAssets, |
| originalAssets: this.originalAssets, |
| dataViewId: this.route.snapshot.params.id, |
| }, |
| }); |
| |
| dialogRef.afterClosed().subscribe(result => { |
| if (result) { |
| this.selectedAssets = result.selectedAssets; |
| this.deselectedAssets = result.deselectedAssets; |
| this.originalAssets = result.originalAssets; |
| } |
| }); |
| } |
| |
| confirmLeaveDialog( |
| _route: ActivatedRouteSnapshot, |
| _state: RouterStateSnapshot, |
| ): Observable<boolean> { |
| if (this.editMode && this.setShouldShowConfirm()) { |
| const dialogRef = this.dialog.open(ConfirmDialogComponent, { |
| width: '500px', |
| data: { |
| title: this.translateService.instant('Save changes?'), |
| subtitle: this.translateService.instant( |
| 'Update all changes to chart or discard current changes.', |
| ), |
| neutralTitle: this.translateService.instant('Keep editing'), |
| cancelTitle: |
| this.translateService.instant('Discard changes'), |
| confirmTitle: this.translateService.instant('Update'), |
| }, |
| }); |
| return dialogRef.afterClosed().pipe( |
| switchMap((dialogResult: ConfirmDialogAction | undefined) => { |
| if (dialogResult === 'confirm') { |
| if (this.legacyMultiSourceChart) { |
| return of(true); |
| } |
| this.dataView.timeSettings = this.timeSettings; |
| this.dataView.healthStatus = 'OK'; |
| this.dataView.affectedSchemaUpdateFields = undefined; |
| return ( |
| this.dataView.elementId !== undefined |
| ? this.dataViewService.updateChart( |
| this.dataView, |
| ) |
| : this.dataViewService.saveChart(this.dataView) |
| ).pipe( |
| switchMap(() => |
| this.savePendingManageChartChanges(), |
| ), |
| map(() => true), |
| ); |
| } |
| |
| if (dialogResult === 'cancel') { |
| return of(true); |
| } |
| |
| return of(false); |
| }), |
| ); |
| } else { |
| return of(true); |
| } |
| } |
| |
| discardChanges() { |
| this.routingService.navigateToDataViewOverview(true); |
| } |
| |
| updateDateRange(timeSettings: TimeSettings) { |
| this.timeSettings = timeSettings; |
| this.timeSelectionService.notify(timeSettings); |
| this.updateQueryParams(timeSettings); |
| } |
| |
| updateQueryParams(timeSettings: TimeSettings) { |
| this.router.navigate([], { |
| relativeTo: this.route, |
| queryParams: { |
| startDate: timeSettings.startTime, |
| endDate: timeSettings.endTime, |
| }, |
| queryParamsHandling: 'merge', |
| replaceUrl: true, |
| }); |
| } |
| |
| downloadDataAsFile() { |
| this.dataExplorerSharedService.downloadDataAsFile( |
| this.timeSettings, |
| this.dataView, |
| ); |
| } |
| |
| onWidthChanged(newWidth: number) { |
| this.drawerWidth = newWidth; |
| this.scheduleChartPanelResize(100); |
| } |
| |
| onDataPreviewSizeChanged(): void { |
| // Preview height animates; send resize updates during and after transition. |
| [0, 100, 220, 350].forEach(delay => |
| this.scheduleChartPanelResize(delay), |
| ); |
| } |
| |
| private scheduleChartPanelResize(delayMs = 0): void { |
| setTimeout( |
| () => requestAnimationFrame(() => this.notifyChartPanelResize()), |
| delayMs, |
| ); |
| } |
| |
| private notifyChartPanelResize(): void { |
| const panel = this.outerPanel?.nativeElement; |
| if (!panel) { |
| return; |
| } |
| |
| const widgetContent = panel.querySelector( |
| '.widget-content', |
| ) as HTMLDivElement | null; |
| const width = widgetContent?.clientWidth ?? panel.offsetWidth; |
| const height = widgetContent?.clientHeight ?? panel.offsetHeight; |
| |
| this.resizeService.notify({ |
| width, |
| height, |
| widgetId: undefined, |
| }); |
| this.resizeEchartsService.notify(width); |
| } |
| |
| private async saveAssets(linkageData: LinkageData[]): Promise<void> { |
| await this.assetSaveService.saveSelectedAssets( |
| this.selectedAssets, |
| linkageData, |
| this.deselectedAssets, |
| this.originalAssets, |
| ); |
| } |
| |
| saveToAssets(data: DataExplorerWidgetModel): void { |
| let linkageData: LinkageData[]; |
| try { |
| linkageData = this.createLinkageData(data); |
| |
| this.saveAssets(linkageData); |
| } catch (err) { |
| console.error('Error in addToAsset:', err); |
| } |
| } |
| private createLinkageData(data: DataExplorerWidgetModel): LinkageData[] { |
| return [ |
| { |
| type: 'chart', |
| id: data.elementId, |
| name: data.baseAppearanceConfig.widgetTitle, |
| }, |
| ]; |
| } |
| |
| private makeChartResource( |
| resource: ManageableChart, |
| ): DataExplorerWidgetModel { |
| const chartResource: Partial<ManageableChart> = { ...resource }; |
| chartResource.baseAppearanceConfig = { |
| ...resource.baseAppearanceConfig, |
| widgetTitle: resource.name, |
| }; |
| delete chartResource.name; |
| delete chartResource.description; |
| return chartResource as DataExplorerWidgetModel; |
| } |
| |
| private makeManageableChart( |
| chart: DataExplorerWidgetModel, |
| ): ManageableChart { |
| return { |
| ...chart, |
| baseAppearanceConfig: { ...chart.baseAppearanceConfig }, |
| name: chart.baseAppearanceConfig.widgetTitle, |
| description: '', |
| }; |
| } |
| |
| private prepareChartForSave(chart: DataExplorerWidgetModel): void { |
| chart.timeSettings = this.timeSettings; |
| chart.healthStatus = 'OK'; |
| chart.affectedSchemaUpdateFields = undefined; |
| chart.metadata ??= { |
| lastModifiedEpochMs: undefined, |
| createdAtEpochMs: undefined, |
| }; |
| chart.metadata.lastModifiedEpochMs = Date.now(); |
| } |
| |
| private async savePendingManageChartChanges(): Promise<void> { |
| const result = this.pendingManageChartResult; |
| if (!result) { |
| return; |
| } |
| |
| if (result.permission) { |
| await firstValueFrom( |
| this.permissionsService.updatePermission(result.permission), |
| ); |
| } |
| |
| if (this.shouldSaveManageChartAssets(result)) { |
| await this.assetSaveService.saveSelectedAssets( |
| result.selectedAssets, |
| this.createLinkageData(this.makeChartResource(result.resource)), |
| result.deselectedAssets, |
| result.originalAssets, |
| ); |
| } |
| |
| this.pendingManageChartResult = undefined; |
| } |
| |
| private shouldSaveManageChartAssets( |
| result: ObjectManageDialogResult<ManageableChart>, |
| ): boolean { |
| return ( |
| result.addToAssets && |
| (result.selectedAssets.length > 0 || |
| result.deselectedAssets.length > 0 || |
| result.originalAssets.length > 0) |
| ); |
| } |
| |
| private onShortcutSave(): void { |
| if (this.editMode) { |
| this.saveDataView(); |
| } |
| } |
| |
| ngOnDestroy() { |
| this.shortcutReg?.unregister(); |
| this.currentUser$?.unsubscribe(); |
| this.queryParams$?.unsubscribe(); |
| } |
| |
| private hasMultipleSourceConfigs(widget: DataExplorerWidgetModel): boolean { |
| return (widget?.dataConfig?.sourceConfigs?.length ?? 0) > 1; |
| } |
| |
| private shouldEnableEditMode(): boolean { |
| return ( |
| this.authService.hasRole(UserRole.ROLE_DATA_EXPLORER_ADMIN) && |
| !!this.route.snapshot.queryParams.editMode && |
| !this.legacyMultiSourceChart |
| ); |
| } |
| } |