This is an automated email from the ASF dual-hosted git repository.

zehnder 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 e48947e15b refacotr(#3116): refactor runtime resolvable tree input 
into smaller subcomponents (#3130)
e48947e15b is described below

commit e48947e15b4289807e0306c7300f997f38c74605
Author: Philipp Zehnder <[email protected]>
AuthorDate: Mon Aug 12 18:16:13 2024 +0200

    refacotr(#3116): refactor runtime resolvable tree input into smaller 
subcomponents (#3130)
    
    * refactor(#3116): Create component for button menu
    
    * refactor(#3116): Move static property utils
    
    * refactor(#3116): Add test to validate tree view of opc ua and buttons
    
    * refactor(#3116): Extract button menu in static-tree-input
    
    * refactor(#3116): Extract component selected-nodes
    
    * refactor(#3116): Fix CSS for component selected-nodes
    
    * refactor(#3116): Fix CSS for component selected-nodes
    
    * refactor(#3116): Harmonized naming of component
    
    * refactor(#3116): Extract static-runtime-resolvable-tree-input component
    
    * refactor(#3116): Add test to show node details
    
    * refactor(#3116): Add component for node details
    
    * refactor(#3116): Add missing headers
    
    * refactor(#3116): Add missing headers
---
 ui/cypress/support/general/dataCy.ts               |  21 +-
 ui/cypress/support/utils/PipelineUtils.ts          |   2 +-
 ui/cypress/support/utils/connect/ConnectUtils.ts   |   2 +-
 .../utils/{ => userInput}/StaticPropertyUtils.ts   |  17 +-
 .../utils/userInput/TreeStaticPropertyUtils.ts     |  99 ++++++++
 .../tests/connect/opcAdapterConfiguration.spec.ts  |  99 ++++++++
 .../connectConfigurationTemplate.spec.ts           |   2 +-
 ui/src/app/core-ui/core-ui.module.ts               |  10 +-
 .../static-property.component.html                 |   4 +-
 ...ic-runtime-resolvable-tree-input.component.html |  59 +++++
 ...c-runtime-resolvable-tree-input.component.scss} |  28 +--
 ...tic-runtime-resolvable-tree-input.component.ts} | 119 +++-------
 .../static-tree-input-browse-nodes.component.html  | 142 ++++++++++++
 .../static-tree-input-browse-nodes.component.scss} |  19 +-
 .../static-tree-input-browse-nodes.component.ts    | 160 +++++++++++++
 .../static-tree-input-button-menu.component.html   |  49 ++++
 .../static-tree-input-button-menu.component.ts}    |  33 ++-
 .../static-tree-input-node-details.component.html  |  29 +++
 .../static-tree-input-node-details.component.ts}   |  24 +-
 ...static-tree-input-selected-nodes.component.html |  40 ++++
 .../static-tree-input-selected-nodes.component.ts} |  29 +--
 .../static-tree-input-service.service.ts}          |  29 +--
 .../static-tree-input.component.html               | 256 ---------------------
 23 files changed, 813 insertions(+), 459 deletions(-)

diff --git a/ui/cypress/support/general/dataCy.ts 
b/ui/cypress/support/general/dataCy.ts
index ff1d54beae..cb81205295 100644
--- a/ui/cypress/support/general/dataCy.ts
+++ b/ui/cypress/support/general/dataCy.ts
@@ -24,10 +24,19 @@ declare global {
     }
 }
 
-export const dataCy = (value: string, config?: any) => {
-    if (config) {
-        return cy.get(`[data-cy=${value}]`, config);
-    } else {
-        return cy.get(`[data-cy=${value}]`);
-    }
+/**
+ * Selects elements based on the `data-cy` attribute.
+ *
+ * @param {string} value - The value of the `data-cy` attribute to match.
+ * @param {object} [config={}] - Optional configuration object for the Cypress 
`get` command.
+ * @param {boolean} [startsWith=false] - If true, selects elements whose 
`data-cy` attribute starts with the given value.
+ * @returns {Cypress.Chainable<JQuery<HTMLElement>>} - A chainable Cypress 
object containing the matched elements.
+ */
+export const dataCy = (
+    value: string,
+    config: any = {},
+    startsWith: boolean = false,
+) => {
+    const selector = startsWith ? `[data-cy^=${value}]` : `[data-cy=${value}]`;
+    return cy.get(selector, config);
 };
diff --git a/ui/cypress/support/utils/PipelineUtils.ts 
b/ui/cypress/support/utils/PipelineUtils.ts
index 846dd478ca..b901e225de 100644
--- a/ui/cypress/support/utils/PipelineUtils.ts
+++ b/ui/cypress/support/utils/PipelineUtils.ts
@@ -17,7 +17,7 @@
  */
 
 import { PipelineInput } from '../model/PipelineInput';
-import { StaticPropertyUtils } from './StaticPropertyUtils';
+import { StaticPropertyUtils } from './userInput/StaticPropertyUtils';
 import { OutputStrategyUtils } from './OutputStrategyUtils';
 import { PipelineElementInput } from '../model/PipelineElementInput';
 
diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts 
b/ui/cypress/support/utils/connect/ConnectUtils.ts
index ca19602dfb..67556897e9 100644
--- a/ui/cypress/support/utils/connect/ConnectUtils.ts
+++ b/ui/cypress/support/utils/connect/ConnectUtils.ts
@@ -16,7 +16,7 @@
  *
  */
 
-import { StaticPropertyUtils } from '../StaticPropertyUtils';
+import { StaticPropertyUtils } from '../userInput/StaticPropertyUtils';
 import { AdapterInput } from '../../model/AdapterInput';
 import { ConnectEventSchemaUtils } from './ConnectEventSchemaUtils';
 import { DataLakeUtils } from '../datalake/DataLakeUtils';
diff --git a/ui/cypress/support/utils/StaticPropertyUtils.ts 
b/ui/cypress/support/utils/userInput/StaticPropertyUtils.ts
similarity index 84%
rename from ui/cypress/support/utils/StaticPropertyUtils.ts
rename to ui/cypress/support/utils/userInput/StaticPropertyUtils.ts
index 25b2d200e5..f588f5db49 100644
--- a/ui/cypress/support/utils/StaticPropertyUtils.ts
+++ b/ui/cypress/support/utils/userInput/StaticPropertyUtils.ts
@@ -16,8 +16,8 @@
  *
  */
 
-import { UserInput } from '../model/UserInput';
-import { TreeNode } from '../model/TreeNode';
+import { UserInput } from '../../model/UserInput';
+import { TreeStaticPropertyUtils } from './TreeStaticPropertyUtils';
 
 export class StaticPropertyUtils {
     public static input(configs: UserInput[]) {
@@ -45,7 +45,7 @@ export class StaticPropertyUtils {
             } else if (config.type === 'slider') {
                 cy.dataCy(config.selector).type(config.value);
             } else if (config.type === 'tree') {
-                this.handleTreeNode(config.treeNode);
+                TreeStaticPropertyUtils.selectTreeNode(config.treeNode);
             } else {
                 cy.dataCy(config.selector).type(config.value);
             }
@@ -70,15 +70,4 @@ export class StaticPropertyUtils {
             cy.get(cssClassName).click();
         });
     }
-
-    private static handleTreeNode(treeNode: TreeNode) {
-        if (treeNode.children && treeNode.children.length > 0) {
-            cy.dataCy('expand-' + treeNode.name).click();
-            treeNode.children.forEach(child => {
-                this.handleTreeNode(child);
-            });
-        } else {
-            cy.dataCy('select-' + treeNode.name).click();
-        }
-    }
 }
diff --git a/ui/cypress/support/utils/userInput/TreeStaticPropertyUtils.ts 
b/ui/cypress/support/utils/userInput/TreeStaticPropertyUtils.ts
new file mode 100644
index 0000000000..118a562542
--- /dev/null
+++ b/ui/cypress/support/utils/userInput/TreeStaticPropertyUtils.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 { TreeNode } from '../../model/TreeNode';
+
+export class TreeStaticPropertyUtils {
+    /**
+     * Selects the @param treeNode in the tree view. If the tree node has
+     * children, it will expand the tree node and recursivly navigate through
+     * the selected node.
+     */
+    public static selectTreeNode(treeNode: TreeNode) {
+        if (treeNode.children && treeNode.children.length > 0) {
+            cy.dataCy('expand-' + treeNode.name).click();
+            treeNode.children.forEach(child => {
+                this.selectTreeNode(child);
+            });
+        } else {
+            cy.dataCy('select-' + treeNode.name).click();
+        }
+    }
+
+    /**
+     * Removes the selected node with the identifier @param nodeIdentifier.
+     * dataCy could not be used because often special characters are used in
+     * the nodeIdentifier.
+     */
+    public static removeSelectedNode(nodeIdentifier: string) {
+        cy.get('[data-cy="remove-' + nodeIdentifier + '"]').click();
+    }
+
+    /**
+     * Validates that the amount of nodes shown in the selected tab are equal
+     * to @param expectedAmount.
+     */
+    public static validateAmountOfSelectedNodes(expectedAmount: number) {
+        cy.dataCy('selected-node-', {}, true).should(
+            'have.length',
+            expectedAmount,
+        );
+    }
+
+    /**
+     * Validates the number of node details metadata rows displayed.
+     */
+    public static validateAmountOfShownNodeDetailsMetaDataRows(
+        expectedAmount: number,
+    ) {
+        cy.dataCy('node-details-metadata-row-', {}, true).should(
+            'have.length',
+            expectedAmount,
+        );
+    }
+
+    /**
+     * Select node to be shown in node details
+     */
+    public static showNodeDetails(nodeName: string) {
+        cy.dataCy(`show-node-details-${nodeName}`).click();
+    }
+
+    /**
+     * Unselect the node to be removed from node details view
+     */
+    public static hideNodeDetails(nodeName: string) {
+        cy.dataCy('hide-node-details-' + nodeName).click();
+    }
+
+    /**
+     * Validates that the @param nodeName is marked as selected in the
+     * tree view.
+     */
+    public static checkThatNodeIsSelectedInTree(nodeName: string) {
+        cy.dataCy('tree-node-' + nodeName).within(() => {
+            cy.get('i.material-icons')
+                .contains('remove_circle')
+                .should('exist');
+        });
+    }
+
+    public static clickClearAndReloadButton() {
+        cy.dataCy('clear-tree-node-selection').click();
+    }
+}
diff --git a/ui/cypress/tests/connect/opcAdapterConfiguration.spec.ts 
b/ui/cypress/tests/connect/opcAdapterConfiguration.spec.ts
new file mode 100644
index 0000000000..57b10bd41a
--- /dev/null
+++ b/ui/cypress/tests/connect/opcAdapterConfiguration.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 { ParameterUtils } from '../../support/utils/ParameterUtils';
+import { AdapterBuilder } from '../../support/builder/AdapterBuilder';
+import { TreeNodeBuilder } from '../../support/builder/TreeNodeBuilder';
+import { StaticPropertyUtils } from 
'../../support/utils/userInput/StaticPropertyUtils';
+import { TreeStaticPropertyUtils } from 
'../../support/utils/userInput/TreeStaticPropertyUtils';
+
+describe('Test OPC-UA Adapter Pull Mode', () => {
+    beforeEach('Setup Test', () => {
+        cy.initStreamPipesTest();
+    });
+
+    it('Test OPC-UA Adapter Pull Mode', () => {
+        const adapterConfiguration = getAdapterBuilder();
+
+        // Set up initial configuration
+        ConnectUtils.goToConnect();
+        ConnectUtils.goToNewAdapterPage();
+        ConnectUtils.selectAdapter(adapterConfiguration.adapterType);
+        StaticPropertyUtils.input(adapterConfiguration.adapterConfiguration);
+
+        TreeStaticPropertyUtils.validateAmountOfSelectedNodes(2);
+
+        TreeStaticPropertyUtils.checkThatNodeIsSelectedInTree(
+            'AlternatingBoolean',
+        );
+
+        // Test if node details view works
+        
TreeStaticPropertyUtils.validateAmountOfShownNodeDetailsMetaDataRows(0);
+        TreeStaticPropertyUtils.showNodeDetails('StepUp');
+        TreeStaticPropertyUtils.validateAmountOfShownNodeDetailsMetaDataRows(
+            10,
+        );
+        TreeStaticPropertyUtils.hideNodeDetails('StepUp');
+        
TreeStaticPropertyUtils.validateAmountOfShownNodeDetailsMetaDataRows(0);
+
+        // Test if delete node works
+        TreeStaticPropertyUtils.removeSelectedNode(
+            'ns=3\\;s=AlternatingBoolean',
+        );
+        TreeStaticPropertyUtils.validateAmountOfSelectedNodes(1);
+
+        // Test clear selection and reload button
+        TreeStaticPropertyUtils.clickClearAndReloadButton();
+        TreeStaticPropertyUtils.validateAmountOfSelectedNodes(0);
+    });
+});
+
+const getAdapterBuilder = () => {
+    const host: string = ParameterUtils.get('localhost', 'opcua');
+
+    const builder = AdapterBuilder.create('OPC_UA')
+        .setName('OPC UA Configuration Test')
+        .addInput('radio', 'adapter_type-pull_mode', '')
+        .addInput('input', 'undefined-PULLING_INTERVAL-0', '1000')
+        .addInput('radio', 'access_mode-none', '')
+        .addInput('radio', 'opc_host_or_url-url', '')
+        .addInput(
+            'input',
+            'undefined-OPC_SERVER_URL-0',
+            'opc.tcp://' + host + ':50000',
+        )
+        .addTreeNode(
+            TreeNodeBuilder.create(
+                'Objects',
+                TreeNodeBuilder.create(
+                    'OpcPlc',
+                    TreeNodeBuilder.create(
+                        'Telemetry',
+                        TreeNodeBuilder.create('Basic').addChildren(
+                            TreeNodeBuilder.create('AlternatingBoolean'),
+                            TreeNodeBuilder.create('StepUp'),
+                        ),
+                    ),
+                ),
+            ),
+        )
+        .setAutoAddTimestampPropery();
+
+    return builder.build();
+};
diff --git 
a/ui/cypress/tests/pipelineElementConfigurationTemplate/connectConfigurationTemplate.spec.ts
 
b/ui/cypress/tests/pipelineElementConfigurationTemplate/connectConfigurationTemplate.spec.ts
index 76eb8d8e56..17b55649a3 100644
--- 
a/ui/cypress/tests/pipelineElementConfigurationTemplate/connectConfigurationTemplate.spec.ts
+++ 
b/ui/cypress/tests/pipelineElementConfigurationTemplate/connectConfigurationTemplate.spec.ts
@@ -17,7 +17,7 @@
  */
 
 import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
-import { StaticPropertyUtils } from '../../support/utils/StaticPropertyUtils';
+import { StaticPropertyUtils } from 
'../../support/utils/userInput/StaticPropertyUtils';
 import { AdapterBuilder } from '../../support/builder/AdapterBuilder';
 import { PipelineElementTemplateUtils } from 
'../../support/utils/PipelineElementTemplateUtils';
 
diff --git a/ui/src/app/core-ui/core-ui.module.ts 
b/ui/src/app/core-ui/core-ui.module.ts
index 85c08d4157..8b589c4b18 100644
--- a/ui/src/app/core-ui/core-ui.module.ts
+++ b/ui/src/app/core-ui/core-ui.module.ts
@@ -58,7 +58,7 @@ import { PipelineStartedStatusComponent } from 
'./pipeline/pipeline-started-stat
 import { ObjectPermissionDialogComponent } from 
'./object-permission-dialog/object-permission-dialog.component';
 import { StaticSlideToggleComponent } from 
'./static-properties/static-slide-toggle/static-slide-toggle.component';
 import { MatSlideToggleModule } from '@angular/material/slide-toggle';
-import { StaticRuntimeResolvableTreeInputComponent } from 
'./static-properties/static-runtime-resolvable-tree-input/static-tree-input.component';
+import { StaticRuntimeResolvableTreeInputComponent } from 
'./static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component';
 import { MatTreeModule } from '@angular/material/tree';
 import { PlatformServicesModule } from '@streampipes/platform-services';
 import { SharedUiModule } from '@streampipes/shared-ui';
@@ -109,6 +109,10 @@ import { LoadingIndicatorComponent } from 
'./loading-indicator/loading-indicator
 import { StatusIndicatorComponent } from 
'./status-indicator/status-indicator.component';
 import { MultiStepStatusIndicatorComponent } from 
'./multi-step-status-indicator/multi-step-status-indicator.component';
 import { PipelineOperationStatusComponent } from 
'./pipeline/pipeline-operation-status/pipeline-operation-status.component';
+import { StaticTreeInputButtonMenuComponent } from 
'./static-properties/static-runtime-resolvable-tree-input/static-tree-input-button-menu/static-tree-input-button-menu.component';
+import { StaticTreeInputSelectedNodesComponent } from 
'./static-properties/static-runtime-resolvable-tree-input/static-tree-input-selected-nodes/static-tree-input-selected-nodes.component';
+import { StaticTreeInputBrowseNodesComponent } from 
'./static-properties/static-runtime-resolvable-tree-input/static-tree-input-browse-nodes/static-tree-input-browse-nodes.component';
+import { StaticTreeInputNodeDetailsComponent } from 
'./static-properties/static-runtime-resolvable-tree-input/static-tree-input-node-details/static-tree-input-node-details.component';
 
 @NgModule({
     imports: [
@@ -183,9 +187,13 @@ import { PipelineOperationStatusComponent } from 
'./pipeline/pipeline-operation-
         StaticCodeInputComponent,
         StaticOneOfInputComponent,
         StaticRuntimeResolvableAnyInputComponent,
+        StaticTreeInputButtonMenuComponent,
+        StaticTreeInputSelectedNodesComponent,
         StaticRuntimeResolvableGroupComponent,
         StaticRuntimeResolvableOneOfInputComponent,
         StaticRuntimeResolvableTreeInputComponent,
+        StaticTreeInputBrowseNodesComponent,
+        StaticTreeInputNodeDetailsComponent,
         StaticSlideToggleComponent,
         ErrorHintComponent,
         AddToCollectionComponent,
diff --git 
a/ui/src/app/core-ui/static-properties/static-property.component.html 
b/ui/src/app/core-ui/static-properties/static-property.component.html
index 4ecbd7c49b..e416735c84 100644
--- a/ui/src/app/core-ui/static-properties/static-property.component.html
+++ b/ui/src/app/core-ui/static-properties/static-property.component.html
@@ -231,7 +231,7 @@
                 (updateEmitter)="emitUpdate($event)"
             >
             </sp-static-slide-toggle>
-            <sp-runtime-resolvable-tree-input
+            <sp-static-runtime-resolvable-tree-input
                 *ngIf="isTreeInputStaticProperty(staticProperty)"
                 [deploymentConfiguration]="deploymentConfiguration"
                 [adapterId]="adapterId"
@@ -246,7 +246,7 @@
                 class="test fullWidth"
                 (updateEmitter)="emitUpdate($event)"
             >
-            </sp-runtime-resolvable-tree-input>
+            </sp-static-runtime-resolvable-tree-input>
         </div>
     </div>
     <mat-divider></mat-divider>
diff --git 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.html
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.html
new file mode 100644
index 0000000000..bcb41d33a8
--- /dev/null
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.html
@@ -0,0 +1,59 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<div [formGroup]="parentForm" id="formWrapper" fxFlex="100" fxLayout="column">
+    <sp-static-tree-input-button-menu
+        [showOptions]="showOptions"
+        [loading]="loading"
+        (resetOptionsAndReload)="resetOptionsAndReload()"
+        (reload)="reload()"
+    >
+    </sp-static-tree-input-button-menu>
+
+    <div fxLayout="column" *ngIf="error" class="mt-10">
+        <sp-exception-message [message]="errorMessage"></sp-exception-message>
+    </div>
+
+    <div fxLayout="row" fxLayoutGap="15px">
+        <div fxFlex="30" class="tree-input-section" fxLayout="column">
+            <sp-static-tree-input-browse-nodes
+                #staticTreeInputBrowseNodesComponent
+                [staticProperty]="staticProperty"
+                (showNodeDetailsEmitter)="showNodeDetails($event)"
+                
(loadOptionsFromRestApiEmitter)="loadOptionsFromRestApi($event)"
+                (performValidationEmitter)="performValidation()"
+            >
+            </sp-static-tree-input-browse-nodes>
+        </div>
+        <div fxFlex="40" class="tree-input-section" fxLayout="column">
+            <sp-static-tree-input-node-details
+                [nodeMetadata]="nodeDetails?.nodeMetadata"
+            >
+            </sp-static-tree-input-node-details>
+        </div>
+
+        <div class="tree-input-section" fxLayout="column" fxFlex="30">
+            <sp-static-tree-input-selected-nodes
+                [selectedNodesInternalNames]="
+                    staticProperty.selectedNodesInternalNames
+                "
+                (removeSelectedNode)="removeSelectedNode($event)"
+            >
+            </sp-static-tree-input-selected-nodes>
+        </div>
+    </div>
+</div>
diff --git 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.scss
similarity index 85%
rename from 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
rename to 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.scss
index 4cb8975c33..ac5d7522bf 100644
--- 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.scss
@@ -54,6 +54,11 @@
     min-height: 30px;
 }
 
+.tree-input-section {
+    border: 1px solid var(--color-bg-3);
+    margin-top: 10px;
+}
+
 .tree-input-header {
     background: var(--color-bg-2);
     height: 40px;
@@ -61,11 +66,6 @@
     border-bottom: 1px solid var(--color-bg-3);
 }
 
-.tree-input-section {
-    border: 1px solid var(--color-bg-3);
-    margin-top: 10px;
-}
-
 .color-primary {
     color: var(--color-primary);
 }
@@ -84,21 +84,3 @@
 .node-name {
     font-weight: bold;
 }
-
-.selected-node {
-    padding: 10px;
-    border-bottom: 1px solid var(--color-bg-3);
-}
-
-.selected-node:nth-child(odd) {
-    background: var(--color-bg-1);
-}
-
-.selected-node:nth-child(even) {
-    background: var(--color-bg-2);
-}
-
-.selected-node {
-    padding: 5px;
-    border: 1px solid var(--color-bg-3);
-}
diff --git 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.ts
similarity index 55%
rename from 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
rename to 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.ts
index 1f7dbf2010..32f77cfb1a 100644
--- 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-runtime-resolvable-tree-input.component.ts
@@ -24,42 +24,32 @@ import {
     TreeInputNode,
 } from '@streampipes/platform-services';
 import { RuntimeResolvableService } from 
'../static-runtime-resolvable-input/runtime-resolvable.service';
-import { NestedTreeControl } from '@angular/cdk/tree';
-import { MatTree, MatTreeNestedDataSource } from '@angular/material/tree';
 import { UntypedFormControl } from '@angular/forms';
-import Tree from 'echarts/types/src/data/Tree';
+import { StaticTreeInputServiceService } from 
'./static-tree-input-service.service';
+import { StaticTreeInputBrowseNodesComponent } from 
'./static-tree-input-browse-nodes/static-tree-input-browse-nodes.component';
 
 @Component({
-    selector: 'sp-runtime-resolvable-tree-input',
-    templateUrl: './static-tree-input.component.html',
-    styleUrls: ['./static-tree-input.component.scss'],
+    selector: 'sp-static-runtime-resolvable-tree-input',
+    templateUrl: './static-runtime-resolvable-tree-input.component.html',
+    styleUrls: ['./static-runtime-resolvable-tree-input.component.scss'],
 })
 export class StaticRuntimeResolvableTreeInputComponent
     extends 
BaseRuntimeResolvableInput<RuntimeResolvableTreeInputStaticProperty>
     implements OnInit
 {
-    treeControl = new NestedTreeControl<TreeInputNode>(node => node.children);
-    dataSource = new MatTreeNestedDataSource<TreeInputNode>();
+    nodeDetails: TreeInputNode;
 
-    selectedNodeMetadata: Record<string, string>;
-    selectedNodeId: string;
+    @ViewChild('staticTreeInputBrowseNodesComponent')
+    staticTreeInputBrowseNodesComponent: StaticTreeInputBrowseNodesComponent;
 
-    largeView = false;
-
-    @ViewChild('tree') tree: MatTree<TreeInputNode>;
-
-    constructor(runtimeResolvableService: RuntimeResolvableService) {
+    constructor(
+        runtimeResolvableService: RuntimeResolvableService,
+        private staticTreeInputServiceService: StaticTreeInputServiceService,
+    ) {
         super(runtimeResolvableService);
     }
 
-    hasChild = (_: number, node: TreeInputNode) => !node.dataNode;
-
     ngOnInit(): void {
-        this.treeControl = new NestedTreeControl<TreeInputNode>(
-            node => node.children,
-        );
-        this.dataSource = new MatTreeNestedDataSource<TreeInputNode>();
-
         if (
             this.staticProperty.nodes.length === 0 &&
             (!this.staticProperty.dependsOn ||
@@ -67,7 +57,9 @@ export class StaticRuntimeResolvableTreeInputComponent
         ) {
             this.loadOptionsFromRestApi();
         } else if (this.staticProperty.nodes.length > 0) {
-            this.dataSource.data = this.staticProperty.nodes;
+            this.staticTreeInputBrowseNodesComponent.updateNodes(
+                this.staticProperty.nodes,
+            );
             this.showOptions = true;
         }
         super.onInit();
@@ -98,12 +90,12 @@ export class StaticRuntimeResolvableTreeInputComponent
             }
         } else {
             this.staticProperty.nodes = staticProperty.nodes;
-            this.dataSource.data = this.staticProperty.nodes;
+            this.staticTreeInputBrowseNodesComponent.updateNodes(
+                this.staticProperty.nodes,
+            );
         }
-        const data = this.dataSource.data.slice();
-        this.dataSource.data = null;
-        this.dataSource = new MatTreeNestedDataSource<TreeInputNode>();
-        this.dataSource.data = [...data];
+        this.staticTreeInputBrowseNodesComponent.refreshTree();
+
         this.performValidation();
     }
 
@@ -131,15 +123,19 @@ export class StaticRuntimeResolvableTreeInputComponent
 
     afterErrorReceived() {
         this.staticProperty.nodes = [];
-        this.dataSource.data = [];
+        this.staticTreeInputBrowseNodesComponent.updateNodes([]);
         this.performValidation();
     }
 
+    showNodeDetails(node: TreeInputNode) {
+        this.nodeDetails = node;
+    }
+
     resetOptionsAndReload(): void {
         this.staticProperty.nextBaseNodeToResolve = undefined;
         this.staticProperty.selectedNodesInternalNames = [];
         this.staticProperty.latestFetchedNodes = [];
-        this.dataSource.data = [];
+        this.staticTreeInputBrowseNodesComponent.updateNodes([]);
         this.loadOptionsFromRestApi();
     }
 
@@ -147,66 +143,11 @@ export class StaticRuntimeResolvableTreeInputComponent
         this.loadOptionsFromRestApi();
     }
 
-    loadChildren(node: TreeInputNode, expanded: boolean): void {
-        this.staticProperty.nextBaseNodeToResolve = node.internalNodeName;
-        if (expanded) {
-            this.loadOptionsFromRestApi(node);
-        }
-    }
-
-    addNode(node: TreeInputNode) {
-        node.selected = true;
-        this.staticProperty.selectedNodesInternalNames.push(
-            node.internalNodeName,
-        );
-        this.performValidation();
-    }
-
-    addAllDirectChildren(node: TreeInputNode) {
-        node.children.forEach(child => {
-            if (child.dataNode && !this.existsSelectedNode(child)) {
-                this.staticProperty.selectedNodesInternalNames.push(
-                    child.internalNodeName,
-                );
-            }
-        });
-        this.performValidation();
-    }
-
-    existsSelectedNode(node: TreeInputNode) {
-        return (
-            this.staticProperty.selectedNodesInternalNames.find(
-                nodeName => nodeName === node.internalNodeName,
-            ) !== undefined
-        );
-    }
-
-    removeNode(node: TreeInputNode) {
-        node.selected = false;
-        const index = this.getSelectedNodeIndex(node.internalNodeName);
-        this.staticProperty.selectedNodesInternalNames.splice(index, 1);
-        this.performValidation();
-    }
-
     removeSelectedNode(selectedNodeInternalId: string): void {
-        const index = this.getSelectedNodeIndex(selectedNodeInternalId);
-        this.staticProperty.selectedNodesInternalNames.splice(index, 1);
-    }
-
-    isNodeSelected(node: TreeInputNode) {
-        return this.getSelectedNodeIndex(node.internalNodeName) > -1;
-    }
-
-    getSelectedNodeIndex(internalNodeName: string) {
-        return this.staticProperty.selectedNodesInternalNames.indexOf(
-            internalNodeName,
-        );
-    }
-
-    hasDataChildren(node: TreeInputNode) {
-        return (
-            node.children.length > 0 &&
-            node.children.find(c => c.dataNode) !== undefined
+        const index = this.staticTreeInputServiceService.getSelectedNodeIndex(
+            this.staticProperty,
+            selectedNodeInternalId,
         );
+        this.staticProperty.selectedNodesInternalNames.splice(index, 1);
     }
 }
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.html
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-browse-nodes/static-tree-input-browse-nodes.component.html
new file mode 100644
index 0000000000..0f3957558e
--- /dev/null
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-browse-nodes/static-tree-input-browse-nodes.component.html
@@ -0,0 +1,142 @@
+<!--
+~ 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.
+~
+-->
+
+<div class="tree-input-header" fxLayoutAlign="start center">
+    <div fxLayout="row" fxFlex="100">
+        <div fxFlex fxLayoutAlign="start center"><b>Browse</b></div>
+        <div fxLayoutAlign="end center">
+            <button
+                mat-icon-button
+                color="accent"
+                (click)="largeView = !largeView"
+            >
+                <mat-icon *ngIf="!largeView">open_in_full</mat-icon>
+                <mat-icon *ngIf="largeView">close_fullscreen </mat-icon>
+            </button>
+        </div>
+    </div>
+</div>
+
+<mat-tree
+    [dataSource]="dataSource"
+    [treeControl]="treeControl"
+    #tree
+    class="sp-tree"
+    [ngClass]="largeView ? 'tree-large-height' : 'tree-normal-height'"
+>
+    <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
+        <div
+            fxLayoutAlign="start center"
+            [attr.data-cy]="'tree-node-' + node.nodeName"
+        >
+            <i class="material-icons color-primary pr-5">wifi</i>
+            <i
+                class="material-icons pr-5 icon-button"
+                *ngIf="node.dataNode && !isNodeSelected(node)"
+                (click)="addNode(node)"
+                [attr.data-cy]="'select-' + node.nodeName"
+                matTooltip="Add node"
+                >add_circle</i
+            >
+            <i
+                class="material-icons pr-5 icon-button"
+                *ngIf="node.dataNode && isNodeSelected(node)"
+                (click)="removeNode(node)"
+                matTooltip="Remove node"
+                >remove_circle</i
+            >
+            <i
+                class="material-icons pr-5 icon-button"
+                *ngIf="selectedNodeId !== node.internalNodeName"
+                [attr.data-cy]="'show-node-details-' + node.nodeName"
+                (click)="showNodeDetails(node)"
+                matTooltip="Show details"
+                >visibility</i
+            >
+            <i
+                class="material-icons pr-5 icon-button"
+                *ngIf="selectedNodeId === node.internalNodeName"
+                [attr.data-cy]="'hide-node-details-' + node.nodeName"
+                (click)="hideNodeDetails()"
+                matTooltip="Show details"
+                >visibility_off</i
+            >
+            <span class="pr-5">{{ node.nodeName }}</span>
+        </div>
+    </mat-tree-node>
+    <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
+        <div class="mat-tree-node">
+            <button
+                mat-icon-button
+                matTreeNodeToggle
+                [attr.data-cy]="'expand-' + node.nodeName"
+                [attr.aria-label]="'Toggle ' + node.nodeName"
+                (click)="loadChildren(node, treeControl.isExpanded(node))"
+            >
+                <mat-icon class="mat-icon-rtl-mirror">
+                    {{
+                        treeControl.isExpanded(node)
+                            ? 'expand_more'
+                            : 'chevron_right'
+                    }}
+                </mat-icon>
+            </button>
+            <div fxLayoutAlign="start center">
+                <i
+                    class="material-icons color-primary pr-5"
+                    *ngIf="!treeControl.isExpanded(node)"
+                    >folder</i
+                >
+                <i
+                    class="material-icons color-primary pr-5"
+                    *ngIf="treeControl.isExpanded(node)"
+                    >folder_open</i
+                >
+                <i
+                    class="material-icons pr-5 icon-button"
+                    *ngIf="
+                        treeControl.isExpanded(node) && hasDataChildren(node)
+                    "
+                    matTooltip="Add all direct children"
+                    (click)="addAllDirectChildren(node)"
+                    >add_circle</i
+                >
+                <i
+                    class="material-icons pr-5 icon-button"
+                    *ngIf="selectedNodeId !== node.internalNodeName"
+                    [attr.data-cy]="'show-node-details-' + node.nodeName"
+                    (click)="showNodeDetails(node)"
+                    matTooltip="Show details"
+                    >visibility</i
+                >
+                <i
+                    class="material-icons pr-5 icon-button"
+                    *ngIf="selectedNodeId === node.internalNodeName"
+                    [attr.data-cy]="'hide-node-details-' + node.nodeName"
+                    (click)="hideNodeDetails()"
+                    matTooltip="Show details"
+                    >visibility_off</i
+                >
+                <span class="node-name pr-5">&nbsp;{{ node.nodeName }}</span>
+            </div>
+        </div>
+        <div *ngIf="treeControl.isExpanded(node)" role="group">
+            <ng-container matTreeNodeOutlet></ng-container>
+        </div>
+    </mat-nested-tree-node>
+</mat-tree>
diff --git a/ui/cypress/support/general/dataCy.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.scss
similarity index 71%
copy from ui/cypress/support/general/dataCy.ts
copy to 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-browse-nodes/static-tree-input-browse-nodes.component.scss
index ff1d54beae..949b23cb05 100644
--- a/ui/cypress/support/general/dataCy.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.scss
@@ -16,18 +16,11 @@
  *
  */
 
-declare global {
-    namespace Cypress {
-        interface Chainable {
-            dataCy: typeof dataCy;
-        }
-    }
+.tree-normal-height {
+    min-height: 300px;
+    max-height: 300px;
 }
 
-export const dataCy = (value: string, config?: any) => {
-    if (config) {
-        return cy.get(`[data-cy=${value}]`, config);
-    } else {
-        return cy.get(`[data-cy=${value}]`);
-    }
-};
+.tree-large-height {
+    min-height: 800px;
+}
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
new file mode 100644
index 0000000000..de04432f80
--- /dev/null
+++ 
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
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+import {
+    Component,
+    EventEmitter,
+    Input,
+    OnInit,
+    Output,
+    ViewChild,
+} from '@angular/core';
+import { NestedTreeControl } from '@angular/cdk/tree';
+import {
+    RuntimeResolvableTreeInputStaticProperty,
+    TreeInputNode,
+} from '@streampipes/platform-services';
+import { MatTree, MatTreeNestedDataSource } from '@angular/material/tree';
+import { StaticTreeInputServiceService } from 
'../static-tree-input-service.service';
+
+@Component({
+    selector: 'sp-static-tree-input-browse-nodes',
+    templateUrl: './static-tree-input-browse-nodes.component.html',
+    styleUrls: [
+        './static-tree-input-browse-nodes.component.scss',
+        '../static-runtime-resolvable-tree-input.component.scss',
+    ],
+})
+export class StaticTreeInputBrowseNodesComponent implements OnInit {
+    @Input()
+    staticProperty: RuntimeResolvableTreeInputStaticProperty;
+
+    @Output()
+    showNodeDetailsEmitter: EventEmitter<TreeInputNode> =
+        new EventEmitter<TreeInputNode>();
+
+    @Output()
+    performValidationEmitter: EventEmitter<void> = new EventEmitter<void>();
+
+    @Output()
+    loadOptionsFromRestApiEmitter: EventEmitter<TreeInputNode> =
+        new EventEmitter<TreeInputNode>();
+
+    @ViewChild('tree')
+    tree: MatTree<TreeInputNode>;
+
+    largeView = false;
+    treeControl = new NestedTreeControl<TreeInputNode>(node => node.children);
+    dataSource = new MatTreeNestedDataSource<TreeInputNode>();
+
+    selectedNodeId: string;
+
+    hasChild = (_: number, node: TreeInputNode) => !node.dataNode;
+
+    constructor(
+        private staticTreeInputServiceService: StaticTreeInputServiceService,
+    ) {}
+
+    ngOnInit(): void {
+        this.treeControl = new NestedTreeControl<TreeInputNode>(
+            node => node.children,
+        );
+
+        this.dataSource = new MatTreeNestedDataSource<TreeInputNode>();
+    }
+
+    updateNodes(nodes: TreeInputNode[]) {
+        this.dataSource.data = nodes;
+    }
+
+    refreshTree() {
+        const data = this.dataSource.data.slice();
+        this.dataSource.data = null;
+        this.dataSource = new MatTreeNestedDataSource<TreeInputNode>();
+        this.dataSource.data = [...data];
+    }
+
+    loadChildren(node: TreeInputNode, expanded: boolean): void {
+        this.staticProperty.nextBaseNodeToResolve = node.internalNodeName;
+        if (expanded) {
+            this.loadOptionsFromRestApiEmitter.emit(node);
+        }
+    }
+
+    addNode(node: TreeInputNode) {
+        node.selected = true;
+        this.staticProperty.selectedNodesInternalNames.push(
+            node.internalNodeName,
+        );
+        this.performValidationEmitter.emit();
+    }
+
+    addAllDirectChildren(node: TreeInputNode) {
+        node.children.forEach(child => {
+            if (child.dataNode && !this.existsSelectedNode(child)) {
+                this.staticProperty.selectedNodesInternalNames.push(
+                    child.internalNodeName,
+                );
+            }
+        });
+        this.performValidationEmitter.emit();
+    }
+
+    removeNode(node: TreeInputNode) {
+        node.selected = false;
+        const index = this.staticTreeInputServiceService.getSelectedNodeIndex(
+            this.staticProperty,
+            node.internalNodeName,
+        );
+        this.staticProperty.selectedNodesInternalNames.splice(index, 1);
+        this.performValidationEmitter.emit();
+    }
+
+    isNodeSelected(node: TreeInputNode) {
+        return (
+            this.staticTreeInputServiceService.getSelectedNodeIndex(
+                this.staticProperty,
+                node.internalNodeName,
+            ) > -1
+        );
+    }
+
+    showNodeDetails(node: TreeInputNode) {
+        this.selectedNodeId = node.internalNodeName;
+        this.showNodeDetailsEmitter.emit(node);
+    }
+
+    hideNodeDetails() {
+        this.selectedNodeId = undefined;
+        this.showNodeDetailsEmitter.emit(undefined);
+    }
+
+    hasDataChildren(node: TreeInputNode) {
+        return (
+            node.children.length > 0 &&
+            node.children.find(c => c.dataNode) !== undefined
+        );
+    }
+
+    existsSelectedNode(node: TreeInputNode) {
+        return (
+            this.staticProperty.selectedNodesInternalNames.find(
+                nodeName => nodeName === node.internalNodeName,
+            ) !== undefined
+        );
+    }
+}
diff --git 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-button-menu/static-tree-input-button-menu.component.html
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-button-menu/static-tree-input-button-menu.component.html
new file mode 100644
index 0000000000..bf008690bb
--- /dev/null
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-button-menu/static-tree-input-button-menu.component.html
@@ -0,0 +1,49 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="start center">
+    <div>
+        <button
+            mat-raised-button
+            color="accent"
+            class="small-button"
+            (click)="onResetOptionsAndReload()"
+            style="margin-right: 10px"
+            [disabled]="!showOptions"
+            data-cy="clear-tree-node-selection"
+        >
+            <span>Clear selection & reload</span>
+        </button>
+        <button
+            mat-raised-button
+            color="accent"
+            class="small-button"
+            (click)="onReload()"
+            style="margin-right: 10px"
+            [disabled]="!showOptions"
+        >
+            <span>Reload</span>
+        </button>
+    </div>
+    <div fxLayout="column" *ngIf="loading" class="mt-10">
+        <mat-spinner
+            color="accent"
+            [mode]="'indeterminate'"
+            [diameter]="20"
+        ></mat-spinner>
+    </div>
+</div>
diff --git a/ui/cypress/support/general/dataCy.ts 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-button-menu/static-tree-input-button-menu.component.ts
similarity index 58%
copy from ui/cypress/support/general/dataCy.ts
copy to 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-button-menu/static-tree-input-button-menu.component.ts
index ff1d54beae..8249baebb3 100644
--- a/ui/cypress/support/general/dataCy.ts
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-button-menu/static-tree-input-button-menu.component.ts
@@ -15,19 +15,28 @@
  * limitations under the License.
  *
  */
+import { Component, EventEmitter, Input, Output } from '@angular/core';
 
-declare global {
-    namespace Cypress {
-        interface Chainable {
-            dataCy: typeof dataCy;
-        }
+@Component({
+    selector: 'sp-static-tree-input-button-menu',
+    templateUrl: './static-tree-input-button-menu.component.html',
+})
+export class StaticTreeInputButtonMenuComponent {
+    @Input()
+    showOptions: boolean;
+    @Input()
+    loading: boolean;
+
+    @Output()
+    resetOptionsAndReload = new EventEmitter<void>();
+    @Output()
+    reload = new EventEmitter<void>();
+
+    onResetOptionsAndReload() {
+        this.resetOptionsAndReload.emit();
     }
-}
 
-export const dataCy = (value: string, config?: any) => {
-    if (config) {
-        return cy.get(`[data-cy=${value}]`, config);
-    } else {
-        return cy.get(`[data-cy=${value}]`);
+    onReload() {
+        this.reload.emit();
     }
-};
+}
diff --git 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-node-details/static-tree-input-node-details.component.html
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-node-details/static-tree-input-node-details.component.html
new file mode 100644
index 0000000000..15d9b37faf
--- /dev/null
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-node-details/static-tree-input-node-details.component.html
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<div class="tree-input-header" fxLayoutAlign="start center">
+    <b>Node Details</b>
+</div>
+<div
+    class="node-metadata"
+    *ngFor="let metadata of nodeMetadata | keyvalue"
+    fxLayout="row"
+    [attr.data-cy]="'node-details-metadata-row-' + metadata.key"
+>
+    <div fxFlex="30">{{ metadata.key }}</div>
+    <div fxFlex="70">{{ metadata.value }}</div>
+</div>
diff --git a/ui/cypress/support/general/dataCy.ts 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-node-details/static-tree-input-node-details.component.ts
similarity index 68%
copy from ui/cypress/support/general/dataCy.ts
copy to 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-node-details/static-tree-input-node-details.component.ts
index ff1d54beae..eb60640d75 100644
--- a/ui/cypress/support/general/dataCy.ts
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-node-details/static-tree-input-node-details.component.ts
@@ -16,18 +16,14 @@
  *
  */
 
-declare global {
-    namespace Cypress {
-        interface Chainable {
-            dataCy: typeof dataCy;
-        }
-    }
-}
+import { Component, Input } from '@angular/core';
 
-export const dataCy = (value: string, config?: any) => {
-    if (config) {
-        return cy.get(`[data-cy=${value}]`, config);
-    } else {
-        return cy.get(`[data-cy=${value}]`);
-    }
-};
+@Component({
+    selector: 'sp-static-tree-input-node-details',
+    templateUrl: './static-tree-input-node-details.component.html',
+    styleUrl: '../static-runtime-resolvable-tree-input.component.scss',
+})
+export class StaticTreeInputNodeDetailsComponent {
+    @Input()
+    nodeMetadata: { [index: string]: any };
+}
diff --git 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-selected-nodes/static-tree-input-selected-nodes.component.html
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-selected-nodes/static-tree-input-selected-nodes.component.html
new file mode 100644
index 0000000000..968e782252
--- /dev/null
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-selected-nodes/static-tree-input-selected-nodes.component.html
@@ -0,0 +1,40 @@
+<!--
+  ~ 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.
+  ~
+  -->
+<div class="tree-input-header" fxLayoutAlign="start center">
+    <b>Selected Nodes</b>
+</div>
+<div *ngFor="let selectedNodeName of selectedNodesInternalNames">
+    <div
+        class="selected-node"
+        fxLayout="row"
+        fxLayoutAlign="start center"
+        [attr.data-cy]="'selected-node-' + selectedNodeName"
+    >
+        <span fxFlex>{{ selectedNodeName }}</span>
+        <div fxLayoutAlign="end center">
+            <button
+                mat-icon-button
+                color="accent"
+                [attr.data-cy]="'remove-' + selectedNodeName"
+                (click)="onRemoveSelectedNode(selectedNodeName)"
+            >
+                <i class="material-icons">remove</i>
+            </button>
+        </div>
+    </div>
+</div>
diff --git a/ui/cypress/support/general/dataCy.ts 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-selected-nodes/static-tree-input-selected-nodes.component.ts
similarity index 58%
copy from ui/cypress/support/general/dataCy.ts
copy to 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-selected-nodes/static-tree-input-selected-nodes.component.ts
index ff1d54beae..d81825c36f 100644
--- a/ui/cypress/support/general/dataCy.ts
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-selected-nodes/static-tree-input-selected-nodes.component.ts
@@ -16,18 +16,21 @@
  *
  */
 
-declare global {
-    namespace Cypress {
-        interface Chainable {
-            dataCy: typeof dataCy;
-        }
-    }
-}
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+
+@Component({
+    selector: 'sp-static-tree-input-selected-nodes',
+    templateUrl: './static-tree-input-selected-nodes.component.html',
+    styleUrl: '../static-runtime-resolvable-tree-input.component.scss',
+})
+export class StaticTreeInputSelectedNodesComponent {
+    @Input()
+    selectedNodesInternalNames: string[];
 
-export const dataCy = (value: string, config?: any) => {
-    if (config) {
-        return cy.get(`[data-cy=${value}]`, config);
-    } else {
-        return cy.get(`[data-cy=${value}]`);
+    @Output()
+    removeSelectedNode = new EventEmitter<string>();
+
+    onRemoveSelectedNode(selectedNodeName: string) {
+        this.removeSelectedNode.emit(selectedNodeName);
     }
-};
+}
diff --git a/ui/cypress/support/general/dataCy.ts 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-service.service.ts
similarity index 62%
copy from ui/cypress/support/general/dataCy.ts
copy to 
ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-service.service.ts
index ff1d54beae..05533f5503 100644
--- a/ui/cypress/support/general/dataCy.ts
+++ 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input-service.service.ts
@@ -16,18 +16,21 @@
  *
  */
 
-declare global {
-    namespace Cypress {
-        interface Chainable {
-            dataCy: typeof dataCy;
-        }
-    }
-}
+import { Injectable } from '@angular/core';
+import { RuntimeResolvableTreeInputStaticProperty } from 
'@streampipes/platform-services';
 
-export const dataCy = (value: string, config?: any) => {
-    if (config) {
-        return cy.get(`[data-cy=${value}]`, config);
-    } else {
-        return cy.get(`[data-cy=${value}]`);
+@Injectable({
+    providedIn: 'root',
+})
+export class StaticTreeInputServiceService {
+    constructor() {}
+
+    getSelectedNodeIndex(
+        staticProperty: RuntimeResolvableTreeInputStaticProperty,
+        internalNodeName: string,
+    ) {
+        return staticProperty.selectedNodesInternalNames.indexOf(
+            internalNodeName,
+        );
     }
-};
+}
diff --git 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
 
b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
deleted file mode 100644
index 1ca90b6a11..0000000000
--- 
a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
+++ /dev/null
@@ -1,256 +0,0 @@
-<!--
-  ~ 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.
-  ~
-  -->
-
-<div [formGroup]="parentForm" id="formWrapper" fxFlex="100" fxLayout="column">
-    <div fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="start center">
-        <div>
-            <button
-                mat-raised-button
-                color="accent"
-                class="small-button"
-                (click)="resetOptionsAndReload()"
-                style="margin-right: 10px"
-                [disabled]="!showOptions"
-            >
-                <span>Clear selection & reload</span>
-            </button>
-            <button
-                mat-raised-button
-                color="accent"
-                class="small-button"
-                (click)="reload()"
-                style="margin-right: 10px"
-                [disabled]="!showOptions"
-            >
-                <span>Reload</span>
-            </button>
-        </div>
-        <div fxLayout="column" *ngIf="loading" class="mt-10">
-            <mat-spinner
-                color="accent"
-                [mode]="'indeterminate'"
-                [diameter]="20"
-            ></mat-spinner>
-        </div>
-    </div>
-    <div fxLayout="column" *ngIf="error" class="mt-10">
-        <sp-exception-message [message]="errorMessage"></sp-exception-message>
-    </div>
-    <div fxLayout="row" fxLayoutGap="15px">
-        <div fxFlex="30" class="tree-input-section" fxLayout="column">
-            <div class="tree-input-header" fxLayoutAlign="start center">
-                <div fxLayout="row" fxFlex="100">
-                    <div fxFlex fxLayoutAlign="start 
center"><b>Browse</b></div>
-                    <div fxLayoutAlign="end center">
-                        <button
-                            mat-icon-button
-                            color="accent"
-                            (click)="largeView = !largeView"
-                        >
-                            <mat-icon 
*ngIf="!largeView">open_in_full</mat-icon>
-                            <mat-icon *ngIf="largeView"
-                                >close_fullscreen</mat-icon
-                            >
-                        </button>
-                    </div>
-                </div>
-            </div>
-            <mat-tree
-                [dataSource]="dataSource"
-                [treeControl]="treeControl"
-                #tree
-                class="sp-tree"
-                [ngClass]="
-                    largeView ? 'tree-large-height' : 'tree-normal-height'
-                "
-            >
-                <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
-                    <div
-                        [ngClass]="
-                            selectedNodeId === node.internalNodeName
-                                ? 'selected-node'
-                                : 'node'
-                        "
-                        fxLayoutAlign="start center"
-                    >
-                        <i class="material-icons color-primary pr-5">wifi</i>
-                        <i
-                            class="material-icons pr-5 icon-button"
-                            *ngIf="node.dataNode && !isNodeSelected(node)"
-                            (click)="addNode(node)"
-                            [attr.data-cy]="'select-' + node.nodeName"
-                            matTooltip="Add node"
-                            >add_circle</i
-                        >
-                        <i
-                            class="material-icons pr-5 icon-button"
-                            *ngIf="node.dataNode && isNodeSelected(node)"
-                            (click)="removeNode(node)"
-                            matTooltip="Remove node"
-                            >remove_circle</i
-                        >
-                        <i
-                            class="material-icons pr-5 icon-button"
-                            *ngIf="selectedNodeId !== node.internalNodeName"
-                            (click)="
-                                selectedNodeMetadata = node.nodeMetadata;
-                                selectedNodeId = node.internalNodeName
-                            "
-                            matTooltip="Show details"
-                            >visibility</i
-                        >
-                        <i
-                            class="material-icons pr-5 icon-button"
-                            *ngIf="selectedNodeId === node.internalNodeName"
-                            (click)="
-                                selectedNodeMetadata = undefined;
-                                selectedNodeId = undefined
-                            "
-                            matTooltip="Show details"
-                            >visibility_off</i
-                        >
-                        <span class="pr-5">{{ node.nodeName }}</span>
-                    </div>
-                </mat-tree-node>
-                <mat-nested-tree-node
-                    *matTreeNodeDef="let node; when: hasChild"
-                >
-                    <div class="mat-tree-node">
-                        <button
-                            mat-icon-button
-                            matTreeNodeToggle
-                            [attr.data-cy]="'expand-' + node.nodeName"
-                            [attr.aria-label]="'Toggle ' + node.nodeName"
-                            (click)="
-                                loadChildren(node, 
treeControl.isExpanded(node))
-                            "
-                        >
-                            <mat-icon class="mat-icon-rtl-mirror">
-                                {{
-                                    treeControl.isExpanded(node)
-                                        ? 'expand_more'
-                                        : 'chevron_right'
-                                }}
-                            </mat-icon>
-                        </button>
-                        <div
-                            [ngClass]="
-                                selectedNodeId === node.internalNodeName
-                                    ? 'selected-node'
-                                    : 'node'
-                            "
-                            fxLayoutAlign="start center"
-                        >
-                            <i
-                                class="material-icons color-primary pr-5"
-                                *ngIf="!treeControl.isExpanded(node)"
-                                >folder</i
-                            >
-                            <i
-                                class="material-icons color-primary pr-5"
-                                *ngIf="treeControl.isExpanded(node)"
-                                >folder_open</i
-                            >
-                            <i
-                                class="material-icons pr-5 icon-button"
-                                *ngIf="
-                                    treeControl.isExpanded(node) &&
-                                    hasDataChildren(node)
-                                "
-                                matTooltip="Add all direct children"
-                                (click)="addAllDirectChildren(node)"
-                                >add_circle</i
-                            >
-                            <i
-                                class="material-icons pr-5 icon-button"
-                                *ngIf="selectedNodeId !== 
node.internalNodeName"
-                                (click)="
-                                    selectedNodeMetadata = node.nodeMetadata;
-                                    selectedNodeId = node.internalNodeName
-                                "
-                                matTooltip="Show details"
-                                >visibility</i
-                            >
-                            <i
-                                class="material-icons pr-5 icon-button"
-                                *ngIf="selectedNodeId === 
node.internalNodeName"
-                                (click)="
-                                    selectedNodeMetadata = undefined;
-                                    selectedNodeId = undefined
-                                "
-                                matTooltip="Show details"
-                                >visibility_off</i
-                            >
-                            <span class="node-name pr-5"
-                                >&nbsp;{{ node.nodeName }}</span
-                            >
-                        </div>
-                    </div>
-                    <div
-                        [class.sp-tree-invisible]="
-                            !treeControl.isExpanded(node)
-                        "
-                        role="group"
-                    >
-                        <ng-container matTreeNodeOutlet></ng-container>
-                    </div>
-                </mat-nested-tree-node>
-            </mat-tree>
-        </div>
-        <div fxFlex="40" class="tree-input-section" fxLayout="column">
-            <div class="tree-input-header" fxLayoutAlign="start center">
-                <b>Node Details</b>
-            </div>
-            <div
-                class="node-metadata"
-                *ngFor="let metadata of selectedNodeMetadata | keyvalue"
-                fxLayout="row"
-            >
-                <div fxFlex="30">{{ metadata.key }}</div>
-                <div fxFlex="70">{{ metadata.value }}</div>
-            </div>
-        </div>
-        <div fxFlex="30" class="tree-input-section" fxLayout="column">
-            <div class="tree-input-header" fxLayoutAlign="start center">
-                <b>Selected Nodes</b>
-            </div>
-            <div
-                *ngFor="
-                    let selectedNode of 
staticProperty.selectedNodesInternalNames
-                "
-            >
-                <div
-                    class="selected-node"
-                    fxLayout="row"
-                    fxLayoutAlign="start center"
-                >
-                    <span fxFlex>{{ selectedNode }}</span>
-                    <div fxLayoutAlign="end center">
-                        <button
-                            mat-icon-button
-                            color="accent"
-                            (click)="removeSelectedNode(selectedNode)"
-                        >
-                            <i class="material-icons">remove</i>
-                        </button>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>

Reply via email to