This is an automated email from the ASF dual-hosted git repository.
dominikriemer pushed a commit to branch improve-chart-selection-loading
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to
refs/heads/improve-chart-selection-loading by this push:
new b5305ec847 Improve table performance
b5305ec847 is described below
commit b5305ec847b1fcba4452ffd2dd41fdffe346027f
Author: Dominik Riemer <[email protected]>
AuthorDate: Wed Jun 17 15:45:11 2026 +0200
Improve table performance
---
.../lib/components/sp-table/sp-table.component.ts | 13 +-
.../chart-overview-table.component.html | 294 +++++++++++----------
.../chart-overview-table.component.ts | 105 +++++---
.../chart-overview/chart-overview.component.scss | 13 +
.../chart-selection/chart-selection.component.html | 18 +-
.../chart-selection/chart-selection.component.scss | 9 +
.../chart-selection/chart-selection.component.ts | 2 +
7 files changed, 275 insertions(+), 179 deletions(-)
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
index d2a5cae146..d7f0b5c3ce 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.ts
@@ -514,9 +514,16 @@ export class SpTableComponent<T>
return;
}
- this.dataSource.filter = value.trim().toLocaleLowerCase();
- this.paginator?.firstPage();
- this.refreshRenderedRows();
+ const normalizedFilter = value.trim().toLocaleLowerCase();
+ if (this.dataSource.filter === normalizedFilter) {
+ return;
+ }
+
+ if (this.paginator && this.paginator.pageIndex !== 0) {
+ this.paginator.pageIndex = 0;
+ }
+
+ this.dataSource.filter = normalizedFilter;
}
clearNameSearch() {
diff --git
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
index 1be768d6a4..9b55684743 100644
---
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
+++
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.html
@@ -20,161 +20,165 @@
<sp-basic-header-title-component
[title]="'Charts' | translate"
></sp-basic-header-title-component>
- <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start">
- <sp-table
+ @if (isLoading) {
+ <div
fxFlex="100"
- [columns]="displayedColumns"
- [dataSource]="dataSource"
- [nameSearchConfig]="{ enabled: true, placeholder: 'Search charts'
}"
- [assetContextConfig]="assetContextConfig"
- featureCardId="chart"
- [showActionsMenu]="true"
- [rowsClickable]="true"
- (rowClicked)="openChart($event, true)"
- matSort
+ fxLayout="row"
+ fxLayoutAlign="center center"
+ class="chart-overview-loading"
>
- <ng-container matColumnDef="name">
- <th mat-header-cell mat-sort-header *matHeaderCellDef>
- {{ 'Chart' | translate }}
- </th>
- <td
- mat-cell
- data-cy="data-views-table-overview"
- *matCellDef="let element"
- >
- <div
- fxLayout="row"
- fxLayoutAlign="start center"
- fxLayoutGap="5px"
+ <div
+ fxLayout="column"
+ fxLayoutAlign="center center"
+ fxLayoutGap="12px"
+ >
+ <mat-progress-spinner
+ mode="indeterminate"
+ diameter="40"
+ ></mat-progress-spinner>
+ <span class="chart-overview-loading-text">{{
+ 'Loading' | translate
+ }}</span>
+ </div>
+ </div>
+ } @else {
+ <div fxFlex="100" fxLayout="row" fxLayoutAlign="center start">
+ <sp-table
+ fxFlex="100"
+ [columns]="displayedColumns"
+ [dataSource]="dataSource"
+ [nameSearchConfig]="nameSearchConfig"
+ [assetContextConfig]="assetContextConfig"
+ featureCardId="chart"
+ [showActionsMenu]="true"
+ [rowsClickable]="true"
+ (rowClicked)="openChart($event, true)"
+ matSort
+ >
+ <ng-container matColumnDef="name">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>
+ {{ 'Chart' | translate }}
+ </th>
+ <td
+ mat-cell
+ data-cy="data-views-table-overview"
+ *matCellDef="let element"
>
- @if (isLegacyMultiSourceChart(element)) {
- <mat-icon
- [matTooltip]="
- 'This chart is outdated and must be
migrated.'
- | translate
- "
- style="color: var(--color-warn)"
- >warning</mat-icon
- >
- }
- @if (requiresAttention(element)) {
- <mat-icon
- data-cy="chart-sync-problem-icon"
- [matTooltip]="
- 'This chart requires attention because the
dataset schema changed.'
- | translate
- "
- style="color: var(--color-warn)"
- >sync_problem</mat-icon
- >
- }
- <div fxLayout="column" fxLayoutAlign="start start">
- <span class="text-sm">{{ element.name }}</span>
+ <div
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ fxLayoutGap="5px"
+ >
+ @if (element.showLegacyWarning) {
+ <mat-icon
+ [matTooltip]="
+ 'This chart is outdated and must be
migrated.'
+ | translate
+ "
+ style="color: var(--color-warn)"
+ >warning</mat-icon
+ >
+ }
+ @if (element.showRequiresAttentionWarning) {
+ <mat-icon
+ data-cy="chart-sync-problem-icon"
+ [matTooltip]="
+ 'This chart requires attention because
the dataset schema changed.'
+ | translate
+ "
+ style="color: var(--color-warn)"
+ >sync_problem</mat-icon
+ >
+ }
+ <div fxLayout="column" fxLayoutAlign="start start">
+ <span class="text-sm">{{ element.name }}</span>
+ </div>
</div>
- </div>
- </td>
- </ng-container>
+ </td>
+ </ng-container>
- <ng-container matColumnDef="chartType">
- <th mat-header-cell mat-sort-header *matHeaderCellDef>
- {{ 'Type' | translate }}
- </th>
- <td mat-cell *matCellDef="let element">
- <div
- fxLayout="row"
- fxLayoutAlign="start center"
- class="text-sm"
- >
- <mat-icon [matTooltip]="getChartTypeName(element)">{{
- getChartTypeIcon(element)
- }}</mat-icon>
- </div>
- </td>
- </ng-container>
- <ng-container matColumnDef="lastModified">
- <th mat-header-cell mat-sort-header *matHeaderCellDef>
- {{ 'Last modified' | translate }}
- </th>
- <td mat-cell *matCellDef="let element">
- @if (element.lastModifiedEpochMs !== null) {
- <div>
- {{ formatDate(element.lastModifiedEpochMs) }}
+ <ng-container matColumnDef="chartType">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>
+ {{ 'Type' | translate }}
+ </th>
+ <td mat-cell *matCellDef="let element">
+ <div
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ class="text-sm"
+ >
+ <mat-icon [matTooltip]="element.chartTypeName">{{
+ element.chartTypeIcon
+ }}</mat-icon>
</div>
- } @else {
- <div>–</div>
- }
- </td>
- </ng-container>
+ </td>
+ </ng-container>
+ <ng-container matColumnDef="lastModified">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>
+ {{ 'Last modified' | translate }}
+ </th>
+ <td mat-cell *matCellDef="let element">
+ <div>{{ element.lastModifiedLabel }}</div>
+ </td>
+ </ng-container>
- <ng-container matColumnDef="createdAt">
- <th mat-header-cell mat-sort-header *matHeaderCellDef>
- {{ 'Created' | translate }}
- </th>
- <td mat-cell *matCellDef="let element">
- @if (element.createdAtEpochMs !== null) {
- <div>
- {{ formatDate(element.createdAtEpochMs) }}
- </div>
- } @else {
- <div>–</div>
- }
- </td>
- </ng-container>
+ <ng-container matColumnDef="createdAt">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>
+ {{ 'Created' | translate }}
+ </th>
+ <td mat-cell *matCellDef="let element">
+ <div>{{ element.createdAtLabel }}</div>
+ </td>
+ </ng-container>
- <ng-template spTableActions let-element>
- <button
- mat-menu-item
- [attr.data-cy]="
- 'show-data-view-' + element.name.replaceAll(' ', '')
- "
- (click)="openChart(element, false)"
- >
- <mat-icon>visibility</mat-icon>
- <span>{{ 'Show' | translate }}</span>
- </button>
- @if (hasDataExplorerWritePrivileges) {
+ <ng-template spTableActions let-element>
<button
mat-menu-item
- [attr.data-cy]="
- 'edit-data-view-' + element.name.replaceAll(' ',
'')
- "
- (click)="openChart(element, true)"
+ [attr.data-cy]="element.showDataCyId"
+ (click)="openChart(element, false)"
>
- <mat-icon>edit</mat-icon>
- <span>{{ 'Edit chart' | translate }}</span>
- </button>
- }
- @if (hasDataExplorerWritePrivileges) {
- <button
- [attr.data-cy]="
- 'open-manage-permissions-' +
- element.name.replaceAll(' ', '')
- "
- mat-menu-item
- (click)="showManageDialog(element)"
- >
- <mat-icon>settings</mat-icon>
- <span>{{ 'Manage' | translate }}</span>
- </button>
- }
- @if (hasDataExplorerWritePrivileges) {
- <button mat-menu-item (click)="cloneChart(element)">
- <mat-icon>flip_to_front</mat-icon>
- <span>{{ 'Clone chart' | translate }}</span>
+ <mat-icon>visibility</mat-icon>
+ <span>{{ 'Show' | translate }}</span>
</button>
- }
- @if (hasDataExplorerWritePrivileges) {
- <button
- mat-menu-item
- [matTooltip]=""
- [attr.data-cy]="'delete-data-view-' + element.name"
- (click)="deleteChart(element)"
- >
- <mat-icon>delete</mat-icon>
- <span>{{ 'Delete chart' | translate }}</span>
- </button>
- }
- </ng-template>
- </sp-table>
- </div>
+ @if (hasDataExplorerWritePrivileges) {
+ <button
+ mat-menu-item
+ [attr.data-cy]="element.editDataCyId"
+ (click)="openChart(element, true)"
+ >
+ <mat-icon>edit</mat-icon>
+ <span>{{ 'Edit chart' | translate }}</span>
+ </button>
+ }
+ @if (hasDataExplorerWritePrivileges) {
+ <button
+ [attr.data-cy]="element.manageDataCyId"
+ mat-menu-item
+ (click)="showManageDialog(element)"
+ >
+ <mat-icon>settings</mat-icon>
+ <span>{{ 'Manage' | translate }}</span>
+ </button>
+ }
+ @if (hasDataExplorerWritePrivileges) {
+ <button mat-menu-item (click)="cloneChart(element)">
+ <mat-icon>flip_to_front</mat-icon>
+ <span>{{ 'Clone chart' | translate }}</span>
+ </button>
+ }
+ @if (hasDataExplorerWritePrivileges) {
+ <button
+ mat-menu-item
+ [matTooltip]=""
+ [attr.data-cy]="element.deleteDataCyId"
+ (click)="deleteChart(element)"
+ >
+ <mat-icon>delete</mat-icon>
+ <span>{{ 'Delete chart' | translate }}</span>
+ </button>
+ }
+ </ng-template>
+ </sp-table>
+ </div>
+ }
</div>
diff --git
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
index 0968a4a169..4b4ff52f91 100644
---
a/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
+++
b/ui/src/app/chart/components/chart-overview/chart-overview-table/chart-overview-table.component.ts
@@ -65,12 +65,26 @@ import { MatMenuItem } from '@angular/material/menu';
import { MatIcon } from '@angular/material/icon';
import { MatTooltip } from '@angular/material/tooltip';
import { ChartRegistry } from
'../../../../chart-shared/registry/chart-registry.service';
+import { MatProgressSpinner } from '@angular/material/progress-spinner';
type ManageableChart = DataExplorerWidgetModel & {
name: string;
description: string;
};
+type ChartOverviewRow = ChartSummaryDto & {
+ chartTypeIcon: string;
+ chartTypeName: string;
+ createdAtLabel: string;
+ lastModifiedLabel: string;
+ showLegacyWarning: boolean;
+ showRequiresAttentionWarning: boolean;
+ showDataCyId: string;
+ editDataCyId: string;
+ manageDataCyId: string;
+ deleteDataCyId: string;
+};
+
@Component({
selector: 'sp-data-explorer-overview-table',
templateUrl: './chart-overview-table.component.html',
@@ -93,6 +107,7 @@ type ManageableChart = DataExplorerWidgetModel & {
MatMenuItem,
MatIcon,
MatTooltip,
+ MatProgressSpinner,
TranslatePipe,
],
})
@@ -101,9 +116,14 @@ export class ChartOverviewTableComponent implements
OnInit, OnDestroy {
hasDataExplorerWritePrivileges: boolean;
@ViewChild(MatSort)
- sort: MatSort;
+ set sort(sort: MatSort | undefined) {
+ this._sort = sort;
+ if (sort) {
+ this.dataSource.sort = sort;
+ }
+ }
- dataSource = new MatTableDataSource<ChartSummaryDto>();
+ dataSource = new MatTableDataSource<ChartOverviewRow>();
displayedColumns: string[] = [
'name',
'chartType',
@@ -116,8 +136,13 @@ export class ChartOverviewTableComponent implements
OnInit, OnDestroy {
resourceLinkType: 'chart',
resourceIdKey: 'elementId',
};
- charts: ChartSummaryDto[] = [];
- filteredCharts: ChartSummaryDto[] = [];
+ readonly nameSearchConfig = {
+ enabled: true,
+ placeholder: 'Search charts',
+ };
+ isLoading = false;
+ charts: ChartOverviewRow[] = [];
+ filteredCharts: ChartOverviewRow[] = [];
private dataViewService = inject(ChartService);
private dialog = inject(MatDialog);
@@ -130,6 +155,7 @@ export class ChartOverviewTableComponent implements OnInit,
OnDestroy {
assetFilter$: Subscription;
currentFilterIds = new Set<string>();
+ private _sort?: MatSort;
private chartTypeMetadata = new Map<
string,
{ icon: string; label: string }
@@ -159,11 +185,20 @@ export class ChartOverviewTableComponent implements
OnInit, OnDestroy {
}
getCharts(): void {
- this.dataViewService.getChartSummary().subscribe(chartSummary => {
- this.charts = chartSummary.resources.sort((a, b) =>
- a.name.localeCompare(b.name),
- );
- this.applyChartFilters(this.currentFilterIds);
+ this.isLoading = true;
+ this.dataViewService.getChartSummary().subscribe({
+ next: chartSummary => {
+ this.charts = chartSummary.resources
+ .map(chart => this.toChartOverviewRow(chart))
+ .sort((a, b) => a.name.localeCompare(b.name));
+ this.applyChartFilters(this.currentFilterIds);
+ },
+ complete: () => {
+ this.isLoading = false;
+ },
+ error: () => {
+ this.isLoading = false;
+ },
});
}
@@ -281,30 +316,12 @@ export class ChartOverviewTableComponent implements
OnInit, OnDestroy {
elementIds.has(a.elementId),
);
}
- this.dataSource.sort = this.sort;
+ if (this._sort) {
+ this.dataSource.sort = this._sort;
+ }
this.dataSource.data = this.filteredCharts;
}
- getChartTypeIcon(chart: ChartSummaryDto): string {
- return this.getChartTypeMetadata(chart.widgetType).icon;
- }
-
- getChartTypeName(chart: ChartSummaryDto): string {
- return this.getChartTypeMetadata(chart.widgetType).label;
- }
-
- formatDate(timestamp?: number): string {
- return this.dateFormatService.formatDate(timestamp);
- }
-
- isLegacyMultiSourceChart(chart: ChartSummaryDto): boolean {
- return chart.multiSourceChart;
- }
-
- requiresAttention(chart: ChartSummaryDto): boolean {
- return chart?.healthStatus === 'REQUIRES_ATTENTION';
- }
-
private withChart(
chartSummary: ChartSummaryDto,
callback: (chart: DataExplorerWidgetModel) => void,
@@ -333,4 +350,32 @@ export class ChartOverviewTableComponent implements
OnInit, OnDestroy {
this.chartTypeMetadata.set(widgetType, metadata);
return metadata;
}
+
+ private toChartOverviewRow(chart: ChartSummaryDto): ChartOverviewRow {
+ const typeMetadata = this.getChartTypeMetadata(chart.widgetType);
+ const sanitizedName = chart.name.replaceAll(' ', '');
+
+ return {
+ ...chart,
+ chartTypeIcon: typeMetadata.icon,
+ chartTypeName: typeMetadata.label,
+ createdAtLabel:
+ chart.createdAtEpochMs !== null
+ ? this.dateFormatService.formatDate(chart.createdAtEpochMs)
+ : '–',
+ lastModifiedLabel:
+ chart.lastModifiedEpochMs !== null
+ ? this.dateFormatService.formatDate(
+ chart.lastModifiedEpochMs,
+ )
+ : '–',
+ showLegacyWarning: !!chart.multiSourceChart,
+ showRequiresAttentionWarning:
+ chart.healthStatus === 'REQUIRES_ATTENTION',
+ showDataCyId: `show-data-view-${sanitizedName}`,
+ editDataCyId: `edit-data-view-${sanitizedName}`,
+ manageDataCyId: `open-manage-permissions-${sanitizedName}`,
+ deleteDataCyId: `delete-data-view-${chart.name}`,
+ };
+ }
}
diff --git
a/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
b/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
index 9103beb12d..06c5911910 100644
--- a/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
+++ b/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
@@ -35,3 +35,16 @@
.mr-10 {
margin-right: 10px;
}
+
+.chart-overview-loading {
+ display: flex;
+ flex: 1 1 auto;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+}
+
+.chart-overview-loading-text {
+ font-size: var(--font-size-sm);
+ color: var(--color-paragraph);
+}
diff --git
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
index 8fd0b96a9e..535eed1f08 100644
---
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
+++
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
@@ -99,7 +99,23 @@
</mat-form-field>
</div>
- @if (charts.length > 0 && filteredCharts.length > 0) {
+ @if (isRefreshing) {
+ <div
+ fxFlex="100"
+ fxLayout="column"
+ fxLayoutAlign="center center"
+ fxLayoutGap="12px"
+ class="chart-selection-loading"
+ >
+ <mat-progress-spinner
+ mode="indeterminate"
+ diameter="36"
+ ></mat-progress-spinner>
+ <span class="chart-selection-loading-text">{{
+ 'Loading charts...' | translate
+ }}</span>
+ </div>
+ } @else if (charts.length > 0 && filteredCharts.length > 0) {
<cdk-virtual-scroll-viewport
fxFlexFill
class="chart-selection-results chart-selection-viewport"
diff --git
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
index 50eee74146..6af04c100e 100644
---
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
+++
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
@@ -94,6 +94,15 @@
max-width: 100%;
}
+.chart-selection-loading {
+ text-align: center;
+}
+
+.chart-selection-loading-text {
+ font-size: var(--font-size-sm);
+ color: var(--color-paragraph);
+}
+
.chart-selection-empty {
min-height: 220px;
text-align: center;
diff --git
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
index e9a346177f..ebba633d40 100644
---
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
+++
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
@@ -47,6 +47,7 @@ import {
MatSuffix,
} from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
+import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { MatTooltip } from '@angular/material/tooltip';
import {
CdkFixedSizeVirtualScroll,
@@ -73,6 +74,7 @@ import {
MatPrefix,
MatSuffix,
MatInput,
+ MatProgressSpinner,
TranslatePipe,
MatTooltip,
CdkVirtualScrollViewport,