diff --git a/README.md b/README.md index c2675e6..4a72672 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,11 @@ A feature that allows you to create simulators with the help of Claude AI. You o ![Advanced simulator](screenshots/advanced-simulator-screenshot.png?raw=true "Advanced simulator screenshot") +## `Application builder dashboard migration plugin` + +A plugin that offers a button to migrate dashboards created via the deprecated [Application Builder app](https://github.com/Cumulocity-IoT/cumulocity-app-builder) to Cockpit reports. +The mentioned button can be found in the reports list in the Cockpit application once the plugin has been installed to the Cockpit app. + ## Contributing diff --git a/angular.json b/angular.json index 4a5aa9a..b2ad528 100644 --- a/angular.json +++ b/angular.json @@ -100,5 +100,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/cumulocity.config.ts b/cumulocity.config.ts index d161461..0095afb 100644 --- a/cumulocity.config.ts +++ b/cumulocity.config.ts @@ -42,6 +42,13 @@ export default { path: './src/app/advanced-simulator/advanced-simulator.module.ts', description: 'Allows to generate simulators with the help of Ai.', }, + { + name: 'Application builder dashboard migration', + module: 'AppBuilderDashboardMigrationModule', + path: './src/app/app-builder-dashboard-migration/app-builder-dashboard-migration.module.ts', + description: + 'Allows to migrate dashboards generated via application builder to be migrated into cockpit reports.', + }, ], versioningMatrix, }, diff --git a/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button.service.ts b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button.service.ts new file mode 100644 index 0000000..1f679e5 --- /dev/null +++ b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ActionBarItem, ExtensionFactory } from '@c8y/ngx-components'; +import { AppBuilderDashboardMigrationButtonComponent } from './app-builder-dashboard-migration-button/app-builder-dashboard-migration-button.component'; +import { Observable, of } from 'rxjs'; +import { distinctUntilChanged, switchMap } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class AppBuilderDashboardMigrationButtonService + implements ExtensionFactory +{ + get( + activatedRoute?: ActivatedRoute + ): Observable { + return (activatedRoute?.url || of([])).pipe( + switchMap(async (url) => { + if (url?.length !== 1 || url?.[0].path !== 'reports') { + return false; + } + + return true; + }), + distinctUntilChanged(), + switchMap(async (show) => { + if (!show) { + return []; + } + + return { + component: AppBuilderDashboardMigrationButtonComponent, + placement: 'right' as const, + }; + }) + ); + } +} diff --git a/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button/app-builder-dashboard-migration-button.component.html b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button/app-builder-dashboard-migration-button.component.html new file mode 100644 index 0000000..5177fb8 --- /dev/null +++ b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button/app-builder-dashboard-migration-button.component.html @@ -0,0 +1,9 @@ + diff --git a/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button/app-builder-dashboard-migration-button.component.ts b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button/app-builder-dashboard-migration-button.component.ts new file mode 100644 index 0000000..ad3e2c2 --- /dev/null +++ b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration-button/app-builder-dashboard-migration-button.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit } from '@angular/core'; +import { AlertService, IconDirective } from '@c8y/ngx-components'; +import { AppBuilderDashboardMigrationService } from '../app-builder-dashboard-migration.service'; +import { IManagedObject } from '@c8y/client'; + +@Component({ + selector: 'li[app-app-builder-dashboard-migration-button]', + templateUrl: './app-builder-dashboard-migration-button.component.html', + standalone: true, + imports: [IconDirective], +}) +export class AppBuilderDashboardMigrationButtonComponent implements OnInit { + dashboards: IManagedObject[] = []; + constructor( + private alert: AlertService, + private migrationService: AppBuilderDashboardMigrationService + ) {} + + async ngOnInit(): Promise { + this.dashboards = + await this.migrationService.getNotMigratedAppBuilderDashboards(); + } + + async migrateDashboards() { + this.alert.info('Migrating dashboards...'); + for (const dashboard of this.dashboards) { + await this.migrationService.migrateDashboard(dashboard); + } + this.alert.success( + 'Dashboards migrated successfully, please reload the page.' + ); + this.dashboards = []; + } +} diff --git a/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration.module.ts b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration.module.ts new file mode 100644 index 0000000..d6b05ee --- /dev/null +++ b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from '@angular/core'; +import { hookActionBar } from '@c8y/ngx-components'; +import { AppBuilderDashboardMigrationButtonService } from './app-builder-dashboard-migration-button.service'; + +@NgModule({ + providers: [hookActionBar(AppBuilderDashboardMigrationButtonService)], +}) +export class AppBuilderDashboardMigrationModule {} diff --git a/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration.service.ts b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration.service.ts new file mode 100644 index 0000000..846d567 --- /dev/null +++ b/src/app/app-builder-dashboard-migration/app-builder-dashboard-migration.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { IManagedObject, InventoryService } from '@c8y/client'; + +@Injectable({ + providedIn: 'root', +}) +export class AppBuilderDashboardMigrationService { + readonly reportPrefix = 'c8y_Dashboard!name!report_'; + readonly identifier = 'migratedAppBuilderDashboardsToReport'; + + constructor(private inventory: InventoryService) {} + + async getNotMigratedAppBuilderDashboards() { + const { data: dashboards } = await this.inventory.list({ + fragmentType: 'c8y_Dashboard', + pageSize: 2000, + }); + + const noReportDashboards = dashboards.filter( + (dashboard) => + !Object.keys(dashboard).some((key) => key.startsWith(this.reportPrefix)) + ); + + const dashboardsWithMarker = noReportDashboards.filter((dashboard) => + Object.keys(dashboard).some( + (key) => + key.startsWith('global!presales!') || + key === 'c8y_Dashboard!name!app-builder-db' + ) + ); + return dashboardsWithMarker; + } + + async migrateDashboard(dashboard: IManagedObject) { + const { id, c8y_Dashboard: dashboardDetails } = dashboard; + const { name, icon, priority } = dashboardDetails; + + const { data: reportObject } = await this.inventory.create({ + name: name || 'Unnamed app builder dashboard', + icon, + priority, + description: null, + c8y_Report: {}, + [this.identifier]: {}, + }); + + await this.inventory.update({ + id, + [`${this.reportPrefix}${reportObject.id}`]: {}, + [this.identifier]: {}, + }); + } +} diff --git a/versioningMatrix.json b/versioningMatrix.json index 648c518..110fd54 100644 --- a/versioningMatrix.json +++ b/versioningMatrix.json @@ -33,10 +33,14 @@ }, "3.4.0": { "sdk": ">=1020.26.2", - "api": ">=1020.26.2" + "api": ">=1020.0.0" }, "3.4.1": { "sdk": ">=1020.26.2", - "api": ">=1020.26.2" + "api": ">=1020.0.0" + }, + "3.5.0": { + "sdk": ">=1020.26.2", + "api": ">=1020.0.0" } }