This is an automated email from the ASF dual-hosted git repository.
riemer 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 8fd62bacc1 feat(#4072): Reduce loading time for dataset counts (#4073)
8fd62bacc1 is described below
commit 8fd62bacc1d22934625a842118dc3d8210a626df
Author: Dominik Riemer <[email protected]>
AuthorDate: Tue Dec 30 19:58:22 2025 +0100
feat(#4072): Reduce loading time for dataset counts (#4073)
---
.../dataexplorer/api/IDataExplorerManager.java | 3 +-
.../influx/DataExplorerManagerInflux.java | 5 +-
.../influx/DataLakeMeasurementCounterInflux.java | 14 +-
.../iotdb/DataExplorerManagerIotDb.java | 6 +-
.../iotdb/DataLakeMeasurementCounterIotDb.java | 9 +-
.../query/DataLakeMeasurementCounter.java | 5 +-
.../query/DataLakeMeasurementCounterTestImpl.java | 4 +-
.../impl/datalake/DataLakeMeasureResource.java | 6 +-
.../utils/dataExplorer/DataExplorerUtils.ts | 1 +
.../tests/dataExplorer/configuration.smoke.spec.ts | 4 +
ui/deployment/feature-cards.yml | 4 +
.../src/lib/apis/datalake-rest.service.ts | 6 +-
.../datalake-configuration-entry.ts | 5 +-
.../datalake-configuration.component.html | 253 ++++++++++++---------
.../datalake-configuration.component.scss | 4 +
.../datalake-configuration.component.ts | 61 +++--
.../dataset-feature-card.component.html | 90 ++++++++
.../dataset-feature-card.component.scss} | 19 +-
.../dataset-feature-card.component.ts | 108 +++++++++
ui/src/app/dataset/dataset.module.ts | 1 +
.../matching-error/matching-error.component.html | 69 +++---
21 files changed, 492 insertions(+), 185 deletions(-)
diff --git
a/streampipes-data-explorer-api/src/main/java/org/apache/streampipes/dataexplorer/api/IDataExplorerManager.java
b/streampipes-data-explorer-api/src/main/java/org/apache/streampipes/dataexplorer/api/IDataExplorerManager.java
index 586a77706c..5711aade33 100644
---
a/streampipes-data-explorer-api/src/main/java/org/apache/streampipes/dataexplorer/api/IDataExplorerManager.java
+++
b/streampipes-data-explorer-api/src/main/java/org/apache/streampipes/dataexplorer/api/IDataExplorerManager.java
@@ -35,7 +35,8 @@ public interface IDataExplorerManager {
*/
IDataLakeMeasurementCounter getMeasurementCounter(
List<DataLakeMeasure> allMeasurements,
- List<String> measurementsToCount
+ List<String> measurementsToCount,
+ int daysBack
);
IDataExplorerQueryManagement
getQueryManagement(IDataExplorerSchemaManagement dataExplorerSchemaManagement);
diff --git
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataExplorerManagerInflux.java
b/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataExplorerManagerInflux.java
index 869f314b1d..f88cf6c585 100644
---
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataExplorerManagerInflux.java
+++
b/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataExplorerManagerInflux.java
@@ -39,8 +39,9 @@ public class DataExplorerManagerInflux implements
IDataExplorerManager {
@Override
public IDataLakeMeasurementCounter getMeasurementCounter(
List<DataLakeMeasure> allMeasurements,
- List<String> measurementsToCount) {
- return new DataLakeMeasurementCounterInflux(allMeasurements,
measurementsToCount);
+ List<String> measurementsToCount,
+ int daysBack) {
+ return new DataLakeMeasurementCounterInflux(allMeasurements,
measurementsToCount, daysBack);
}
@Override
diff --git
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataLakeMeasurementCounterInflux.java
b/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataLakeMeasurementCounterInflux.java
index 0a58b8b29a..a54a3db9e0 100644
---
a/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataLakeMeasurementCounterInflux.java
+++
b/streampipes-data-explorer-influx/src/main/java/org/apache/streampipes/dataexplorer/influx/DataLakeMeasurementCounterInflux.java
@@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
public class DataLakeMeasurementCounterInflux extends
DataLakeMeasurementCounter {
@@ -37,9 +38,10 @@ public class DataLakeMeasurementCounterInflux extends
DataLakeMeasurementCounter
public DataLakeMeasurementCounterInflux(
List<DataLakeMeasure> allMeasurements,
- List<String> measurementNames
+ List<String> measurementNames,
+ int daysBack
) {
- super(allMeasurements, measurementNames);
+ super(allMeasurements, measurementNames, daysBack);
}
@Override
@@ -54,10 +56,16 @@ public class DataLakeMeasurementCounterInflux extends
DataLakeMeasurementCounter
return 0;
}
+ var endTime = System.currentTimeMillis();
+ long startTime = endTime - TimeUnit.DAYS.toMillis(daysBack);
var builder = DataLakeInfluxQueryBuilder
.create(measure.getMeasureName())
- .withEndTime(System.currentTimeMillis())
+ .withEndTime(endTime)
.withAggregatedColumn(firstColumn, AggregationFunction.COUNT);
+
+ if (daysBack > -1) {
+ builder.withStartTime(startTime);
+ }
var queryResult = new
DataExplorerInfluxQueryExecutor().executeQuery(builder.build(),
Optional.empty(), true);
return queryResult.getTotal() > 0 ? extractResult(queryResult,
COUNT_FIELD) : 0;
diff --git
a/streampipes-data-explorer-iotdb/src/main/java/org/apache/streampipes/dataexplorer/iotdb/DataExplorerManagerIotDb.java
b/streampipes-data-explorer-iotdb/src/main/java/org/apache/streampipes/dataexplorer/iotdb/DataExplorerManagerIotDb.java
index 612c2dee6c..e931403809 100644
---
a/streampipes-data-explorer-iotdb/src/main/java/org/apache/streampipes/dataexplorer/iotdb/DataExplorerManagerIotDb.java
+++
b/streampipes-data-explorer-iotdb/src/main/java/org/apache/streampipes/dataexplorer/iotdb/DataExplorerManagerIotDb.java
@@ -36,8 +36,10 @@ import java.util.List;
public class DataExplorerManagerIotDb implements IDataExplorerManager {
@Override
- public IDataLakeMeasurementCounter
getMeasurementCounter(List<DataLakeMeasure> allMeasurements, List<String>
measurementsToCount) {
- return new DataLakeMeasurementCounterIotDb(allMeasurements,
measurementsToCount);
+ public IDataLakeMeasurementCounter
getMeasurementCounter(List<DataLakeMeasure> allMeasurements,
+ List<String>
measurementsToCount,
+ int daysBack) {
+ return new DataLakeMeasurementCounterIotDb(allMeasurements,
measurementsToCount, daysBack);
}
@Override
diff --git
a/streampipes-data-explorer-iotdb/src/main/java/org/apache/streampipes/dataexplorer/iotdb/DataLakeMeasurementCounterIotDb.java
b/streampipes-data-explorer-iotdb/src/main/java/org/apache/streampipes/dataexplorer/iotdb/DataLakeMeasurementCounterIotDb.java
index 14cbf5aaf7..83470d4b78 100644
---
a/streampipes-data-explorer-iotdb/src/main/java/org/apache/streampipes/dataexplorer/iotdb/DataLakeMeasurementCounterIotDb.java
+++
b/streampipes-data-explorer-iotdb/src/main/java/org/apache/streampipes/dataexplorer/iotdb/DataLakeMeasurementCounterIotDb.java
@@ -18,12 +18,12 @@
package org.apache.streampipes.dataexplorer.iotdb;
-import org.apache.iotdb.rpc.IoTDBConnectionException;
-import org.apache.iotdb.rpc.StatementExecutionException;
import org.apache.streampipes.commons.environment.Environments;
import org.apache.streampipes.dataexplorer.query.DataLakeMeasurementCounter;
import org.apache.streampipes.model.datalake.DataLakeMeasure;
+import org.apache.iotdb.rpc.IoTDBConnectionException;
+import org.apache.iotdb.rpc.StatementExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,8 +35,9 @@ public class DataLakeMeasurementCounterIotDb extends
DataLakeMeasurementCounter
private static final Logger LOG =
LoggerFactory.getLogger(DataLakeMeasurementCounterIotDb.class);
public DataLakeMeasurementCounterIotDb(List<DataLakeMeasure> allMeasurements,
- List<String> measurementNames) {
- super(allMeasurements, measurementNames);
+ List<String> measurementNames,
+ int daysBack) {
+ super(allMeasurements, measurementNames, daysBack);
}
/**
diff --git
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/query/DataLakeMeasurementCounter.java
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/query/DataLakeMeasurementCounter.java
index bc3a551ed7..2263c06192 100644
---
a/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/query/DataLakeMeasurementCounter.java
+++
b/streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/query/DataLakeMeasurementCounter.java
@@ -41,13 +41,16 @@ public abstract class DataLakeMeasurementCounter implements
IDataLakeMeasurement
protected final List<DataLakeMeasure> allMeasurements;
protected final List<String> measurementNames;
+ protected final int daysBack;
public DataLakeMeasurementCounter(
List<DataLakeMeasure> allMeasurements,
- List<String> measurementNames
+ List<String> measurementNames,
+ int daysBack
) {
this.allMeasurements = allMeasurements;
this.measurementNames = measurementNames;
+ this.daysBack = daysBack;
}
@Override
diff --git
a/streampipes-data-explorer/src/test/java/org/apache/streampipes/dataexplorer/query/DataLakeMeasurementCounterTestImpl.java
b/streampipes-data-explorer/src/test/java/org/apache/streampipes/dataexplorer/query/DataLakeMeasurementCounterTestImpl.java
index 4642c5f227..20013aad9d 100644
---
a/streampipes-data-explorer/src/test/java/org/apache/streampipes/dataexplorer/query/DataLakeMeasurementCounterTestImpl.java
+++
b/streampipes-data-explorer/src/test/java/org/apache/streampipes/dataexplorer/query/DataLakeMeasurementCounterTestImpl.java
@@ -27,7 +27,7 @@ public class DataLakeMeasurementCounterTestImpl extends
DataLakeMeasurementCount
public DataLakeMeasurementCounterTestImpl(
List<DataLakeMeasure> allMeasurements, List<String> measurementNames
) {
- super(allMeasurements, measurementNames);
+ super(allMeasurements, measurementNames, -1);
}
@Override
@@ -35,4 +35,4 @@ public class DataLakeMeasurementCounterTestImpl extends
DataLakeMeasurementCount
// Mock implementation for testing
return CompletableFuture.completedFuture(1);
}
-}
\ No newline at end of file
+}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/datalake/DataLakeMeasureResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/datalake/DataLakeMeasureResource.java
index 32d6a26293..4c36c5a06c 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/datalake/DataLakeMeasureResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/datalake/DataLakeMeasureResource.java
@@ -69,13 +69,15 @@ public class DataLakeMeasureResource extends
AbstractAuthGuardedRestResource {
@Operation(summary = "Retrieve measurement counts", description = "Retrieves
the entry counts for the specified measurements from the data lake.")
@GetMapping(path = "/count", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Integer>> getEntryCountsOfMeasurments(
- @Parameter(description = "A list of measurement names to return the
count.") @RequestParam(value = "measurementNames") List<String>
measurementNames) {
+ @Parameter(description = "A list of measurement names to return the
count.") @RequestParam(value = "measurementNames") List<String>
measurementNames,
+ @Parameter(description = "The number of days from today where the count
should start") @RequestParam(value = "daysBack", defaultValue = "-1") int
daysBack) {
var allMeasurements = this.dataLakeMeasureManagement.getAllMeasurements();
var result = new DataExplorerDispatcher()
.getDataExplorerManager()
.getMeasurementCounter(
allMeasurements,
- measurementNames)
+ measurementNames,
+ daysBack)
.countMeasurementSizes();
return ok(result);
}
diff --git a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
index a34bf7d20e..88194fa69a 100644
--- a/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
+++ b/ui/cypress/support/utils/dataExplorer/DataExplorerUtils.ts
@@ -704,6 +704,7 @@ export class DataExplorerUtils {
}
public static waitForCountingResults() {
+ cy.dataCy('datalake-total-count-button').click();
cy.dataCy('datalake-number-of-events-spinner', {
timeout: 10000,
}).should('exist');
diff --git a/ui/cypress/tests/dataExplorer/configuration.smoke.spec.ts
b/ui/cypress/tests/dataExplorer/configuration.smoke.spec.ts
index 0033da2d30..10bf20f5ff 100644
--- a/ui/cypress/tests/dataExplorer/configuration.smoke.spec.ts
+++ b/ui/cypress/tests/dataExplorer/configuration.smoke.spec.ts
@@ -30,6 +30,7 @@ describe('Test Truncate data in datalake', () => {
it('Perform Test', () => {
DataExplorerUtils.goToDatalakeConfiguration();
+ cy.dataCy('datalake-total-count-button').click();
// Check if amount of events is correct
DataExplorerBtns.datalakeNumberEvents()
@@ -43,6 +44,8 @@ describe('Test Truncate data in datalake', () => {
.should('be.visible')
.click();
+ cy.dataCy('datalake-total-count-button').click();
+
// Check if amount of events is zero. The should('have.text, '0') is
required to check for text equality
DataExplorerBtns.datalakeNumberEvents()
.should('be.visible')
@@ -62,6 +65,7 @@ describe('Delete data in datalake', () => {
it('Perform Test', () => {
DataExplorerUtils.goToDatalakeConfiguration();
+ cy.dataCy('datalake-total-count-button').click();
// Check if amount of events is correct
DataExplorerBtns.datalakeNumberEvents()
diff --git a/ui/deployment/feature-cards.yml b/ui/deployment/feature-cards.yml
index e00d829d4a..27b4dded52 100644
--- a/ui/deployment/feature-cards.yml
+++ b/ui/deployment/feature-cards.yml
@@ -25,3 +25,7 @@
componentPath:
'./dashboard/components/dashboard-feature-card/dashboard-feature-card.component'
componentName: DashboardFeatureCardComponent
moduleName: spDashboard
+- id: measurement
+ componentPath:
'./dataset/components/dataset-feature-card/dataset-feature-card.component'
+ componentName: DatasetFeatureCardComponent
+ moduleName: spDatasets
diff --git
a/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
b/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
index bade947937..b119cd6296 100644
---
a/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
+++
b/ui/projects/streampipes/platform-services/src/lib/apis/datalake-rest.service.ts
@@ -50,10 +50,14 @@ export class DatalakeRestService {
getMeasurementEntryCounts(
measurementNames: string[],
+ daysBack = -1,
): Observable<Record<string, number>> {
return this.http
.get(`${this.dataLakeMeasureUrl}/count`, {
- params: { measurementNames: measurementNames },
+ params: {
+ measurementNames,
+ daysBack,
+ },
})
.pipe(map(r => r as Record<string, number>));
}
diff --git
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration-entry.ts
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration-entry.ts
index a56a29e6e5..8e814381f8 100644
---
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration-entry.ts
+++
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration-entry.ts
@@ -21,7 +21,10 @@ import { RetentionConfig } from
'../../dialog/data-retention-dialog/model/retent
export class DataLakeConfigurationEntry {
public name: string;
public pipelines: string[] = [];
- public events = 0;
+ public eventsTotal = 0;
+ public eventsLatest = 0;
+ public eventsTotalLoading = false;
+ public eventsLatestLoading = false;
public remove = true;
public elementId: string;
public retention: RetentionTimeConfig;
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 bbcdeb81ab..a6aa18e361 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
@@ -38,8 +38,17 @@
<mat-icon>refresh</mat-icon>
</button>
</div>
- <div fxLayout="column" class="w-100">
+ <div fxLayout="column" class="w-100" fxLayoutGap="10px">
+ <sp-alert-banner
+ type="info"
+ [title]="'Caution when loading total count' | translate"
+ [description]="
+ 'For large datasets, computing the total number of events
can take a long time.'
+ "
+ >
+ </sp-alert-banner>
<sp-table
+ featureCardId="measurement"
[dataSource]="dataSource"
[columns]="displayedColumns"
[showActionsMenu]="true"
@@ -64,141 +73,165 @@
</td>
</ng-container>
- <ng-container matColumnDef="events">
+ <ng-container matColumnDef="eventsLatest">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
- {{ '# Events' | translate }}
+ {{ '# Events (7d)' | translate }}
</th>
- <td
- mat-cell
- data-cy="datalake-number-of-events"
- *matCellDef="let configurationEntry"
- >
- @if (configurationEntry.events < 0) {
+ <td mat-cell *matCellDef="let configurationEntry">
+ @if (configurationEntry.eventsLatestLoading) {
<mat-spinner
[diameter]="20"
fxLayoutAlign="center"
- style="margin: 10px 0 5px 0"
color="accent"
- data-cy="datalake-number-of-events-spinner"
>{{ 'Loading' | translate }}
</mat-spinner>
} @else {
- <span>
- {{ configurationEntry.events | number }}
- </span>
+ <sp-label
+ tone="neutral"
+ minWidth="100px"
+ [labelText]="
+ configurationEntry.eventsLatest | number
+ "
+ size="small"
+ >
+ </sp-label>
}
</td>
</ng-container>
- <ng-container matColumnDef="retention">
- <th mat-header-cell *matHeaderCellDef>
- {{ 'Retention Rate' | translate }}
+ <ng-container matColumnDef="eventsTotal">
+ <th mat-header-cell mat-sort-header *matHeaderCellDef>
+ {{ '# Events (total)' | translate }}
</th>
- <td mat-cell *matCellDef="let configurationEntry">
- <div fxLayout="row">
- <span
- fxFlex
- fxFlexOrder="3"
- fxLayout="row"
- fxLayoutAlign="start center"
- >
- <button
+ <td
+ mat-cell
+ data-cy="datalake-number-of-events"
+ *matCellDef="let configurationEntry"
+ >
+ <div fxLayout="row" fxLayoutAlign="start center">
+ @if (configurationEntry.eventsTotalLoading) {
+ <mat-spinner
+ [diameter]="20"
+ fxLayoutAlign="center"
color="accent"
- mat-icon-button
- [matTooltip]="
- 'Set retention rate' | translate
- "
- data-cy="datalake-retention-btn"
- matTooltipPosition="above"
- (click)="
- openRetentionDialog(
- configurationEntry.elementId
- )
- "
- >
- <i
- class="material-icons"
- [ngStyle]="{
- color:
configurationEntry?.retention
- ? 'var(--color-success)'
- : 'var(--color-neutral',
- }"
- >history</i
+ data-cy="datalake-number-of-events-spinner"
+ >{{ 'Loading' | translate }}
+ </mat-spinner>
+ } @else {
+ @if (configurationEntry.eventsTotal > -1) {
+ <sp-label
+ tone="neutral"
+ class="cursor-pointer"
+ minWidth="100px"
+ [labelText]="
+ configurationEntry.eventsTotal
+ | number
+ "
+ size="small"
+ (click)="
+ receiveTotalMeasurementSize(
+ configurationEntry
+ )
+ "
>
- </button>
- </span>
+ </sp-label>
+ } @else {
+ <sp-label
+ tone="neutral"
+ class="cursor-pointer"
+ data-cy="datalake-total-count-button"
+ minWidth="100px"
+ [labelText]="
+ 'Click to load' | translate
+ "
+ size="small"
+ (click)="
+ receiveTotalMeasurementSize(
+ configurationEntry
+ )
+ "
+ >
+ </sp-label>
+ }
+ }
</div>
</td>
</ng-container>
- <ng-container matColumnDef="retentionlog">
+ <ng-container matColumnDef="retention">
<th mat-header-cell *matHeaderCellDef>
- {{ 'Retention Log' | translate }}
+ {{ 'Retention' | translate }}
</th>
<td mat-cell *matCellDef="let configurationEntry">
- <div fxLayout="row">
- <span
- fxFlex
- fxFlexOrder="3"
- fxLayout="row"
- fxLayoutAlign="start center"
+ <button
+ color="accent"
+ mat-icon-button
+ [matTooltip]="'Set retention rate' | translate"
+ data-cy="datalake-retention-btn"
+ matTooltipPosition="above"
+ (click)="
+ openRetentionDialog(
+ configurationEntry.elementId
+ )
+ "
+ >
+ <i
+ class="material-icons"
+ [ngStyle]="{
+ color: configurationEntry?.retention
+ ? 'var(--color-success)'
+ : 'var(--color-neutral',
+ }"
+ >history</i
+ >
+ </button>
+ @if (
+
configurationEntry?.retention?.retentionExportConfig
+ ?.retentionLog?.length > 0
+ ) {
+ <button
+ color="accent"
+ mat-icon-button
+ [matTooltip]="
+ ('Open Retention Log' | translate) +
+ (configurationEntry?.retention
+ ?.retentionExportConfig?.lastExport
+ ? ' • ' +
+ (configurationEntry.retention
+ .retentionExportConfig.lastExport
+ | date: 'yyyy-MM-dd HH:mm:ss')
+ : '')
+ "
+ data-cy="datalake-retention-log-btn"
+ matTooltipPosition="above"
+ (click)="
+ openRetentionLog(
+ configurationEntry?.retention
+ .retentionExportConfig.retentionLog
+ )
+ "
>
- @if (configurationEntry?.retention) {
- <button
- color="accent"
- mat-icon-button
- [matTooltip]="
- ('Open Retention Log' | translate)
+
- (configurationEntry?.retention
+ <i
+ class="material-icons"
+ [ngStyle]="{
+ color:
+ configurationEntry?.retention
?.retentionExportConfig
- ?.lastExport
- ? ' • ' +
- (configurationEntry.retention
- .retentionExportConfig
- .lastExport
- | date
- : 'yyyy-MM-dd
HH:mm:ss')
- : '')
- "
- data-cy="datalake-retention-log-btn"
- matTooltipPosition="above"
- (click)="
- openRetentionLog(
- configurationEntry?.retention
+ ?.retentionLog?.length &&
+ configurationEntry.retention
+ .retentionExportConfig
+ .retentionLog[
+ configurationEntry.retention
.retentionExportConfig
- .retentionLog
- )
- "
- >
- <i
- class="material-icons"
- [ngStyle]="{
- color:
- configurationEntry
- ?.retention
- ?.retentionExportConfig
- ?.retentionLog
- ?.length &&
-
configurationEntry.retention
- .retentionExportConfig
- .retentionLog[
- configurationEntry
- .retention
-
.retentionExportConfig
- .retentionLog
- .length - 1
- ].status
- ? 'green'
- : 'red',
- }"
- >list_alt</i
- >
- </button>
- } @else {
- <p>-</p>
- }
- </span>
- </div>
+ .retentionLog.length - 1
+ ].status
+ ? 'var(--color-success)'
+ : 'var(--color-error)',
+ }"
+ >list_alt</i
+ >
+ </button>
+ }
</td>
</ng-container>
diff --git
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.scss
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.scss
index 13cbc4aacb..b93bc4701c 100644
---
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.scss
+++
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.scss
@@ -15,3 +15,7 @@
* limitations under the License.
*
*/
+
+.cursor-pointer {
+ cursor: pointer;
+}
diff --git
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.ts
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.ts
index 6779808799..80a3d7841f 100644
---
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.ts
+++
b/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.ts
@@ -51,6 +51,7 @@ import { DeleteExportProviderComponent } from
'../../dialog/delete-export-provid
import { TranslateService } from '@ngx-translate/core';
import { ExportProviderConnectionTestComponent } from
'../../dialog/export-provider-connection-test/export-provider-connection-test.component';
import { DataRetentionLogDialogComponent } from
'../../dialog/data-retention-log-dialog/data-retention-log-dialog.component';
+import { LocalStorageService } from
'../../../../../projects/streampipes/shared-ui/src/lib/services/local-storage-settings.service';
@Component({
selector: 'sp-datalake-configuration',
@@ -82,9 +83,9 @@ export class DatalakeConfigurationComponent implements
OnInit, AfterViewInit {
displayedColumns: string[] = [
'name',
'pipeline',
- 'events',
+ 'eventsLatest',
+ 'eventsTotal',
'retention',
- 'retentionlog',
'actions',
];
@@ -97,7 +98,9 @@ export class DatalakeConfigurationComponent implements
OnInit, AfterViewInit {
'test',
];
- pageSize = 10;
+ private localStorageService = inject(LocalStorageService);
+
+ pageSize = this.localStorageService.get('paginator-page-size', 10);
pageIndex = 0;
ngOnInit(): void {
@@ -144,7 +147,8 @@ export class DatalakeConfigurationComponent implements
OnInit, AfterViewInit {
const entry = new DataLakeConfigurationEntry();
entry.elementId = measurement.elementId;
entry.name = measurement.measureName;
- entry.events = -1;
+ entry.eventsLatest = -1;
+ entry.eventsTotal = -1;
if (measurement?.retentionTime != null) {
entry.retention = measurement.retentionTime;
}
@@ -327,7 +331,11 @@ export class DatalakeConfigurationComponent implements
OnInit, AfterViewInit {
onPageChange(event: any) {
this.pageIndex = event.pageIndex;
this.pageSize = event.pageSize;
- this.receiveMeasurementSizes(this.pageIndex);
+ //this.receiveMeasurementSizes(this.pageIndex);
+ }
+
+ receiveTotalMeasurementSize(entry: DataLakeConfigurationEntry) {
+ this.queryEntryCounts([entry.name], 'eventsTotal');
}
receiveMeasurementSizes(pageIndex: number) {
@@ -335,18 +343,41 @@ export class DatalakeConfigurationComponent implements
OnInit, AfterViewInit {
const end = start + this.pageSize;
const measurements = this.availableMeasurements
.slice(start, end)
- .filter(m => m.events === -1)
+ .filter(m => m.eventsLatest === -1)
.map(m => m.name);
if (measurements.length > 0) {
- this.datalakeRestService
- .getMeasurementEntryCounts(measurements)
- .subscribe(res => {
- this.availableMeasurements.forEach(m => {
- if (res[m.name] !== undefined) {
- m.events = res[m.name];
- }
- });
- });
+ this.queryEntryCounts(measurements, 'eventsLatest', 7);
}
}
+
+ queryEntryCounts(
+ measurements: string[],
+ targetField: string,
+ daysBack = -1,
+ ): void {
+ this.applyLoadingStatus(measurements, targetField, true);
+ this.datalakeRestService
+ .getMeasurementEntryCounts(measurements, daysBack)
+ .subscribe(res => {
+ this.applyLoadingStatus(measurements, targetField, false);
+ this.availableMeasurements.forEach(m => {
+ if (res[m.name] !== undefined) {
+ m[targetField] = res[m.name];
+ }
+ });
+ });
+ }
+
+ applyLoadingStatus(
+ measurements: string[],
+ targetField: string,
+ status: boolean,
+ ): void {
+ const loadingField = targetField + 'Loading';
+ this.availableMeasurements.forEach(m => {
+ if (measurements.includes(m.name)) {
+ m[loadingField] = status;
+ }
+ });
+ }
}
diff --git
a/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.html
b/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.html
new file mode 100644
index 0000000000..d5904bcc4a
--- /dev/null
+++
b/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.html
@@ -0,0 +1,90 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+@if (dataset) {
+ <div fxFlexFill fxLayout="column" class="feature-card-outer">
+ <sp-feature-card-header
+ [title]="dataset.measureName"
+ [icon]="assetLinkType.linkIcon"
+ [iconColor]="assetLinkType.linkColor"
+ [detailsLink]="assetLinkType.navPaths"
+ (close)="onClose?.()"
+ (onDetailsClick)="navigateToChartView()"
+ >
+ </sp-feature-card-header>
+
+ <div class="feature-card-meta-card meta-card">
+ <sp-feature-card-meta-section [label]="'Status' | translate">
+ <div
+ class="feature-card-meta-box"
+ fxLayout="row"
+ fxLayoutAlign="start center"
+ fxLayoutGap="0.75rem"
+ >
+ <mat-icon class="feature-card-meta-icon"
+ >fiber_manual_record</mat-icon
+ >
+ <div>
+ <div class="feature-card-meta-field-label">
+ {{ 'Last event' | translate }}
+ </div>
+ <sp-label
+ class="mt-xs"
+ textCase="uppercase"
+ tone="neutral"
+ size="small"
+ variant="soft"
+ >
+ {{ (lastEventTs | date: 'short') || '-' }}
+ </sp-label>
+ </div>
+ </div>
+ </sp-feature-card-meta-section>
+
+ <div class="feature-card-meta-section__label">
+ {{ 'Preview' | translate }}
+ </div>
+ <div class="dataset-preview-inner">
+ @if (dataPreview && dataPreview.total > 0) {
+ <div fxLayout="column">
+ @for (
+ header of dataPreview.headers;
+ let i = $index;
+ track $index
+ ) {
+ <div fxLayout="row" fxFlex="100">
+ <span class="text-sm" fxFlex="70">{{
+ header
+ }}</span>
+ <span class="text-sm font-bold" fxFlex="30">
+ {{
+ dataPreview.allDataSeries[0].rows[0][i]
+ }}</span
+ >
+ </div>
+ }
+ </div>
+ } @else {
+ <span class="text-sm">{{
+ 'No data available.' | translate
+ }}</span>
+ }
+ </div>
+ </div>
+ </div>
+}
diff --git
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.scss
b/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.scss
similarity index 78%
copy from
ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.scss
copy to
ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.scss
index 13cbc4aacb..f224c48e64 100644
---
a/ui/src/app/dataset/components/datalake-configuration/datalake-configuration.component.scss
+++
b/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.scss
@@ -1,4 +1,4 @@
-/*
+/*!
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -15,3 +15,20 @@
* limitations under the License.
*
*/
+
+:host {
+ width: 100%;
+ height: 100%;
+}
+
+.dataset-preview-inner {
+ overflow-y: scroll;
+ min-height: 0;
+}
+
+.meta-card {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 auto;
+ min-height: 0;
+}
diff --git
a/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.ts
b/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.ts
new file mode 100644
index 0000000000..fab3b673b0
--- /dev/null
+++
b/ui/src/app/dataset/components/dataset-feature-card/dataset-feature-card.component.ts
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Component, inject, Input, OnInit } from '@angular/core';
+import {
+ DefaultFlexDirective,
+ DefaultLayoutAlignDirective,
+ DefaultLayoutDirective,
+ DefaultLayoutGapDirective,
+ FlexFillDirective,
+} from '@ngbracket/ngx-layout';
+import { SharedUiModule } from '@streampipes/shared-ui';
+import { DashboardSharedModule } from
'../../../dashboard-shared/dashboard-shared.module';
+import {
+ AssetConstants,
+ AssetLinkType,
+ DataLakeMeasure,
+ DatalakeRestService,
+ GenericStorageService,
+ SpQueryResult,
+} from '@streampipes/platform-services';
+import { forkJoin } from 'rxjs';
+import { TranslatePipe } from '@ngx-translate/core';
+import { MatIcon } from '@angular/material/icon';
+import { DatePipe } from '@angular/common';
+
+@Component({
+ selector: 'sp-dataset-feature-card',
+ templateUrl: './dataset-feature-card.component.html',
+ styleUrls: ['./dataset-feature-card.component.scss'],
+ imports: [
+ SharedUiModule,
+ FlexFillDirective,
+ DashboardSharedModule,
+ DefaultFlexDirective,
+ DefaultLayoutDirective,
+ TranslatePipe,
+ DefaultLayoutAlignDirective,
+ DefaultLayoutGapDirective,
+ MatIcon,
+ DatePipe,
+ ],
+})
+export class DatasetFeatureCardComponent implements OnInit {
+ @Input()
+ resourceId: string;
+
+ @Input()
+ onClose?: () => void;
+
+ dataset: DataLakeMeasure;
+ assetLinkType: AssetLinkType;
+ dataPreview: SpQueryResult;
+ lastEventTs: number;
+
+ private datalakeRestService = inject(DatalakeRestService);
+ private genericStorageService = inject(GenericStorageService);
+
+ ngOnInit() {
+ forkJoin([
+ this.datalakeRestService.getMeasurement(this.resourceId),
+ this.genericStorageService.getAllDocuments(
+ AssetConstants.ASSET_LINK_TYPES_DOC_NAME,
+ ),
+ ]).subscribe(res => {
+ this.dataset = res[0];
+ this.assetLinkType = res[1].find(a => a.linkType ===
'measurement');
+ this.loadSampleData();
+ });
+ }
+
+ loadSampleData(): void {
+ this.datalakeRestService
+ .getData(this.dataset.measureName, {
+ endDate: new Date().getTime(),
+ startDate: 0,
+ limit: 1,
+ order: 'DESC',
+ missingValueBehaviour: 'empty',
+ columns: this.dataset.eventSchema.eventProperties
+ .map(ep => ep.runtimeName)
+ .toString(),
+ })
+ .subscribe(res => {
+ this.dataPreview = res;
+ if (res.total > 0) {
+ this.lastEventTs = res.allDataSeries[0].rows[0][0];
+ }
+ });
+ }
+
+ navigateToChartView(): void {}
+}
diff --git a/ui/src/app/dataset/dataset.module.ts
b/ui/src/app/dataset/dataset.module.ts
index 7d64352ef6..392fe1739a 100644
--- a/ui/src/app/dataset/dataset.module.ts
+++ b/ui/src/app/dataset/dataset.module.ts
@@ -50,6 +50,7 @@ import { MatMenuModule } from '@angular/material/menu';
import { DataRetentionNowDialogComponent } from
'./dialog/data-retention-now-dialog/data-retention-now-dialog.component';
import { DataRetentionLogDialogComponent } from
'./dialog/data-retention-log-dialog/data-retention-log-dialog.component';
import { ExportProviderConnectionTestComponent } from
'./dialog/export-provider-connection-test/export-provider-connection-test.component';
+import { DatasetFeatureCardComponent } from
'./components/dataset-feature-card/dataset-feature-card.component';
@NgModule({
imports: [
diff --git
a/ui/src/app/editor/dialog/matching-error/matching-error.component.html
b/ui/src/app/editor/dialog/matching-error/matching-error.component.html
index 8d6ead9900..dcdbd0ef33 100644
--- a/ui/src/app/editor/dialog/matching-error/matching-error.component.html
+++ b/ui/src/app/editor/dialog/matching-error/matching-error.component.html
@@ -18,53 +18,42 @@
<div class="sp-dialog-container">
<div class="sp-dialog-content p-15">
- <div>
- <h4>{{ 'These elements can't be connected.' | translate }}</h4>
- <h4>
+ <div fxLayout="column">
+ <h5>{{ "These elements can't be connected." | translate }}</h5>
+ <span class="text-md">
{{
'The input data stream does not satisfy the requirements
specified by the data processor.' | translate
}}
- </h4>
- <button
- mat-button
- (click)="toggleStatusDetailsVisible()"
- type="button"
- class="md-accent"
- >
- @if (!statusDetailsVisible) {
- <div>
- {{ 'Show Details' | translate }}
- </div>
- }
- @if (statusDetailsVisible) {
- <div>
- {{ 'Hide Details' | translate }}
- </div>
- }
- </button>
- @if (statusDetailsVisible) {
- <div>
- @for (entry of notifications; track entry) {
+ </span>
+ <div>
+ <button
+ mat-flat-button
+ (click)="toggleStatusDetailsVisible()"
+ type="button"
+ class="mt-md mat-basic"
+ >
+ @if (!statusDetailsVisible) {
+ <div>
+ {{ 'Show Details' | translate }}
+ </div>
+ }
+ @if (statusDetailsVisible) {
<div>
- <div
- fxFlex="100"
- class="md-whiteframe-z1"
- style="margin-bottom: 10px"
- >
- <div
- fxFlex
- fxLayout="column"
- class="md-padding"
- >
- <div fxFlex="column">
- {{ entry.title }}<br />
- {{ entry.description }}
- </div>
- </div>
- </div>
+ {{ 'Hide Details' | translate }}
</div>
}
+ </button>
+ </div>
+ @if (statusDetailsVisible) {
+ <div class="mt-md" fxLayout="column">
+ @for (entry of notifications; track entry) {
+ <sp-alert-banner
+ type="warning"
+ [title]="entry.title"
+ [description]="entry.description"
+ ></sp-alert-banner>
+ }
</div>
}
</div>