diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/dashboard/DataLakeDashboardResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/dashboard/DataLakeDashboardResource.java index ec015f2390..b4944312be 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/dashboard/DataLakeDashboardResource.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/dashboard/DataLakeDashboardResource.java @@ -104,11 +104,12 @@ public ResponseEntity deleteDashboard(@PathVariable("dashboardId") String @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("this.hasWriteAuthority()") - public ResponseEntity createDashboard(@RequestBody DashboardModel dashboardModel) { - getResourceManager().create(dashboardModel, getAuthenticatedUserSid()); - return ok(); + public ResponseEntity createDashboard(@RequestBody DashboardModel dashboardModel) { + var response = getResourceManager().create(dashboardModel, getAuthenticatedUserSid()); + return ok(response); } + private DataExplorerResourceManager getResourceManager() { return getSpResourceManager().manageDataExplorer(); } diff --git a/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts b/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts index 35b3ca1b66..429c785b04 100644 --- a/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts +++ b/ui/cypress/support/utils/dataExplorer/DataExplorerBtns.ts @@ -27,6 +27,10 @@ export class DataLakeBtns { return cy.dataCy('save-data-view-btn').click(); } + public static saveDashboard() { + return cy.dataCy('save-data-view').click(); + } + public static editDataViewButton(widgetName: string) { GeneralUtils.openMenuForRow(widgetName); return cy diff --git a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts index 3bdaf3a855..db51364d14 100644 --- a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts +++ b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts @@ -114,7 +114,54 @@ export class DataExplorerUtils { cy.wait(1000); } + public static addAssetsToDashboard(assetNameList) { + cy.dataCy('sp-show-dashboard-asset-checkbox') + .find('input[type="checkbox"]') + .then($checkbox => { + if (!$checkbox.prop('checked')) { + cy.wrap($checkbox).click(); + } + }); + cy.get('mat-tree.asset-tree', { timeout: 10000 }).should('exist'); + assetNameList.forEach(assetName => { + console.log(assetName); + cy.get('mat-tree.asset-tree') + .find('.mat-tree-node') + .contains(assetName) + .click(); + }); + } + + public static createDashboard(name) { + // Create new data view + cy.dataCy('open-new-dashboard-dialog').click(); + + // Configure data view + cy.dataCy('data-view-name').type(name); + } + public static createDashboardWithLinkedAssets( + dataView, + name, + assetNameList, + ) { + DataExplorerUtils.goToDatalake(); + + DataExplorerUtils.addDataViewAndTableWidget(dataView, 'Persist'); + + DataExplorerUtils.saveDataViewConfiguration(); + + DataExplorerUtils.goToDashboard(); + + //ADD Assets + DataExplorerUtils.createDashboard(name); + DataExplorerUtils.addAssetsToDashboard(assetNameList); + DataExplorerUtils.saveDashboard(); + } + + public static saveDashboard() { + return cy.dataCy('save-data-view').click(); + } public static addDataViewAndTableWidget( dataViewName: string, dataSet: string, @@ -144,6 +191,11 @@ export class DataExplorerUtils { ); } + public static renameDashboard(newName: string) { + cy.dataCy('data-view-name').clear().type(newName); + cy.dataCy('data-view-name').should('have.value', newName); + } + public static loadRandomDataSetIntoDataLake() { PrepareTestDataUtils.loadDataIntoDataLake('fileTest/random.csv'); } @@ -186,6 +238,11 @@ export class DataExplorerUtils { cy.dataCy('edit-dashboard-' + dashboardName).click(); } + public static editDashboardSettings(dashboardName: string) { + GeneralUtils.openMenuForRow(dashboardName); + cy.dataCy('edit-dashboard-settings-' + dashboardName).click(); + } + public static editDataView(dataViewName: string) { // Click edit button // following only works if single view is available @@ -225,7 +282,9 @@ export class DataExplorerUtils { .contains(assetName) .click(); }); + } + public static saveAssetLinkFromChart() { cy.dataCy('asset-dialog-confirm-delete', { timeout: 10000 }).click({ force: true, }); @@ -606,5 +665,6 @@ export class DataExplorerUtils { //Save DataExplorerUtils.saveToAddAssets(); DataExplorerUtils.addToAsset(assetNames); + DataExplorerUtils.saveAssetLinkFromChart(); } } diff --git a/ui/cypress/tests/dataExplorer/addAssetToDashboard.smoke.spec.ts b/ui/cypress/tests/dataExplorer/addAssetToDashboard.smoke.spec.ts new file mode 100644 index 0000000000..defb473209 --- /dev/null +++ b/ui/cypress/tests/dataExplorer/addAssetToDashboard.smoke.spec.ts @@ -0,0 +1,81 @@ +/* + * 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 { AssetBtns } from '../../support/utils/asset/AssetBtns'; +import { AssetUtils } from '../../support/utils/asset/AssetUtils'; +import { DataExplorerUtils } from '../../support/utils/dataExplorer/DataExplorerUtils'; + +describe('Test add Assets To Dashboard', () => { + const assetName1 = 'TestAsset1'; + const assetName2 = 'TestAsset2'; + const assetName3 = 'TestAsset3'; + beforeEach('Setup Test', () => { + cy.initStreamPipesTest(); + AssetUtils.goToAssets(); + AssetUtils.addAndSaveAsset(assetName3); + AssetUtils.addAndSaveAsset(assetName2); + AssetUtils.addAndSaveAsset(assetName1); + DataExplorerUtils.loadDataIntoDataLake('datalake/sample.csv'); + }); + + it('Create Dashboard and add Assets', () => { + const dataView = 'TestView'; + + const name = 'Dashboard1'; + + const assetNameList = [assetName1, assetName2]; + DataExplorerUtils.createDashboardWithLinkedAssets( + dataView, + name, + assetNameList, + ); + + //Go Back to Asset + AssetUtils.goToAssets(); + AssetUtils.checkAmountOfAssetsGreaterThan(0); + + AssetUtils.editAsset(assetName1); + AssetBtns.assetLinksTab().click(); + + //Check if Link is there + AssetUtils.checkAmountOfLinkedResources(1); + }); + + it('Edit Dashboard and edit Asset Links', () => { + const dataView = 'TestView'; + + const name = 'Dashboard1'; + + const assetNameList = [assetName1, assetName2]; + DataExplorerUtils.createDashboardWithLinkedAssets( + dataView, + name, + assetNameList, + ); + DataExplorerUtils.editDashboardSettings(name); + DataExplorerUtils.renameDashboard('NEW'); + const assetNameList2 = [assetName2, assetName3]; + DataExplorerUtils.addToAsset(assetNameList2); + DataExplorerUtils.saveDashboard(); + + AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName1, 1); + AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName3, 1); + + // Test Renaming + AssetUtils.checkResourceNamingByAssetName(assetName1, 'NEW'); + }); +}); diff --git a/ui/cypress/tests/dataExplorer/addAssetsToDataView.smoke.spec.ts b/ui/cypress/tests/dataExplorer/addAssetsToDataView.smoke.spec.ts new file mode 100644 index 0000000000..beb3d5f5c6 --- /dev/null +++ b/ui/cypress/tests/dataExplorer/addAssetsToDataView.smoke.spec.ts @@ -0,0 +1,61 @@ +/* + * 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 { AssetUtils } from '../../support/utils/asset/AssetUtils'; +import { DataExplorerUtils } from '../../support/utils/dataExplorer/DataExplorerUtils'; + +describe('Creates a new adapter with a linked asset', () => { + const assetName1 = 'TestAsset1'; + const assetName2 = 'TestAsset2'; + const assetName3 = 'TestAsset3'; + + beforeEach('Setup Test', () => { + cy.initStreamPipesTest(); + AssetUtils.goToAssets(); + AssetUtils.addAndSaveAsset(assetName3); + AssetUtils.addAndSaveAsset(assetName2); + AssetUtils.addAndSaveAsset(assetName1); + }); + + it('Add Assets during Chart generation', () => { + DataExplorerUtils.createDataViewWithAssets([assetName1, assetName2]); + //Test + AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName1, 1); + AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName2, 1); + }); + + it('Edit Assets during Chart generation', () => { + DataExplorerUtils.createDataViewWithAssets([assetName1, assetName2]); + //Test + AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName1, 1); + AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName2, 1); + + // Go To Chart and Edit + DataExplorerUtils.goToDatalake(); + DataExplorerUtils.editDataView('NewWidget'); + DataExplorerUtils.renameWidget('Rename'); + + DataExplorerUtils.saveToAddAssets(); + DataExplorerUtils.addToAsset([assetName1, assetName3]); + DataExplorerUtils.saveAssetLinkFromChart(); + + AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName2, 1); + AssetUtils.checkAmountOfLinkedResourcesByAssetName(assetName3, 1); + AssetUtils.checkResourceNamingByAssetName(assetName2, 'Rename'); + }); +}); diff --git a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html index 69d5054e40..3da870685e 100644 --- a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html +++ b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html @@ -97,20 +97,47 @@ }} + +
+ + + + {{ + 'Add the current dashboard to an existing asset' + | translate + }} + + + @if (addToAssets) { +
+ + +
+ } +
+ -
- +
+
diff --git a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.ts b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.ts index 112e70cc41..426b676362 100644 --- a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.ts +++ b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.ts @@ -16,9 +16,21 @@ * */ -import { Component, inject, Input, OnInit } from '@angular/core'; -import { Dashboard, DashboardService } from '@streampipes/platform-services'; -import { DialogRef } from '@streampipes/shared-ui'; +import { + Component, + EventEmitter, + inject, + Input, + OnInit, + Output, +} from '@angular/core'; +import { + Dashboard, + DashboardService, + LinkageData, + SpAssetTreeNode, +} from '@streampipes/platform-services'; +import { AssetSaveService, DialogRef } from '@streampipes/shared-ui'; @Component({ selector: 'sp-edit-dashboard-dialog-component', @@ -29,9 +41,19 @@ import { DialogRef } from '@streampipes/shared-ui'; export class EditDashboardDialogComponent implements OnInit { @Input() createMode: boolean; @Input() dashboard: Dashboard; + @Input() selectedAssets: SpAssetTreeNode[]; + @Input() deselectedAssets: SpAssetTreeNode[]; + @Input() originalAssets: SpAssetTreeNode[]; + + @Output() selectedAssetsChange = new EventEmitter(); + @Output() deselectedAssetsChange = new EventEmitter(); + @Output() originalAssetsChange = new EventEmitter(); private dialogRef = inject(DialogRef); private dashboardService = inject(DashboardService); + private assetSaveService = inject(AssetSaveService); + + addToAssets: boolean = false; ngOnInit() { if (!this.dashboard.dashboardGeneralSettings.defaultViewMode) { @@ -43,24 +65,75 @@ export class EditDashboardDialogComponent implements OnInit { ) { this.dashboard.dashboardGeneralSettings.globalTimeEnabled = true; } + if (!this.createMode) { + this.addToAssets = true; + } } onCancel(): void { this.dialogRef.close(); } + onSelectedAssetsChange(updatedAssets: SpAssetTreeNode[]): void { + this.selectedAssets = updatedAssets; + this.selectedAssetsChange.emit(this.selectedAssets); + } + + onDeselectedAssetsChange(updatedAssets: SpAssetTreeNode[]): void { + this.deselectedAssets = updatedAssets; + this.deselectedAssetsChange.emit(this.deselectedAssets); + } + + onOriginalAssetsEmitted(updatedAssets: SpAssetTreeNode[]): void { + this.originalAssets = updatedAssets; + this.originalAssetsChange.emit(this.originalAssets); + } + + saveToAssets(data): void { + let linkageData: LinkageData[]; + try { + linkageData = this.createLinkageData(data); + + this.saveAssets(linkageData); + } catch (err) { + console.error('Error in addToAsset:', err); + } + } + + private createLinkageData(data): LinkageData[] { + return [ + { + type: 'dashboard', + id: data.elementId, + name: data.name, + }, + ]; + } + + private async saveAssets(linkageData: LinkageData[]): Promise { + await this.assetSaveService.saveSelectedAssets( + this.selectedAssets, + linkageData, + this.deselectedAssets, + this.originalAssets, + ); + this.dialogRef.close(true); + } + onSave(): void { this.dashboard.metadata.lastModifiedEpochMs = Date.now(); if (this.createMode) { this.dashboardService .saveDashboard(this.dashboard) - .subscribe(() => { + .subscribe(data => { + this.saveToAssets(data); this.dialogRef.close(); }); } else { this.dashboardService .updateDashboard(this.dashboard) - .subscribe(() => { + .subscribe(data => { + this.saveToAssets(data); this.dialogRef.close(); }); } diff --git a/ui/src/app/data-explorer/dialog/asset-dialog.component.html b/ui/src/app/data-explorer/dialog/asset-dialog.component.html index bab116961a..4f99200ef6 100644 --- a/ui/src/app/data-explorer/dialog/asset-dialog.component.html +++ b/ui/src/app/data-explorer/dialog/asset-dialog.component.html @@ -25,7 +25,7 @@

{{ data.title }}

color="accent" data-cy="sp-show-chart-asset-checkbox" > - Add Pipeline to Assets + Add Chart to Assets @if (addToAssets) {