Repository: ProcessMaker/modeler
Pull request #1297 (2020-12-09 19:19:25)

Toggle task compensation and loop markers
Duplicate of #1280 but it removes code that causes hanging issue in combination with the changes in this PR. 



Patch:
diff --git a/package.json b/package.json
index ff4b25e9..7adf3b8a 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
     "check-peer-deps": "npm ls --depth=0",
     "posttest": "npm run report:combined",
     "test-unit": "JEST_JUNIT_OUTPUT_DIR=test_summary_reports/jest/ vue-cli-service test:unit --ci --runInBand --reporters=default --reporters=jest-junit",
-    "test-ci": "TZ=UTC vue-cli-service test:e2e --spec tests/e2e/specs/Modeler.spec.js --mode test --headless --browser chrome --reporter mocha-junit-reporter --reporter-options mochaFile=test_summary_reports/mocha/test-results.xml",
+    "test-ci": "TZ=UTC vue-cli-service test:e2e --mode test --headless --browser chrome -- --reporter mocha-junit-reporter --reporter-options mochaFile=reports/mocha/test-results.xml",
     "mkdir:reports": "mkdir reports || true",
     "precopy:reports": "npm run mkdir:reports",
     "copy:reports": "cp cypress-coverage/coverage-final.json reports/from-cypress.json && cp jest-coverage/coverage-final.json reports/from-jest.json",
diff --git a/src/components/inspectors/InspectorPanel.vue b/src/components/inspectors/InspectorPanel.vue
index 682990b4..d54ace55 100644
--- a/src/components/inspectors/InspectorPanel.vue
+++ b/src/components/inspectors/InspectorPanel.vue
@@ -78,12 +78,12 @@ export default {
       this.prepareData();
       this.prepareConfig();
     },
-    'highlightedNode.definition': {
-      handler() {
-        this.prepareData();
-      },
-      deep: true,
-    },
+    'highlightedNode.definition.assignment'(current, previous) { this.handleAssignmentChanges(current, previous); },
+    'highlightedNode.definition.assignmentLock'(current, previous) { this.handleAssignmentChanges(current, previous); },
+    'highlightedNode.definition.allowReassignment'(current, previous) { this.handleAssignmentChanges(current, previous); },
+    'highlightedNode.definition.assignedUsers'(current, previous) { this.handleAssignmentChanges(current, previous); },
+    'highlightedNode.definition.assignedGroups'(current, previous) { this.handleAssignmentChanges(current, previous); },
+    'highlightedNode.definition.assignmentRules'(current, previous) { this.handleAssignmentChanges(current, previous); },
   },
   computed: {
     highlightedNode() {
@@ -131,6 +131,12 @@ export default {
     },
   },
   methods: {
+    handleAssignmentChanges(currentValue, previousValue) {
+      if (currentValue === previousValue) {
+        return;
+      }
+      this.prepareData();
+    },
     prepareConfig() {
       if (!this.highlightedNode) {
         return this.config = {
@@ -181,13 +187,15 @@ export default {
 
       const type = this.highlightedNode && this.highlightedNode.type;
 
-      this.data = type && this.nodeRegistry[type].inspectorData
-        ? this.nodeRegistry[type].inspectorData(this.highlightedNode)
-        : Object.entries(this.highlightedNode.definition).reduce((data, [key, value]) => {
-          data[key] = value;
+      const defaultDataTransform = (node) => Object.entries(node.definition).reduce((data, [key, value]) => {
+        data[key] = value;
+
+        return data;
+      }, {});
 
-          return data;
-        }, {});
+      this.data = type && this.nodeRegistry[type].inspectorData
+        ? this.nodeRegistry[type].inspectorData(this.highlightedNode, defaultDataTransform)
+        : defaultDataTransform(this.highlightedNode);
     },
     isSequenceFlow(type) {
       return type === sequenceFlowId;
@@ -199,7 +207,7 @@ export default {
       return definition.targetRef.$type === 'bpmn:CallActivity';
     },
     customInspectorHandler(value) {
-      return this.nodeRegistry[this.highlightedNode.type].inspectorHandler(value, this.highlightedNode, this.setNodeProp, this.moddle, this.definitions);
+      return this.nodeRegistry[this.highlightedNode.type].inspectorHandler(value, this.highlightedNode, this.setNodeProp, this.moddle, this.definitions, this.defaultInspectorHandler);
     },
     processNodeInspectorHandler(value) {
       return this.defaultInspectorHandler(omit(value, ['artifacts', 'flowElements', 'laneSets']));
diff --git a/src/components/inspectors/MarkerFlags.vue b/src/components/inspectors/MarkerFlags.vue
new file mode 100644
index 00000000..aba60689
--- /dev/null
+++ b/src/components/inspectors/MarkerFlags.vue
@@ -0,0 +1,48 @@
+<template>
+  <div>
+    <b-form-group label="Loop Characteristics">
+      <b-form-radio data-test="no_loop" v-model="loopCharacteristics" name="marker-flags-radios" value="no_loop">No loop characteristics</b-form-radio>
+      <b-form-radio data-test="parallel_mi" v-model="loopCharacteristics" name="marker-flags-radios" value="parallel_mi">Parallel multi-instance</b-form-radio>
+      <b-form-radio data-test="sequential_mi" v-model="loopCharacteristics" name="marker-flags-radios" value="sequential_mi">Sequential multi-instance</b-form-radio>
+      <b-form-radio data-test="loop" v-model="loopCharacteristics" name="marker-flags-radios" value="loop">Loop</b-form-radio>
+    </b-form-group>
+    <b-form-group>
+      <b-form-checkbox data-test="for-compensation" v-model="isForCompensation" name="for-compensation">For Compensation</b-form-checkbox>
+    </b-form-group>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    value: {
+      type: Object,
+      default() {
+        return {
+          loopCharacteristics: 'no_loop',
+          isForCompensation: false,
+        };
+      },
+    },
+  },
+  name: 'MarkerFlags',
+  data() {
+    return {
+      loopCharacteristics: this.value.loopCharacteristics,
+      isForCompensation: this.value.isForCompensation,
+    };
+  },
+  watch: {
+    loopCharacteristics(newVal) {
+      this.$emit('input', {loopCharacteristics: newVal});
+    },
+    isForCompensation(newVal) {
+      this.$emit('input', {isForCompensation: newVal});
+    },
+  },
+};
+</script>
+
+<style scoped>
+
+</style>
diff --git a/src/components/inspectors/advancedAccordionConfigWithMarkerFlags.js b/src/components/inspectors/advancedAccordionConfigWithMarkerFlags.js
new file mode 100644
index 00000000..2901f7f6
--- /dev/null
+++ b/src/components/inspectors/advancedAccordionConfigWithMarkerFlags.js
@@ -0,0 +1,35 @@
+import idConfigSettings from './idConfigSettings';
+import DocumentationFormTextArea from './DocumentationFormTextArea';
+import MarkerFlags from '@/components/inspectors/MarkerFlags';
+
+export default {
+  component: 'FormAccordion',
+  container: true,
+  config: {
+    initiallyOpen: false,
+    label: 'Advanced',
+    icon: 'cogs',
+    name: 'advanced-accordion',
+  },
+  items: [
+    {
+      component: 'FormInput',
+      config: idConfigSettings,
+    },
+    {
+      component: DocumentationFormTextArea,
+      config: {
+        label: 'Description',
+        name: 'documentation',
+      },
+    },
+    {
+      component: MarkerFlags,
+      name: 'taskMarkers',
+      config: {
+        label: 'Marker Flags',
+        name: 'markerFlags',
+      },
+    },
+  ],
+};
diff --git a/src/components/inspectors/process.js b/src/components/inspectors/process.js
index 6aa3434d..4f70cd3d 100644
--- a/src/components/inspectors/process.js
+++ b/src/components/inspectors/process.js
@@ -16,7 +16,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-process',
           },
           items: [
             {
diff --git a/src/components/modeler/Modeler.vue b/src/components/modeler/Modeler.vue
index 68f0dd7f..601abae4 100644
--- a/src/components/modeler/Modeler.vue
+++ b/src/components/modeler/Modeler.vue
@@ -125,7 +125,8 @@ import { id as laneId } from '../nodes/poolLane';
 import { id as sequenceFlowId } from '../nodes/sequenceFlow';
 import { id as associationId } from '../nodes/association';
 import { id as messageFlowId } from '../nodes/messageFlow';
-import { id as dataAssociationFlowId } from '../nodes/dataOutputAssociation';
+import { id as dataOutputAssociationFlowId } from '../nodes/dataOutputAssociation/config';
+import { id as dataInputAssociationFlowId } from '../nodes/dataInputAssociation/config';
 
 import PaperManager from '../paperManager';
 import registerInspectorExtension from '@/components/InspectorExtensionManager';
@@ -431,7 +432,7 @@ export default {
 
       types.forEach(bpmnType => {
         if (!this.parsers[bpmnType]) {
-          this.parsers[bpmnType] = { custom: [], implementation: [], default: [] };
+          this.parsers[bpmnType] = { custom: [], implementation: [], default: []};
         }
 
         if (customParser) {
@@ -712,9 +713,10 @@ export default {
         this.setShapeCenterUnderCursor(diagram);
       }
 
+      this.highlightNode(newNode);
+      await this.addNode(newNode);
+
       if (!nodeThatWillBeReplaced) {
-        await this.addNode(newNode);
-        this.highlightNode(newNode);
         return;
       }
 
@@ -727,9 +729,7 @@ export default {
         this.collaboration,
       );
       nodeMigrator.migrate();
-
-      await this.addNode(newNode);
-      this.highlightNode(newNode);
+      
       return newNode;
     },
     setShapeCenterUnderCursor(diagram) {
@@ -747,7 +747,7 @@ export default {
       store.commit('addNode', node);
       this.poolTarget = null;
 
-      if ([sequenceFlowId, laneId, associationId, messageFlowId, dataAssociationFlowId].includes(node.type)) {
+      if ([sequenceFlowId, laneId, associationId, messageFlowId, dataOutputAssociationFlowId, dataInputAssociationFlowId].includes(node.type)) {
         return;
       }
 
diff --git a/src/components/nodes/association/index.js b/src/components/nodes/association/index.js
index cbb94c00..e6809dc2 100644
--- a/src/components/nodes/association/index.js
+++ b/src/components/nodes/association/index.js
@@ -25,7 +25,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-association',
           },
           items: [
             {
diff --git a/src/components/nodes/baseStartEvent/index.js b/src/components/nodes/baseStartEvent/index.js
index 081b3d9f..f6953ca2 100644
--- a/src/components/nodes/baseStartEvent/index.js
+++ b/src/components/nodes/baseStartEvent/index.js
@@ -34,7 +34,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-start-event',
           },
           items: [
             {
diff --git a/src/components/nodes/boundaryEvent/index.js b/src/components/nodes/boundaryEvent/index.js
index 7378edf8..56ffce63 100644
--- a/src/components/nodes/boundaryEvent/index.js
+++ b/src/components/nodes/boundaryEvent/index.js
@@ -36,7 +36,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-boundary-event',
           },
           items: [
             {
diff --git a/src/components/nodes/boundaryTimerEvent/index.js b/src/components/nodes/boundaryTimerEvent/index.js
index 69deefc8..72b47bd2 100644
--- a/src/components/nodes/boundaryTimerEvent/index.js
+++ b/src/components/nodes/boundaryTimerEvent/index.js
@@ -83,7 +83,7 @@ export default merge(cloneDeep(boundaryEventConfig), {
         config: {
           label: 'Timing Control',
           icon: 'clock',
-          name: 'inspector-accordion',
+          name: 'inspector-accordion-boundary-timer-event',
         },
         items: [
           {
diff --git a/src/components/nodes/dataInputAssociation/index.js b/src/components/nodes/dataInputAssociation/index.js
index fe9e7984..4b71220d 100644
--- a/src/components/nodes/dataInputAssociation/index.js
+++ b/src/components/nodes/dataInputAssociation/index.js
@@ -17,7 +17,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-data-input-association',
           },
           items: [
             {
diff --git a/src/components/nodes/dataObject/index.js b/src/components/nodes/dataObject/index.js
index c38c6c4b..4ec28661 100644
--- a/src/components/nodes/dataObject/index.js
+++ b/src/components/nodes/dataObject/index.js
@@ -41,7 +41,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-data-object',
           },
           items: [
             {
diff --git a/src/components/nodes/dataOutputAssociation/index.js b/src/components/nodes/dataOutputAssociation/index.js
index ee30cce7..5538d5f5 100644
--- a/src/components/nodes/dataOutputAssociation/index.js
+++ b/src/components/nodes/dataOutputAssociation/index.js
@@ -17,7 +17,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-data-output-association',
           },
           items: [
             {
diff --git a/src/components/nodes/dataStore/index.js b/src/components/nodes/dataStore/index.js
index aef667a7..2e9da87b 100644
--- a/src/components/nodes/dataStore/index.js
+++ b/src/components/nodes/dataStore/index.js
@@ -41,7 +41,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-data-store',
           },
           items: [
             {
diff --git a/src/components/nodes/endEvent/index.js b/src/components/nodes/endEvent/index.js
index eef30d03..db5745a2 100644
--- a/src/components/nodes/endEvent/index.js
+++ b/src/components/nodes/endEvent/index.js
@@ -40,7 +40,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-end-event',
           },
           items: [
             {
diff --git a/src/components/nodes/eventBasedGateway/index.js b/src/components/nodes/eventBasedGateway/index.js
index 0cac5967..7a6b7f2f 100644
--- a/src/components/nodes/eventBasedGateway/index.js
+++ b/src/components/nodes/eventBasedGateway/index.js
@@ -35,7 +35,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-event-based-gateway',
           },
           items: [
             {
diff --git a/src/components/nodes/exclusiveGateway/index.js b/src/components/nodes/exclusiveGateway/index.js
index 36653db8..d8df819c 100644
--- a/src/components/nodes/exclusiveGateway/index.js
+++ b/src/components/nodes/exclusiveGateway/index.js
@@ -38,7 +38,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-exlcusive-gateway',
           },
           items: [
             {
diff --git a/src/components/nodes/gateway/index.js b/src/components/nodes/gateway/index.js
index 93057298..a4e428a7 100644
--- a/src/components/nodes/gateway/index.js
+++ b/src/components/nodes/gateway/index.js
@@ -33,7 +33,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-gateway',
           },
           items: [
             {
diff --git a/src/components/nodes/inclusiveGateway/index.js b/src/components/nodes/inclusiveGateway/index.js
index 07b07405..a800ae91 100644
--- a/src/components/nodes/inclusiveGateway/index.js
+++ b/src/components/nodes/inclusiveGateway/index.js
@@ -39,7 +39,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-inclusive-gateway-config',
           },
           items: [
             {
@@ -55,7 +55,7 @@ export default {
             initiallyOpen: false,
             label: 'Advanced',
             icon: 'cogs',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-inclusive-gateway-advanced',
           },
           items: [
             {
diff --git a/src/components/nodes/intermediateEvent/index.js b/src/components/nodes/intermediateEvent/index.js
index faeaf3f0..42880532 100644
--- a/src/components/nodes/intermediateEvent/index.js
+++ b/src/components/nodes/intermediateEvent/index.js
@@ -35,7 +35,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-intermediate-gateway-config',
           },
           items: [
             {
diff --git a/src/components/nodes/intermediateMessageEvent/index.js b/src/components/nodes/intermediateMessageEvent/index.js
index 6153134d..91c957f4 100644
--- a/src/components/nodes/intermediateMessageEvent/index.js
+++ b/src/components/nodes/intermediateMessageEvent/index.js
@@ -38,7 +38,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-intermediate-message-event',
           },
           items: [
             {
diff --git a/src/components/nodes/intermediateTimerEvent/index.js b/src/components/nodes/intermediateTimerEvent/index.js
index a9392c01..7fbd9fbb 100644
--- a/src/components/nodes/intermediateTimerEvent/index.js
+++ b/src/components/nodes/intermediateTimerEvent/index.js
@@ -92,7 +92,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-intermediate-timer-config',
           },
           items: [
             {
@@ -107,7 +107,7 @@ export default {
           config: {
             label: 'Timing Control',
             icon: 'clock',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-intermediate-timer-event-timing-control',
           },
           items: [
             {
diff --git a/src/components/nodes/manualTask/index.js b/src/components/nodes/manualTask/index.js
index c66dc230..616e1c14 100644
--- a/src/components/nodes/manualTask/index.js
+++ b/src/components/nodes/manualTask/index.js
@@ -38,7 +38,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-manual-task',
           },
           items: [
             {
diff --git a/src/components/nodes/messageFlow/index.js b/src/components/nodes/messageFlow/index.js
index 8a4267a8..09df3c86 100644
--- a/src/components/nodes/messageFlow/index.js
+++ b/src/components/nodes/messageFlow/index.js
@@ -25,7 +25,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-message-flow',
           },
           items: [
             {
diff --git a/src/components/nodes/parallelGateway/index.js b/src/components/nodes/parallelGateway/index.js
index 71cf06f6..d957c298 100644
--- a/src/components/nodes/parallelGateway/index.js
+++ b/src/components/nodes/parallelGateway/index.js
@@ -36,7 +36,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-parallel-gateway-config',
           },
           items: [
             {
@@ -52,7 +52,7 @@ export default {
             initiallyOpen: false,
             label: 'Advanced',
             icon: 'cogs',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-parallel-gateway-direction',
           },
           items: [
             {
diff --git a/src/components/nodes/pool/index.js b/src/components/nodes/pool/index.js
index 8c497f2c..04a7d608 100644
--- a/src/components/nodes/pool/index.js
+++ b/src/components/nodes/pool/index.js
@@ -37,7 +37,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-pool',
           },
           items: [
             {
diff --git a/src/components/nodes/poolLane/index.js b/src/components/nodes/poolLane/index.js
index 512684ed..a16a86b7 100644
--- a/src/components/nodes/poolLane/index.js
+++ b/src/components/nodes/poolLane/index.js
@@ -34,7 +34,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-pool-lane',
           },
           items: [
             {
diff --git a/src/components/nodes/scriptTask/index.js b/src/components/nodes/scriptTask/index.js
index 944cc813..6d97e6ca 100644
--- a/src/components/nodes/scriptTask/index.js
+++ b/src/components/nodes/scriptTask/index.js
@@ -38,7 +38,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-script-task',
           },
           items: [
             {
diff --git a/src/components/nodes/sequenceFlow/index.js b/src/components/nodes/sequenceFlow/index.js
index 4d7e1142..fe87d994 100644
--- a/src/components/nodes/sequenceFlow/index.js
+++ b/src/components/nodes/sequenceFlow/index.js
@@ -67,7 +67,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-sequence-flow',
           },
           items: [
             {
diff --git a/src/components/nodes/serviceTask/index.js b/src/components/nodes/serviceTask/index.js
index b52d1dc7..226d3e0e 100644
--- a/src/components/nodes/serviceTask/index.js
+++ b/src/components/nodes/serviceTask/index.js
@@ -24,7 +24,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-service-task',
           },
           items: [
             {
diff --git a/src/components/nodes/startTimerEvent/index.js b/src/components/nodes/startTimerEvent/index.js
index e8336400..de8bf645 100644
--- a/src/components/nodes/startTimerEvent/index.js
+++ b/src/components/nodes/startTimerEvent/index.js
@@ -66,7 +66,7 @@ export default merge(cloneDeep(baseStartEventConfig), {
           config: {
             label: 'Timing Control',
             icon: 'clock',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-start-timer-timing-control',
           },
           items: [
             {
diff --git a/src/components/nodes/subProcess/index.js b/src/components/nodes/subProcess/index.js
index 571c7195..33da56de 100644
--- a/src/components/nodes/subProcess/index.js
+++ b/src/components/nodes/subProcess/index.js
@@ -57,7 +57,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-subprocess',
           },
           items: [
             {
diff --git a/src/components/nodes/task/index.js b/src/components/nodes/task/index.js
index 850a2d30..27e340b4 100644
--- a/src/components/nodes/task/index.js
+++ b/src/components/nodes/task/index.js
@@ -1,8 +1,9 @@
 import component from './task.vue';
 import nameConfigSettings from '@/components/inspectors/nameConfigSettings';
 import { taskHeight, taskWidth } from './taskConfig';
-import advancedAccordionConfig from '@/components/inspectors/advancedAccordionConfig';
 import defaultNames from '@/components/nodes/task/defaultNames';
+import advancedAccordionConfigWithMarkerFlags from '@/components/inspectors/advancedAccordionConfigWithMarkerFlags';
+import omit from 'lodash/omit';
 
 export const id = 'processmaker-modeler-task';
 
@@ -29,6 +30,22 @@ export default {
       }),
     });
   },
+  inspectorHandler(value, node, setNodeProp, moddle, definitions, defaultInspectorHandler) {
+    handleMarkerFlagsValue(value.markerFlags, node, setNodeProp, moddle);
+    defaultInspectorHandler(omit(value, 'markerFlags'));
+  },
+  inspectorData(node, defaultDataTransform) {
+    const inspectorData = defaultDataTransform(node);
+
+    inspectorData.markerFlags = {
+      isForCompensation: inspectorData.isForCompensation,
+      loopCharacteristics: getLoopCharacteristicsRadioValue(inspectorData.loopCharacteristics),
+    };
+    delete inspectorData.isForCompensation;
+    delete inspectorData.loopCharacteristics;
+
+    return inspectorData;
+  },
   inspectorConfig: [
     {
       name: defaultNames[id],
@@ -40,7 +57,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-task',
           },
           items: [
             {
@@ -49,8 +66,67 @@ export default {
             },
           ],
         },
-        advancedAccordionConfig,
+        advancedAccordionConfigWithMarkerFlags,
       ],
     },
   ],
 };
+
+function handleMarkerFlagsValue(markerFlags, node, setNodeProp, moddle) {
+  if (!markerFlags) {
+    return;
+  }
+
+  if (markerFlags.loopCharacteristics) {
+    if (markerFlags.loopCharacteristics === 'no_loop') {
+      setNodeProp(node, 'loopCharacteristics', null);
+    }
+
+    const currentLoopCharacteristics = node.definition.get('loopCharacteristics') || {};
+
+    if (markerFlags.loopCharacteristics === 'loop' && currentLoopCharacteristics.$type !== 'bpmn:StandardLoopCharacteristics') {
+      setNodeProp(node, 'loopCharacteristics', moddle.create('bpmn:StandardLoopCharacteristics'));
+    }
+
+    if (markerFlags.loopCharacteristics === 'parallel_mi' ) {
+      if (currentLoopCharacteristics.$type === 'bpmn:MultiInstanceLoopCharacteristics' && !currentLoopCharacteristics.isSequential){
+        return;
+      }
+      setNodeProp(node, 'loopCharacteristics', moddle.create('bpmn:MultiInstanceLoopCharacteristics'));
+    }
+
+    if (markerFlags.loopCharacteristics === 'sequential_mi') {
+      if (currentLoopCharacteristics.$type === 'bpmn:MultiInstanceLoopCharacteristics' && currentLoopCharacteristics.isSequential){
+        return;
+      }
+      setNodeProp(node, 'loopCharacteristics', moddle.create('bpmn:MultiInstanceLoopCharacteristics', { isSequential: true }));
+    }
+  }
+
+  const currentIsForCompensationValue = node.definition.get('isForCompensation');
+  const newIsForCompensationValue = markerFlags.isForCompensation;
+
+  if (newIsForCompensationValue != null && newIsForCompensationValue !== currentIsForCompensationValue) {
+    setNodeProp(node, 'isForCompensation', newIsForCompensationValue);
+  }
+}
+
+function getLoopCharacteristicsRadioValue(loopCharacteristics) {
+  if (!loopCharacteristics) {
+    return 'no_loop';
+  }
+
+  if (loopCharacteristics.$type === 'bpmn:StandardLoopCharacteristics') {
+    return 'loop';
+  }
+
+  if (loopCharacteristics.$type === 'bpmn:MultiInstanceLoopCharacteristics' && !loopCharacteristics.isSequential) {
+    return 'parallel_mi';
+  }
+
+  if (loopCharacteristics.$type === 'bpmn:MultiInstanceLoopCharacteristics' && loopCharacteristics.isSequential) {
+    return 'sequential_mi';
+  }
+
+  return 'no_loop';
+}
diff --git a/src/components/nodes/task/setupCompensationMarker.js b/src/components/nodes/task/setupCompensationMarker.js
index 07feb9e1..8c7d9f7a 100644
--- a/src/components/nodes/task/setupCompensationMarker.js
+++ b/src/components/nodes/task/setupCompensationMarker.js
@@ -1,7 +1,10 @@
 import compensationIcon from '@/assets/compensation.svg';
 
-export default function setupCompensationMarker(nodeDefinition, markers, $set) {
-  if (nodeDefinition.isForCompensation) {
-    $set(markers.bottomCenter, 'compensation', compensationIcon);
+export default function setupCompensationMarker(nodeDefinition, markers, $set, $delete) {
+  if (!nodeDefinition.isForCompensation) {
+    $delete(markers.bottomCenter, 'compensation');
+    return;
   }
+
+  $set(markers.bottomCenter, 'compensation', compensationIcon);
 }
diff --git a/src/components/nodes/task/setupMultiInstanceMarkers.js b/src/components/nodes/task/setupMultiInstanceMarkers.js
index 82bc0b66..8c7c11a7 100644
--- a/src/components/nodes/task/setupMultiInstanceMarkers.js
+++ b/src/components/nodes/task/setupMultiInstanceMarkers.js
@@ -5,7 +5,7 @@ import loopIcon from '@/assets/loop.svg';
 const standardLoop = 'bpmn:StandardLoopCharacteristics';
 const multiInstanceLoop = 'bpmn:MultiInstanceLoopCharacteristics';
 
-export default function setupMultiInstanceMarkers(nodeDefinition, markers, $set) {
+export default function setupMultiInstanceMarkers(nodeDefinition, markers, $set, $delete) {
   const loopCharacteristics = nodeDefinition.get('loopCharacteristics');
 
   const handledLoopTypes = [
@@ -14,6 +14,7 @@ export default function setupMultiInstanceMarkers(nodeDefinition, markers, $set)
   ];
 
   if (!loopCharacteristics || !handledLoopTypes.includes(loopCharacteristics.$type)) {
+    $delete(markers.bottomCenter, 'loopCharacteristics');
     return;
   }
 
diff --git a/src/components/nodes/task/task.vue b/src/components/nodes/task/task.vue
index 61516f72..6e588d45 100644
--- a/src/components/nodes/task/task.vue
+++ b/src/components/nodes/task/task.vue
@@ -29,7 +29,7 @@ import CrownConfig from '@/components/crown/crownConfig/crownConfig';
 import { gridSize } from '@/graph';
 import defaultNames from '@/components/nodes/task/defaultNames';
 import boundaryEventDropdownData from '@/components/nodes/boundaryEvent/boundaryEventDropdownData';
-import setupMultiInstanceMarkers from '@/components/nodes/task/setupMultiInstanceMarkers';
+import setupLoopCharacteristicsMarkers from '@/components/nodes/task/setupMultiInstanceMarkers';
 import setupCompensationMarker from '@/components/nodes/task/setupCompensationMarker';
 import { getRectangleAnchorPoint } from '@/portsUtils';
 
@@ -103,6 +103,12 @@ export default {
         this.recalcMarkersAlignment();
       }
     },
+    'node.definition.isForCompensation'() {
+      setupCompensationMarker(this.node.definition, this.markers, this.$set, this.$delete);
+    },
+    'node.definition.loopCharacteristics'() {
+      setupLoopCharacteristicsMarkers(this.node.definition, this.markers, this.$set, this.$delete);
+    },
   },
   methods: {
     getElementsUnderArea(element) {
@@ -127,15 +133,14 @@ export default {
     middleIsOddNumber(value) {
       return Math.abs((value / 2) % 2) === 1;
     },
-
   },
   mounted() {
     this.shape = new TaskShape();
     let bounds = this.node.diagram.bounds;
     this.shape.position(bounds.x, bounds.y);
     this.shape.resize(bounds.width, bounds.height);
-    setupCompensationMarker(this.node.definition, this.markers, this.$set);
-    setupMultiInstanceMarkers(this.node.definition, this.markers, this.$set);
+    setupCompensationMarker(this.node.definition, this.markers, this.$set, this.$delete);
+    setupLoopCharacteristicsMarkers(this.node.definition, this.markers, this.$set, this.$delete);
     this.shape.attr({
       body: {
         rx: 8,
diff --git a/src/components/nodes/textAnnotation/index.js b/src/components/nodes/textAnnotation/index.js
index 34c651d6..09107e71 100644
--- a/src/components/nodes/textAnnotation/index.js
+++ b/src/components/nodes/textAnnotation/index.js
@@ -52,7 +52,7 @@ export default {
             initiallyOpen: true,
             label: 'Configuration',
             icon: 'cog',
-            name: 'inspector-accordion',
+            name: 'inspector-accordion-text-annotation',
           },
           items: [
             {
diff --git a/src/components/validationStatus/ValidationStatus.vue b/src/components/validationStatus/ValidationStatus.vue
index c3d268b1..06c2adc5 100644
--- a/src/components/validationStatus/ValidationStatus.vue
+++ b/src/components/validationStatus/ValidationStatus.vue
@@ -15,7 +15,7 @@
 
     <div class="status-bar-container d-flex align-items-center justify-content-end">
       <template v-if="autoValidate">
-        <button v-if="numberOfProblemsToDisplay === 0" type="button" class="btn btn-light" :disabled="true">
+        <button v-if="numberOfProblemsToDisplay === 0" type="button" data-test="validation-list-valid" class="btn btn-light" :disabled="true">
           {{ $t('BPMN Valid') }}
           <span class="badge badge-success badge-pill">
             <font-awesome-icon :icon="faCheck"/>
diff --git a/src/mixins/modules/recalculateMarkerAlignment.js b/src/mixins/modules/recalculateMarkerAlignment.js
index 0ccf331a..fb99589f 100644
--- a/src/mixins/modules/recalculateMarkerAlignment.js
+++ b/src/mixins/modules/recalculateMarkerAlignment.js
@@ -4,6 +4,7 @@ export const markerPadding = 4;
 
 export default function recalculateMarkerAlignment(markers, shape) {
   const { width, height } = shape.size();
+
   for (let position in markers) {
 
     alignMarkersFromLeftToRight(shape, position, width);
@@ -38,7 +39,10 @@ function alignMarkersFromCenter(shape, position, markers, width) {
   for (let i = 0; i < markersLimit; i++) {
     shape.attr(position + '.' + i + '/xlink:href', null);
   }
-  for (let marker in markers[position]) {
+
+  const orderedMarkers = orderMarkers(markers, position);
+
+  for (let marker in orderedMarkers) {
     shape.attr(position + '.' + c + '/xlink:href', markers[position][marker]);
     c++;
     if (c >= markersLimit) {
@@ -51,6 +55,17 @@ function alignMarkersFromCenter(shape, position, markers, width) {
   }
 }
 
+function orderMarkers(markers, position) {
+  if (position === 'bottomCenter') {
+    const orderedMarkers = {};
+    Object.keys(markers[position]).sort().forEach(key => {
+      orderedMarkers[key] = markers[position][key];
+    });
+    return orderedMarkers;
+  }
+  return markers[position];
+}
+
 function hasTaskMarker(shape) {
   return !!shape.attr('image/xlink:href');
 }
diff --git a/src/store.js b/src/store.js
index b7e507e4..954881d2 100644
--- a/src/store.js
+++ b/src/store.js
@@ -5,11 +5,13 @@ import uniq from 'lodash/uniq';
 
 Vue.use(Vuex);
 
-function makeDefinitionPropertyReactive(definition, key, value) {
+function setDefinitionPropertyReactive(definition, key, value) {
   if (definition.hasOwnProperty(key)) {
+    definition.set(key, value);
     return;
   }
 
+  delete Object.getPrototypeOf(definition)[key];
   Vue.set(definition, key, value);
 }
 
@@ -74,14 +76,11 @@ export default new Vuex.Store({
       });
     },
     updateNodeProp(state, { node, key, value }) {
-      node.definition.set(key, value);
+      setDefinitionPropertyReactive(node.definition, key, value);
 
-      if (value === undefined) {
+      if (value == null) {
         Vue.delete(node.definition, key);
-        return;
       }
-
-      makeDefinitionPropertyReactive(node.definition, key, value);
     },
     clearNodes(state) {
       state.nodes = [];


Test patch:
diff --git a/tests/e2e/fixtures/withLoopMarker.xml b/tests/e2e/fixtures/withLoopMarker.xml
new file mode 100644
index 00000000..563c8d01
--- /dev/null
+++ b/tests/e2e/fixtures/withLoopMarker.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:pm="http://processmaker.com/BPMN/2.0/Schema.xsd" id="Definitions_03dabax" targetNamespace="http://bpmn.io/schema/bpmn" exporter="ProcessMaker Modeler" exporterVersion="1.0">
+    <bpmn:process id="Process_1" isExecutable="true">
+        <bpmn:task id="node_1" name="Form Task" pm:assignment="requester">
+            <bpmn:standardLoopCharacteristics />
+        </bpmn:task>
+    </bpmn:process>
+    <bpmndi:BPMNDiagram id="BPMNDiagram_1">
+        <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
+            <bpmndi:BPMNShape id="node_1_di" bpmnElement="node_1">
+                <dc:Bounds x="130" y="210" width="116" height="76" />
+            </bpmndi:BPMNShape>
+        </bpmndi:BPMNPlane>
+    </bpmndi:BPMNDiagram>
+</bpmn:definitions>
+s
diff --git a/tests/e2e/specs/TaskMarkerFlags.spec.js b/tests/e2e/specs/TaskMarkerFlags.spec.js
new file mode 100644
index 00000000..9d395d8f
--- /dev/null
+++ b/tests/e2e/specs/TaskMarkerFlags.spec.js
@@ -0,0 +1,92 @@
+import {
+  assertDownloadedXmlContainsExpected, assertDownloadedXmlDoesNotContainExpected,
+  dragFromSourceToDest, uploadXml, waitToRenderNodeUpdates,
+} from '../support/utils';
+
+import { nodeTypes } from '../support/constants';
+
+function assertBottomCenterTaskMarkerHasImage(iconName, markerIndex = 0) {
+  cy.get(`.main-paper [data-type="processmaker.components.nodes.task.Shape"] [joint-selector="bottomCenter.${markerIndex}"]`)
+    .should('have.attr', 'xlink:href')
+    // eslint-disable-next-line no-useless-escape
+    .and('match', new RegExp(`${iconName}.*\.svg`));
+}
+
+describe('Task Marker Flags', () => {
+  beforeEach(() => {
+    const taskPosition = { x: 250, y: 250 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+    cy.contains('Advanced').click();
+  });
+
+  it('sets a task as "parallel multi-instance"', () => {
+    cy.get('[data-test=parallel_mi]').check({ force: true });
+    cy.get('[data-test=for-compensation').uncheck({ force: true });
+
+    assertDownloadedXmlContainsExpected('<bpmn:multiInstanceLoopCharacteristics />');
+    assertBottomCenterTaskMarkerHasImage('parallel');
+  });
+
+  it('sets a task as "sequential multi-instance"', () => {
+    cy.get('[data-test=sequential_mi]').check({ force: true });
+    cy.get('[data-test=for-compensation').uncheck({ force: true });
+
+    assertDownloadedXmlContainsExpected('<bpmn:multiInstanceLoopCharacteristics isSequential="true" />');
+    assertBottomCenterTaskMarkerHasImage('sequential');
+  });
+
+  it('sets a task as "standard loop"', () => {
+    cy.get('[data-test=loop]').check({ force: true });
+    cy.get('[data-test=for-compensation').uncheck({ force: true });
+
+    assertDownloadedXmlContainsExpected('<bpmn:standardLoopCharacteristics />');
+    assertBottomCenterTaskMarkerHasImage('loop');
+  });
+
+  it('can unset loop characteristics', () => {
+    cy.get('[data-test=loop]').check({ force: true });
+    waitToRenderNodeUpdates();
+    cy.get('[data-test=for-compensation').uncheck({ force: true });
+    waitToRenderNodeUpdates();
+
+    assertDownloadedXmlContainsExpected('<bpmn:standardLoopCharacteristics />');
+    assertBottomCenterTaskMarkerHasImage('loop');
+
+    cy.get('[data-test=no_loop]').check({ force: true });
+    waitToRenderNodeUpdates();
+
+    assertDownloadedXmlDoesNotContainExpected('<bpmn:standardLoopCharacteristics />');
+    cy.get('.main-paper [data-type="processmaker.components.nodes.task.Shape"] [joint-selector="bottomCenter.0"]')
+      .should('not.have.attr', 'xlink:href');
+  });
+
+  it('sets a task as "for compensation"', () => {
+    cy.get('[data-test=for-compensation').check({ force: true });
+    assertDownloadedXmlContainsExpected('<bpmn:task id="node_2" name="Form Task" isForCompensation="true" pm:assignment="requester" />');
+    assertBottomCenterTaskMarkerHasImage('compensation');
+  });
+
+  it('keeps compensation as the leftmost of the center icons', () => {
+    cy.get('[data-test=loop]').check({ force: true });
+    cy.get('[data-test=for-compensation').check({ force: true });
+
+    assertBottomCenterTaskMarkerHasImage('compensation');
+    assertBottomCenterTaskMarkerHasImage('loop', '1');
+  });
+
+  it('can still set "for compensation" after undo/redo', () => {
+    uploadXml('withLoopMarker.xml');
+
+    cy.get('.main-paper [data-type="processmaker.components.nodes.task.Shape"]').click();
+    cy.contains('Advanced').click();
+    cy.get('[data-test=for-compensation').check({ force: true });
+
+    assertBottomCenterTaskMarkerHasImage('compensation');
+    assertBottomCenterTaskMarkerHasImage('loop', 1);
+    assertDownloadedXmlContainsExpected(`
+      <bpmn:task id="node_1" name="Form Task" isForCompensation="true" pm:assignment="requester">
+        <bpmn:standardLoopCharacteristics />
+      </bpmn:task>
+    `);
+  });
+});
diff --git a/tests/e2e/specs/Validation.spec.js b/tests/e2e/specs/Validation.spec.js
index 6e6d33c7..c3ca4182 100644
--- a/tests/e2e/specs/Validation.spec.js
+++ b/tests/e2e/specs/Validation.spec.js
@@ -131,4 +131,21 @@ describe('Validation', () => {
       expect(win.console.error).to.have.callCount(0);
     });
   });
+
+  it('has no validation errors for a valid basic diagram', () => {
+    const startEventPosition = { x: 150, y: 150 };
+    const taskPosition = { x: 250, y: 250 };
+    const endEventPosition = { x: 350, y: 350 };
+
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    connectNodesWithFlow('sequence-flow-button', startEventPosition, taskPosition);
+    connectNodesWithFlow('sequence-flow-button', taskPosition, endEventPosition);
+
+    cy.get('[data-test="validation-toggle"]').click({ force: true });
+
+    cy.get('[data-test=validation-list-valid]')
+      .should('contain.text', 'BPMN Valid');
+  });
 });
diff --git a/tests/unit/components/mixins/modules/recalculateMarkerAlignment.spec.js b/tests/unit/components/mixins/modules/recalculateMarkerAlignment.spec.js
index 8988c683..30df8690 100644
--- a/tests/unit/components/mixins/modules/recalculateMarkerAlignment.spec.js
+++ b/tests/unit/components/mixins/modules/recalculateMarkerAlignment.spec.js
@@ -42,4 +42,22 @@ describe('recalculateMarkerAlignment', () => {
     });
 
   });
+
+  it('sorts the bottom centre markers alphabetically by key', () => {
+    const shape = new TaskShape();
+    const markers = {
+      topLeft: {},
+      topCenter: {},
+      topRight: {},
+      bottomLeft: {},
+      bottomCenter: { c: 'third', b: 'second', a: 'first' },
+      bottomRight: {},
+    };
+
+    recalculateMarkerAlignment(markers, shape);
+
+    expect(shape.attributes.attrs['bottomCenter.0']['xlink:href']).toEqual('first');
+    expect(shape.attributes.attrs['bottomCenter.1']['xlink:href']).toEqual('second');
+    expect(shape.attributes.attrs['bottomCenter.2']['xlink:href']).toEqual('third');
+  });
 });
diff --git a/tests/unit/components/nodes/task/setupMultiInstanceMarkers.spec.js b/tests/unit/components/nodes/task/setupMultiInstanceMarkers.spec.js
index 1bdc6363..4e1f121d 100644
--- a/tests/unit/components/nodes/task/setupMultiInstanceMarkers.spec.js
+++ b/tests/unit/components/nodes/task/setupMultiInstanceMarkers.spec.js
@@ -5,6 +5,7 @@ import loopIcon from '@/assets/loop.svg';
 
 describe('setupMultiInstanceMarkers', () => {
   let $set = jest.fn();
+  let $delete = jest.fn();
   let markers = {
     bottomCenter: {},
   };
@@ -12,9 +13,10 @@ describe('setupMultiInstanceMarkers', () => {
   it('does not set up markers for task without loop characteristics', () => {
     const nodeDefinition = { get(){} };
 
-    setupMultiInstanceMarkers(nodeDefinition, {}, $set);
+    setupMultiInstanceMarkers(nodeDefinition, {}, $set, $delete);
 
     expect($set).not.toHaveBeenCalled();
+    expect($delete).toHaveBeenCalledTimes(1);
   });
 
   it('sets sequential icon for sequential multi instance task', () => {