Repository: ProcessMaker/processmaker
Pull request #8204: FOUR-22485: Open launchpad buttons appears templates edit
Author: login: henryjonathanquispe
Labels: name: merge-epic
Created: 2025-04-23T16:54:55Z | Merged: 2025-04-25T14:25:29Z

## Related Tickets & Packages
- https://processmaker.atlassian.net/browse/FOUR-23575

## 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


Diff:
diff --git a/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue b/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue
index 36954a5443..79f1f8153c 100644
--- a/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue
+++ b/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue
@@ -1,69 +1,63 @@
 <template>
-  <div id="processCollapseInfo" v-if="!isArchived">
+  <div
+    v-if="!isArchived"
+    id="processCollapseInfo">
     <div id="processData">
       <process-header-start
+        v-if="!mobileApp"
         :process="process"
         :ellipsis-permission="ellipsisPermission"
         :show-process-info="showProcessInfo"
         @goBack="goBack()"
         @onProcessNavigate="onProcessNavigate"
-        @toggle-info="toggleInfo"
-        v-if="!mobileApp"
-      />
+        @toggle-info="toggleInfo" />
       <div v-if="showProcessInfo">
         <process-header
           :process="process"
           :hide-header-options="true"
           :icon-wizard-template="createdFromWizardTemplate"
           @goBack="goBack()"
-          @onProcessInfoCollapsed="onProcessInfoCollapsed"
-        /> 
-         <div
+          @onProcessInfoCollapsed="onProcessInfoCollapsed" />
+        <div
           id="collapseProcessInfo"
-          class="collapse show custom-class"
-        >
+          class="collapse show custom-class">
           <div class="info-collapse">
             <b-row>
               <b-col class="process-carousel col-12">
                 <processes-carousel
                   :process="process"
-                  :full-carousel="{ url: null, hideLaunchpad: false }"
-                />
+                  :full-carousel="{ url: null, hideLaunchpad: false }" />
               </b-col>
               <b-col class="process-options col-12">
-                <process-options 
-                  :process="process" 
-                  :collapsed="collapsed"
-                />
+                <process-options
+                  :process="process"
+                  :collapsed="collapsed" />
               </b-col>
             </b-row>
           </div>
         </div>
       </div>
     </div>
-    
+    <Home />
     <create-template-modal
       id="create-template-modal"
       ref="create-template-modal"
       asset-type="process"
       :current-user-id="currentUserId"
       :asset-name="processTemplateName"
-      :asset-id="processId"
-    />
+      :asset-id="processId" />
     <create-pm-block-modal
       id="create-pm-block-modal"
       ref="create-pm-block-modal"
       :current-user-id="currentUserId"
       :asset-name="pmBlockName"
-      :asset-id="processId"
-    />
+      :asset-id="processId" />
     <add-to-project-modal
       id="add-to-project-modal"
       ref="add-to-project-modal"
       asset-type="process"
       :asset-id="processId"
-      :asset-name="assetName"
-    />
+      :asset-name="assetName" />
     <launchpad-settings-modal
       id="launchpad-settings-modal"
       ref="launchpad-settings-modal"
@@ -73,8 +67,7 @@
       :description-settings="process.description"
       :process="process"
       :my-tasks-columns="myTasksColumns"
-      @updateMyTasksColumns="updateMyTasksColumns"
-    />
+      @updateMyTasksColumns="updateMyTasksColumns" />
   </div>
 </template>
 
@@ -91,6 +84,8 @@ import ProcessesMixin from "./mixins/ProcessesMixin";
 import ProcessHeader from "./ProcessHeader.vue";
 import ProcessHeaderStart from "./ProcessHeaderStart.vue";
 
+import Home from "./home/Home.vue";
+
 export default {
   components: {
     CreateTemplateModal,
@@ -101,15 +96,23 @@ export default {
     ProcessesCarousel,
     ProcessHeader,
     ProcessHeaderStart,
+    Home,
   },
   mixins: [ProcessesMixin, ellipsisMenuMixin, processNavigationMixin],
   props: ["process", "currentUserId", "ellipsisPermission", "myTasksColumns"],
+  data() {
+    return {
+      mobileApp: window.ProcessMaker.mobileApp,
+      showProcessInfo: false,
+      collapsed: true,
+    };
+  },
   computed: {
     createdFromWizardTemplate() {
       return !!this.process?.properties?.wizardTemplateUuid;
     },
     isArchived() {
-      return this.process?.status === 'ARCHIVED';
+      return this.process?.status === "ARCHIVED";
     },
     wizardTemplateUuid() {
       return this.process?.properties?.wizardTemplateUuid;
@@ -121,13 +124,6 @@ export default {
       window.location.reload();
     });
   },
-  data() {
-    return {
-      mobileApp: window.ProcessMaker.mobileApp,
-      showProcessInfo: false,
-      collapsed: true,
-    };
-  },
   methods: {
     /**
      * Verify if the Description is large
@@ -150,7 +146,7 @@ export default {
       this.showProcessInfo = !this.showProcessInfo;
     },
     updateMyTasksColumns(columns) {
-      this.$emit('updateMyTasksColumns', columns);
+      this.$emit("updateMyTasksColumns", columns);
     },
     onProcessInfoCollapsed(collapsed) {
       this.collapsed = collapsed;
diff --git a/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue
new file mode 100644
index 0000000000..3d172df238
--- /dev/null
+++ b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue
@@ -0,0 +1,48 @@
+<template>
+  <div :class="`tw-flex tw-relative tw-w-full`">
+    <div class=" tw-flex tw-flex-col tw-w-full">
+      <div class="tw-flex tw-flex-col tw-justify-start tw-px-6 tw-py-3">
+        <slot name="header">
+          <div class="tw-text-xl tw-font-bold">
+            {{ header }}
+          </div>
+        </slot>
+        <slot name="body">
+          <div class="tw-text-base">
+            {{ body }}
+          </div>
+        </slot>
+      </div>
+    </div>
+    <div
+      :class="`tw-h-full tw-w-[20px] tw-absolute tw-bg-inherit
+          tw-right-[-20px] clip-path-triangle-right tw-z-10`">
+        &nbsp;
+    </div>
+  </div>
+</template>
+
+<script setup>
+
+const props = defineProps({
+  header: {
+    type: String,
+    required: true,
+  },
+  body: {
+    type: String,
+    required: true,
+  },
+  color: {
+    type: String,
+    required: true,
+  },
+});
+
+</script>
+
+<style scoped>
+.clip-path-triangle-right {
+  clip-path: polygon(-1% 0, 50% 50%, -1% 100%);
+}
+</style>
diff --git a/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue
new file mode 100644
index 0000000000..b1076b63aa
--- /dev/null
+++ b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue
@@ -0,0 +1,53 @@
+<template>
+  <div
+    class="tw-w-auto tw-flex tw-flex-row">
+    <ArrowButton
+      v-for="(item, index) in data"
+      :id="item.id"
+      :key="index"
+      :header="item.header"
+      :body="item.body"
+      :color="item.color"
+      :style="{'opacity': item.opacity}"
+      :class="`first:tw-rounded-l-xl
+        last:tw-rounded-r-xl
+        last:tw-overflow-hidden
+        ${[`tw-bg-${item.color}-${(index+1) * 100}`]}`"
+      @click="onClick(item, index)" />
+  </div>
+</template>
+
+<script setup>
+import { onMounted } from "vue";
+import ArrowButton from "./ArrowButton.vue";
+
+const props = defineProps({
+  data: {
+    type: Array({
+      id: String,
+      header: String,
+      body: String,
+      color: String,
+    }),
+    default: () => [],
+  },
+  color: {
+    type: String,
+    default: "amber",
+  },
+});
+
+const emit = defineEmits(["change"]);
+
+const onClick = (counter, idxCounter) => {
+  emit("change", counter, idxCounter);
+};
+
+onMounted(() => {
+  if (props.color) {
+    props.data.forEach((item) => {
+      item.color = props.color;
+    });
+  }
+});
+</script>
diff --git a/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButton.vue b/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButton.vue
new file mode 100644
index 0000000000..30eddc37f6
--- /dev/null
+++ b/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButton.vue
@@ -0,0 +1,75 @@
+<template>
+  <div
+    class="tw-flex tw-justify-between tw-items-center
+            tw-px-4 tw-py-3 tw-transition tw-duration-150 tw-ease-out
+            hover:tw-ease-in tw-cursor-pointer tw-rounded-lg tw-text-gray-600"
+    :class="[{
+      [`tw-bg-${color}-100 hover:tw-bg-${color}-100`]:!active,
+      [`tw-bg-${color}-100 tw-bg-${color}-100 hover:tw-bg-${color}-200`]:active
+    }]"
+    @click="handleClick">
+    <div>
+      <slot name="icon">
+        <i
+          :class="[`tw-text-lg`, icon]" />
+      </slot>
+      <slot name="header">
+        <div class="tw-text-base">
+          {{ header }}
+        </div>
+      </slot>
+      <slot name="body">
+        <div class="tw-text-xs">
+          {{ body }}
+        </div>
+      </slot>
+    </div>
+
+    <slot name="content">
+      <div class="tw-text-2xl tw-font-bold">
+        {{ content }}
+      </div>
+    </slot>
+  </div>
+</template>
+
+<script>
+import { defineComponent } from "vue";
+
+export default defineComponent({
+  props: {
+    header: {
+      type: String,
+      default: () => "header",
+    },
+    body: {
+      type: String,
+      default: () => "body",
+    },
+    icon: {
+      type: String,
+      default: () => "",
+    },
+    active: {
+      type: Boolean,
+      default: () => false,
+    },
+    color: {
+      type: String,
+      default: () => "gray",
+    },
+    content: {
+      type: String,
+      default: () => "",
+    },
+  },
+  setup(props, { emit }) {
+    const handleClick = () => {
+      emit("click");
+    };
+    return {
+      handleClick,
+    };
+  },
+});
+</script>
diff --git a/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButtonGroup.vue b/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButtonGroup.vue
new file mode 100644
index 0000000000..5a76eaab0d
--- /dev/null
+++ b/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButtonGroup.vue
@@ -0,0 +1,48 @@
+<template>
+  <div
+    class="tw-w-auto tw-flex tw-flex-col sm:tw-flex-row tw-space-y-2 sm:tw-space-y-0 sm:tw-space-x-4 sm:tw-py-0">
+    <BaseCardButton
+      v-for="(item, index) in data"
+      :id="item.id"
+      :key="index"
+      :header="item.header"
+      :body="item.body"
+      :active="index === active"
+      class="tw-w-full"
+      :color="item.color"
+      :icon="item.icon"
+      :content="item.content"
+      @click="onClick(item, index)" />
+  </div>
+</template>
+
+<script setup>
+import { onMounted } from "vue";
+import BaseCardButton from "./BaseCardButton.vue";
+
+const props = defineProps({
+  data: {
+    type: Array({
+      id: String,
+      header: String,
+      body: String,
+      color: String,
+      icon: String,
+    }),
+    default: () => [],
+  },
+  active: {
+    type: Number,
+    default: () => 0,
+  },
+});
+
+const emit = defineEmits(["change"]);
+
+const onClick = (counter, idxCounter) => {
+  emit("change", counter, idxCounter);
+};
+
+onMounted(() => {});
+
+</script>
diff --git a/resources/js/processes-catalogue/components/home/Home.vue b/resources/js/processes-catalogue/components/home/Home.vue
new file mode 100644
index 0000000000..dd9b6154a1
--- /dev/null
+++ b/resources/js/processes-catalogue/components/home/Home.vue
@@ -0,0 +1,131 @@
+<template>
+  <div class="tw-flex tw-flex-col tw-gap-4">
+    <BaseCardButtonGroup :data="data" />
+    <PercentageCardButtonGroup :data="percentageData" />
+    <ArrowButtonGroup
+      :data="arrowData"
+      color="emerald" />
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import BaseCardButtonGroup from "./ButtonGroup/BaseCardButtonGroup.vue";
+import PercentageCardButtonGroup from "./PercentageButtonGroup/PercentageCardButtonGroup.vue";
+import ArrowButtonGroup from "./ArrowButtonGroup/ArrowButtonGroup.vue";
+
+const data = ref([
+  {
+    id: "1",
+    header: "Max amount available",
+    body: "Across 10 aplicants",
+    icon: "fas fa-reply",
+    content: "84K",
+    active: true,
+  },
+  {
+    id: "2",
+    header: "Application awarded",
+    body: "30% of all submitted",
+    icon: "fas fa-user",
+    content: "3",
+    color: "amber",
+    active: false,
+  },
+  {
+    id: "3",
+    header: "Total amount awarded",
+    body: "Across 3 aplicants",
+    icon: "fas fa-user",
+    content: "46K+",
+    color: "green",
+    active: false,
+  },
+]);
+
+const percentageData = ref([
+  {
+    id: "1",
+    header: "Grants",
+    body: "40%",
+    percentage: 40,
+    content: "28,678",
+    color: "amber",
+  },
+  {
+    id: "2",
+    header: "Scholarships",
+    body: "20%",
+    percentage: 20,
+    content: "4,678",
+    color: "green",
+  },
+  {
+    id: "3",
+    header: "Loans",
+    body: "15%",
+    percentage: 15,
+    content: "11,678",
+    color: "blue",
+  },
+  {
+    id: "4",
+    header: "Out of pocket remaining",
+    body: "25%",
+    percentage: 25,
+    content: "17,649",
+    color: "red",
+  },
+]);
+
+const arrowData = ref([
+  {
+    id: "1",
+    header: "Grants",
+    body: "40%",
+    percentage: 40,
+    content: "28,678",
+    color: "amber",
+  },
+  {
+    id: "2",
+    header: "Scholarships",
+    body: "20%",
+    percentage: 20,
+    content: "4,678",
+    color: "green",
+  },
+  {
+    id: "3",
+    header: "Loans",
+    body: "15%",
+    percentage: 15,
+    content: "11,678",
+    color: "blue",
+  },
+  {
+    id: "4",
+    header: "Out of pocket remaining",
+    body: "25%",
+    percentage: 25,
+    content: "17,649",
+    color: "red",
+  },
+  {
+    id: "4",
+    header: "Out of pocket remaining",
+    body: "25%",
+    percentage: 25,
+    content: "17,649",
+    color: "red",
+  },
+  {
+    id: "4",
+    header: "Out of pocket remaining",
+    body: "25%",
+    percentage: 25,
+    content: "17,649",
+    color: "red",
+  },
+]);
+</script>
diff --git a/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButton.vue b/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButton.vue
new file mode 100644
index 0000000000..b4ba1597cf
--- /dev/null
+++ b/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButton.vue
@@ -0,0 +1,77 @@
+<template>
+  <div
+    class="tw-flex tw-justify-between tw-items-center
+            tw-px-4 tw-py-3 tw-transition tw-duration-150 tw-ease-out
+            hover:tw-ease-in tw-cursor-pointer tw-text-gray-600"
+    :class="[{
+      [`tw-bg-${color}-100 hover:tw-bg-${color}-100`]:!active,
+      [`tw-bg-${color}-100 tw-bg-${color}-100 hover:tw-bg-${color}-200`]:active
+    }]"
+    @click="handleClick">
+    <div class="tw-flex tw-flex-col tw-w-full">
+      <div class="tw-flex tw-flex-col tw-justify-start">
+        <slot name="icon">
+          <i
+            :class="[`tw-text-lg`, icon]" />
+        </slot>
+        <slot name="header">
+          <div class="tw-text-base">
+            {{ header }}
+          </div>
+        </slot>
+        <slot name="body">
+          <div class="tw-text-xs">
+            {{ body }}
+          </div>
+        </slot>
+      </div>
+      <slot
+        name="content">
+        <div class="tw-flex tw-justify-end tw-text-sm">
+          {{ content }}
+        </div>
+      </slot>
+    </div>
+  </div>
+</template>
+
+<script>
+import { defineComponent } from "vue";
+
+export default defineComponent({
+  props: {
+    header: {
+      type: String,
+      default: () => "header",
+    },
+    body: {
+      type: String,
+      default: () => "body",
+    },
+    icon: {
+      type: String,
+      default: () => "",
+    },
+    active: {
+      type: Boolean,
+      default: () => false,
+    },
+    color: {
+      type: String,
+      default: () => "gray",
+    },
+    content: {
+      type: String,
+      default: () => "",
+    },
+  },
+  setup(props, { emit }) {
+    const handleClick = () => {
+      emit("click");
+    };
+    return {
+      handleClick,
+    };
+  },
+});
+</script>
diff --git a/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButtonGroup.vue b/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButtonGroup.vue
new file mode 100644
index 0000000000..2970d76227
--- /dev/null
+++ b/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButtonGroup.vue
@@ -0,0 +1,45 @@
+<template>
+  <div
+    class="tw-w-auto tw-flex tw-flex-col sm:tw-flex-row sm:tw-space-y-0 sm:tw-py-0">
+    <PercentageCardButton
+      v-for="(item, index) in data"
+      :id="item.id"
+      :key="index"
+      :header="item.header"
+      :body="item.body"
+      :percentage="item.percentage"
+      :style="{ width: `${item.percentage}%` }"
+      :color="item.color"
+      :icon="item.icon"
+      :content="item.content"
+      @click="onClick(item, index)" />
+  </div>
+</template>
+
+<script setup>
+import { onMounted } from "vue";
+import PercentageCardButton from "./PercentageCardButton.vue";
+
+const props = defineProps({
+  data: {
+    type: Array({
+      id: String,
+      header: String,
+      body: String,
+      color: String,
+      icon: String,
+      percentage: Number,
+    }),
+    default: () => [],
+  },
+});
+
+const emit = defineEmits(["change"]);
+
+const onClick = (counter, idxCounter) => {
+  emit("change", counter, idxCounter);
+};
+
+onMounted(() => {});
+
+</script>
diff --git a/tailwind.config.js b/tailwind.config.js
index 1ecc0680bc..7bd5c9a36d 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -15,7 +15,11 @@ module.exports = {
   },
   safelist: [
     {
-      pattern: /(bg|outline|border|text)-(gray|purple|blue|amber|green|gray|emerald|red)-(100|200|300|400|500)/,
+      pattern: /(outline|border|text)-(gray|purple|blue|amber|green|gray|emerald|red)-(100|200|300|400|500)/,
+      variants: ["hover"],
+    },
+    {
+      pattern: /(bg)-([^-]+)-([0-9]+)/,
       variants: ["hover"],
     },
   ],


Reviews:
user: login: fagubla
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T20:06:51Z
user: login: gproly
body: The changes are good, but it's necessary to handle cases when the data is undefined. It's also important to document the properties and methods.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T20:46:48Z
user: login: henryjonathanquispe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T04:45:51Z

Review comments:
diff_hunk: @@ -0,0 +1,56 @@
+<template>
+  <div
+    class="tw-w-auto tw-flex tw-flex-row">
+    <ArrowButton
+      v-for="(item, index) in data"
+      :id="item.id"
+      :key="index"
+      :header="item.header"
+      :body="item.body"
+      :color="item.color"
+      :style="{'opacity': item.opacity}"
+      :class="[
+        `tw-bg-${item.color}-${(index+1) * 100}`,
+        {
+          'tw-rounded-l-xl': index === 0,
+          'tw-rounded-r-xl tw-overflow-hidden': index === data.length - 1
+        }
+      ]"
path: resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue
user: login: fagubla
body: this logic can be moved to script section
created_at: 2025-04-23T20:02:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+<template>
+  <div
+    class="tw-w-auto tw-flex tw-flex-row">
+    <ArrowButton
+      v-for="(item, index) in data"
+      :id="item.id"
+      :key="index"
+      :header="item.header"
+      :body="item.body"
+      :color="item.color"
+      :style="{'opacity': item.opacity}"
+      :class="[
+        `tw-bg-${item.color}-${(index+1) * 100}`,
+        {
+          'tw-rounded-l-xl': index === 0,
+          'tw-rounded-r-xl tw-overflow-hidden': index === data.length - 1
+        }
+      ]"
path: resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue
user: login: henryjonathanquispe
body: @fagubla updated
created_at: 2025-04-25T04:45:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: vladyrichter
created_at: 2025-04-25T05:29:53Z
body: QA server K8S was successfully deployed https://ci-7c8671b95b.engk8s.processmaker.net
author_association: NONE
user: login: processmaker-sonarqube[bot]
created_at: 2025-04-25T05:35:12Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarqube.processmaker.net/dashboard?id=ProcessMaker_processmaker_AYkzkfKexYvY_isvKt07&pullRequest=8204) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.processmaker.net/project/issues?id=ProcessMaker_processmaker_AYkzkfKexYvY_isvKt07&pullRequest=8204&resolved=false) [0 New issues](https://sonarqube.processmaker.net/project/issues?id=ProcessMaker_processmaker_AYkzkfKexYvY_isvKt07&pullRequest=8204&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://sonarqube.processmaker.net/project/issues?id=ProcessMaker_processmaker_AYkzkfKexYvY_isvKt07&pullRequest=8204&issueStatuses=ACCEPTED) [0 Accepted issues](https://sonarqube.processmaker.net/project/issues?id=ProcessMaker_processmaker_AYkzkfKexYvY_isvKt07&pullRequest=8204&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.processmaker.net/security_hotspots?id=ProcessMaker_processmaker_AYkzkfKexYvY_isvKt07&pullRequest=8204) [0 Security Hotspots](https://sonarqube.processmaker.net/security_hotspots?id=ProcessMaker_processmaker_AYkzkfKexYvY_isvKt07&pullRequest=8204)  
![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/no-data-16px.png '') No data about Coverage  
![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/no-data-16px.png '') No data about Duplication  
  
[See analysis details on SonarQube](https://sonarqube.processmaker.net/dashboard?id=ProcessMaker_processmaker_AYkzkfKexYvY_isvKt07&pullRequest=8204)


author_association: NONE