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 5252c16de8 feat: Enable search in overview tables (#4554)
5252c16de8 is described below
commit 5252c16de8562e0bc2827342bde6d98d3dcbc698
Author: Dominik Riemer <[email protected]>
AuthorDate: Sat Jun 13 12:12:05 2026 +0200
feat: Enable search in overview tables (#4554)
---
ui/deployment/i18n/de.json | 2 +-
ui/deployment/i18n/en.json | 2 +-
ui/deployment/i18n/pl.json | 2 +-
.../components/sp-table/sp-table.component.html | 55 +++++++++++--
.../components/sp-table/sp-table.component.scss | 16 +++-
.../lib/components/sp-table/sp-table.component.ts | 91 +++++++++++++++++++++-
.../src/lib/components/sp-table/sp-table.model.ts | 6 ++
.../asset-overview/asset-overview.component.html | 5 ++
.../chart-overview-table.component.html | 1 +
.../existing-adapters.component.html | 10 +--
.../existing-adapters.component.ts | 21 +----
.../dashboard-overview-table.component.html | 4 +
.../datalake-configuration.component.html | 4 +
.../pipeline-overview.component.html | 4 +
14 files changed, 186 insertions(+), 37 deletions(-)
diff --git a/ui/deployment/i18n/de.json b/ui/deployment/i18n/de.json
index b861858a15..be13f060a4 100644
--- a/ui/deployment/i18n/de.json
+++ b/ui/deployment/i18n/de.json
@@ -190,6 +190,7 @@
"Clear": "Zurücksetzen",
"Clear & use simple filters": "Löschen und einfache Filter verwenden",
"Clear Assembly Area": "Pipeline-Bereich leeren",
+ "Clear search": "Suche löschen",
"Clear selection & reload": "Auswahl löschen & neu laden",
"Click to add label": "Klicken, um Label hinzuzufügen",
"Click to load": "Klicken zum Laden",
@@ -339,7 +340,6 @@
"Dimension": "Dimension",
"Disable live preview": "Live-Vorschau deaktivieren",
"Disable script": "Skript deaktivieren",
- "Discard": "Verwerfen",
"Discard changes": "Änderungen verwerfen",
"Display Name": "Anzeigename",
"Display an image": "Ein Bild anzeigen",
diff --git a/ui/deployment/i18n/en.json b/ui/deployment/i18n/en.json
index 175bf2e9d3..81888abf2b 100644
--- a/ui/deployment/i18n/en.json
+++ b/ui/deployment/i18n/en.json
@@ -190,6 +190,7 @@
"Clear": null,
"Clear & use simple filters": null,
"Clear Assembly Area": null,
+ "Clear search": null,
"Clear selection & reload": null,
"Click to add label": null,
"Click to load": null,
@@ -339,7 +340,6 @@
"Dimension": null,
"Disable live preview": null,
"Disable script": null,
- "Discard": null,
"Discard changes": null,
"Display Name": null,
"Display an image": null,
diff --git a/ui/deployment/i18n/pl.json b/ui/deployment/i18n/pl.json
index 7bbc0436e1..c888b0c757 100644
--- a/ui/deployment/i18n/pl.json
+++ b/ui/deployment/i18n/pl.json
@@ -190,6 +190,7 @@
"Clear": "Wyczyść",
"Clear & use simple filters": "Wyczyść i użyj prostych filtrów",
"Clear Assembly Area": "Wyczyść obszar montażu",
+ "Clear search": "Wyczyść wyszukiwanie",
"Clear selection & reload": "Wyczyść wybór i przeładuj",
"Click to add label": "Kliknij, aby dodać etykietę",
"Click to load": "Kliknij, aby wczytać",
@@ -339,7 +340,6 @@
"Dimension": "Wymiar",
"Disable live preview": "Wyłącz podgląd na żywo",
"Disable script": "Wyłącz skrypt",
- "Discard": "Odrzuć",
"Discard changes": "Odrzuć zmiany",
"Display Name": "Nazwa wyświetlana",
"Display an image": "Wyświetl obraz",
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
index 540a4be3d9..01dbff145d 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
@@ -17,7 +17,10 @@
-->
<div fxLayout="column">
- @if (shouldShowGroupingControls && !showSelectionCheckboxes) {
+ @if (
+ (shouldShowGroupingControls || shouldShowNameSearch) &&
+ !showSelectionCheckboxes
+ ) {
<div
class="grouping-toolbar grouping-toolbar--standalone"
fxLayout="row wrap"
@@ -31,8 +34,14 @@
fxLayoutAlign="end center"
fxLayoutGap="12px"
>
- <ng-container *ngTemplateOutlet="groupingControls">
- </ng-container>
+ @if (shouldShowNameSearch) {
+ <ng-container *ngTemplateOutlet="nameSearchField">
+ </ng-container>
+ }
+ @if (shouldShowGroupingControls) {
+ <ng-container *ngTemplateOutlet="groupingControls">
+ </ng-container>
+ }
</div>
</div>
}
@@ -148,20 +157,52 @@
}
</div>
- @if (shouldShowGroupingControls) {
+ @if (shouldShowGroupingControls || shouldShowNameSearch) {
<div
class="selection-toolbar__right"
- fxLayout="row"
+ fxLayout="row wrap"
fxLayoutAlign="end center"
fxLayoutGap="8px"
>
- <ng-container *ngTemplateOutlet="groupingControls">
- </ng-container>
+ @if (shouldShowNameSearch) {
+ <ng-container *ngTemplateOutlet="nameSearchField">
+ </ng-container>
+ }
+ @if (shouldShowGroupingControls) {
+ <ng-container *ngTemplateOutlet="groupingControls">
+ </ng-container>
+ }
</div>
}
</div>
}
+ <ng-template #nameSearchField>
+ <mat-form-field class="form-field-small table-search-field">
+ <mat-icon matPrefix>search</mat-icon>
+ <input
+ matInput
+ [placeholder]="
+ nameSearchConfig?.placeholder ?? ('Search' | translate)
+ "
+ [value]="nameSearchTerm"
+ (input)="onNameSearchInput($any($event.target).value)"
+ />
+ @if (nameSearchTerm) {
+ <button
+ mat-icon-button
+ matSuffix
+ type="button"
+ class="table-search-field__clear"
+ [attr.aria-label]="'Clear search' | translate"
+ (click)="clearNameSearch()"
+ >
+ <mat-icon>close</mat-icon>
+ </button>
+ }
+ </mat-form-field>
+ </ng-template>
+
<ng-template #groupingControls>
@if (viewMode === 'grouped') {
<mat-form-field class="form-field-small grouping-toolbar__select">
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
index 28969295e4..3d122a76ca 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.scss
@@ -46,6 +46,10 @@
margin-left: auto;
}
+.grouping-toolbar__left {
+ flex: 1 1 auto;
+}
+
.selection-toolbar__actions-inline {
margin-left: var(--space-xs);
align-items: center;
@@ -61,7 +65,9 @@
}
.selection-toolbar__actions-inline .mat-mdc-form-field,
-.grouping-toolbar__controls .mat-mdc-form-field {
+.grouping-toolbar__controls .mat-mdc-form-field,
+.selection-toolbar__left .mat-mdc-form-field,
+.grouping-toolbar__left .mat-mdc-form-field {
margin-bottom: 0;
}
@@ -74,6 +80,14 @@
margin-left: auto;
}
+.table-search-field {
+ min-width: 220px;
+}
+
+.table-search-field__clear {
+ color: var(--color-text-2);
+}
+
.mat-mdc-row:hover {
background-color: var(--color-bg-1);
}
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 379b0c8b9e..d2a5cae146 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
@@ -69,7 +69,12 @@ import { NgClass, NgTemplateOutlet } from '@angular/common';
import { ClassDirective } from '@ngbracket/ngx-layout/extended';
import { TranslatePipe } from '@ngx-translate/core';
import { MatCheckbox } from '@angular/material/checkbox';
-import { MatFormField } from '@angular/material/form-field';
+import {
+ MatFormField,
+ MatPrefix,
+ MatSuffix,
+} from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
import { Subscription } from 'rxjs';
import { MatOption, MatSelect } from '@angular/material/select';
import { SpAssetBrowserService } from '../asset-browser/asset-browser.service';
@@ -82,6 +87,7 @@ import {
SpTableAssetContextConfig,
SpTableMultiActionExecuteEvent,
SpTableMultiActionOption,
+ SpTableNameSearchConfig,
SpTableResolvedAssetContext,
} from './sp-table.model';
import { SpTableAssetContextService } from
'./sp-asset-context/sp-table-asset-context.service';
@@ -126,6 +132,9 @@ type SpTableRenderedRow<T> = T | SpTableGroupHeaderRow;
MatIcon,
MatCheckbox,
MatFormField,
+ MatPrefix,
+ MatSuffix,
+ MatInput,
MatMenuTrigger,
MatMenu,
MatSelect,
@@ -175,6 +184,7 @@ export class SpTableComponent<T>
@Input() featureCardId: string;
@Input() resourceIdKey = 'elementId';
@Input() assetContextConfig?: SpTableAssetContextConfig;
+ @Input() nameSearchConfig?: SpTableNameSearchConfig<T>;
@Input() dataSource: MatTableDataSource<T>;
@@ -193,6 +203,7 @@ export class SpTableComponent<T>
visiblePageRows: T[] = [];
selectedMultiAction: string | null = null;
+ nameSearchTerm = '';
viewMode: SpTableGroupViewMode = 'list';
groupBy: SpTableGroupingMode = 'asset';
groupedSections: SpTableGroupedSection<T>[] = [];
@@ -206,6 +217,10 @@ export class SpTableComponent<T>
private renderedDataSubscription?: Subscription;
private assetDataSubscription?: Subscription;
private viewInitialized = false;
+ private defaultFilterPredicates = new WeakMap<
+ MatTableDataSource<T>,
+ (data: T, filter: string) => boolean
+ >();
private assetContextIndex = new Map<
string,
Map<string, SpTableResolvedAssetContext>
@@ -252,6 +267,7 @@ export class SpTableComponent<T>
this.selection.clear();
this.emitSelection();
this.visiblePageRows = [];
+ this.configureNameSearch();
if (this.viewInitialized) {
this.bindDataSource();
}
@@ -275,6 +291,10 @@ export class SpTableComponent<T>
this.applyAssetContextSortingAccessor();
this.refreshRenderedRows();
}
+
+ if (changes['nameSearchConfig'] && !changes['dataSource']) {
+ this.configureNameSearch();
+ }
}
ngOnDestroy() {
@@ -323,6 +343,18 @@ export class SpTableComponent<T>
return !!this.assetContextConfig;
}
+ get shouldShowNameSearch(): boolean {
+ return !!this.nameSearchConfig?.enabled;
+ }
+
+ get nameSearchKeys(): Array<keyof T | 'name'> {
+ if (!this.nameSearchConfig?.searchKey?.length) {
+ return ['name'];
+ }
+
+ return this.nameSearchConfig.searchKey;
+ }
+
get renderedDataSource(): MatTableDataSource<T> | SpTableRenderedRow<T>[] {
return this.viewMode === 'grouped'
? this.groupedSections.flatMap(section => [
@@ -476,6 +508,21 @@ export class SpTableComponent<T>
this.refreshRenderedRows();
}
+ onNameSearchInput(value: string) {
+ this.nameSearchTerm = value;
+ if (!this.dataSource) {
+ return;
+ }
+
+ this.dataSource.filter = value.trim().toLocaleLowerCase();
+ this.paginator?.firstPage();
+ this.refreshRenderedRows();
+ }
+
+ clearNameSearch() {
+ this.onNameSearchInput('');
+ }
+
isGroupHeaderRow = (_: number, row: SpTableRenderedRow<T>) =>
this.hasGroupHeaderMarker(row);
@@ -487,6 +534,7 @@ export class SpTableComponent<T>
return;
}
+ this.configureNameSearch();
this.dataSource.paginator = this.paginator;
this.renderedDataSubscription?.unsubscribe();
@@ -499,6 +547,47 @@ export class SpTableComponent<T>
this.updateRenderedState(this.getCurrentPageRows(), false);
}
+ private configureNameSearch() {
+ if (!this.dataSource) {
+ return;
+ }
+
+ if (!this.defaultFilterPredicates.has(this.dataSource)) {
+ this.defaultFilterPredicates.set(
+ this.dataSource,
+ this.dataSource.filterPredicate,
+ );
+ }
+
+ const defaultFilterPredicate =
+ this.defaultFilterPredicates.get(this.dataSource) ??
+ this.dataSource.filterPredicate;
+
+ if (!this.shouldShowNameSearch) {
+ this.nameSearchTerm = '';
+ this.dataSource.filterPredicate = defaultFilterPredicate;
+ this.dataSource.filter = '';
+ return;
+ }
+
+ this.dataSource.filterPredicate = (row, filter) => {
+ const normalizedFilter = filter.trim().toLocaleLowerCase();
+ if (!normalizedFilter) {
+ return true;
+ }
+
+ return this.nameSearchKeys.some(searchKey => {
+ const searchValue = (row as Record<string, unknown>)[
+ searchKey as string
+ ];
+ return String(searchValue ?? '')
+ .toLocaleLowerCase()
+ .includes(normalizedFilter);
+ });
+ };
+ this.dataSource.filter =
this.nameSearchTerm.trim().toLocaleLowerCase();
+ }
+
private getCurrentPageRows(): T[] {
const rows =
this.dataSource?.filteredData ?? this.dataSource?.data ?? [];
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.model.ts
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.model.ts
index 9ce38d0571..7a69a85f4c 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.model.ts
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.model.ts
@@ -36,6 +36,12 @@ export interface SpTableAssetContextConfig {
hideBelowWidth?: number;
}
+export interface SpTableNameSearchConfig<T = Record<string, unknown>> {
+ enabled: boolean;
+ placeholder?: string;
+ searchKey?: Array<keyof T>;
+}
+
export class SpTableAssetContextValue {
id: string;
label: string;
diff --git
a/ui/src/app/assets/components/asset-overview/asset-overview.component.html
b/ui/src/app/assets/components/asset-overview/asset-overview.component.html
index 3fd8bb3d83..80a6478999 100644
--- a/ui/src/app/assets/components/asset-overview/asset-overview.component.html
+++ b/ui/src/app/assets/components/asset-overview/asset-overview.component.html
@@ -58,6 +58,11 @@
fxFlex="100"
[columns]="displayedColumns"
[dataSource]="dataSource"
+ [nameSearchConfig]="{
+ enabled: true,
+ placeholder: 'Search assets',
+ searchKey: ['assetName', 'assetDescription'],
+ }"
[showActionsMenu]="true"
[rowsClickable]="true"
(rowClicked)="goToDetailsView($event)"
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 6c0803791f..1be768d6a4 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
@@ -25,6 +25,7 @@
fxFlex="100"
[columns]="displayedColumns"
[dataSource]="dataSource"
+ [nameSearchConfig]="{ enabled: true, placeholder: 'Search charts'
}"
[assetContextConfig]="assetContextConfig"
featureCardId="chart"
[showActionsMenu]="true"
diff --git
a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
index 91ba5b3cf1..ac68fa8b6f 100644
---
a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
+++
b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
@@ -35,11 +35,6 @@
}}
</button>
<div fxFlex fxLayout="row" fxLayoutAlign="end center">
- <sp-connect-filter-toolbar
- class="filter-bar-margin"
- (filterChangedEmitter)="applyFilter($event)"
- >
- </sp-connect-filter-toolbar>
<div
fxFlex="100"
fxLayout="row"
@@ -79,6 +74,11 @@
resourceIdKey="elementId"
[columns]="displayedColumns"
[dataSource]="dataSource"
+ [nameSearchConfig]="{
+ enabled: true,
+ placeholder: 'Search',
+ searchKey: ['name', 'description'],
+ }"
[assetContextConfig]="assetContextConfig"
[showSelectionCheckboxes]="true"
[showMultiActionsExecuteButton]="true"
diff --git
a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts
b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts
index 01b82347ad..25c7340a41 100644
---
a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts
+++
b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts
@@ -55,8 +55,6 @@ import { DeleteAdapterDialogComponent } from
'../../dialog/delete-adapter-dialog
import { AllAdapterActionsComponent } from
'../../dialog/start-all-adapters/all-adapter-actions-dialog.component';
import { MatSort, MatSortHeader } from '@angular/material/sort';
import { Router } from '@angular/router';
-import { AdapterFilterSettingsModel } from
'../../model/adapter-filter-settings.model';
-import { AdapterFilterPipe } from '../../filter/adapter-filter.pipe';
import { SpConnectRoutes } from '../../connect.breadcrumb';
import { Subscription } from 'rxjs';
import { ShepherdService } from '../../../services/tour/shepherd.service';
@@ -69,7 +67,6 @@ import {
} from '@ngbracket/ngx-layout/flex';
import { MatButton, MatIconButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
-import { SpConnectFilterToolbarComponent } from
'../filter-toolbar/filter-toolbar.component';
import { MatTooltip } from '@angular/material/tooltip';
import { AdapterStatusLightComponent } from
'./adapter-status-light/adapter-status-light.component';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
@@ -88,7 +85,6 @@ import { DatePipe } from '@angular/common';
LayoutGapDirective,
MatButton,
MatIcon,
- SpConnectFilterToolbarComponent,
MatIconButton,
MatTooltip,
SpBasicHeaderTitleComponent,
@@ -112,8 +108,6 @@ import { DatePipe } from '@angular/common';
export class ExistingAdaptersComponent implements OnInit, OnDestroy {
existingAdapters: AdapterSummaryDto[] = [];
filteredAdapters: AdapterSummaryDto[] = [];
-
- currentFilter: AdapterFilterSettingsModel;
operationInProgressAdapterId: string | undefined;
@ViewChild(MatSort)
@@ -158,7 +152,6 @@ export class ExistingAdaptersComponent implements OnInit,
OnDestroy {
private currentUserService = inject(CurrentUserService);
private router = inject(Router);
private pipelineElementAssetService = inject(PipelineElementAssetService);
- private adapterFilter = inject(AdapterFilterPipe);
private breadcrumbService = inject(SpBreadcrumbService);
private shepherdService = inject(ShepherdService);
private translate = inject(TranslateService);
@@ -379,12 +372,7 @@ export class ExistingAdaptersComponent implements OnInit,
OnDestroy {
applyAdapterFilters(elementIds: Set<string>): void {
this.currentFilterIds = elementIds;
- this.filteredAdapters = (
- this.adapterFilter.transform(
- this.existingAdapters,
- this.currentFilter,
- ) as AdapterSummaryDto[]
- ).filter(a => {
+ this.filteredAdapters = this.existingAdapters.filter(a => {
if (elementIds === undefined) {
return false;
} else if (elementIds.size === 0) {
@@ -406,13 +394,6 @@ export class ExistingAdaptersComponent implements OnInit,
OnDestroy {
});
}
- applyFilter(filter: AdapterFilterSettingsModel): void {
- this.currentFilter = filter;
- if (this.dataSource) {
- this.applyAdapterFilters(this.currentFilterIds);
- }
- }
-
navigateToDetailsOverviewPage(adapter: AdapterSummaryDto): void {
this.router.navigate(['connect', 'details', adapter.elementId]);
}
diff --git
a/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.html
b/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.html
index 561f4657c3..f31201ea25 100644
---
a/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.html
+++
b/ui/src/app/dashboard/components/overview/dashboard-overview-table/dashboard-overview-table.component.html
@@ -25,6 +25,10 @@
fxFlex="100"
[columns]="displayedColumns"
[assetContextConfig]="assetContextConfig"
+ [nameSearchConfig]="{
+ enabled: true,
+ searchKey: ['name', 'description'],
+ }"
featureCardId="dashboard"
[showActionsMenu]="true"
[rowsClickable]="true"
diff --git
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.html
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.html
index 198b41c9f0..d8792a53a4 100644
---
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.html
+++
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.html
@@ -61,6 +61,10 @@
featureCardId="measurement"
[dataSource]="dataSource"
[columns]="displayedColumns"
+ [nameSearchConfig]="{
+ enabled: true,
+ placeholder: 'Search datasets',
+ }"
[assetContextConfig]="assetContextConfig"
[showActionsMenu]="true"
matSort
diff --git
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
index bc1d182188..4fb0cb6d3f 100644
---
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
+++
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
@@ -24,6 +24,10 @@
[multiActionOptions]="
hasPipelineWritePrivileges ? bulkPipelineActionOptions : []
"
+ [nameSearchConfig]="{
+ enabled: true,
+ searchKey: ['name', 'description'],
+ }"
featureCardId="pipeline"
resourceIdKey="elementId"
[assetContextConfig]="assetContextConfig"