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;
         });
     }
 

Reply via email to