This is an automated email from the ASF dual-hosted git repository.
dominikriemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new fac5220632 fix: Improve performance of charts (#4583)
fac5220632 is described below
commit fac52206325b9d36a0ce823c86a54fc937edd1ce
Author: Dominik Riemer <[email protected]>
AuthorDate: Wed Jun 17 23:04:22 2026 +0200
fix: Improve performance of charts (#4583)
---
.../lib/components/sp-table/sp-table.component.ts | 45 ++++++--
.../registry/chart-registry.service.ts | 21 ++++
.../chart-overview-table.component.html | 28 +++--
.../chart-overview-table.component.ts | 57 ++++++----
.../chart-overview/chart-overview.component.html | 2 +
.../chart-overview/chart-overview.component.scss | 8 --
.../chart-selection-panel.component.ts | 8 +-
.../chart-preview/chart-preview.component.html | 19 ++--
.../chart-preview/chart-preview.component.ts | 29 ++---
.../chart-selection/chart-selection.component.html | 15 ++-
.../chart-selection/chart-selection.component.ts | 119 ++++++++++++++-------
.../chart-selection/chart-selection.model.ts} | 37 ++-----
12 files changed, 229 insertions(+), 159 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 d7f0b5c3ce..2b01a2b6cd 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
@@ -75,7 +75,12 @@ import {
MatSuffix,
} from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
-import { Subscription } from 'rxjs';
+import {
+ debounceTime,
+ distinctUntilChanged,
+ Subject,
+ Subscription,
+} from 'rxjs';
import { MatOption, MatSelect } from '@angular/material/select';
import { SpAssetBrowserService } from '../asset-browser/asset-browser.service';
import { SpLabelComponent } from '../sp-label/sp-label.component';
@@ -207,6 +212,7 @@ export class SpTableComponent<T>
viewMode: SpTableGroupViewMode = 'list';
groupBy: SpTableGroupingMode = 'asset';
groupedSections: SpTableGroupedSection<T>[] = [];
+ renderedGroupedRows: SpTableRenderedRow<T>[] = [];
readonly selection = new SelectionModel<T>(true, []);
@@ -216,6 +222,8 @@ export class SpTableComponent<T>
private assetContextService = inject(SpTableAssetContextService);
private renderedDataSubscription?: Subscription;
private assetDataSubscription?: Subscription;
+ private nameSearchSubscription?: Subscription;
+ private nameSearchInput$ = new Subject<string>();
private viewInitialized = false;
private defaultFilterPredicates = new WeakMap<
MatTableDataSource<T>,
@@ -241,6 +249,9 @@ export class SpTableComponent<T>
this.applyAssetContextSortingAccessor();
this.refreshRenderedRows();
});
+ this.nameSearchSubscription = this.nameSearchInput$
+ .pipe(debounceTime(150), distinctUntilChanged())
+ .subscribe(value => this.applyNameSearchFilter(value));
this.updateCompactLayout();
}
@@ -300,6 +311,7 @@ export class SpTableComponent<T>
ngOnDestroy() {
this.renderedDataSubscription?.unsubscribe();
this.assetDataSubscription?.unsubscribe();
+ this.nameSearchSubscription?.unsubscribe();
}
@HostListener('window:resize')
@@ -357,16 +369,7 @@ export class SpTableComponent<T>
get renderedDataSource(): MatTableDataSource<T> | SpTableRenderedRow<T>[] {
return this.viewMode === 'grouped'
- ? this.groupedSections.flatMap(section => [
- {
- __spGroupHeader: true as const,
- id: section.id,
- title: section.title,
- color: section.color,
- count: section.count,
- },
- ...section.rows,
- ])
+ ? this.renderedGroupedRows
: this.dataSource;
}
@@ -510,10 +513,19 @@ export class SpTableComponent<T>
onNameSearchInput(value: string) {
this.nameSearchTerm = value;
+ this.nameSearchInput$.next(value);
+ }
+
+ private applyNameSearchFilter(value: string) {
if (!this.dataSource) {
return;
}
+ if (!this.shouldShowNameSearch) {
+ this.dataSource.filter = '';
+ return;
+ }
+
const normalizedFilter = value.trim().toLocaleLowerCase();
if (this.dataSource.filter === normalizedFilter) {
return;
@@ -730,6 +742,7 @@ export class SpTableComponent<T>
private rebuildGroupedSections(rows: T[]) {
if (!this.assetContextConfig || this.viewMode !== 'grouped') {
this.groupedSections = [];
+ this.renderedGroupedRows = [];
return;
}
@@ -756,6 +769,16 @@ export class SpTableComponent<T>
...group,
rows: [...group.rows],
}));
+ this.renderedGroupedRows = this.groupedSections.flatMap(section => [
+ {
+ __spGroupHeader: true as const,
+ id: section.id,
+ title: section.title,
+ color: section.color,
+ count: section.count,
+ },
+ ...section.rows,
+ ]);
}
private resolveGroups(
diff --git a/ui/src/app/chart-shared/registry/chart-registry.service.ts
b/ui/src/app/chart-shared/registry/chart-registry.service.ts
index d1d32dd65d..e7108a6257 100644
--- a/ui/src/app/chart-shared/registry/chart-registry.service.ts
+++ b/ui/src/app/chart-shared/registry/chart-registry.service.ts
@@ -82,6 +82,8 @@ export class ChartRegistry {
private translateService = inject(TranslateService);
chartTypes: IWidget<any>[] = [];
+ registeredChartSummary: Record<string, { icon: string; label: string }> =
+ {};
constructor() {
this.chartTypes = [
@@ -303,10 +305,22 @@ export class ChartRegistry {
),
},
];
+ this.makeChartSummary();
+ }
+
+ private makeChartSummary() {
+ this.registeredChartSummary = {};
+ this.chartTypes.forEach(c => {
+ this.registeredChartSummary[c.id] = {
+ label: c.label,
+ icon: c.icon,
+ };
+ });
}
registerChart(chart: IWidget<any>): void {
this.chartTypes.push(chart);
+ this.makeChartSummary();
}
getAvailableChartTemplates(): IWidget<any>[] {
@@ -325,6 +339,13 @@ export class ChartRegistry {
return this.getChartTemplate(chartId).id;
}
+ getRegisteredChartSummary(chartId: string): {
+ icon: string;
+ label: string;
+ } {
+ return this.registeredChartSummary[chartId];
+ }
+
private findBackwardsCompatibleChart(chartId: string): IWidget<any> {
return this.chartTypes.find(
chart => chart.alias !== undefined && chart.alias === chartId,
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 9b55684743..48adec8070 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,28 +20,38 @@
<sp-basic-header-title-component
[title]="'Charts' | translate"
></sp-basic-header-title-component>
- @if (isLoading) {
+ @if (isLoading && !hasLoadedCharts) {
<div
+ fxLayout="column"
fxFlex="100"
- fxLayout="row"
fxLayoutAlign="center center"
- class="chart-overview-loading"
+ fxLayoutGap="12px"
>
+ <mat-progress-spinner
+ mode="indeterminate"
+ diameter="40"
+ ></mat-progress-spinner>
+ <span class="chart-overview-loading-text">{{
+ 'Loading' | translate
+ }}</span>
+ </div>
+ } @else {
+ @if (isLoading) {
<div
- fxLayout="column"
- fxLayoutAlign="center center"
- fxLayoutGap="12px"
+ fxLayout="row"
+ fxLayoutAlign="end center"
+ fxLayoutGap="8px"
+ class="p-2"
>
<mat-progress-spinner
mode="indeterminate"
- diameter="40"
+ diameter="18"
></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"
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 4b4ff52f91..28d2659fda 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
@@ -17,6 +17,8 @@
*/
import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
Component,
inject,
Input,
@@ -53,7 +55,7 @@ import {
import { MatDialog } from '@angular/material/dialog';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { ChartRoutingService } from
'../../../../chart-shared/services/chart-routing.service';
-import { Subscription } from 'rxjs';
+import { finalize, Subscription } from 'rxjs';
import { MatSort, MatSortHeader } from '@angular/material/sort';
import {
FlexDirective,
@@ -110,6 +112,7 @@ type ChartOverviewRow = ChartSummaryDto & {
MatProgressSpinner,
TranslatePipe,
],
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChartOverviewTableComponent implements OnInit, OnDestroy {
@Input()
@@ -117,7 +120,6 @@ export class ChartOverviewTableComponent implements OnInit,
OnDestroy {
@ViewChild(MatSort)
set sort(sort: MatSort | undefined) {
- this._sort = sort;
if (sort) {
this.dataSource.sort = sort;
}
@@ -141,6 +143,7 @@ export class ChartOverviewTableComponent implements OnInit,
OnDestroy {
placeholder: 'Search charts',
};
isLoading = false;
+ hasLoadedCharts = false;
charts: ChartOverviewRow[] = [];
filteredCharts: ChartOverviewRow[] = [];
@@ -152,10 +155,10 @@ export class ChartOverviewTableComponent implements
OnInit, OnDestroy {
private routingService = inject(ChartRoutingService);
private assetFilterService = inject(SpAssetBrowserService);
private chartRegistryService = inject(ChartRegistry);
+ private cdr = inject(ChangeDetectorRef);
assetFilter$: Subscription;
- currentFilterIds = new Set<string>();
- private _sort?: MatSort;
+ currentFilterIds?: Set<string>;
private chartTypeMetadata = new Map<
string,
{ icon: string; label: string }
@@ -167,6 +170,7 @@ export class ChartOverviewTableComponent implements OnInit,
OnDestroy {
this.assetFilterService.currentAssetFilter$.subscribe(filter => {
this.currentFilterIds = filter?.activeElementIds;
this.applyChartFilters(this.currentFilterIds);
+ this.cdr.markForCheck();
});
this.dataSource.sortingDataAccessor = (chart, column) => {
@@ -186,20 +190,29 @@ export class ChartOverviewTableComponent implements
OnInit, OnDestroy {
getCharts(): void {
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;
- },
- });
+ this.cdr.markForCheck();
+ this.dataViewService
+ .getChartSummary()
+ .pipe(
+ finalize(() => {
+ this.isLoading = false;
+ this.cdr.markForCheck();
+ }),
+ )
+ .subscribe({
+ next: chartSummary => {
+ this.charts = chartSummary.resources
+ .map(chart => this.toChartOverviewRow(chart))
+ .sort((a, b) => a.name.localeCompare(b.name));
+ this.hasLoadedCharts = true;
+ this.applyChartFilters(this.currentFilterIds);
+ this.cdr.markForCheck();
+ },
+ error: () => {
+ this.hasLoadedCharts = true;
+ this.cdr.markForCheck();
+ },
+ });
}
ngOnDestroy(): void {
@@ -306,7 +319,7 @@ export class ChartOverviewTableComponent implements OnInit,
OnDestroy {
});
}
- applyChartFilters(elementIds: Set<string>): void {
+ applyChartFilters(elementIds?: Set<string>): void {
if (elementIds === undefined) {
this.filteredCharts = [];
} else if (elementIds.size === 0) {
@@ -316,9 +329,6 @@ export class ChartOverviewTableComponent implements OnInit,
OnDestroy {
elementIds.has(a.elementId),
);
}
- if (this._sort) {
- this.dataSource.sort = this._sort;
- }
this.dataSource.data = this.filteredCharts;
}
@@ -342,7 +352,8 @@ export class ChartOverviewTableComponent implements OnInit,
OnDestroy {
return cached;
}
- const template =
this.chartRegistryService.getChartTemplate(widgetType);
+ const template =
+ this.chartRegistryService.getRegisteredChartSummary(widgetType);
const metadata = {
icon: template?.icon ?? 'insert_chart',
label: template?.label ?? widgetType,
diff --git
a/ui/src/app/chart/components/chart-overview/chart-overview.component.html
b/ui/src/app/chart/components/chart-overview/chart-overview.component.html
index e4ee9d1afb..299e3ce368 100644
--- a/ui/src/app/chart/components/chart-overview/chart-overview.component.html
+++ b/ui/src/app/chart/components/chart-overview/chart-overview.component.html
@@ -40,6 +40,8 @@
</div>
<div fxFlex="100" fxLayout="column">
<sp-data-explorer-overview-table
+ fxLayout="column"
+ fxFlex="100"
[hasDataExplorerWritePrivileges]="hasDataExplorerWritePrivileges"
></sp-data-explorer-overview-table>
</div>
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 06c5911910..c10f9025ea 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
@@ -36,14 +36,6 @@
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-panel.component.ts
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.ts
index a6eef3e077..1f1204530e 100644
---
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.ts
+++
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.ts
@@ -16,7 +16,12 @@
*
*/
-import { Component, EventEmitter, Output } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ EventEmitter,
+ Output,
+} from '@angular/core';
import { MatTab, MatTabGroup } from '@angular/material/tabs';
import {
FlexDirective,
@@ -42,6 +47,7 @@ import { TranslatePipe } from '@ngx-translate/core';
ChartSelectionComponent,
TranslatePipe,
],
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChartSelectionPanelComponent {
@Output()
diff --git
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
index 29eddbab1d..d046ac5236 100644
---
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
+++
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
@@ -18,27 +18,30 @@
<div
class="data-view-preview-outer"
- [attr.data-cy]="dataCyId"
- [attr.title]="chart.name"
+ [attr.data-cy]="chartItem.dataCyId"
+ [attr.title]="chartItem.chart.name"
(click)="addChart()"
>
<div class="chart-preview-header">
- <div class="chart-preview-icon-shell" [attr.title]="widgetTypeLabel">
- <mat-icon>{{ widgetTypeIcon }}</mat-icon>
+ <div
+ class="chart-preview-icon-shell"
+ [attr.title]="chartItem.widgetTypeLabel"
+ >
+ <mat-icon>{{ chartItem.widgetTypeIcon }}</mat-icon>
</div>
<div class="chart-preview-copy">
<div class="chart-preview-title-row">
- <h5 class="chart-preview-title">{{ chart.name }}</h5>
+ <h5 class="chart-preview-title">{{ chartItem.chart.name }}</h5>
</div>
<div class="chart-preview-meta">
- @if (chart.datasetName) {
+ @if (chartItem.chart.datasetName) {
<span
class="chart-preview-meta-item chart-preview-dataset"
- [attr.title]="chart.datasetName"
+ [attr.title]="chartItem.chart.datasetName"
>
- {{ chart.datasetName }}
+ {{ chartItem.chart.datasetName }}
</span>
}
</div>
diff --git
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
index 4ca3306b26..327a252307 100644
---
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
+++
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
@@ -21,16 +21,14 @@ import {
Component,
EventEmitter,
Input,
- OnInit,
Output,
inject,
} from '@angular/core';
-import { ChartSummaryDto } from '@streampipes/platform-services';
-import { ChartRegistry } from
'../../../../../../chart-shared/registry/chart-registry.service';
import { MatIcon } from '@angular/material/icon';
import { MatIconButton } from '@angular/material/button';
import { FeatureCardService } from '@streampipes/shared-ui';
import { TranslatePipe } from '@ngx-translate/core';
+import { ChartSelectionItem } from '../chart-selection.model';
@Component({
selector: 'sp-chart-preview',
@@ -39,35 +37,24 @@ import { TranslatePipe } from '@ngx-translate/core';
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MatIcon, MatIconButton, TranslatePipe],
})
-export class ChartPreviewComponent implements OnInit {
- private widgetRegistryService = inject(ChartRegistry);
+export class ChartPreviewComponent {
private featureCardService = inject(FeatureCardService);
@Input()
- chart: ChartSummaryDto;
-
- widgetTypeLabel = '';
- widgetTypeIcon = 'insert_chart';
- dataCyId = '';
+ chartItem!: ChartSelectionItem;
@Output()
addChartEmitter: EventEmitter<string> = new EventEmitter<string>();
- ngOnInit(): void {
- const template = this.widgetRegistryService.getChartTemplate(
- this.chart.widgetType,
- );
- this.widgetTypeLabel = template?.label ?? this.chart.widgetType;
- this.widgetTypeIcon = template?.icon ?? 'insert_chart';
- this.dataCyId = `add-data-view-btn-${this.chart.name.replaceAll(' ',
'')}`;
- }
-
addChart(): void {
- this.addChartEmitter.emit(this.chart.elementId);
+ this.addChartEmitter.emit(this.chartItem.chart.elementId);
}
openPreview(event: MouseEvent): void {
event.stopPropagation();
- this.featureCardService.openFeatureCard('chart', this.chart.elementId);
+ this.featureCardService.openFeatureCard(
+ 'chart',
+ this.chartItem.chart.elementId,
+ );
}
}
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 535eed1f08..0554e8ffdd 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
@@ -30,8 +30,8 @@
>
<div fxLayout="column" class="chart-selection-heading">
<span class="chart-selection-caption">
- {{ filteredCharts.length }}
- @if (filteredCharts.length === 1) {
+ {{ filteredChartItems.length }}
+ @if (filteredChartItems.length === 1) {
{{ 'chart available' | translate }}
} @else {
{{ 'charts available' | translate }}
@@ -81,11 +81,10 @@
<input
[placeholder]="'Search charts' | translate"
matInput
- [value]="searchTerm"
- (input)="onSearchTermChanged($any($event.target).value)"
+ [formControl]="searchControl"
data-cy="chart-selection-search"
/>
- @if (hasActiveSearch()) {
+ @if (hasActiveSearch) {
<button
mat-icon-button
matSuffix
@@ -115,7 +114,7 @@
'Loading charts...' | translate
}}</span>
</div>
- } @else if (charts.length > 0 && filteredCharts.length > 0) {
+ } @else if (charts.length > 0 && filteredChartItems.length > 0) {
<cdk-virtual-scroll-viewport
fxFlexFill
class="chart-selection-results chart-selection-viewport"
@@ -125,13 +124,13 @@
>
<div
*cdkVirtualFor="
- let chart of filteredCharts;
+ let chartItem of filteredChartItems;
trackBy: trackByChartId
"
class="chart-selection-item"
>
<sp-chart-preview
- [chart]="chart"
+ [chartItem]="chartItem"
(addChartEmitter)="addChartEmitter.emit($event)"
>
</sp-chart-preview>
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 ebba633d40..dc3bd91733 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
@@ -20,12 +20,15 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
+ DestroyRef,
EventEmitter,
inject,
OnInit,
Output,
} from '@angular/core';
import { ChartService, ChartSummaryDto } from '@streampipes/platform-services';
+import { FormControl, ReactiveFormsModule } from '@angular/forms';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AuthService } from '../../../../../services/auth.service';
import { UserPrivilege } from '../../../../../core/auth/user-privilege.enum';
import { ChartRegistry } from
'../../../../../chart-shared/registry/chart-registry.service';
@@ -54,6 +57,8 @@ import {
CdkVirtualForOf,
CdkVirtualScrollViewport,
} from '@angular/cdk/scrolling';
+import { debounceTime, distinctUntilChanged, finalize } from 'rxjs';
+import { ChartSelectionItem } from './chart-selection.model';
@Component({
selector: 'sp-chart-selection',
@@ -80,6 +85,7 @@ import {
CdkVirtualScrollViewport,
CdkFixedSizeVirtualScroll,
CdkVirtualForOf,
+ ReactiveFormsModule,
],
})
export class ChartSelectionComponent implements OnInit {
@@ -88,14 +94,18 @@ export class ChartSelectionComponent implements OnInit {
private chartRegistryService = inject(ChartRegistry);
private chartRoutingService = inject(ChartRoutingService);
private cdr = inject(ChangeDetectorRef);
+ private destroyRef = inject(DestroyRef);
@Output()
addChartEmitter: EventEmitter<string> = new EventEmitter();
charts: ChartSummaryDto[] = [];
- filteredCharts: ChartSummaryDto[] = [];
+ chartItems: ChartSelectionItem[] = [];
+ filteredChartItems: ChartSelectionItem[] = [];
searchTerm = '';
+ searchControl = new FormControl('', { nonNullable: true });
isRefreshing = false;
+ hasActiveSearch = false;
readonly chartItemSize = 132;
hasChartWritePrivileges: boolean = false;
@@ -106,6 +116,16 @@ export class ChartSelectionComponent implements OnInit {
);
this.refreshCharts();
+
+ this.searchControl.valueChanges
+ .pipe(
+ debounceTime(150),
+ distinctUntilChanged(),
+ takeUntilDestroyed(this.destroyRef),
+ )
+ .subscribe(value => {
+ this.setSearchTerm(value);
+ });
}
navigateToDataViewCreation(): void {
@@ -115,62 +135,81 @@ export class ChartSelectionComponent implements OnInit {
refreshCharts(): void {
this.isRefreshing = true;
this.cdr.markForCheck();
- this.dataViewService.getChartSummary().subscribe({
- next: chartSummary => {
- this.charts = chartSummary.resources.sort((a, b) =>
- a.name.localeCompare(b.name),
- );
- this.applySearch();
- this.cdr.markForCheck();
- },
- complete: () => {
- this.isRefreshing = false;
- this.cdr.markForCheck();
- },
- error: () => {
- this.isRefreshing = false;
- this.cdr.markForCheck();
- },
- });
+ this.dataViewService
+ .getChartSummary()
+ .pipe(
+ finalize(() => {
+ this.isRefreshing = false;
+ this.cdr.markForCheck();
+ }),
+ )
+ .subscribe({
+ next: chartSummary => {
+ this.charts = [...chartSummary.resources].sort((a, b) =>
+ a.name.localeCompare(b.name),
+ );
+ this.chartItems = this.charts.map(chart =>
+ this.toChartSelectionItem(chart),
+ );
+ this.applySearch();
+ this.cdr.markForCheck();
+ },
+ error: () => {
+ this.charts = [];
+ this.chartItems = [];
+ this.filteredChartItems = [];
+ },
+ });
}
- onSearchTermChanged(value: string): void {
- this.searchTerm = value;
- this.applySearch();
- this.cdr.markForCheck();
+ clearSearch(): void {
+ this.searchControl.setValue('', { emitEvent: false });
+ this.setSearchTerm('');
}
- clearSearch(): void {
- this.searchTerm = '';
+ trackByChartId(index: number, item: ChartSelectionItem): string {
+ return item.chart.elementId;
+ }
+
+ private setSearchTerm(value: string): void {
+ this.searchTerm = value;
+ this.hasActiveSearch = this.searchTerm.trim().length > 0;
this.applySearch();
this.cdr.markForCheck();
}
- hasActiveSearch(): boolean {
- return this.searchTerm.trim().length > 0;
- }
+ private toChartSelectionItem(chart: ChartSummaryDto): ChartSelectionItem {
+ const template = this.chartRegistryService.getRegisteredChartSummary(
+ chart.widgetType,
+ );
+ const widgetTypeLabel = template?.label ?? chart.widgetType;
- trackByChartId(index: number, chart: ChartSummaryDto): string {
- return chart.elementId;
+ return {
+ chart,
+ widgetTypeLabel,
+ widgetTypeIcon: template?.icon ?? 'insert_chart',
+ dataCyId: `add-data-view-btn-${chart.name.replaceAll(' ', '')}`,
+ searchText: [
+ chart.name,
+ chart.datasetName,
+ chart.widgetType,
+ widgetTypeLabel,
+ ]
+ .filter((value): value is string => !!value)
+ .join(' ')
+ .toLowerCase(),
+ };
}
private applySearch(): void {
const query = this.searchTerm.trim().toLowerCase();
if (!query) {
- this.filteredCharts = this.charts;
+ this.filteredChartItems = this.chartItems;
return;
}
- this.filteredCharts = this.charts.filter(chart =>
- [
- chart.name,
- chart.datasetName,
- chart.widgetType,
- this.chartRegistryService.getChartTemplate(chart.widgetType)
- ?.label,
- ]
- .filter((value): value is string => !!value)
- .some(value => value.toLowerCase().includes(query)),
+ this.filteredChartItems = this.chartItems.filter(item =>
+ item.searchText.includes(query),
);
}
}
diff --git
a/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.model.ts
similarity index 65%
copy from
ui/src/app/chart/components/chart-overview/chart-overview.component.scss
copy to
ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.model.ts
index 06c5911910..78e4f80aa5 100644
--- a/ui/src/app/chart/components/chart-overview/chart-overview.component.scss
+++
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.model.ts
@@ -16,35 +16,12 @@
*
*/
-.mat-mdc-header-cell {
- font-weight: bold;
-}
-
-.w-100 {
- width: 100%;
-}
-
-.p-2 {
- padding: 2px;
-}
-
-.m-20 {
- margin: 20px;
-}
-
-.mr-10 {
- margin-right: 10px;
-}
-
-.chart-overview-loading {
- display: flex;
- flex: 1 1 auto;
- align-items: center;
- justify-content: center;
- text-align: center;
-}
+import { ChartSummaryDto } from '@streampipes/platform-services';
-.chart-overview-loading-text {
- font-size: var(--font-size-sm);
- color: var(--color-paragraph);
+export interface ChartSelectionItem {
+ chart: ChartSummaryDto;
+ widgetTypeLabel: string;
+ widgetTypeIcon: string;
+ dataCyId: string;
+ searchText: string;
}