diff --git a/ui/cypress/support/utils/connect/OpcUaUtils.ts b/ui/cypress/support/utils/connect/OpcUaUtils.ts new file mode 100644 index 0000000000..5c70b8c655 --- /dev/null +++ b/ui/cypress/support/utils/connect/OpcUaUtils.ts @@ -0,0 +1,100 @@ +/* + * 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 { AdapterInput } from '../../model/AdapterInput'; +import { ConnectUtils } from './ConnectUtils'; +import { ErrorMessageUtils } from '../ErrorMessageUtils'; +import { StaticPropertyUtils } from '../userInput/StaticPropertyUtils'; +import { TreeNodeUserInputBuilder } from '../../builder/TreeNodeUserInputBuilder'; +import { AdapterBuilder } from '../../builder/AdapterBuilder'; +import { ParameterUtils } from '../ParameterUtils'; + +export class OpcUaUtils { + public static setUpInitialConfiguration(adapterInput: AdapterInput) { + ConnectUtils.goToConnect(); + ConnectUtils.goToNewAdapterPage(); + ConnectUtils.selectAdapter(adapterInput.adapterType); + + // Wait for the first static property to be rendered + cy.dataCy(adapterInput.adapterConfiguration[0].selector).should( + 'be.visible', + ); + // Validate that no error is not shown when nothing is configured + cy.dataCy('reloading-nodes', { timeout: 3000 }).should('not.exist'); + ErrorMessageUtils.getExceptionComponent().should('not.exist'); + + StaticPropertyUtils.input(adapterInput.adapterConfiguration); + } + + public static getAdapterBuilderWithTreeNodes(pullMode: boolean) { + const builder = OpcUaUtils.getBaseAdapterConfigBuilder(pullMode); + builder.addTreeNode( + TreeNodeUserInputBuilder.create( + 'Objects', + TreeNodeUserInputBuilder.create( + 'OpcPlc', + TreeNodeUserInputBuilder.create( + 'Telemetry', + TreeNodeUserInputBuilder.create('Basic').addChildren( + TreeNodeUserInputBuilder.create( + 'AlternatingBoolean', + ), + TreeNodeUserInputBuilder.create('StepUp'), + TreeNodeUserInputBuilder.create( + 'RandomSignedInt32', + ), + TreeNodeUserInputBuilder.create( + 'RandomUnsignedInt32', + ), + ), + ), + ), + ), + ); + + return builder.build(); + } + + public static getBaseAdapterConfigBuilder( + pullMode: boolean, + ): AdapterBuilder { + const host: string = ParameterUtils.get('localhost', 'opcua'); + + const builder = AdapterBuilder.create('OPC_UA').setName('OPC UA Test'); + + if (pullMode) { + builder.addInput('radio', 'adapter_type-pull_mode', ''); + builder.addInput('input', 'undefined-PULLING_INTERVAL-0', '1000'); + } else { + builder.addInput('radio', 'adapter_type-subscription_mode', ''); + } + + builder + .addInput('radio', 'access_mode-none', '') + .addInput('radio', 'opc_host_or_url-url', '') + .addInput( + 'input', + 'undefined-OPC_SERVER_URL-0', + 'opc.tcp://' + host + ':50000', + ); + + builder.setAutoAddTimestampPropery(); + + return builder; + } +} diff --git a/ui/cypress/tests/connect/opcua/opcAdapterConfiguration.spec.ts b/ui/cypress/tests/connect/opcua/opcAdapterConfiguration.spec.ts index 8889c18542..9fb08f78bd 100644 --- a/ui/cypress/tests/connect/opcua/opcAdapterConfiguration.spec.ts +++ b/ui/cypress/tests/connect/opcua/opcAdapterConfiguration.spec.ts @@ -20,10 +20,9 @@ import { ConnectUtils } from '../../../support/utils/connect/ConnectUtils'; import { ParameterUtils } from '../../../support/utils/ParameterUtils'; import { AdapterBuilder } from '../../../support/builder/AdapterBuilder'; import { TreeNodeUserInputBuilder } from '../../../support/builder/TreeNodeUserInputBuilder'; -import { StaticPropertyUtils } from '../../../support/utils/userInput/StaticPropertyUtils'; import { TreeStaticPropertyUtils } from '../../../support/utils/userInput/TreeStaticPropertyUtils'; import { ErrorMessageUtils } from '../../../support/utils/ErrorMessageUtils'; -import { AdapterInput } from '../../../support/model/AdapterInput'; +import { OpcUaUtils } from '../../../support/utils/connect/OpcUaUtils'; describe('Test OPC-UA Adapter Configuration', () => { beforeEach('Setup Test', () => { @@ -51,7 +50,7 @@ describe('Test OPC-UA Adapter Configuration', () => { ); const adapterInput = adapterBuilder.build(); - setUpInitialConfiguration(adapterInput); + OpcUaUtils.setUpInitialConfiguration(adapterInput); TreeStaticPropertyUtils.validateAmountOfSelectedNodes(2); @@ -81,7 +80,7 @@ describe('Test OPC-UA Adapter Configuration', () => { it('Test OPC-UA Text Editor', () => { const adapterInput = getAdapterBuilder().build(); - setUpInitialConfiguration(adapterInput); + OpcUaUtils.setUpInitialConfiguration(adapterInput); TreeStaticPropertyUtils.treeEditor().should('be.visible'); TreeStaticPropertyUtils.textEditor().should('not.exist'); @@ -123,7 +122,7 @@ describe('Test OPC-UA Adapter Configuration', () => { it('Test OPC-UA Node does not exist', () => { const adapterInput = getAdapterBuilder().build(); - setUpInitialConfiguration(adapterInput); + OpcUaUtils.setUpInitialConfiguration(adapterInput); // Switch to text editor TreeStaticPropertyUtils.switchToTextEditor(); @@ -137,7 +136,7 @@ describe('Test OPC-UA Adapter Configuration', () => { it('Test OPC-UA Wrong Node Id Format', () => { const adapterInput = getAdapterBuilder().build(); - setUpInitialConfiguration(adapterInput); + OpcUaUtils.setUpInitialConfiguration(adapterInput); // Switch to text editor TreeStaticPropertyUtils.switchToTextEditor(); @@ -166,19 +165,3 @@ const getAdapterBuilder = () => { ) .setAutoAddTimestampPropery(); }; - -const setUpInitialConfiguration = (adapterInput: AdapterInput) => { - ConnectUtils.goToConnect(); - ConnectUtils.goToNewAdapterPage(); - ConnectUtils.selectAdapter(adapterInput.adapterType); - - // Wait for the first static property to be rendered - cy.dataCy(adapterInput.adapterConfiguration[0].selector).should( - 'be.visible', - ); - // Validate that no error is not shown when nothing is configured - cy.dataCy('reloading-nodes', { timeout: 3000 }).should('not.exist'); - ErrorMessageUtils.getExceptionComponent().should('not.exist'); - - StaticPropertyUtils.input(adapterInput.adapterConfiguration); -}; diff --git a/ui/cypress/tests/connect/opcua/startAndEditOpcAdapters.spec.ts b/ui/cypress/tests/connect/opcua/startAndEditOpcAdapters.spec.ts index 4e8ccba5fe..0930395268 100644 --- a/ui/cypress/tests/connect/opcua/startAndEditOpcAdapters.spec.ts +++ b/ui/cypress/tests/connect/opcua/startAndEditOpcAdapters.spec.ts @@ -17,13 +17,12 @@ */ import { ConnectUtils } from '../../../support/utils/connect/ConnectUtils'; -import { ParameterUtils } from '../../../support/utils/ParameterUtils'; -import { AdapterBuilder } from '../../../support/builder/AdapterBuilder'; import { TreeNodeUserInputBuilder } from '../../../support/builder/TreeNodeUserInputBuilder'; import { ConnectBtns } from '../../../support/utils/connect/ConnectBtns'; import { TreeStaticPropertyUtils } from '../../../support/utils/userInput/TreeStaticPropertyUtils'; import { ConnectEventSchemaUtils } from '../../../support/utils/connect/ConnectEventSchemaUtils'; import { AdapterInput } from '../../../support/model/AdapterInput'; +import { OpcUaUtils } from '../../../support/utils/connect/OpcUaUtils'; describe('Test starting and editing OPC-UA Adapters in different configurations', () => { beforeEach('Setup Test', () => { @@ -31,12 +30,12 @@ describe('Test starting and editing OPC-UA Adapters in different configurations' }); it('Create OPC-UA Adapter Tree Editor Pull Mode', () => { - const adapterInput = getAdapterBuilderWithTreeNodes(true); + const adapterInput = OpcUaUtils.getAdapterBuilderWithTreeNodes(true); startAdapterTest(adapterInput); }); it('Create OPC-UA Adapter Tree Editor Subscription Mode', () => { - const adapterInput = getAdapterBuilderWithTreeNodes(false); + const adapterInput = OpcUaUtils.getAdapterBuilderWithTreeNodes(false); startAdapterTest(adapterInput); }); @@ -51,7 +50,7 @@ describe('Test starting and editing OPC-UA Adapters in different configurations' }); it('Edit OPC-UA Adapter created with Tree editor', () => { - const adapterInput = getAdapterBuilderWithTreeNodes(true); + const adapterInput = OpcUaUtils.getAdapterBuilderWithTreeNodes(true); editAdapterTest(adapterInput); }); @@ -63,29 +62,6 @@ describe('Test starting and editing OPC-UA Adapters in different configurations' }); }); -const getAdapterBuilderWithTreeNodes = (pullMode: boolean) => { - const builder = getBaseAdapterConfigBuilder(pullMode); - builder.addTreeNode( - TreeNodeUserInputBuilder.create( - 'Objects', - TreeNodeUserInputBuilder.create( - 'OpcPlc', - TreeNodeUserInputBuilder.create( - 'Telemetry', - TreeNodeUserInputBuilder.create('Basic').addChildren( - TreeNodeUserInputBuilder.create('AlternatingBoolean'), - TreeNodeUserInputBuilder.create('StepUp'), - TreeNodeUserInputBuilder.create('RandomSignedInt32'), - TreeNodeUserInputBuilder.create('RandomUnsignedInt32'), - ), - ), - ), - ), - ); - - return builder.build(); -}; - /** * The start adapter test expects an adapter input with the same schema * description for all tests. Only the opc ua related options might differ. @@ -117,7 +93,7 @@ const editAdapterTest = (adapterInput: AdapterInput) => { }; const getAdapterBuilderWithTextNodes = (pullMode: boolean) => { - const builder = getBaseAdapterConfigBuilder(pullMode); + const builder = OpcUaUtils.getBaseAdapterConfigBuilder(pullMode); builder.addTreeNode( TreeNodeUserInputBuilder.create( 'ns=3;s=AlternatingBoolean', @@ -139,29 +115,3 @@ const getAdapterBuilderWithTextNodes = (pullMode: boolean) => { return builder.build(); }; - -const getBaseAdapterConfigBuilder = (pullMode: boolean): AdapterBuilder => { - const host: string = ParameterUtils.get('localhost', 'opcua'); - - const builder = AdapterBuilder.create('OPC_UA').setName('OPC UA Test'); - - if (pullMode) { - builder.addInput('radio', 'adapter_type-pull_mode', ''); - builder.addInput('input', 'undefined-PULLING_INTERVAL-0', '1000'); - } else { - builder.addInput('radio', 'adapter_type-subscription_mode', ''); - } - - builder - .addInput('radio', 'access_mode-none', '') - .addInput('radio', 'opc_host_or_url-url', '') - .addInput( - 'input', - 'undefined-OPC_SERVER_URL-0', - 'opc.tcp://' + host + ':50000', - ); - - builder.setAutoAddTimestampPropery(); - - return builder; -}; diff --git a/ui/cypress/tests/connect/opcua/staticPropertyTreeNodesTest.ts b/ui/cypress/tests/connect/opcua/staticPropertyTreeNodesTest.ts new file mode 100644 index 0000000000..13c6b39150 --- /dev/null +++ b/ui/cypress/tests/connect/opcua/staticPropertyTreeNodesTest.ts @@ -0,0 +1,85 @@ +/* + * 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 { ConnectEventSchemaUtils } from '../../../support/utils/connect/ConnectEventSchemaUtils'; +import { OpcUaUtils } from '../../../support/utils/connect/OpcUaUtils'; + +describe('Test Tree Node Configuration', () => { + beforeEach('Setup Test', () => { + cy.initStreamPipesTest(); + }); + + /** + * This test creates an OPC UA adapter and ensures that no node information is sent to the backend, + * to reduce the size of the stored adapter. + */ + it('Tree nodes should not be submitted to the backend', () => { + const adapterInput = OpcUaUtils.getAdapterBuilderWithTreeNodes(true); + + OpcUaUtils.setUpInitialConfiguration(adapterInput); + + cy.intercept( + 'POST', + 'streampipes-backend/api/v2/connect/master/guess/schema', + ).as('guessSchema'); + + ConnectUtils.finishAdapterSettings(); + + cy.wait('@guessSchema').then(interception => { + validateNodesAreEmptyInAdapterDescriptionBody(interception); + }); + + cy.intercept( + 'POST', + 'streampipes-backend/api/v2/connect/master/adapters', + ).as('startAdapter'); + + ConnectEventSchemaUtils.addTimestampProperty(); + ConnectUtils.finishEventSchemaConfiguration(); + ConnectUtils.startAdapter(adapterInput); + + cy.wait('@startAdapter').then(interception => { + validateNodesAreEmptyInAdapterDescriptionBody(interception); + }); + }); +}); + +/** + * This method intercepts the http requests, validates that it was successfully. + * Further it validates that the client does not send node information to the backend. + */ +const validateNodesAreEmptyInAdapterDescriptionBody = interception => { + expect(interception.response.statusCode).to.equal(200); + + const adapterDescription = interception.request.body; + console.log(adapterDescription); + + const runtimeResolvableTreeInput = adapterDescription.config.find( + (configItem: any) => + configItem['@class'] === + 'org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputStaticProperty', + ); + + expect(runtimeResolvableTreeInput).to.exist; + + expect(runtimeResolvableTreeInput.nodes).to.be.an('array').that.is.empty; + + expect(runtimeResolvableTreeInput.latestFetchedNodes).to.be.an('array').that + .is.empty; +}; diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.ts index 1a1a60ea85..8d0ff9d3b2 100644 --- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.ts +++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.ts @@ -41,6 +41,12 @@ export class StaticRuntimeResolvableTreeInputComponent editorMode: 'tree' | 'text' = 'tree'; + // The following two arrays store the fetched nodes from the backend to + // present them to the user in the UI. For performance reasons, the nodes + // should not be stored in the static property object + latestFetchedNodes = []; + nodes = []; + @ViewChild('staticTreeInputBrowseNodesComponent') private staticTreeInputBrowseNodesComponent: StaticTreeInputBrowseNodesComponent; @@ -87,16 +93,13 @@ export class StaticRuntimeResolvableTreeInputComponent staticProperty.latestFetchedNodes && staticProperty.latestFetchedNodes.length > 0 ) { - this.staticProperty.latestFetchedNodes = - staticProperty.latestFetchedNodes; + this.latestFetchedNodes = staticProperty.latestFetchedNodes; if (node) { node.children = staticProperty.latestFetchedNodes; } } else { - this.staticProperty.nodes = staticProperty.nodes; - this.staticTreeInputBrowseNodesComponent?.updateNodes( - this.staticProperty.nodes, - ); + this.nodes = staticProperty.nodes; + this.staticTreeInputBrowseNodesComponent?.updateNodes(this.nodes); } this.staticTreeInputBrowseNodesComponent?.refreshTree(); @@ -175,6 +178,7 @@ export class StaticRuntimeResolvableTreeInputComponent private resetStaticPropertyState(): void { this.staticProperty.latestFetchedNodes = []; + this.latestFetchedNodes = []; this.staticProperty.nextBaseNodeToResolve = undefined; } } diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-browse-nodes/static-tree-input-browse-nodes.component.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-browse-nodes/static-tree-input-browse-nodes.component.ts index 478c719509..de04432f80 100644 --- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-browse-nodes/static-tree-input-browse-nodes.component.ts +++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-browse-nodes/static-tree-input-browse-nodes.component.ts @@ -78,7 +78,6 @@ export class StaticTreeInputBrowseNodesComponent implements OnInit { } updateNodes(nodes: TreeInputNode[]) { - console.log(nodes); this.dataSource.data = nodes; }