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 c39d0c4cac test: enable live audit download checks (#5155)
c39d0c4cac is described below
commit c39d0c4cac98bcb4f9ac044e587199e18bfde2cd
Author: Jacqueline Höllig <[email protected]>
AuthorDate: Fri Oct 30 11:30:02 2026 +0100
test: enable live audit download checks (#5155)
Co-authored-by: Dominik Riemer <[email protected]>
---
.github/workflows/cypress-test.yml | 5 ++
docker-compose.yml | 4 +
.../streampipes/audit/api/AuditEventStore.java | 4 +
.../apache/streampipes/audit/api/AuditService.java | 10 +++
.../audit/influx/InfluxAuditEventStore.java | 12 +++
.../audit/influx/InfluxAuditEventStoreTest.java | 26 ++++++
.../audit/management/DefaultAuditService.java | 5 ++
.../audit/management/DefaultAuditServiceTest.java | 19 +++++
.../apache/streampipes/rest/ResetManagement.java | 4 +
.../streampipes/rest/impl/ResetResource.java | 2 +
.../streampipes/rest/ResetManagementTest.java | 10 +++
.../utils/configuration/ConfigurationBtns.ts | 28 +++++++
.../audit-log/audit-log-download.spec.ts | 61 ++++++++------
.../audit-log/audit-log-filter.spec.ts | 95 ++++++++++++++++++++++
.../audit-log/audit-log.component.html | 18 +++-
15 files changed, 274 insertions(+), 29 deletions(-)
diff --git a/.github/workflows/cypress-test.yml
b/.github/workflows/cypress-test.yml
index 9b100d07cf..95fc615244 100644
--- a/.github/workflows/cypress-test.yml
+++ b/.github/workflows/cypress-test.yml
@@ -63,6 +63,11 @@ jobs:
run: |
./installer/compose/configure
docker compose up --build -d
+ env:
+ SP_AUDIT_ENABLED: true
+ SP_AUDIT_INFLUX_URL: http://influxdb:8086
+ SP_AUDIT_INFLUX_DATABASE: audit
+ SP_AUDIT_INFLUX_TOKEN: sp-admin
- name: Wait 80 seconds
working-directory: ./installer/compose
diff --git a/docker-compose.yml b/docker-compose.yml
index bf10b6f230..0ada05cfe9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -40,6 +40,10 @@ services:
- SP_PRIORITIZED_PROTOCOL=nats
- SP_SETUP_PROMETHEUS_ENDPOINT=true
- SP_RESET_ENDPOINT_ENABLED=true
+ - SP_AUDIT_ENABLED=${SP_AUDIT_ENABLED:-false}
+ - SP_AUDIT_INFLUX_URL=${SP_AUDIT_INFLUX_URL:-}
+ - SP_AUDIT_INFLUX_DATABASE=${SP_AUDIT_INFLUX_DATABASE:-}
+ - SP_AUDIT_INFLUX_TOKEN=${SP_AUDIT_INFLUX_TOKEN:-}
depends_on:
- couchdb
volumes:
diff --git
a/streampipes-audit-api/src/main/java/org/apache/streampipes/audit/api/AuditEventStore.java
b/streampipes-audit-api/src/main/java/org/apache/streampipes/audit/api/AuditEventStore.java
index 9eaee82feb..3e9d8c04c2 100644
---
a/streampipes-audit-api/src/main/java/org/apache/streampipes/audit/api/AuditEventStore.java
+++
b/streampipes-audit-api/src/main/java/org/apache/streampipes/audit/api/AuditEventStore.java
@@ -28,6 +28,10 @@ public interface AuditEventStore extends AutoCloseable {
void append(AuditEvent<?> event);
+ default void clear() {
+ throw new IllegalStateException("Audit clearing is unavailable");
+ }
+
default void deleteEventsOlderThan(Instant cutoff) {
throw new IllegalStateException("Audit retention is unavailable");
}
diff --git
a/streampipes-audit-api/src/main/java/org/apache/streampipes/audit/api/AuditService.java
b/streampipes-audit-api/src/main/java/org/apache/streampipes/audit/api/AuditService.java
index 1fd4eb2145..5d9a894eeb 100644
---
a/streampipes-audit-api/src/main/java/org/apache/streampipes/audit/api/AuditService.java
+++
b/streampipes-audit-api/src/main/java/org/apache/streampipes/audit/api/AuditService.java
@@ -35,6 +35,11 @@ public interface AuditService extends AutoCloseable {
<T extends AuditDetails> void record(AuditEventDefinition<T> definition,
AuditOutcome outcome,
String actor, String resourceId, T
details);
+
+ default void clear() {
+ throw new IllegalStateException("Audit clearing unavailable");
+ }
+
default AuditPage query(AuditQuery query) {
throw new IllegalStateException("Audit reading unavailable");
}
@@ -63,6 +68,11 @@ public interface AuditService extends AutoCloseable {
String actor, String
resourceId, T details) {
// Explicitly disabled for deployments without audit configuration and
legacy constructors.
}
+
+ @Override
+ public void clear() {
+ // Explicitly disabled for deployments without audit configuration and
legacy constructors.
+ }
};
}
}
diff --git
a/streampipes-audit-influx/src/main/java/org/apache/streampipes/audit/influx/InfluxAuditEventStore.java
b/streampipes-audit-influx/src/main/java/org/apache/streampipes/audit/influx/InfluxAuditEventStore.java
index 104a777b32..2faf964796 100644
---
a/streampipes-audit-influx/src/main/java/org/apache/streampipes/audit/influx/InfluxAuditEventStore.java
+++
b/streampipes-audit-influx/src/main/java/org/apache/streampipes/audit/influx/InfluxAuditEventStore.java
@@ -182,6 +182,18 @@ public final class InfluxAuditEventStore implements
AuditEventStore, AuditEventR
return new InfluxAuditEventReader(queries).find(eventId, locator);
}
+ @Override
+ public synchronized void clear() {
+ initialize();
+ var result = queries.execute(new Query("DELETE FROM
\"%s\"".formatted(InfluxAuditSchema.MEASUREMENT)));
+ if (result.hasError() || result.getResults() != null
+ && result.getResults().stream().anyMatch(statement ->
statement.hasError())) {
+ throw new IllegalStateException("Audit log clear failed");
+ }
+ initialized = false;
+ lastTimestamp = 0;
+ }
+
@Override
public synchronized void deleteEventsOlderThan(Instant cutoff) {
if (cutoff == null) {
diff --git
a/streampipes-audit-influx/src/test/java/org/apache/streampipes/audit/influx/InfluxAuditEventStoreTest.java
b/streampipes-audit-influx/src/test/java/org/apache/streampipes/audit/influx/InfluxAuditEventStoreTest.java
index 4f24c83325..59b6e1af24 100644
---
a/streampipes-audit-influx/src/test/java/org/apache/streampipes/audit/influx/InfluxAuditEventStoreTest.java
+++
b/streampipes-audit-influx/src/test/java/org/apache/streampipes/audit/influx/InfluxAuditEventStoreTest.java
@@ -156,6 +156,32 @@ class InfluxAuditEventStoreTest {
}
}
+ @Test
+ void clearsAllAuditEvents() throws Exception {
+ var statement = new AtomicReference<String>();
+ var server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
+ server.createContext("/query", exchange -> {
+ for (var parameter : exchange.getRequestURI().getRawQuery().split("&")) {
+ if (parameter.startsWith("q=")) {
+ statement.set(URLDecoder.decode(parameter.substring(2),
StandardCharsets.UTF_8));
+ }
+ }
+ byte[] body = "{\"results\":[{}]}".getBytes(StandardCharsets.UTF_8);
+ exchange.sendResponseHeaders(200, body.length);
+ exchange.getResponseBody().write(body);
+ exchange.close();
+ });
+ InfluxAuditDatabaseProvisionerTest.existingDatabase(server);
+ server.start();
+ try (var store = new InfluxAuditEventStore("http://127.0.0.1:" +
server.getAddress().getPort(),
+ "audit-only", "audit-token")) {
+ store.clear();
+ assertEquals("DELETE FROM \"audit_events\"", statement.get());
+ } finally {
+ server.stop(0);
+ }
+ }
+
@Test
void reportsInfluxDeleteErrors() throws Exception {
var server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
diff --git
a/streampipes-audit-management/src/main/java/org/apache/streampipes/audit/management/DefaultAuditService.java
b/streampipes-audit-management/src/main/java/org/apache/streampipes/audit/management/DefaultAuditService.java
index 424b7c3ffc..f17359fafe 100644
---
a/streampipes-audit-management/src/main/java/org/apache/streampipes/audit/management/DefaultAuditService.java
+++
b/streampipes-audit-management/src/main/java/org/apache/streampipes/audit/management/DefaultAuditService.java
@@ -119,6 +119,11 @@ public final class DefaultAuditService implements
AuditService {
}
}
+ @Override
+ public void clear() {
+ store.clear();
+ }
+
private <T extends AuditDetails> PendingEvent<T>
snapshot(AuditEventDefinition<T> definition, AuditOutcome outcome,
String actor,
String resourceId, T details)
throws JsonProcessingException {
diff --git
a/streampipes-audit-management/src/test/java/org/apache/streampipes/audit/management/DefaultAuditServiceTest.java
b/streampipes-audit-management/src/test/java/org/apache/streampipes/audit/management/DefaultAuditServiceTest.java
index 07a5309b95..1a0c40fffb 100644
---
a/streampipes-audit-management/src/test/java/org/apache/streampipes/audit/management/DefaultAuditServiceTest.java
+++
b/streampipes-audit-management/src/test/java/org/apache/streampipes/audit/management/DefaultAuditServiceTest.java
@@ -166,6 +166,25 @@ class DefaultAuditServiceTest {
assertNull(service.lastSuccessfulWrite());
}
+ @Test
+ void clearsPersistedAuditEvents() {
+ var cleared = new AtomicBoolean();
+ AuditEventStore store = new AuditEventStore() {
+ @Override
+ public void append(AuditEvent<?> event) {
+ }
+
+ @Override
+ public void clear() {
+ cleared.set(true);
+ }
+ };
+ try (var service = new DefaultAuditService(List.of(new
StandardAuditEvents()), store, CLOCK)) {
+ service.clear();
+ }
+ assertTrue(cleared.get());
+ }
+
@Test
void invalidOrOversizedEventsNeverReachStorage() {
var events = new ArrayList<AuditEvent<?>>();
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java
index 7fb240fca2..2fdaf01251 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java
@@ -118,6 +118,10 @@ public class ResetManagement {
logger.info("Resetting the system was completed");
}
+ public void clearAuditLog() {
+ resourceManager.getAuditService().clear();
+ }
+
private void setHideTutorialToFalse(String username) {
resourceManager.manageUsers().setHideTutorial(username, true);
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ResetResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ResetResource.java
index 3e02f7f39b..4e37175382 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ResetResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ResetResource.java
@@ -108,6 +108,8 @@ public class ResetResource extends
AbstractAuthGuardedRestResource {
templateStorage.deleteElementById(template.getElementId());
}
+ resetManagement.clearAuditLog();
+
var message = Notifications.success("Reset of system successfully
performed");
return ok(message);
}
diff --git
a/streampipes-rest/src/test/java/org/apache/streampipes/rest/ResetManagementTest.java
b/streampipes-rest/src/test/java/org/apache/streampipes/rest/ResetManagementTest.java
index 9fd9e49424..433f226d81 100644
---
a/streampipes-rest/src/test/java/org/apache/streampipes/rest/ResetManagementTest.java
+++
b/streampipes-rest/src/test/java/org/apache/streampipes/rest/ResetManagementTest.java
@@ -18,6 +18,7 @@
package org.apache.streampipes.rest;
+import org.apache.streampipes.audit.api.AuditService;
import org.apache.streampipes.model.configuration.GeneralConfig;
import org.apache.streampipes.model.configuration.SpCoreConfiguration;
import org.apache.streampipes.model.configuration.SystemNotificationConfig;
@@ -40,10 +41,12 @@ import static org.mockito.Mockito.when;
class ResetManagementTest {
private final ISpCoreConfigurationStorage configStorage =
mock(ISpCoreConfigurationStorage.class);
+ private final AuditService auditService = mock(AuditService.class);
ResetManagement newResetManagement(SpCoreConfiguration storedConfig) {
var resourceManager = mock(SpResourceManager.class);
when(resourceManager.getCoreConfigurationStorage()).thenReturn(configStorage);
+ when(resourceManager.getAuditService()).thenReturn(auditService);
when(configStorage.get()).thenReturn(storedConfig);
return new ResetManagement(null, null, null, null, null, resourceManager,
null, null);
}
@@ -79,4 +82,11 @@ class ResetManagementTest {
newResetManagement(new SpCoreConfiguration()).disableSystemNotification();
verify(configStorage, never()).updateElement(any());
}
+
+ @Test
+ void testClearAuditLog_clearsPersistedAuditEvents() {
+ newResetManagement(null).clearAuditLog();
+
+ verify(auditService).clear();
+ }
}
diff --git a/ui/cypress/support/utils/configuration/ConfigurationBtns.ts
b/ui/cypress/support/utils/configuration/ConfigurationBtns.ts
index e07fe13e56..fdf685296a 100644
--- a/ui/cypress/support/utils/configuration/ConfigurationBtns.ts
+++ b/ui/cypress/support/utils/configuration/ConfigurationBtns.ts
@@ -62,6 +62,34 @@ export class ConfigurationBtns {
return cy.dataCy('audit-log-download-button');
}
+ public static auditTimeRangeFilter() {
+ return cy.dataCy('audit-time-range-filter');
+ }
+
+ public static auditTimeRangeOption(days: 1 | 7 | 30) {
+ return cy.dataCy(`audit-time-range-${days}`);
+ }
+
+ public static auditEventTypeFilter() {
+ return cy.dataCy('audit-event-type-filter');
+ }
+
+ public static auditActorFilter() {
+ return cy.dataCy('audit-actor-filter');
+ }
+
+ public static auditOutcomeFilter() {
+ return cy.dataCy('audit-outcome-filter');
+ }
+
+ public static auditApplyFiltersBtn() {
+ return cy.dataCy('audit-apply-filters-button');
+ }
+
+ public static auditLogRows() {
+ return cy.dataCy('audit-log-table').find('tbody tr');
+ }
+
public static auditDownloadDialog() {
return cy.dataCy('audit-download-dialog');
}
diff --git
a/ui/cypress/tests/configuration/audit-log/audit-log-download.spec.ts
b/ui/cypress/tests/configuration/audit-log/audit-log-download.spec.ts
index 15af86fa52..d70080e11a 100644
--- a/ui/cypress/tests/configuration/audit-log/audit-log-download.spec.ts
+++ b/ui/cypress/tests/configuration/audit-log/audit-log-download.spec.ts
@@ -18,40 +18,49 @@
import { ConfigurationBtns } from
'../../../support/utils/configuration/ConfigurationBtns';
import { ConfigurationUtils } from
'../../../support/utils/configuration/ConfigurationUtils';
+import { ConnectUtils } from '../../../support/utils/connect/ConnectUtils';
+import * as CSV from 'csv-string';
describe('Download audit log', () => {
- const auditEntry = {
- eventId: 'audit-event-1',
- locator: 'audit-event-1',
- storedAt: '2026-10-15T10:00:00.000Z',
- eventType: 'pipeline.started',
- outcome: 'succeeded',
- actor: 'admin',
- resourceId: 'pipeline-1',
- };
+ const adapterName = 'Audit log download simulator';
beforeEach('Setup test', () => {
+ cy.removeDownloadDirectory();
cy.initStreamPipesTest();
- cy.intercept('GET', '**/admin/audit/status', { enabled: true });
- cy.intercept('GET', '**/admin/audit/event-types', [
- auditEntry.eventType,
- ]);
- cy.intercept('GET', '**/admin/audit/events*', {
- items: [auditEntry],
- nextCursor: null,
- }).as('getAuditEvents');
- // The glob above does not cross the slash in `events/download`, and
the
- // CI stack runs with auditing disabled, so the export must be stubbed
too.
- cy.intercept('GET', '**/admin/audit/events/download*', {
- statusCode: 200,
- headers: { 'content-type': 'application/octet-stream' },
- body: JSON.stringify([auditEntry]),
- }).as('downloadAuditEvents');
+
+ ConnectUtils.addMachineDataSimulator(adapterName);
ConfigurationUtils.goToAuditLogConfiguration();
- cy.wait('@getAuditEvents');
});
+ const assertLatestAdapterEvents = (
+ format: 'csv' | 'json',
+ delimiter?: 'comma' | 'semicolon',
+ ) => {
+ const downloadsFolder = Cypress.config('downloadsFolder');
+ cy.readFile(`${downloadsFolder}/audit-log.${format}`).then(
+ (content: unknown) => {
+ const eventTypes =
+ format === 'json'
+ ? (content as { eventType: string }[]).map(
+ event => event.eventType,
+ )
+ : CSV.parse(
+ content as string,
+ delimiter === 'semicolon' ? ';' : ',',
+ )
+ .slice(1)
+ .map(event => event[1]);
+
+ // Audit exports are ordered newest first.
+ expect(eventTypes.slice(0, 2)).to.deep.equal([
+ 'sp.adapter.start',
+ 'sp.adapter.create',
+ ]);
+ },
+ );
+ };
+
const downloadAuditLog = (
format: 'csv' | 'json',
delimiter?: 'comma' | 'semicolon',
@@ -65,12 +74,12 @@ describe('Download audit log', () => {
ConfigurationBtns.auditDownloadDelimiter(delimiter).click();
}
ConfigurationBtns.auditDownloadBtn().click();
- cy.wait('@downloadAuditEvents');
ConfigurationBtns.auditDownloadDialog().should(
'contain.text',
'Download successful',
);
+ assertLatestAdapterEvents(format, delimiter);
};
it('shows a confirmation after downloading the audit log as JSON', () => {
diff --git a/ui/cypress/tests/configuration/audit-log/audit-log-filter.spec.ts
b/ui/cypress/tests/configuration/audit-log/audit-log-filter.spec.ts
new file mode 100644
index 0000000000..ce82da4a5d
--- /dev/null
+++ b/ui/cypress/tests/configuration/audit-log/audit-log-filter.spec.ts
@@ -0,0 +1,95 @@
+/*
+ * 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 { ConfigurationBtns } from
'../../../support/utils/configuration/ConfigurationBtns';
+import { ConfigurationUtils } from
'../../../support/utils/configuration/ConfigurationUtils';
+import { ConnectUtils } from '../../../support/utils/connect/ConnectUtils';
+import { ChartUtils } from '../../../support/utils/chart/ChartUtils';
+import { UserUtils } from '../../../support/utils/UserUtils';
+import { UserRole } from '../../../../src/app/core/auth/user-role.enum';
+import { User } from '../../../support/model/User';
+
+describe('Filter audit log', () => {
+ const adapterName = 'Audit log filter simulator';
+ const chartName = 'Audit log filter chart';
+ const adapterStartEvent = 'sp.adapter.start';
+ const adminEmail = '[email protected]';
+ let chartOwner: User;
+
+ beforeEach('Open audit log', () => {
+ cy.initStreamPipesTest();
+ ConnectUtils.addMachineDataSimulator(adapterName, true);
+
+ chartOwner = UserUtils.createUser(
+ 'auditLogChartOwner',
+ UserRole.ROLE_DATA_EXPLORER_ADMIN,
+ );
+ cy.login(chartOwner);
+ ChartUtils.goToDataset();
+ ChartUtils.createAndEditChart();
+ ChartUtils.saveChartConfiguration(false, false, chartName);
+
+ cy.login(UserUtils.adminUser);
+ ConfigurationUtils.goToAuditLogConfiguration();
+ });
+
+ it('filters events by time range', () => {
+ ConfigurationBtns.auditTimeRangeFilter().click();
+ ConfigurationBtns.auditTimeRangeOption(7).click();
+
+ ConfigurationBtns.auditApplyFiltersBtn()
+ .should('not.be.disabled')
+ .click();
+ //adapter.create, adapter.start, auth.login, auth.login,
pipeline.create, pipeline.start, dataset.create, chart.create
+ ConfigurationBtns.auditLogRows().should('have.length', 8);
+ });
+
+ it('filters events by type', () => {
+ ConfigurationBtns.auditEventTypeFilter()
+ .click()
+ .type(`${adapterStartEvent}{enter}`);
+
+ ConfigurationBtns.auditApplyFiltersBtn()
+ .should('not.be.disabled')
+ .click();
+ //adapter.start
+ ConfigurationBtns.auditLogRows().should('have.length', 1);
+ });
+
+ it('filters events by actor', () => {
+ ConfigurationBtns.auditActorFilter()
+ .click()
+ .type(`${adminEmail}{enter}`);
+
+ ConfigurationBtns.auditApplyFiltersBtn()
+ .should('not.be.disabled')
+ .click();
+ //adapter.create, adapter.start, auth.login, auth.logout,
pipeline.create, pipeline.start, dataset.create
+ ConfigurationBtns.auditLogRows().should('have.length', 7);
+ });
+
+ it('filters events by outcome', () => {
+
ConfigurationBtns.auditOutcomeFilter().click().type('succeeded{enter}');
+
+ ConfigurationBtns.auditApplyFiltersBtn()
+ .should('not.be.disabled')
+ .click();
+ //adapter.create, adapter.start, auth.login, auth.login,
pipeline.create, pipeline.start, dataset.create, chart.create
+ ConfigurationBtns.auditLogRows().should('have.length', 8);
+ });
+});
diff --git a/ui/src/app/configuration/audit-log/audit-log.component.html
b/ui/src/app/configuration/audit-log/audit-log.component.html
index b80300a993..395345991a 100644
--- a/ui/src/app/configuration/audit-log/audit-log.component.html
+++ b/ui/src/app/configuration/audit-log/audit-log.component.html
@@ -103,17 +103,24 @@
subscriptSizing="dynamic"
>
<mat-select
+ data-cy="audit-time-range-filter"
[attr.aria-label]="'Time range' | translate"
name="days"
[(ngModel)]="days"
>
- <mat-option [value]="1"
+ <mat-option
+ data-cy="audit-time-range-1"
+ [value]="1"
>{{ 'Last 24 hours' | translate }}
</mat-option>
- <mat-option [value]="7"
+ <mat-option
+ data-cy="audit-time-range-7"
+ [value]="7"
>{{ 'Last 7 days' | translate }}
</mat-option>
- <mat-option [value]="30"
+ <mat-option
+ data-cy="audit-time-range-30"
+ [value]="30"
>{{ 'Last 30 days' | translate }}
</mat-option>
</mat-select>
@@ -125,6 +132,7 @@
margin="0"
>
<sp-search-select
+ dataCy="audit-event-type-filter"
[items]="types"
[value]="eventType || undefined"
[freeTextValue]="eventTypeFromText"
@@ -139,6 +147,7 @@
margin="0"
>
<sp-search-select
+ dataCy="audit-actor-filter"
[items]="actorOptions"
[value]="selectedActor"
[freeTextValue]="actorFromText"
@@ -164,6 +173,7 @@
margin="0"
>
<sp-search-select
+ dataCy="audit-outcome-filter"
[items]="outcomeOptions"
[value]="selectedOutcome"
[placeholder]="'All outcomes' | translate"
@@ -180,6 +190,7 @@
<button
mat-flat-button
type="submit"
+ data-cy="audit-apply-filters-button"
[disabled]="loading"
>
{{ 'Apply filters' | translate }}
@@ -219,6 +230,7 @@
}
<div class="audit-table" [attr.aria-busy]="loading">
<sp-table
+ data-cy="audit-log-table"
[rowsClickable]="true"
(rowClicked)="showDetails($event)"
[columns]="columns"