Repository: ProcessMaker/modeler
Pull request #1547 (2023-05-12 17:30:02)

FOUR-8422 Ability to filter the node types from a search bar
## Issue & Reproduction Steps

Expected behavior: 
The user should be able to filter the nodeTypes from a search bar located in the explorer bar

## Solution
- Added a new component to handle the logic and styling for the search bar
- Created a new state and getters, mutations into the `nodeTypesStore` to share the data between components
- Added another component to loop through and share the logic/styling to which nodeTypes are we showing in the screen

## How to Test
Test the steps above
* Clone modeler
* Switch to this branch
* `npm install`
* Run `npm run serve` and go to `localhost:8080`

## Related Tickets & Packages
- https://processmaker.atlassian.net/browse/FOUR-8422

## Code Review Checklist
- [ ] I have pulled this code locally and tested it on my instance, along with any associated packages.
- [ ] This code adheres to [ProcessMaker Coding Guidelines](https://github.com/ProcessMaker/processmaker/wiki/Coding-Guidelines).
- [ ] This code includes a unit test or an E2E test that tests its functionality, or is covered by an existing test.
- [ ] This solution fixes the bug reported in the original ticket.
- [ ] This solution does not alter the expected output of a component in a way that would break existing Processes.
- [ ] This solution does not implement any breaking changes that would invalidate documentation or cause existing Processes to fail.
- [ ] This solution has been tested with enterprise packages that rely on its functionality and does not introduce bugs in those packages.
- [ ] This code does not duplicate functionality that already exists in the framework or in ProcessMaker.
- [ ] This ticket conforms to the PRD associated with this part of ProcessMaker.

ci:deploy



Patch:
diff --git a/src/components/rails/explorer-rail/explorer-rail.scss b/src/components/rails/explorer-rail/explorer-rail.scss
index b56432ef..b436e0aa 100644
--- a/src/components/rails/explorer-rail/explorer-rail.scss
+++ b/src/components/rails/explorer-rail/explorer-rail.scss
@@ -47,19 +47,4 @@ $secondary-color-nav: #6c757d;
 			color: #000;
 		}
 	}
-
-	&__item {
-		display: flex;
-		margin-bottom: 1rem;
-		align-items: center;
-		img {
-			height: 1.5rem;
-			width: 1.5rem;
-		}
-		span {
-			margin-left: 0.8rem;
-			font-size: 13px;
-			line-height: 19px;
-		}
-	}
 }
diff --git a/src/components/rails/explorer-rail/explorer.vue b/src/components/rails/explorer-rail/explorer.vue
index 64c0372d..8fa2ce61 100644
--- a/src/components/rails/explorer-rail/explorer.vue
+++ b/src/components/rails/explorer-rail/explorer.vue
@@ -2,6 +2,8 @@
 import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
 import { faTimes } from '@fortawesome/free-solid-svg-icons';
 import nodeTypesStore from '@/nodeTypesStore';
+import FilterNodeTypes from '@/components/rails/explorer-rail/filterNodeTypes/filterNodeTypes.vue';
+import NodeTypesLoop from '@/components/rails/explorer-rail/nodeTypesLoop/nodeTypesLoop.vue';
 
 export default {
   name: 'ExplorerRail',
@@ -12,6 +14,8 @@ export default {
   },
   components: {
     FontAwesomeIcon,
+    FilterNodeTypes,
+    NodeTypesLoop,
   },
   data() {
     return {
@@ -34,6 +38,9 @@ export default {
     pinnedObjects() {
       return nodeTypesStore.getters.getPinnedNodeTypes;
     },
+    filteredNodes() {
+      return nodeTypesStore.getters.getFilteredNodeTypes;
+    },
   },
   created() {
     nodeTypesStore.commit('setNodeTypes', this.nodeTypes);
@@ -70,23 +77,19 @@ export default {
       </div>
     </div>
     <div class="node-types__container" v-if="tabIndex === 0">
-      <template v-if="pinnedObjects.length > 0">
-        <p>{{ $t('Pinned Objects') }}</p>
-        <template v-for="pinnedObject in pinnedObjects">
-          <div class="node-types__item" :key="pinnedObject.id">
-            <img :src="pinnedObject.icon" :alt="$t(pinnedObject.label)">
-            <span>{{ $t(pinnedObject.label) }}</span>
-          </div>
-        </template>
+      <filter-node-types />
+      <template v-if="filteredNodes.length > 0">
+        <node-types-loop :nodeTypes="filteredNodes" />
       </template>
-      <template v-if="objects.length > 0">
-        <p>{{ $t('Object Category') }}</p>
-        <template v-for="object in objects">
-          <div class="node-types__item" :key="object.id">
-            <img :src="object.icon" :alt="$t(object.label)">
-            <span>{{ $t(object.label) }}</span>
-          </div>
-        </template>
+      <template v-else>
+        <node-types-loop  v-if="pinnedObjects.length > 0" 
+          label="Pinned Objects"
+          :nodeTypes="pinnedObjects"
+        />
+        <node-types-loop v-if="objects.length > 0"
+          label="Object Category"
+          :nodeTypes="objects"
+        />
       </template>
     </div>
     <div class="pm-blocks__container">
diff --git a/src/components/rails/explorer-rail/filterNodeTypes/filterNodeTypes.vue b/src/components/rails/explorer-rail/filterNodeTypes/filterNodeTypes.vue
new file mode 100644
index 00000000..e3b478df
--- /dev/null
+++ b/src/components/rails/explorer-rail/filterNodeTypes/filterNodeTypes.vue
@@ -0,0 +1,54 @@
+<script>
+import nodeTypesStore from '@/nodeTypesStore';
+export default {
+  name: 'FilterNodeTypes',
+  data() {
+    return {
+      searchTerm: '',
+      placeholder: this.$t('Seach Objects'),
+    };
+  },
+  watch: {
+    searchTerm(value) {
+      if (value.length === 0) nodeTypesStore.commit('clearFilteredNodes');
+      if (value.length > 0) nodeTypesStore.commit('setFilteredNodeTypes', value);
+    },
+  },
+  computed: {
+    nodeTypes() {
+      const pinnedNodeTypes = nodeTypesStore.getters.getPinnedNodeTypes;
+      const nodeTypes = nodeTypesStore.getters.getNodeTypes;
+      return [...pinnedNodeTypes, ...nodeTypes];
+    },
+  },
+};
+</script>
+
+<template>
+  <div id="searchNodeTypes">
+    <label class="position-relative d-block">
+      <i class="fas fa-search position-absolute h-100 text-muted ml-2" />
+      <b-input :placeholder="placeholder" v-model="searchTerm" />
+    </label>
+  </div>
+
+</template>
+
+<style lang="scss" scoped>
+#searchNodeTypes {
+  i {
+    top: 27%;
+  }
+  input {
+    border: 1px solid #5F666D;
+    border-radius: 4px;
+    margin-bottom: 1rem;
+    font-size: 14px;
+    line-height: 21px;
+    padding-left: 30px;
+    &::placeholder {
+      color: #929FAC;
+    }
+  }
+}
+</style>
diff --git a/src/components/rails/explorer-rail/nodeTypesLoop/nodeTypesLoop.vue b/src/components/rails/explorer-rail/nodeTypesLoop/nodeTypesLoop.vue
new file mode 100644
index 00000000..c5289e65
--- /dev/null
+++ b/src/components/rails/explorer-rail/nodeTypesLoop/nodeTypesLoop.vue
@@ -0,0 +1,44 @@
+<script>
+export default {
+  name: 'NodeTypesLoop',
+  props: {
+    label: {
+      type: String,
+    },
+    nodeTypes: {
+      type: Array,
+    },
+  },
+};
+</script>
+
+<template>
+  <div>
+    <p>{{ $t(label) }}</p>
+    <template v-for="nodeType in nodeTypes">
+      <div class="node-types__item" :key="nodeType.id">
+        <img :src="nodeType.icon" :alt="$t(nodeType.label)">
+        <span>{{ $t(nodeType.label) }}</span>
+      </div>
+    </template>
+  </div>
+</template>
+
+<style lang="scss">
+.node-types {
+  &__item {
+    display: flex;
+    margin-bottom: 1rem;
+    align-items: center;
+    img {
+      height: 1.5rem;
+      width: 1.5rem;
+    }
+    span {
+      margin-left: 0.8rem;
+      font-size: 13px;
+      line-height: 19px;
+    }
+  }
+}
+</style>
diff --git a/src/nodeTypesStore.js b/src/nodeTypesStore.js
index 5ce86293..a98d026d 100644
--- a/src/nodeTypesStore.js
+++ b/src/nodeTypesStore.js
@@ -8,10 +8,12 @@ export default new Vuex.Store({
   state: {
     nodeTypes: [],
     pinnedNodeTypes: [],
+    filteredNodeTypes: [],
   },
   getters: {
     getNodeTypes: state => state.nodeTypes,
     getPinnedNodeTypes: state => state.pinnedNodeTypes,
+    getFilteredNodeTypes: state => state.filteredNodeTypes,
   },
   mutations: {
     setNodeTypes(state, nodeTypes) {
@@ -29,6 +31,17 @@ export default new Vuex.Store({
     setPinnedNodes(state, payload) {
       state.pinnedNodeTypes = payload;
     },
+    setFilteredNodeTypes(state, searchTerm) {
+      const pinnedNodeTypes = state.pinnedNodeTypes;
+      const nodeTypes = state.nodeTypes;
+      const allNodes = [...pinnedNodeTypes, ...nodeTypes];
+      state.filteredNodeTypes = allNodes.filter(node => {
+        return node.label.toLowerCase().includes(searchTerm.toLowerCase());
+      });
+    },
+    clearFilteredNodes(state) {
+      state.filteredNodeTypes.length = 0;
+    },
   },
   actions: {
     // eslint-disable-next-line no-unused-vars


Test patch:
diff --git a/tests/unit/rails/__snapshots__/explorer.spec.js.snap b/tests/unit/rails/__snapshots__/explorer.spec.js.snap
index 5045a0bc..b55cf7a0 100644
--- a/tests/unit/rails/__snapshots__/explorer.spec.js.snap
+++ b/tests/unit/rails/__snapshots__/explorer.spec.js.snap
@@ -34,6 +34,8 @@ exports[`Explorer Rail should render the component 1`] = `
   <div
     class="node-types__container"
   >
+    <filter-node-types-stub />
+     
     <!---->
      
     <!---->