This is an automated email from the ASF dual-hosted git repository. dominikriemer pushed a commit to branch improve-home-screen-loading-behaviour in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 482d898ee0a8889899cdaae301852f4cb9b04310 Author: Dominik Riemer <[email protected]> AuthorDate: Sat Jun 13 12:40:49 2026 +0200 fix: Improve loading behaviour of home screen --- ui/deployment/modules.yml | 8 +- ui/src/app/home/components/status.component.ts | 2 +- .../welcome-tour/welcome-tour.component.html | 45 +++++-- .../welcome-tour/welcome-tour.component.scss | 10 ++ .../dialog/welcome-tour/welcome-tour.component.ts | 127 +++++++++++++++++- ui/src/app/home/home.component.ts | 148 +++------------------ 6 files changed, 193 insertions(+), 147 deletions(-) diff --git a/ui/deployment/modules.yml b/ui/deployment/modules.yml index f8a7b05de1..429c018463 100644 --- a/ui/deployment/modules.yml +++ b/ui/deployment/modules.yml @@ -59,7 +59,7 @@ spConnect: createLinks: "['connect', 'catalog']" title: 'Adapters' createTitle: 'New adapter' - dataFns: '[this.adapterService.getAdapters()]' + dataFns: '[this.adapterService.getAdapterSummary()]' viewRoles: '[UserPrivilege.PRIVILEGE_READ_ADAPTER, UserPrivilege.PRIVILEGE_WRITE_ADAPTER]' createRoles: '[UserPrivilege.PRIVILEGE_WRITE_ADAPTER]' category: 'management' @@ -78,7 +78,7 @@ spPipelines: createLinks: "['pipelines', 'create']" title: 'Pipelines' createTitle: 'New pipeline' - dataFns: '[this.pipelineService.getPipelines()]' + dataFns: '[this.pipelineService.getPipelineSummary()]' viewRoles: '[UserPrivilege.PRIVILEGE_READ_PIPELINE, UserPrivilege.PRIVILEGE_WRITE_PIPELINE]' createRoles: '[UserPrivilege.PRIVILEGE_WRITE_PIPELINE]' category: 'analytics' @@ -107,7 +107,7 @@ spDashboard: createLinks: "['dashboard']" title: 'Dashboards' createTitle: 'New dashboard' - dataFns: '[this.dashboardService.getDashboards()]' + dataFns: '[this.dashboardService.getDashboardSummary()]' viewRoles: '[UserPrivilege.PRIVILEGE_READ_DASHBOARD, UserPrivilege.PRIVILEGE_WRITE_DASHBOARD]' createRoles: '[UserPrivilege.PRIVILEGE_WRITE_DASHBOARD]' category: 'visualization' @@ -127,7 +127,7 @@ spChart: createLinks: "['chart']" title: 'Charts' createTitle: 'New chart' - dataFns: '[this.chartService.getAllCharts()]' + dataFns: '[this.chartService.getChartSummary()]' viewRoles: '[UserPrivilege.PRIVILEGE_READ_DATA_EXPLORER_VIEW, UserPrivilege.PRIVILEGE_WRITE_DATA_EXPLORER_VIEW]' createRoles: '[UserPrivilege.PRIVILEGE_WRITE_DATA_EXPLORER_VIEW]' category: 'visualization' diff --git a/ui/src/app/home/components/status.component.ts b/ui/src/app/home/components/status.component.ts index 58872ec0cf..5143929bd0 100644 --- a/ui/src/app/home/components/status.component.ts +++ b/ui/src/app/home/components/status.component.ts @@ -55,7 +55,7 @@ export class StatusComponent implements OnInit { zip(this.statusBox.dataFns).subscribe(res => { let totalLength = 0; res.forEach(response => { - totalLength += response.length; + totalLength += response.totalCount; }); this.resourceCount = totalLength; diff --git a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.html b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.html index 150983d1a9..90c028bd2c 100644 --- a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.html +++ b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.html @@ -24,12 +24,6 @@ ><h3>Thank you for using {{ appConstants.APP_NAME }}.</h3></b > </div> - <div> - <img - src="assets/img/sp-pipeline-white-small.png" - style="margin-left: auto; margin-right: auto; display: block" - /> - </div> <div fxFlex="100" fxLayoutAlign="center center" fxLayout="column"> <div fxFlex="100" fxLayoutAlign="center center" fxLayout="column"> <b @@ -40,15 +34,38 @@ If you are using {{ appConstants.APP_NAME }} for the first time, we recommend taking the interactive tour! </p> + @if (loadingTutorialResources) { + <div + class="tutorial-status" + fxLayout="row" + fxLayoutAlign="center center" + fxLayoutGap="10px" + > + <mat-spinner [diameter]="20"></mat-spinner> + <span>Checking tutorial pipeline elements...</span> + </div> + } + @if (showMissingElementsHint) { + <sp-alert-banner + type="info" + title="Missing Elements for Tutorial" + description="The tutorial needs additional pipeline elements. + Install the missing extensions (Settings -> Extensions) and reopen the tour." + > + </sp-alert-banner> + } </div> - <button - mat-button - mat-flat-button - color="accent" - (click)="close(true)" - > - Start Tour - </button> + @if (!loadingTutorialResources && !showMissingElementsHint) { + <button + mat-button + mat-flat-button + color="accent" + [disabled]="loadingTutorialResources" + (click)="startTutorial()" + > + Start Tour + </button> + } </div> </div> <mat-divider></mat-divider> diff --git a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.scss b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.scss index c6405f3d58..a06a4f52eb 100644 --- a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.scss +++ b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.scss @@ -38,3 +38,13 @@ border-color: transparent; } } + +.tutorial-status { + margin-top: 0.5rem; +} + +.tutorial-hint { + margin: 0.5rem 0 0; + text-align: center; + max-width: 28rem; +} diff --git a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.ts b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.ts index 60fe452a98..8e57d2e7f4 100644 --- a/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.ts +++ b/ui/src/app/home/dialog/welcome-tour/welcome-tour.component.ts @@ -16,11 +16,23 @@ * */ -import { DialogRef } from '@streampipes/shared-ui'; +import { + DialogRef, + DialogService, + PanelType, + SpAlertBannerComponent, +} from '@streampipes/shared-ui'; import { Component, Input, OnInit, inject } from '@angular/core'; import { AppConstants } from '../../../services/app.constants'; import { AuthService } from '../../../services/auth.service'; -import { UserAccount, UserInfo } from '@streampipes/platform-services'; +import { + AdapterDescription, + AdapterService, + NamedStreamPipesEntity, + PipelineElementService, + UserAccount, + UserInfo, +} from '@streampipes/platform-services'; import { ProfileService } from '../../../profile/profile.service'; import { FlexDirective, @@ -29,6 +41,10 @@ import { } from '@ngbracket/ngx-layout/flex'; import { MatButton } from '@angular/material/button'; import { MatDivider } from '@angular/material/divider'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { MissingElementsForTutorialComponent } from '../../../editor/dialog/missing-elements-for-tutorial/missing-elements-for-tutorial.component'; +import { forkJoin } from 'rxjs'; +import { LayoutGapDirective } from '@ngbracket/ngx-layout'; @Component({ selector: 'sp-welcome-tour', @@ -40,18 +56,36 @@ import { MatDivider } from '@angular/material/divider'; LayoutAlignDirective, MatButton, MatDivider, + MatProgressSpinner, + LayoutGapDirective, + SpAlertBannerComponent, ], }) export class WelcomeTourComponent implements OnInit { + private static readonly REQUIRED_ADAPTER_FOR_TUTORIAL_APP_ID = + 'org.apache.streampipes.connect.iiot.adapters.simulator.machine'; + private static readonly REQUIRED_PROCESSOR_FOR_TUTORIAL_APP_ID = + 'org.apache.streampipes.processors.filters.jvm.numericalfilter'; + private static readonly REQUIRED_SINK_FOR_TUTORIAL_APP_ID = + 'org.apache.streampipes.sinks.internal.jvm.datalake'; + private authService = inject(AuthService); private dialogRef = inject<DialogRef<WelcomeTourComponent>>(DialogRef); private profileService = inject(ProfileService); + private dialogService = inject(DialogService); + private adapterService = inject(AdapterService); + private pipelineElementService = inject(PipelineElementService); appConstants = inject(AppConstants); @Input() userInfo: UserInfo; currentUser: UserAccount; + availablePipelineElements: NamedStreamPipesEntity[] = []; + availableAdapters: AdapterDescription[] = []; + missingElementsForTutorial = []; + tutorialAvailable = false; + loadingTutorialResources = true; ngOnInit(): void { this.profileService @@ -59,6 +93,24 @@ export class WelcomeTourComponent implements OnInit { .subscribe(data => { this.currentUser = data; }); + + forkJoin([ + this.adapterService.getAdapterDescriptions(), + this.pipelineElementService.getDataStreams(), + this.pipelineElementService.getDataProcessors(), + this.pipelineElementService.getDataSinks(), + ]).subscribe(res => { + this.availableAdapters = res[0]; + this.availablePipelineElements = [] + .concat(...res[1]) + .concat(...res[2]) + .concat(...res[3]); + this.missingElementsForTutorial = + this.getMissingElementsForTutorial(); + this.tutorialAvailable = + this.missingElementsForTutorial.length === 0; + this.loadingTutorialResources = false; + }); } hideTourForever() { @@ -71,7 +123,78 @@ export class WelcomeTourComponent implements OnInit { }); } + startTutorial() { + if (this.loadingTutorialResources) { + return; + } + + if (this.tutorialAvailable) { + this.close(true); + } else { + this.dialogService.open(MissingElementsForTutorialComponent, { + panelType: PanelType.STANDARD_PANEL, + title: 'Tutorial requires pipeline elements', + data: { + missingElementsForTutorial: this.missingElementsForTutorial, + }, + }); + } + } + close(startTutorial = false) { this.dialogRef.close(startTutorial); } + + get showMissingElementsHint() { + return !this.loadingTutorialResources && !this.tutorialAvailable; + } + + private getMissingElementsForTutorial() { + const missingElements = []; + + if ( + !this.requiredPeForTourPresent( + this.availableAdapters, + WelcomeTourComponent.REQUIRED_ADAPTER_FOR_TUTORIAL_APP_ID, + ) + ) { + missingElements.push({ + name: 'Machine Data Simulator', + appId: WelcomeTourComponent.REQUIRED_ADAPTER_FOR_TUTORIAL_APP_ID, + }); + } + + if ( + !this.requiredPeForTourPresent( + this.availablePipelineElements, + WelcomeTourComponent.REQUIRED_PROCESSOR_FOR_TUTORIAL_APP_ID, + ) + ) { + missingElements.push({ + name: 'Numerical Filter', + appId: WelcomeTourComponent.REQUIRED_PROCESSOR_FOR_TUTORIAL_APP_ID, + }); + } + + if ( + !this.requiredPeForTourPresent( + this.availablePipelineElements, + WelcomeTourComponent.REQUIRED_SINK_FOR_TUTORIAL_APP_ID, + ) + ) { + missingElements.push({ + name: 'Dashboard Sink', + appId: WelcomeTourComponent.REQUIRED_SINK_FOR_TUTORIAL_APP_ID, + }); + } + + return missingElements; + } + + private requiredPeForTourPresent( + list: Array<{ appId: string }>, + appId: string, + ) { + return list?.some(el => el.appId === appId); + } } diff --git a/ui/src/app/home/home.component.ts b/ui/src/app/home/home.component.ts index 5a625422db..a73f4303aa 100644 --- a/ui/src/app/home/home.component.ts +++ b/ui/src/app/home/home.component.ts @@ -31,12 +31,9 @@ import { SplitSectionComponent, } from '@streampipes/shared-ui'; import { UserRole } from '../core/auth/user-role.enum'; -import { MissingElementsForTutorialComponent } from '../editor/dialog/missing-elements-for-tutorial/missing-elements-for-tutorial.component'; import { WelcomeTourComponent } from './dialog/welcome-tour/welcome-tour.component'; import { ShepherdService } from '../services/tour/shepherd.service'; import { - AdapterDescription, - AdapterService, AssetConstants, AssetLinkType, AssetManagementService, @@ -44,12 +41,10 @@ import { GenericStorageService, LocationConfig, LocationConfigService, - NamedStreamPipesEntity, - PipelineElementService, SpAssetModel, UserInfo, } from '@streampipes/platform-services'; -import { forkJoin, Subscription, zip } from 'rxjs'; +import { forkJoin, Subscription } from 'rxjs'; import { StatusBox } from './models/home.model'; import { FlexDirective, @@ -92,9 +87,6 @@ export class HomeComponent implements OnInit, OnDestroy { serviceLinks = []; showStatus = false; - availablePipelineElements: NamedStreamPipesEntity[] = []; - availableAdapters: AdapterDescription[] = []; - statusBoxes: StatusBox[] = []; locationConfig: LocationConfig; assets: SpAssetModel[] = []; @@ -102,14 +94,6 @@ export class HomeComponent implements OnInit, OnDestroy { sites: Record<string, AssetSiteDesc> = {}; assetLinkTypes: Record<string, AssetLinkType> = {}; - requiredAdapterForTutorialAppId: any = - 'org.apache.streampipes.connect.iiot.adapters.simulator.machine'; - requiredProcessorForTutorialAppId: any = - 'org.apache.streampipes.processors.filters.jvm.numericalfilter'; - requiredSinkForTutorialAppId: any = - 'org.apache.streampipes.sinks.internal.jvm.datalake'; - missingElementsForTutorial: any = []; - isTutorialOpen = false; currentUser: UserInfo; selectedView = signal<string>('table'); @@ -122,8 +106,6 @@ export class HomeComponent implements OnInit, OnDestroy { private breadcrumbService = inject(SpBreadcrumbService); private dialogService = inject(DialogService); private shepherdService = inject(ShepherdService); - private pipelineElementService = inject(PipelineElementService); - private adapterService = inject(AdapterService); private genericStorageService = inject(GenericStorageService); private locationService = inject(LocationConfigService); private assetService = inject(AssetManagementService); @@ -175,8 +157,10 @@ export class HomeComponent implements OnInit, OnDestroy { this.contentLoaded = true; this.showStatus = true; }); - if (isAdmin) { - this.loadResources(); + console.log(isAdmin); + console.log(this.currentUser.showTutorial); + if (isAdmin && this.currentUser.showTutorial) { + this.checkForTutorial(); } this.breadcrumbService.updateBreadcrumb([]); } @@ -199,115 +183,27 @@ export class HomeComponent implements OnInit, OnDestroy { } checkForTutorial() { - if (this.currentUser.showTutorial) { - if (this.requiredPipelineElementsForTourPresent()) { - this.isTutorialOpen = true; - const dialogRef = this.dialogService.open( - WelcomeTourComponent, - { - panelType: PanelType.STANDARD_PANEL, - title: 'Welcome to ' + this.appConstants.APP_NAME, - data: { - userInfo: this.currentUser, - }, - }, - ); - dialogRef.afterClosed().subscribe(startTutorial => { - if (startTutorial) { - this.startTutorial(); - } - }); + this.isTutorialOpen = true; + const dialogRef = this.dialogService.open(WelcomeTourComponent, { + panelType: PanelType.STANDARD_PANEL, + title: 'Welcome to ' + this.appConstants.APP_NAME, + data: { + userInfo: this.currentUser, + }, + }); + dialogRef.afterClosed().subscribe(startTutorial => { + if (startTutorial) { + this.startTutorial(); + } else { + this.isTutorialOpen = false; } - } + }); } startTutorial() { - if (this.requiredPipelineElementsForTourPresent()) { - this.router.navigate(['connect']).then(() => { - this.shepherdService.startAdapterTour(); - }); - } else { - this.missingElementsForTutorial = []; - if (!this.requiredAdapterForTutorialAppId()) { - this.missingElementsForTutorial.push({ - name: 'Machine Data Simulator', - appId: this.requiredAdapterForTutorialAppId, - }); - } - if (!this.requiredProcessorForTourPresent()) { - this.missingElementsForTutorial.push({ - name: 'Numerical Filter', - appId: this.requiredProcessorForTutorialAppId, - }); - } - if (!this.requiredSinkForTourPresent()) { - this.missingElementsForTutorial.push({ - name: 'Dashboard Sink', - appId: this.requiredSinkForTutorialAppId, - }); - } - - this.dialogService.open(MissingElementsForTutorialComponent, { - panelType: PanelType.STANDARD_PANEL, - title: 'Tutorial requires pipeline elements', - data: { - missingElementsForTutorial: this.missingElementsForTutorial, - }, - }); - } - } - - requiredPipelineElementsForTourPresent() { - return ( - this.requiredAdapterForTourPresent() && - this.requiredProcessorForTourPresent() && - this.requiredSinkForTourPresent() - ); - } - - requiredAdapterForTourPresent() { - return this.requiredPeForTourPresent( - this.availableAdapters, - this.requiredAdapterForTutorialAppId, - ); - } - - requiredProcessorForTourPresent() { - return this.requiredPeForTourPresent( - this.availablePipelineElements, - this.requiredProcessorForTutorialAppId, - ); - } - - requiredSinkForTourPresent() { - return this.requiredPeForTourPresent( - this.availablePipelineElements, - this.requiredSinkForTutorialAppId, - ); - } - - requiredPeForTourPresent(list: NamedStreamPipesEntity[], appId: string) { - return ( - list && - list.some(el => { - return el.appId === appId; - }) - ); - } - - loadResources(): void { - zip( - this.adapterService.getAdapterDescriptions(), - this.pipelineElementService.getDataStreams(), - this.pipelineElementService.getDataProcessors(), - this.pipelineElementService.getDataSinks(), - ).subscribe(res => { - this.availableAdapters = res[0]; - this.availablePipelineElements = this.availablePipelineElements - .concat(...res[1]) - .concat(...res[2]) - .concat(...res[3]); - this.checkForTutorial(); + this.router.navigate(['connect']).then(() => { + this.shepherdService.startAdapterTour(); + this.isTutorialOpen = false; }); }
