feat(#3298): add cypress e2e tests for compact adapter feature (#3308)
* feat(#3298): Add test to validate code editor for compact adapters in UI (#3299)
* feat(#3298): Add test to validate code editor for compact adapters in UI
* feat(#3298): Add test to validate success path of add compact adapters (#3301)
* feat(#3298): Add test to validate success path of add compact adapters
* feat(#3298): Add header to yml file
* feat(#3298): Return Conflict Status Code for Duplicate Adapter ID (#3302)
* feat(#3298): Return status code conflict when adapter id is already taken
* feat(#3298): Fix checkstyle
* feat(#3298): Add test to check rename rule for compact adapters
* feat(#3298): Add test to check rename unit transformation rule for compact adapters
* feat(#3298): Fix e2e tests and improve error message in ui
diff --git a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java
index 62c514b..5c42478 100644
--- a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java
+++ b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java
@@ -53,10 +53,11 @@
private final DataStreamResourceManager dataStreamResourceManager;
- public AdapterMasterManagement(IAdapterStorage adapterInstanceStorage,
- AdapterResourceManager adapterResourceManager,
- DataStreamResourceManager dataStreamResourceManager,
- AdapterMetrics adapterMetrics
+ public AdapterMasterManagement(
+ IAdapterStorage adapterInstanceStorage,
+ AdapterResourceManager adapterResourceManager,
+ DataStreamResourceManager dataStreamResourceManager,
+ AdapterMetrics adapterMetrics
) {
this.adapterInstanceStorage = adapterInstanceStorage;
this.adapterMetrics = adapterMetrics;
@@ -64,28 +65,40 @@
this.dataStreamResourceManager = dataStreamResourceManager;
}
- public void addAdapter(AdapterDescription ad,
- String adapterElementId,
- String principalSid)
+ public void addAdapter(
+ AdapterDescription adapterDescription,
+ String adapterId,
+ String principalSid
+ )
throws AdapterException {
- // Create elementId for adapter
+ // Create elementId for datastream
var dataStreamElementId = ElementIdGenerator.makeElementId(SpDataStream.class);
- ad.setElementId(adapterElementId);
- ad.setCreatedAt(System.currentTimeMillis());
- ad.setCorrespondingDataStreamElementId(dataStreamElementId);
+ adapterDescription.setElementId(adapterId);
+ adapterDescription.setCreatedAt(System.currentTimeMillis());
+ adapterDescription.setCorrespondingDataStreamElementId(dataStreamElementId);
// Add EventGrounding to AdapterDescription
var eventGrounding = GroundingUtils.createEventGrounding();
- ad.setEventGrounding(eventGrounding);
+ adapterDescription.setEventGrounding(eventGrounding);
- this.adapterResourceManager.encryptAndCreate(ad);
+ this.adapterResourceManager.encryptAndCreate(adapterDescription);
- // Create stream
- var storedDescription = new SourcesManagement().createAdapterDataStream(ad, dataStreamElementId);
- storedDescription.setCorrespondingAdapterId(adapterElementId);
+ // Stream is only created if the adpater is successfully stored
+ createDataStreamForAdapter(adapterDescription, adapterId, dataStreamElementId, principalSid);
+ }
+
+ private void createDataStreamForAdapter(
+ AdapterDescription adapterDescription,
+ String adapterId,
+ String streamId,
+ String principalSid
+ ) throws AdapterException {
+ var storedDescription = new SourcesManagement()
+ .createAdapterDataStream(adapterDescription, streamId);
+ storedDescription.setCorrespondingAdapterId(adapterId);
installDataSource(storedDescription, principalSid, true);
- LOG.info("Install source (source URL: {} in backend", ad.getElementId());
+ LOG.info("Install source (source URL: {} in backend", adapterDescription.getElementId());
}
public AdapterDescription getAdapter(String elementId) throws AdapterException {
@@ -163,7 +176,8 @@
var baseUrl = new ExtensionsServiceEndpointGenerator().getEndpointBaseUrl(
ad.getAppId(),
SpServiceUrlProvider.ADAPTER,
- ad.getDeploymentConfiguration().getDesiredServiceTags()
+ ad.getDeploymentConfiguration()
+ .getDesiredServiceTags()
);
// Update selected endpoint URL of adapter
@@ -182,9 +196,11 @@
}
}
- private void installDataSource(SpDataStream stream,
- String principalSid,
- boolean publicElement) throws AdapterException {
+ private void installDataSource(
+ SpDataStream stream,
+ String principalSid,
+ boolean publicElement
+ ) throws AdapterException {
try {
new DataStreamVerifier(stream).verifyAndAdd(principalSid, publicElement);
} catch (SepaParseException e) {
diff --git a/streampipes-connect-management/src/test/java/org/apache/streampipes/connect/management/management/AdapterMasterManagementTest.java b/streampipes-connect-management/src/test/java/org/apache/streampipes/connect/management/management/AdapterMasterManagementTest.java
index 6d920fb..e1e1832 100644
--- a/streampipes-connect-management/src/test/java/org/apache/streampipes/connect/management/management/AdapterMasterManagementTest.java
+++ b/streampipes-connect-management/src/test/java/org/apache/streampipes/connect/management/management/AdapterMasterManagementTest.java
@@ -36,12 +36,12 @@
public class AdapterMasterManagementTest {
@Test
- public void getAdapterFailNull() {
- AdapterInstanceStorageImpl adapterStorage = mock(AdapterInstanceStorageImpl.class);
- AdapterResourceManager resourceManager = mock(AdapterResourceManager.class);
+ public void getAdapter_FailNull() {
+ var adapterStorage = mock(AdapterInstanceStorageImpl.class);
+ var resourceManager = mock(AdapterResourceManager.class);
when(adapterStorage.findAll()).thenReturn(null);
- AdapterMasterManagement adapterMasterManagement =
+ var adapterMasterManagement =
new AdapterMasterManagement(
adapterStorage,
resourceManager,
@@ -53,13 +53,13 @@
}
@Test
- public void getAdapterFail() {
- List<AdapterDescription> adapterDescriptions = List.of(new AdapterDescription());
- AdapterInstanceStorageImpl adapterStorage = mock(AdapterInstanceStorageImpl.class);
- AdapterResourceManager resourceManager = mock(AdapterResourceManager.class);
+ public void getAdapter_Fail() {
+ var adapterDescriptions = List.of(new AdapterDescription());
+ var adapterStorage = mock(AdapterInstanceStorageImpl.class);
+ var resourceManager = mock(AdapterResourceManager.class);
when(adapterStorage.findAll()).thenReturn(adapterDescriptions);
- AdapterMasterManagement adapterMasterManagement =
+ var adapterMasterManagement =
new AdapterMasterManagement(
adapterStorage,
resourceManager,
@@ -71,10 +71,10 @@
}
@Test
- public void getAllAdaptersSuccess() throws AdapterException {
- List<AdapterDescription> adapterDescriptions = List.of(new AdapterDescription());
- AdapterInstanceStorageImpl adapterStorage = mock(AdapterInstanceStorageImpl.class);
- AdapterResourceManager resourceManager = mock(AdapterResourceManager.class);
+ public void getAllAdapters_Success() throws AdapterException {
+ var adapterDescriptions = List.of(new AdapterDescription());
+ var adapterStorage = mock(AdapterInstanceStorageImpl.class);
+ var resourceManager = mock(AdapterResourceManager.class);
when(adapterStorage.findAll()).thenReturn(adapterDescriptions);
AdapterMasterManagement adapterMasterManagement =
@@ -91,12 +91,12 @@
}
@Test
- public void getAllAdaptersFail() {
- AdapterInstanceStorageImpl adapterStorage = mock(AdapterInstanceStorageImpl.class);
- AdapterResourceManager resourceManager = mock(AdapterResourceManager.class);
+ public void getAllAdapters_Fail() {
+ var adapterStorage = mock(AdapterInstanceStorageImpl.class);
+ var resourceManager = mock(AdapterResourceManager.class);
when(adapterStorage.findAll()).thenReturn(null);
- AdapterMasterManagement adapterMasterManagement =
+ var adapterMasterManagement =
new AdapterMasterManagement(
adapterStorage,
resourceManager,
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/message/Notifications.java b/streampipes-model/src/main/java/org/apache/streampipes/model/message/Notifications.java
index 4e14af9..965a842 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/message/Notifications.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/message/Notifications.java
@@ -44,8 +44,12 @@
return new ErrorMessage(new Notification(type.name(), type.description()));
}
+ public static ErrorMessage error(String message, String description) {
+ return new ErrorMessage(new Notification(message, description));
+ }
+
public static ErrorMessage error(String message) {
- return new ErrorMessage(new Notification(message, ""));
+ return error(message, "");
}
public static ErrorMessage error(NotificationType type, String info) {
diff --git a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AdapterResourceManager.java b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AdapterResourceManager.java
index 72c7712..8e3d4d4 100644
--- a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AdapterResourceManager.java
+++ b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/AdapterResourceManager.java
@@ -17,6 +17,7 @@
*/
package org.apache.streampipes.resource.management;
+import org.apache.streampipes.commons.exceptions.connect.AdapterException;
import org.apache.streampipes.model.connect.adapter.AdapterDescription;
import org.apache.streampipes.model.util.Cloner;
import org.apache.streampipes.resource.management.secret.SecretProvider;
@@ -30,7 +31,8 @@
}
public AdapterResourceManager() {
- super(StorageDispatcher.INSTANCE.getNoSqlStore().getAdapterInstanceStorage());
+ super(StorageDispatcher.INSTANCE.getNoSqlStore()
+ .getAdapterInstanceStorage());
}
/**
@@ -39,10 +41,15 @@
* @param adapterDescription input adapter description
* @return the id of the created adapter
*/
- public String encryptAndCreate(AdapterDescription adapterDescription) {
- AdapterDescription encryptedAdapterDescription = cloneAndEncrypt(adapterDescription);
+ public String encryptAndCreate(AdapterDescription adapterDescription) throws AdapterException {
+ var encryptedAdapterDescription = cloneAndEncrypt(adapterDescription);
encryptedAdapterDescription.setRev(null);
- return db.persist(encryptedAdapterDescription).v;
+
+ try {
+ return db.persist(encryptedAdapterDescription).v;
+ } catch (org.lightcouch.DocumentConflictException e) {
+ throw new AdapterException("Conflict occurred while creating the adapter", e);
+ }
}
/**
@@ -50,8 +57,15 @@
*
* @param adapterDescription input adapter description
*/
- public void encryptAndUpdate(AdapterDescription adapterDescription) {
- db.updateElement(cloneAndEncrypt(adapterDescription));
+ public void encryptAndUpdate(AdapterDescription adapterDescription) throws AdapterException {
+ try {
+ db.updateElement(cloneAndEncrypt(adapterDescription));
+ } catch (org.lightcouch.DocumentConflictException e) {
+ throw new AdapterException(
+ "Conflict occurred while editing the adapter with id: %s".formatted(adapterDescription.getElementId()),
+ e
+ );
+ }
}
public void delete(String elementId) {
@@ -63,7 +77,8 @@
*/
private AdapterDescription cloneAndEncrypt(AdapterDescription adapterDescription) {
AdapterDescription encryptedAdapterDescription = new Cloner().adapterDescription(adapterDescription);
- SecretProvider.getEncryptionService().apply(encryptedAdapterDescription);
+ SecretProvider.getEncryptionService()
+ .apply(encryptedAdapterDescription);
return encryptedAdapterDescription;
}
diff --git a/streampipes-resource-management/src/test/java/org/apache/streampipes/resource/management/AdapterResourceManagerTest.java b/streampipes-resource-management/src/test/java/org/apache/streampipes/resource/management/AdapterResourceManagerTest.java
index d815855..d461671 100644
--- a/streampipes-resource-management/src/test/java/org/apache/streampipes/resource/management/AdapterResourceManagerTest.java
+++ b/streampipes-resource-management/src/test/java/org/apache/streampipes/resource/management/AdapterResourceManagerTest.java
@@ -18,25 +18,59 @@
package org.apache.streampipes.resource.management;
+import org.apache.streampipes.commons.exceptions.connect.AdapterException;
+import org.apache.streampipes.model.Tuple2;
import org.apache.streampipes.model.connect.adapter.AdapterDescription;
import org.apache.streampipes.storage.api.IAdapterStorage;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class AdapterResourceManagerTest {
- @Test
- public void encryptAndUpdateValidateDescriptionStored() {
+ private IAdapterStorage storage;
+ private AdapterResourceManager adapterResourceManager;
- IAdapterStorage storage = mock(IAdapterStorage.class);
- AdapterResourceManager adapterResourceManager = new AdapterResourceManager(storage);
+ @BeforeEach
+ void setUp() {
+ storage = mock(IAdapterStorage.class);
+ adapterResourceManager = new AdapterResourceManager(storage);
+ }
+
+ @Test
+ public void encryptAndUpdate_ValidateDescriptionStored() throws AdapterException {
+
adapterResourceManager.encryptAndUpdate(new AdapterDescription());
verify(storage, times(1)).updateElement(any());
}
+
+ @Test
+ void encryptAndCreate_ReturnsIdOnSuccess() throws AdapterException {
+ var id = "adapterId";
+
+ when(storage.persist(any())).thenReturn(new Tuple2<>(true, id));
+ var result = adapterResourceManager.encryptAndCreate(new AdapterDescription());
+
+ verify(storage, times(1)).persist(any());
+ assertEquals(id, result);
+ }
+
+ @Test
+ void encryptAndCreate_ThrowsAdapterExceptionOnConflict() {
+
+ doThrow(new org.lightcouch.DocumentConflictException("Conflict")).when(storage).persist(any());
+
+ assertThrows(AdapterException.class, () -> adapterResourceManager.encryptAndCreate(new AdapterDescription()));
+ }
+
}
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java
index b972aaa..372769a 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java
@@ -40,6 +40,7 @@
import org.apache.streampipes.storage.api.IPipelineStorage;
import org.apache.streampipes.storage.management.StorageDispatcher;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -115,7 +116,7 @@
updateManager.updateAdapter(adapterDescription);
} catch (AdapterException e) {
LOG.error("Error while updating adapter with id {}", adapterDescription.getElementId(), e);
- return ok(Notifications.error(e.getMessage()));
+ return ok(Notifications.error(e.getMessage(), ExceptionUtils.getStackTrace(e)));
}
return ok(Notifications.success(adapterDescription.getElementId()));
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java
index 88424c2..0e51f00 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java
@@ -37,6 +37,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -58,7 +59,7 @@
public CompactAdapterResource() {
super(() -> new AdapterMasterManagement(
StorageDispatcher.INSTANCE.getNoSqlStore()
- .getAdapterInstanceStorage(),
+ .getAdapterInstanceStorage(),
new SpResourceManager().manageAdapters(),
new SpResourceManager().manageDataStreams(),
AdapterMetricsManager.INSTANCE.getAdapterMetrics()
@@ -82,11 +83,23 @@
var adapterDescription = getGeneratedAdapterDescription(compactAdapter);
var principalSid = getAuthenticatedUserSid();
+ var adapterId = adapterDescription.getElementId();
+
try {
- var adapterId = adapterDescription.getElementId();
managementService.addAdapter(adapterDescription, adapterId, principalSid);
+ } catch (AdapterException e) {
+ LOG.error(
+ "Error while storing the adapterDescription with appId {}. An adapter with the given id already exists.",
+ adapterDescription.getAppId(), e
+ );
+ return ResponseEntity.status(HttpStatus.CONFLICT)
+ .body(Notifications.error(e.getMessage()));
+ }
+
+ try {
if (compactAdapter.createOptions() != null) {
- if (compactAdapter.createOptions().persist()) {
+ if (compactAdapter.createOptions()
+ .persist()) {
var storedAdapter = managementService.getAdapter(adapterId);
var status = new PersistPipelineHandler(
getNoSqlStorage().getPipelineTemplateStorage(),
@@ -96,7 +109,8 @@
getAuthenticatedUserSid()
).createAndStartPersistPipeline(storedAdapter);
}
- if (compactAdapter.createOptions().start()) {
+ if (compactAdapter.createOptions()
+ .start()) {
managementService.startStreamAdapter(adapterId);
}
}
@@ -143,8 +157,10 @@
return new CompactAdapterManagement(generators).convertToAdapterDescription(compactAdapter);
}
- private AdapterDescription getGeneratedAdapterDescription(CompactAdapter compactAdapter,
- AdapterDescription existingAdapter) throws Exception {
+ private AdapterDescription getGeneratedAdapterDescription(
+ CompactAdapter compactAdapter,
+ AdapterDescription existingAdapter
+ ) throws Exception {
var generators = adapterGenerationSteps.getGenerators();
return new CompactAdapterManagement(generators).convertToAdapterDescription(compactAdapter, existingAdapter);
}
diff --git a/ui/cypress/fixtures/connect/compact/machineDataSimulator.yml b/ui/cypress/fixtures/connect/compact/machineDataSimulator.yml
new file mode 100644
index 0000000..128b249
--- /dev/null
+++ b/ui/cypress/fixtures/connect/compact/machineDataSimulator.yml
@@ -0,0 +1,24 @@
+# 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.
+
+id: sp:adapterdescription:4pxl2w
+name: Test
+appId: org.apache.streampipes.connect.iiot.adapters.simulator.machine
+configuration:
+ - wait-time-ms: '1000'
+ - selected-simulator-option: flowrate
+createOptions:
+ persist: false
+ start: false
diff --git a/ui/cypress/support/builder/CompactAdapterBuilder.ts b/ui/cypress/support/builder/CompactAdapterBuilder.ts
new file mode 100644
index 0000000..2325985
--- /dev/null
+++ b/ui/cypress/support/builder/CompactAdapterBuilder.ts
@@ -0,0 +1,93 @@
+/*
+ * 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 { CompactAdapter } from '../../../projects/streampipes/platform-services/src/lib/model/gen/streampipes-model';
+
+export class CompactAdapterBuilder {
+ private compactAdapter: CompactAdapter;
+
+ constructor() {
+ this.compactAdapter = new CompactAdapter();
+ this.compactAdapter.configuration = [];
+ this.compactAdapter.createOptions = {
+ persist: false,
+ start: false,
+ };
+ this.compactAdapter.transform = {
+ rename: {},
+ measurementUnit: {},
+ };
+ }
+
+ public static create(appId: string) {
+ const builder = new CompactAdapterBuilder();
+ builder.compactAdapter.appId = appId;
+ return builder;
+ }
+
+ // Optional parameter, when not set a random id will be generated
+ public setId(id: string) {
+ this.compactAdapter.id = id;
+ return this;
+ }
+
+ public setName(name: string) {
+ this.compactAdapter.name = name;
+ return this;
+ }
+
+ public setDescription(description: string) {
+ this.compactAdapter.description = description;
+ return this;
+ }
+
+ public withRename(from: string, to: string) {
+ this.compactAdapter.transform.rename[from] = to;
+ return this;
+ }
+
+ public withMeasurementUnit(property: string, unit: string) {
+ this.compactAdapter.transform.measurementUnit[property] = unit;
+ return this;
+ }
+
+ public addConfiguration(key: string, value: string) {
+ const configuration = { [key]: value };
+ this.compactAdapter.configuration.push(configuration);
+ return this;
+ }
+
+ public setPersist() {
+ this.compactAdapter.createOptions.persist = true;
+ return this;
+ }
+
+ public setStart() {
+ this.compactAdapter.createOptions.start = true;
+ return this;
+ }
+
+ public build() {
+ if (!this.compactAdapter.id) {
+ this.compactAdapter.id =
+ 'sp:adapterdescription:' +
+ Math.random().toString(36).substring(7);
+ }
+ return this.compactAdapter;
+ }
+}
diff --git a/ui/cypress/support/utils/CompactUtils.ts b/ui/cypress/support/utils/CompactUtils.ts
new file mode 100644
index 0000000..38df176
--- /dev/null
+++ b/ui/cypress/support/utils/CompactUtils.ts
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ *
+ */
+
+export class CompactUtils {
+ public static ymlConfiguration() {
+ return cy.dataCy('yaml-configuration', { timeout: 5000 });
+ }
+
+ public static jsonConfiguration() {
+ return cy.dataCy('json-configuration', { timeout: 5000 });
+ }
+}
diff --git a/ui/cypress/support/utils/GeneralUtils.ts b/ui/cypress/support/utils/GeneralUtils.ts
new file mode 100644
index 0000000..8db505e
--- /dev/null
+++ b/ui/cypress/support/utils/GeneralUtils.ts
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ *
+ */
+
+export class GeneralUtils {
+ public static tab(identifier: string) {
+ return cy.dataCy(`tab-${identifier}`).click();
+ }
+}
diff --git a/ui/cypress/support/utils/ProcessingElementTestUtils.ts b/ui/cypress/support/utils/ProcessingElementTestUtils.ts
index f2607a1..6340635 100644
--- a/ui/cypress/support/utils/ProcessingElementTestUtils.ts
+++ b/ui/cypress/support/utils/ProcessingElementTestUtils.ts
@@ -18,7 +18,7 @@
import { FileManagementUtils } from './FileManagementUtils';
import { ConnectUtils } from './connect/ConnectUtils';
-import { PipelineUtils } from './PipelineUtils';
+import { PipelineUtils } from './pipeline/PipelineUtils';
import { DataLakeUtils } from './datalake/DataLakeUtils';
import { PipelineBuilder } from '../builder/PipelineBuilder';
import { PipelineElementBuilder } from '../builder/PipelineElementBuilder';
diff --git a/ui/cypress/support/utils/ThirdPartyIntegrationUtils.ts b/ui/cypress/support/utils/ThirdPartyIntegrationUtils.ts
index a9229a9..09a871c 100644
--- a/ui/cypress/support/utils/ThirdPartyIntegrationUtils.ts
+++ b/ui/cypress/support/utils/ThirdPartyIntegrationUtils.ts
@@ -18,7 +18,7 @@
import { ConnectUtils } from './connect/ConnectUtils';
import { PipelineBuilder } from '../builder/PipelineBuilder';
-import { PipelineUtils } from './PipelineUtils';
+import { PipelineUtils } from './pipeline/PipelineUtils';
import { PipelineElementInput } from '../model/PipelineElementInput';
import { AdapterInput } from '../model/AdapterInput';
import { AdapterBuilder } from '../builder/AdapterBuilder';
diff --git a/ui/cypress/support/utils/connect/CompactAdapterUtils.ts b/ui/cypress/support/utils/connect/CompactAdapterUtils.ts
new file mode 100644
index 0000000..43757f5
--- /dev/null
+++ b/ui/cypress/support/utils/connect/CompactAdapterUtils.ts
@@ -0,0 +1,81 @@
+/*
+ * 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 { CompactAdapter } from '../../../../projects/streampipes/platform-services/src/lib/model/gen/streampipes-model';
+import { CompactAdapterBuilder } from '../../builder/CompactAdapterBuilder';
+
+export class CompactAdapterUtils {
+ /**
+ * Stores a compact adapter by sending a POST request to the backend.
+ *
+ * @param {CompactAdapter} compactAdapter - The compact adapter to be stored.
+ * @param {boolean} [failOnStatusCode=true] - Whether to fail the request on a non-2xx status code.
+ * @returns {Cypress.Chainable} - The Cypress chainable object representing the request.
+ */
+ public static storeCompactAdapter(
+ compactAdapter: CompactAdapter,
+ failOnStatusCode: boolean = true,
+ ): Cypress.Chainable {
+ return this.postCompactAdapterRequest(
+ 'application/json',
+ compactAdapter,
+ failOnStatusCode,
+ );
+ }
+
+ /**
+ * Stores a compact YAML adapter by sending a POST request to the backend.
+ *
+ * @param {string} body - The YAML string representing the compact adapter.
+ * @returns {Cypress.Chainable} - The Cypress chainable object representing the request.
+ */
+ public static storeCompactYmlAdapter(body: string) {
+ return this.postCompactAdapterRequest('application/yml', body);
+ }
+
+ private static postCompactAdapterRequest(
+ contentType: string,
+ body: any,
+ failOnStatusCode = true,
+ ) {
+ const token = window.localStorage.getItem('auth-token');
+ return cy.request({
+ method: 'POST',
+ url: '/streampipes-backend/api/v2/connect/compact-adapters',
+ body: body,
+ failOnStatusCode: failOnStatusCode,
+ headers: {
+ 'Accept': contentType,
+ 'Content-Type': contentType,
+ 'Authorization': `Bearer ${token}`,
+ },
+ });
+ }
+
+ /**
+ * Creates a CompactAdapterBuilder instance configured for a machine data simulator.
+ */
+ public static getMachineDataSimulator(): CompactAdapterBuilder {
+ return CompactAdapterBuilder.create(
+ 'org.apache.streampipes.connect.iiot.adapters.simulator.machine',
+ )
+ .setName('Test')
+ .addConfiguration('wait-time-ms', '1000')
+ .addConfiguration('selected-simulator-option', 'flowrate');
+ }
+}
diff --git a/ui/cypress/support/utils/connect/ConnectBtns.ts b/ui/cypress/support/utils/connect/ConnectBtns.ts
index 20bfd69..ea8b2d8 100644
--- a/ui/cypress/support/utils/connect/ConnectBtns.ts
+++ b/ui/cypress/support/utils/connect/ConnectBtns.ts
@@ -17,7 +17,11 @@
*/
export class ConnectBtns {
public static detailsAdapter() {
- return cy.dataCy('details-adapter');
+ return cy.dataCy('details-adapter', { timeout: 10000 });
+ }
+
+ public static deleteAdapter() {
+ return cy.dataCy('delete-adapter', { timeout: 10000 });
}
public static editAdapter() {
@@ -69,6 +73,10 @@
return cy.dataCy('stop-all-adapters-btn');
}
+ public static showCodeCheckbox() {
+ return cy.dataCy('show-code-checkbox');
+ }
+
// ========================================================================
// ===================== Event Schema buttons ==========================
diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts b/ui/cypress/support/utils/connect/ConnectUtils.ts
index aaeccff..0a0eae2 100644
--- a/ui/cypress/support/utils/connect/ConnectUtils.ts
+++ b/ui/cypress/support/utils/connect/ConnectUtils.ts
@@ -23,7 +23,7 @@
import { ConnectBtns } from './ConnectBtns';
import { AdapterBuilder } from '../../builder/AdapterBuilder';
import { UserUtils } from '../UserUtils';
-import { PipelineUtils } from '../PipelineUtils';
+import { PipelineUtils } from '../pipeline/PipelineUtils';
export class ConnectUtils {
public static testAdapter(
@@ -76,9 +76,11 @@
);
}
- ConnectEventSchemaUtils.markPropertyAsTimestamp(
- adapterConfiguration.timestampProperty,
- );
+ if (adapterConfiguration.timestampProperty) {
+ ConnectEventSchemaUtils.markPropertyAsTimestamp(
+ adapterConfiguration.timestampProperty,
+ );
+ }
ConnectEventSchemaUtils.finishEventSchemaConfiguration();
}
@@ -356,6 +358,12 @@
ConnectUtils.validateEventsInPreview(amountOfProperties);
}
+ public static getLivePreviewValue(runtimeName: string) {
+ return cy.dataCy(`live-preview-value-${runtimeName}`, {
+ timeout: 10000,
+ });
+ }
+
public static validateEventsInPreview(amountOfProperties: number) {
// View data
ConnectBtns.detailsAdapter().click();
@@ -370,11 +378,25 @@
amountOfProperties,
);
- cy.wait(1000);
+ cy.dataCy('live-preview-table-no-data', { timout: 1000 }).should(
+ 'not.exist',
+ );
+ }
- cy.dataCy('live-preview-table-value')
- .invoke('text')
- .then(text => expect(text).not.to.include('no data'));
+ /**
+ * Validates the event schema for an adapter by checking the amount of properties
+ * and the runtime names of the event properties
+ * @param runtimeNames runtime names of the event properties
+ */
+ public static validateEventSchema(runtimeNames: string[]) {
+ ConnectUtils.goToConnect();
+ ConnectBtns.detailsAdapter().click();
+
+ cy.get('tr.mat-mdc-row').should('have.length', runtimeNames.length);
+
+ runtimeNames.forEach(name => {
+ cy.get('td.mat-column-runtimeName').contains(name).should('exist');
+ });
}
public static tearDownPreprocessingRuleTest(
@@ -403,4 +425,21 @@
// Close dialog
cy.get('button').contains('Close').parent().click();
}
+
+ public static validateAdapterIsRunning() {
+ ConnectUtils.goToConnect();
+ ConnectBtns.startAdapter().should('have.length', 0);
+ ConnectBtns.stopAdapter().should('have.length', 1);
+ }
+
+ public static validateAdapterIsStopped() {
+ ConnectUtils.goToConnect();
+ ConnectBtns.startAdapter().should('have.length', 1);
+ ConnectBtns.stopAdapter().should('have.length', 0);
+ }
+
+ public static checkAmountOfAdapters(amount: number) {
+ ConnectUtils.goToConnect();
+ ConnectBtns.deleteAdapter().should('have.length', amount);
+ }
}
diff --git a/ui/cypress/support/utils/pipeline/PipelineBtns.ts b/ui/cypress/support/utils/pipeline/PipelineBtns.ts
new file mode 100644
index 0000000..6ace822
--- /dev/null
+++ b/ui/cypress/support/utils/pipeline/PipelineBtns.ts
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ *
+ */
+
+export class PipelineBtns {
+ public static deletePipeline() {
+ return cy.dataCy('delete-pipeline', { timeout: 10000 });
+ }
+}
diff --git a/ui/cypress/support/utils/PipelineUtils.ts b/ui/cypress/support/utils/pipeline/PipelineUtils.ts
similarity index 72%
rename from ui/cypress/support/utils/PipelineUtils.ts
rename to ui/cypress/support/utils/pipeline/PipelineUtils.ts
index b901e22..0fa9ce4 100644
--- a/ui/cypress/support/utils/PipelineUtils.ts
+++ b/ui/cypress/support/utils/pipeline/PipelineUtils.ts
@@ -1,25 +1,26 @@
/*
- * 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
+ * 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
+ * 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.
+ * 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 { PipelineInput } from '../model/PipelineInput';
-import { StaticPropertyUtils } from './userInput/StaticPropertyUtils';
-import { OutputStrategyUtils } from './OutputStrategyUtils';
-import { PipelineElementInput } from '../model/PipelineElementInput';
+import { PipelineInput } from '../../model/PipelineInput';
+import { StaticPropertyUtils } from '../userInput/StaticPropertyUtils';
+import { OutputStrategyUtils } from '../OutputStrategyUtils';
+import { PipelineElementInput } from '../../model/PipelineElementInput';
+import { PipelineBtns } from './PipelineBtns';
export class PipelineUtils {
public static addPipeline(pipelineInput: PipelineInput) {
@@ -131,21 +132,18 @@
}
public static checkAmountOfPipelinesPipeline(amount: number) {
- cy.visit('#/pipelines');
- cy.dataCy('delete-pipeline').should('have.length', amount);
+ PipelineUtils.goToPipelines();
+ PipelineBtns.deletePipeline().should('have.length', amount);
}
public static deletePipeline() {
// Delete pipeline
- cy.visit('#/pipelines');
- cy.dataCy('delete-pipeline').should('have.length', 1);
- cy.dataCy('delete-pipeline').click({ force: true });
+ PipelineUtils.goToPipelines();
+ PipelineBtns.deletePipeline().should('have.length', 1);
+ PipelineBtns.deletePipeline().click({ force: true });
cy.dataCy('sp-pipeline-stop-and-delete').click();
- cy.dataCy('delete-pipeline', { timeout: 10000 }).should(
- 'have.length',
- 0,
- );
+ PipelineBtns.deletePipeline().should('have.length', 0);
}
}
diff --git a/ui/cypress/tests/connect/compact/addCompactAdapter.spec.ts b/ui/cypress/tests/connect/compact/addCompactAdapter.spec.ts
new file mode 100644
index 0000000..c19e345
--- /dev/null
+++ b/ui/cypress/tests/connect/compact/addCompactAdapter.spec.ts
@@ -0,0 +1,99 @@
+/*
+ * 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 { ConnectUtils } from '../../../support/utils/connect/ConnectUtils';
+import { CompactAdapterUtils } from '../../../support/utils/connect/CompactAdapterUtils';
+import { PipelineUtils } from '../../../support/utils/pipeline/PipelineUtils';
+
+describe('Add Compact Adapters', () => {
+ beforeEach('Setup Test', () => {
+ cy.initStreamPipesTest();
+ });
+
+ it('Add an adapter via the compact API. Do not start', () => {
+ const compactAdapter =
+ CompactAdapterUtils.getMachineDataSimulator().build();
+
+ CompactAdapterUtils.storeCompactAdapter(compactAdapter).then(() => {
+ ConnectUtils.validateAdapterIsStopped();
+
+ PipelineUtils.checkAmountOfPipelinesPipeline(0);
+ });
+ });
+
+ it('Add an adapter via the compact API. Start Adapter', () => {
+ const compactAdapter = CompactAdapterUtils.getMachineDataSimulator()
+ .setStart()
+ .build();
+
+ CompactAdapterUtils.storeCompactAdapter(compactAdapter).then(() => {
+ ConnectUtils.validateAdapterIsRunning();
+
+ PipelineUtils.checkAmountOfPipelinesPipeline(0);
+ });
+ });
+
+ it('Add an adapter via the compact API. Start Adapter and start persist pipeline', () => {
+ const compactAdapter = CompactAdapterUtils.getMachineDataSimulator()
+ .setStart()
+ .setPersist()
+ .build();
+
+ CompactAdapterUtils.storeCompactAdapter(compactAdapter).then(() => {
+ ConnectUtils.validateAdapterIsRunning();
+
+ PipelineUtils.checkAmountOfPipelinesPipeline(1);
+ });
+ });
+
+ it('Add an adapter via the compact API via yml API.', () => {
+ cy.readFile(
+ 'cypress/fixtures/connect/compact/machineDataSimulator.yml',
+ ).then(ymlDescription => {
+ CompactAdapterUtils.storeCompactYmlAdapter(ymlDescription).then(
+ () => {
+ ConnectUtils.validateAdapterIsStopped();
+ },
+ );
+ });
+ });
+
+ it('Ensure correct error code when adapter with the same id already exists', () => {
+ const compactAdapter = CompactAdapterUtils.getMachineDataSimulator()
+ .setStart()
+ .setPersist()
+ .build();
+
+ CompactAdapterUtils.storeCompactAdapter(compactAdapter).then(
+ response => {
+ expect(response.status).to.equal(200);
+ ConnectUtils.checkAmountOfAdapters(1);
+
+ // Store the same adapter a second time and validate that resource returns status of conflict
+ CompactAdapterUtils.storeCompactAdapter(
+ compactAdapter,
+ false,
+ ).then(response => {
+ expect(response.status).to.equal(409);
+
+ ConnectUtils.checkAmountOfAdapters(1);
+ });
+ },
+ );
+ });
+});
diff --git a/ui/cypress/tests/connect/compact/compactAdapterWithTransformation.spec.ts b/ui/cypress/tests/connect/compact/compactAdapterWithTransformation.spec.ts
new file mode 100644
index 0000000..4af2f35
--- /dev/null
+++ b/ui/cypress/tests/connect/compact/compactAdapterWithTransformation.spec.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { ConnectUtils } from '../../../support/utils/connect/ConnectUtils';
+import { CompactAdapterUtils } from '../../../support/utils/connect/CompactAdapterUtils';
+import { ConnectBtns } from '../../../support/utils/connect/ConnectBtns';
+
+describe('Add Compact Adapters', () => {
+ beforeEach('Setup Test', () => {
+ cy.initStreamPipesTest();
+ });
+
+ it('Add an adapter and rename a property', () => {
+ const newPropertyName = 'temperature_renamed';
+ const compactAdapter = CompactAdapterUtils.getMachineDataSimulator()
+ .withRename('temperature', newPropertyName)
+ .setStart()
+ .build();
+
+ CompactAdapterUtils.storeCompactAdapter(compactAdapter).then(() => {
+ const runtimeNames = [
+ 'density',
+ 'mass_flow',
+ 'sensor_fault_flags',
+ 'sensorId',
+ newPropertyName,
+ 'timestamp',
+ 'volume_flow',
+ ];
+
+ ConnectUtils.validateEventSchema(runtimeNames);
+ });
+ });
+
+ it('Add an adapter and change measurement unit', () => {
+ const compactAdapter = CompactAdapterUtils.getMachineDataSimulator()
+ .withMeasurementUnit(
+ 'temperature',
+ 'http://qudt.org/vocab/unit#DegreeFahrenheit',
+ )
+ .setStart()
+ .build();
+
+ CompactAdapterUtils.storeCompactAdapter(compactAdapter).then(() => {
+ ConnectUtils.goToConnect();
+ ConnectBtns.detailsAdapter().click();
+
+ // This assertion works because the original value is below 100
+ // with the transformation the value is above 100
+ ConnectUtils.getLivePreviewValue('temperature')
+ .invoke('text')
+ .then(text => {
+ const value = parseFloat(text.trim());
+ expect(value).to.be.greaterThan(100);
+ });
+ });
+ });
+});
diff --git a/ui/cypress/tests/connect/compact/uiConfiguration.spec.ts b/ui/cypress/tests/connect/compact/uiConfiguration.spec.ts
new file mode 100644
index 0000000..798d8a8
--- /dev/null
+++ b/ui/cypress/tests/connect/compact/uiConfiguration.spec.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 { ConnectUtils } from '../../../support/utils/connect/ConnectUtils';
+import { AdapterBuilder } from '../../../support/builder/AdapterBuilder';
+import { ConnectBtns } from '../../../support/utils/connect/ConnectBtns';
+import { GeneralUtils } from '../../../support/utils/GeneralUtils';
+import { CompactUtils } from '../../../support/utils/CompactUtils';
+
+describe('Test Compact Adapters', () => {
+ beforeEach('Setup Test', () => {
+ cy.initStreamPipesTest();
+ });
+
+ it('Validate that code for existing adapter is displayed correctly', () => {
+ const adapterInput = AdapterBuilder.create('Machine_Data_Simulator')
+ .setName('Machine Data Simulator Test')
+ .addInput('input', 'wait-time-ms', '1000')
+ .build();
+
+ ConnectUtils.addAdapter(adapterInput);
+
+ // Validate code editor in start dialog
+ ConnectBtns.showCodeCheckbox().parent().click();
+ validateCodeEditor();
+
+ ConnectUtils.startAdapter(adapterInput);
+
+ // Validate code editor in adapter details
+ ConnectBtns.detailsAdapter().click();
+ GeneralUtils.tab('Code');
+ validateCodeEditor();
+ });
+
+ const validateCodeEditor = () => {
+ CompactUtils.ymlConfiguration().should('be.visible');
+ cy.contains('span', 'JSON').click();
+ CompactUtils.jsonConfiguration().should('be.visible');
+ };
+});
diff --git a/ui/cypress/tests/connect/deleteAdapterWithMultipleUsers.smoke.spec.ts b/ui/cypress/tests/connect/deleteAdapterWithMultipleUsers.smoke.spec.ts
index 29c854c..a11332a 100644
--- a/ui/cypress/tests/connect/deleteAdapterWithMultipleUsers.smoke.spec.ts
+++ b/ui/cypress/tests/connect/deleteAdapterWithMultipleUsers.smoke.spec.ts
@@ -20,7 +20,7 @@
import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
import { PipelineElementBuilder } from '../../support/builder/PipelineElementBuilder';
import { UserUtils } from '../../support/utils/UserUtils';
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
const adapterName = 'simulator';
diff --git a/ui/cypress/tests/connect/editAdapter.smoke.spec.ts b/ui/cypress/tests/connect/editAdapter.smoke.spec.ts
index 214e843..a1d2013 100644
--- a/ui/cypress/tests/connect/editAdapter.smoke.spec.ts
+++ b/ui/cypress/tests/connect/editAdapter.smoke.spec.ts
@@ -57,6 +57,8 @@
cy.dataCy('sp-adapter-name').clear().type(newAdapterName);
+ // This wait is required to ensure that there is no couch db update conflict
+ cy.wait(1000);
ConnectBtns.storeEditAdapter().click();
cy.dataCy('sp-connect-adapter-success-added', {
diff --git a/ui/cypress/tests/connect/editAdapterSettingsAndPipeline.spec.ts b/ui/cypress/tests/connect/editAdapterSettingsAndPipeline.spec.ts
index c30f355..c3f14b7 100644
--- a/ui/cypress/tests/connect/editAdapterSettingsAndPipeline.spec.ts
+++ b/ui/cypress/tests/connect/editAdapterSettingsAndPipeline.spec.ts
@@ -18,7 +18,7 @@
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
import { ConnectBtns } from '../../support/utils/connect/ConnectBtns';
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { PipelineElementBuilder } from '../../support/builder/PipelineElementBuilder';
import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
import { AdapterBuilder } from '../../support/builder/AdapterBuilder';
diff --git a/ui/cypress/tests/connect/persistInDataLake.smoke.spec.ts b/ui/cypress/tests/connect/persistInDataLake.smoke.spec.ts
index 39c672b..939bf44 100644
--- a/ui/cypress/tests/connect/persistInDataLake.smoke.spec.ts
+++ b/ui/cypress/tests/connect/persistInDataLake.smoke.spec.ts
@@ -17,7 +17,7 @@
*/
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { FileManagementUtils } from '../../support/utils/FileManagementUtils';
import { AdapterBuilder } from '../../support/builder/AdapterBuilder';
import { ConnectBtns } from '../../support/utils/connect/ConnectBtns';
diff --git a/ui/cypress/tests/datalake/configuration.smoke.spec.ts b/ui/cypress/tests/datalake/configuration.smoke.spec.ts
index 3b5532e..1b095ab 100644
--- a/ui/cypress/tests/datalake/configuration.smoke.spec.ts
+++ b/ui/cypress/tests/datalake/configuration.smoke.spec.ts
@@ -16,7 +16,7 @@
*
*/
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { DataLakeUtils } from '../../support/utils/datalake/DataLakeUtils';
describe('Test Truncate data in datalake', () => {
diff --git a/ui/cypress/tests/experimental/restartStreamPipes/restartStreamPipes1.ts b/ui/cypress/tests/experimental/restartStreamPipes/restartStreamPipes1.ts
index 3f098de..d8eafe8 100644
--- a/ui/cypress/tests/experimental/restartStreamPipes/restartStreamPipes1.ts
+++ b/ui/cypress/tests/experimental/restartStreamPipes/restartStreamPipes1.ts
@@ -17,7 +17,7 @@
*/
import { ConnectUtils } from '../../../support/utils/connect/ConnectUtils';
-import { PipelineUtils } from '../../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../../support/utils/pipeline/PipelineUtils';
import { PipelineElementBuilder } from '../../../support/builder/PipelineElementBuilder';
import { PipelineBuilder } from '../../../support/builder/PipelineBuilder';
import { DashboardUtils } from '../../../support/utils/DashboardUtils';
diff --git a/ui/cypress/tests/experimental/testJvmArchetype/testJvmArchetype.ts b/ui/cypress/tests/experimental/testJvmArchetype/testJvmArchetype.ts
index 2a41d84..c15490d 100644
--- a/ui/cypress/tests/experimental/testJvmArchetype/testJvmArchetype.ts
+++ b/ui/cypress/tests/experimental/testJvmArchetype/testJvmArchetype.ts
@@ -17,7 +17,7 @@
*/
import { ConnectUtils } from '../../../support/utils/connect/ConnectUtils';
-import { PipelineUtils } from '../../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../../support/utils/pipeline/PipelineUtils';
import { PipelineElementBuilder } from '../../../support/builder/PipelineElementBuilder';
import { PipelineBuilder } from '../../../support/builder/PipelineBuilder';
import { DashboardUtils } from '../../../support/utils/DashboardUtils';
diff --git a/ui/cypress/tests/pipeline/pipelineTest.smoke.spec.ts b/ui/cypress/tests/pipeline/pipelineTest.smoke.spec.ts
index 1de708d..010f769 100644
--- a/ui/cypress/tests/pipeline/pipelineTest.smoke.spec.ts
+++ b/ui/cypress/tests/pipeline/pipelineTest.smoke.spec.ts
@@ -17,7 +17,7 @@
*/
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { PipelineElementBuilder } from '../../support/builder/PipelineElementBuilder';
import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
diff --git a/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts b/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
index b2e69bd..5de2359 100644
--- a/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
+++ b/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
@@ -17,7 +17,7 @@
*/
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { PipelineElementBuilder } from '../../support/builder/PipelineElementBuilder';
import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
diff --git a/ui/cypress/tests/pipelineElement/PipelineElementDocumentation.spec.ts b/ui/cypress/tests/pipelineElement/PipelineElementDocumentation.spec.ts
index 726ed1c..4387a2e 100644
--- a/ui/cypress/tests/pipelineElement/PipelineElementDocumentation.spec.ts
+++ b/ui/cypress/tests/pipelineElement/PipelineElementDocumentation.spec.ts
@@ -16,7 +16,7 @@
*
*/
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
describe('Validate that the markdown documentation for pipeline elements works', () => {
beforeEach('Setup Test', () => {
diff --git a/ui/cypress/tests/pipelineElementConfigurationTemplate/pipelineElementConfigurationTemplate.ts b/ui/cypress/tests/pipelineElementConfigurationTemplate/pipelineElementConfigurationTemplate.ts
index 2b9fff9..16ea5c3 100644
--- a/ui/cypress/tests/pipelineElementConfigurationTemplate/pipelineElementConfigurationTemplate.ts
+++ b/ui/cypress/tests/pipelineElementConfigurationTemplate/pipelineElementConfigurationTemplate.ts
@@ -16,7 +16,7 @@
*
*/
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
import { PipelineElementBuilder } from '../../support/builder/PipelineElementBuilder';
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
diff --git a/ui/cypress/tests/userManagement/testGroupManagement.spec.ts b/ui/cypress/tests/userManagement/testGroupManagement.spec.ts
index 2f4de9e..03d7159 100644
--- a/ui/cypress/tests/userManagement/testGroupManagement.spec.ts
+++ b/ui/cypress/tests/userManagement/testGroupManagement.spec.ts
@@ -20,7 +20,7 @@
import { UserRole } from '../../../src/app/_enums/user-role.enum';
import { UserUtils } from '../../support/utils/UserUtils';
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { PipelineElementBuilder } from '../../support/builder/PipelineElementBuilder';
import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
diff --git a/ui/cypress/tests/userManagement/testUserRolePipeline.spec.ts b/ui/cypress/tests/userManagement/testUserRolePipeline.spec.ts
index a992334..94d8341 100644
--- a/ui/cypress/tests/userManagement/testUserRolePipeline.spec.ts
+++ b/ui/cypress/tests/userManagement/testUserRolePipeline.spec.ts
@@ -20,7 +20,7 @@
import { UserRole } from '../../../src/app/_enums/user-role.enum';
import { UserUtils } from '../../support/utils/UserUtils';
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
-import { PipelineUtils } from '../../support/utils/PipelineUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { PipelineElementBuilder } from '../../support/builder/PipelineElementBuilder';
import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/basic-nav-tabs/basic-nav-tabs.component.html b/ui/projects/streampipes/shared-ui/src/lib/components/basic-nav-tabs/basic-nav-tabs.component.html
index 68f9377..c613d3c 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/basic-nav-tabs/basic-nav-tabs.component.html
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/basic-nav-tabs/basic-nav-tabs.component.html
@@ -42,6 +42,7 @@
*ngFor="let item of spNavigationItems"
(click)="navigateTo(item)"
[active]="activeLink === item.itemId"
+ [attr.data-cy]="'tab-' + item.itemTitle"
>
<span>{{ item.itemTitle }}</span>
</a>
diff --git a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
index f7ff5b8..d4eecdd 100644
--- a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
+++ b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
@@ -150,6 +150,7 @@
optionTitle="Show code"
optionDescription="Show code to programmatically deploy this adapter over the API"
optionIcon="code"
+ dataCy="show-code-checkbox"
(optionSelectedEmitter)="showCode = $event"
>
@if (showCode) {
diff --git a/ui/src/app/connect/dialog/adapter-started/adapter-started-dialog.component.ts b/ui/src/app/connect/dialog/adapter-started/adapter-started-dialog.component.ts
index 598dae3..459327f 100644
--- a/ui/src/app/connect/dialog/adapter-started/adapter-started-dialog.component.ts
+++ b/ui/src/app/connect/dialog/adapter-started/adapter-started-dialog.component.ts
@@ -24,6 +24,7 @@
CompactPipeline,
CompactPipelineElement,
ErrorMessage,
+ Message,
PipelineOperationStatus,
PipelineTemplateService,
PipelineUpdateInfo,
@@ -112,16 +113,22 @@
updateAdapter(): void {
this.loadingText = `Updating adapter ${this.adapter.name}`;
this.loading = true;
- this.adapterService.updateAdapter(this.adapter).subscribe(
- res => {
- this.onAdapterReady(
- `Adapter ${this.adapter.name} was successfully updated and is available in the pipeline editor.`,
- );
+ this.adapterService.updateAdapter(this.adapter).subscribe({
+ next: status => {
+ if (status.success) {
+ this.onAdapterReady(
+ `Adapter ${this.adapter.name} was successfully updated and is available in the pipeline editor.`,
+ );
+ } else {
+ const errorLogMessage = this.getErrorLogMessage(status);
+
+ this.onAdapterFailure(errorLogMessage);
+ }
},
- error => {
+ error: error => {
this.onAdapterFailure(error.error);
},
- );
+ });
}
addAdapter() {
@@ -137,16 +144,8 @@
this.startAdapter(adapterElementId, true);
}
} else {
- const errorMsg: SpLogMessage = {
- cause:
- status.notifications.length > 0
- ? status.notifications[0].title
- : 'Unknown Error',
- detail: '',
- fullStackTrace: '',
- level: 'ERROR',
- title: 'Unknown Error',
- };
+ const errorMsg: SpLogMessage =
+ this.getErrorLogMessage(status);
this.onAdapterFailure(errorMsg);
}
@@ -157,6 +156,20 @@
);
}
+ private getErrorLogMessage(status: Message): SpLogMessage {
+ const notification = status.notifications[0] || {
+ title: 'Unknown Error',
+ description: '',
+ };
+ return {
+ cause: notification.title,
+ detail: '',
+ fullStackTrace: notification.description,
+ level: 'ERROR',
+ title: 'Unknown Error',
+ };
+ }
+
startAdapter(adapterElementId: string, showPreview = false) {
const successMessage =
'Your new data stream is now available in the pipeline editor.';
diff --git a/ui/src/app/core-ui/configuration-code-panel/configuration-code-panel.component.html b/ui/src/app/core-ui/configuration-code-panel/configuration-code-panel.component.html
index 083d929..a6590ce 100644
--- a/ui/src/app/core-ui/configuration-code-panel/configuration-code-panel.component.html
+++ b/ui/src/app/core-ui/configuration-code-panel/configuration-code-panel.component.html
@@ -22,6 +22,7 @@
[innerHTML]="configuration | yamlpretty"
class="preview-text"
[ngStyle]="{ maxHeight: maxHeight }"
+ data-cy="yaml-configuration"
></pre>
</mat-tab>
<mat-tab label="JSON">
@@ -29,6 +30,7 @@
[innerHTML]="configuration | jsonpretty"
class="preview-text"
[ngStyle]="{ maxHeight: maxHeight }"
+ data-cy="json-configuration"
></pre>
</mat-tab>
</mat-tab-group>
diff --git a/ui/src/app/core-ui/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.html b/ui/src/app/core-ui/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.html
index ffaf3bd..ceb6851 100644
--- a/ui/src/app/core-ui/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.html
+++ b/ui/src/app/core-ui/pipeline-element-runtime-info/live-preview-table/live-preview-table.component.html
@@ -57,7 +57,12 @@
<th mat-header-cell *matHeaderCellDef><strong>Value</strong></th>
<td mat-cell *matCellDef="let element" style="width: 200px">
@if (element.value === undefined) {
- <div class="value no-data">no data</div>
+ <div
+ data-cy="live-preview-table-no-data"
+ class="value no-data"
+ >
+ no data
+ </div>
} @else {
<ng-container *ngIf="element.isImage">
<img
@@ -75,7 +80,9 @@
</ng-container>
<ng-container *ngIf="element.hasNoDomainProperty">
<div
- data-cy="live-preview-table-value"
+ [attr.data-cy]="
+ 'live-preview-value-' + element.runtimeName
+ "
class="value"
[class.value-changed]="element.valueChanged"
>