Repository: ProcessMaker/modeler (js)

Issue:
Add messageRef attribute to intermediate message throw event
Add a text input in the inspector to specify the message to be thrown:
<img width="347" alt="Screen Shot 2019-10-15 at 1 33 10 PM" src="https://user-images.githubusercontent.com/7561061/66854949-58ef2300-ef50-11e9-8ba2-c12235b2cf90.png">


Pull request description:
Add message ref to intermediate message throw event
Closes #746

Added unit and cypress tests.

Some odd commits got in here after branching off #747, so it needs to be squashed.

Patch:
diff --git a/src/components/nodes/intermediateMessageCatchEvent/CatchEventMessageSelect.vue b/src/components/nodes/intermediateMessageCatchEvent/CatchEventMessageSelect.vue
index 089813eb..b6ddc861 100644
--- a/src/components/nodes/intermediateMessageCatchEvent/CatchEventMessageSelect.vue
+++ b/src/components/nodes/intermediateMessageCatchEvent/CatchEventMessageSelect.vue
@@ -1,7 +1,8 @@
 <template>
   <form-select
     v-bind="$attrs"
-    v-on="$listeners"
+    :value="messageId"
+    @input="emitMessage"
     :disabled="messagesList.length === 0"
     :options="messagesList"
     class="p-0 mb-2"
@@ -10,7 +11,7 @@
 
 <script>
 import store from '@/store';
-import { getMessagesList } from './intermediateMessageCatchEventUtils';
+import { getMessagesList, getMessage } from './intermediateMessageCatchEventUtils';
 
 export default {
   inheritAttrs: false,
@@ -19,6 +20,15 @@ export default {
     messagesList() {
       return getMessagesList(store);
     },
+    messageId() {
+      return this.value ? this.value.id : '';
+    },
+  },
+  methods: {
+    emitMessage(messageId) {
+      const message = getMessage(store, messageId);
+      this.$emit('input', message);
+    },
   },
 };
 </script>
diff --git a/src/components/nodes/intermediateMessageCatchEvent/index.js b/src/components/nodes/intermediateMessageCatchEvent/index.js
index c53eb06a..7e3dd7be 100644
--- a/src/components/nodes/intermediateMessageCatchEvent/index.js
+++ b/src/components/nodes/intermediateMessageCatchEvent/index.js
@@ -1,14 +1,10 @@
 import component from './intermediateMessageCatchEvent.vue';
-import MessageEventIdGenerator from '../../../MessageEventIdGenerator';
 import CatchEventMessageSelect from './CatchEventMessageSelect';
 import merge from 'lodash/merge';
 import cloneDeep from 'lodash/cloneDeep';
 import intermediateMessageEventConfig from '@/components/nodes/intermediateMessageEvent';
-import idConfigSettings from '@/components/inspectors/idConfigSettings';
 import omit from 'lodash/omit';
 
-const messageEventIdGenerator = new MessageEventIdGenerator();
-
 export default merge(cloneDeep(intermediateMessageEventConfig), {
   id: 'processmaker-modeler-intermediate-message-catch-event',
   component,
@@ -18,22 +14,15 @@ export default merge(cloneDeep(intermediateMessageEventConfig), {
   definition(moddle, $t) {
     return moddle.create('bpmn:IntermediateCatchEvent', {
       name: $t('Intermediate Message Catch Event'),
-      allowedUsers: '',
-      allowedGroups: '',
-      whitelist: '',
       eventDefinitions: [
-        moddle.create('bpmn:MessageEventDefinition', {
-          id: messageEventIdGenerator.generateNewMessageEventId(),
-          variableName: 'message',
-        }),
+        moddle.create('bpmn:MessageEventDefinition'),
       ],
     });
   },
   inspectorData(node) {
     return Object.entries(node.definition).reduce((data, [key, value]) => {
       if (key === 'eventDefinitions') {
-        data.eventDefinitionId = value[0].get('id');
-        data.variableName = value[0].get('variableName');
+        data.messageRef = value[0].get('messageRef');
       } else {
         data[key] = value;
       }
@@ -42,7 +31,7 @@ export default merge(cloneDeep(intermediateMessageEventConfig), {
     }, {});
   },
   inspectorHandler(value, node, setNodeProp, moddle) {
-    for (const key in omit(value, ['$type', 'eventDefinitionId', 'variableName', 'messageRef'])) {
+    for (const key in omit(value, ['$type', 'messageRef'])) {
       if (node.definition[key] === value[key]) {
         continue;
       }
@@ -50,14 +39,11 @@ export default merge(cloneDeep(intermediateMessageEventConfig), {
       setNodeProp(node, key, value[key]);
     }
 
-    if (
-      node.definition.eventDefinitions[0].get('id') !== value.variableName ||
-      node.definition.eventDefinitions[0].get('variableName') !== value.eventDefinitionId
-    ) {
+    if (node.definition.eventDefinitions[0].get('messageRef') !== value.messageRef) {
+
       setNodeProp(node, 'eventDefinitions', [
         moddle.create('bpmn:MessageEventDefinition', {
-          id: value['eventDefinitionId'],
-          variableName: value['variableName'],
+          messageRef: value.messageRef,
         }),
       ]);
     }
@@ -68,55 +54,6 @@ export default merge(cloneDeep(intermediateMessageEventConfig), {
         {
           items: [
             {},
-            {
-              component: 'FormInput',
-              config: {
-                label: 'Message Event Identifier',
-                helper: idConfigSettings.helper,
-                name: 'eventDefinitionId',
-                validation: 'required',
-              },
-            },
-            {
-              component: 'FormInput',
-              config: {
-                label: 'Variable Name',
-                helper: 'The Name of the variable',
-                name: 'variableName',
-              },
-            },
-            {
-              component: 'FormSelect',
-              config: {
-                label: 'Allowed Users',
-                helper: 'Select allowed users',
-                name: 'allowedUsers',
-                options: [
-                  {},
-                  { value: '1,10', content: '1,10' },
-                ],
-              },
-            },
-            {
-              component: 'FormSelect',
-              config: {
-                label: 'Allowed Groups',
-                helper: 'Select allowed groups',
-                name: 'allowedGroups',
-                options: [
-                  {},
-                  { value: '20,30', content: '20,30' },
-                ],
-              },
-            },
-            {
-              component: 'FormInput',
-              config: {
-                label: 'Whitelist',
-                helper: 'IP/Domain whitelist',
-                name: 'whitelist',
-              },
-            },
             {
               component: CatchEventMessageSelect,
               config: {
diff --git a/src/components/nodes/intermediateMessageCatchEvent/intermediateMessageCatchEventUtils.js b/src/components/nodes/intermediateMessageCatchEvent/intermediateMessageCatchEventUtils.js
index 0c9d1fa5..cdaca696 100644
--- a/src/components/nodes/intermediateMessageCatchEvent/intermediateMessageCatchEventUtils.js
+++ b/src/components/nodes/intermediateMessageCatchEvent/intermediateMessageCatchEventUtils.js
@@ -14,3 +14,8 @@ export function getMessagesList(store) {
     .filter(isMessageElement)
     .map(toDropdownFormat);
 }
+
+export function getMessage(store, id) {
+  return store.getters.rootElements
+    .find(element => element.get('id') === id);
+}
diff --git a/src/components/nodes/intermediateMessageThrowEvent/index.js b/src/components/nodes/intermediateMessageThrowEvent/index.js
index ec89a648..58315f1a 100644
--- a/src/components/nodes/intermediateMessageThrowEvent/index.js
+++ b/src/components/nodes/intermediateMessageThrowEvent/index.js
@@ -2,6 +2,7 @@ import component from './intermediateMessageThrowEvent.vue';
 import merge from 'lodash/merge';
 import cloneDeep from 'lodash/cloneDeep';
 import intermediateMessageEventConfig from '@/components/nodes/intermediateMessageEvent';
+import omit from 'lodash/omit';
 
 export default merge(cloneDeep(intermediateMessageEventConfig), {
   id: 'processmaker-modeler-intermediate-message-throw-event',
@@ -13,8 +14,51 @@ export default merge(cloneDeep(intermediateMessageEventConfig), {
     return moddle.create('bpmn:IntermediateThrowEvent', {
       name: $t('Intermediate Message Throw Event'),
       eventDefinitions: [
-        moddle.create('bpmn:MessageEventDefinition', {}),
+        moddle.create('bpmn:MessageEventDefinition'),
       ],
     });
   },
+  inspectorData(node) {
+    return Object.entries(node.definition).reduce((data, [key, value]) => {
+      if (key === 'eventDefinitions') {
+        data.messageName = value[0].get('messageRef').name;
+      } else {
+        data[key] = value;
+      }
+
+      return data;
+    }, {});
+  },
+  inspectorHandler(value, node, setNodeProp) {
+    for (const key in omit(value, ['$type', 'eventDefinitions', 'messageName'])) {
+      if (node.definition[key] === value[key]) {
+        continue;
+      }
+
+      setNodeProp(node, key, value[key]);
+    }
+
+    const message = node.definition.get('eventDefinitions')[0].messageRef;
+    if (message.name !== value.messageName) {
+      message.name = value.messageName;
+    }
+  },
+  inspectorConfig: [
+    {
+      items: [
+        {
+          items: [
+            {},
+            {
+              component: 'FormInput',
+              config: {
+                label: 'Message Name',
+                name: 'messageName',
+              },
+            },
+          ],
+        },
+      ],
+    },
+  ],
 });
diff --git a/src/components/nodes/intermediateMessageThrowEvent/intermediateMessageThrowEvent.vue b/src/components/nodes/intermediateMessageThrowEvent/intermediateMessageThrowEvent.vue
index d4458189..53afaec1 100644
--- a/src/components/nodes/intermediateMessageThrowEvent/intermediateMessageThrowEvent.vue
+++ b/src/components/nodes/intermediateMessageThrowEvent/intermediateMessageThrowEvent.vue
@@ -5,9 +5,19 @@
 <script>
 import IntermediateEvent from '@/components/nodes/intermediateEvent/intermediateEvent';
 import intermediateMailSymbol from '@/assets/intermediate-mail-alt.svg';
+import pull from 'lodash/pull';
 
 export default {
   extends: IntermediateEvent,
+  props: ['moddle', 'rootElements', 'id'],
+  data() {
+    return {
+      message: this.moddle.create('bpmn:Message', {
+        id: `${ this.id }_message`,
+        name: `${ this.id }_message`,
+      }),
+    };
+  },
   mounted() {
     this.shape.attr('image/xlink:href', intermediateMailSymbol);
     this.shape.attr({
@@ -28,6 +38,11 @@ export default {
         x: 2,
       },
     });
+    this.rootElements.push(this.message);
+    this.node.definition.get('eventDefinitions')[0].messageRef = this.message;
+  },
+  destroyed() {
+    pull(this.rootElements, this.message);
   },
 };
 </script>


Test patch:
diff --git a/tests/e2e/specs/IntermediateMessageCatchEvent.spec.js b/tests/e2e/specs/IntermediateMessageCatchEvent.spec.js
deleted file mode 100644
index 5eac4085..00000000
--- a/tests/e2e/specs/IntermediateMessageCatchEvent.spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import {
-  dragFromSourceToDest,
-  getElementAtPosition,
-  typeIntoTextInput,
-  uploadXml,
-  waitToRenderAllShapes,
-} from '../support/utils';
-
-import { nodeTypes } from '../support/constants';
-
-describe('Intermediate Message Catch Event', () => {
-  it('Update properties', function() {
-    const intermediateMessageCatchEventPosition = { x: 200, y: 200 };
-
-    dragFromSourceToDest(nodeTypes.intermediateMessageCatchEvent, intermediateMessageCatchEventPosition);
-
-    getElementAtPosition(intermediateMessageCatchEventPosition).click();
-
-    const name = 'Test name';
-    typeIntoTextInput('[name=name]', name);
-
-    const eventId = '1234';
-    typeIntoTextInput('[name=eventDefinitionId]', eventId);
-
-    const variableName = 'test variablename';
-    typeIntoTextInput('[name=variableName]', variableName);
-
-    cy.get('[name=allowedUsers]').select('1,10');
-    cy.get('[name=allowedGroups]').select('20,30');
-
-    const whiteList = 'example.com';
-    typeIntoTextInput('[name=whitelist]', whiteList);
-
-    const validXML =
-      `<bpmn:intermediateCatchEvent id="node_2" name="${name}" pm:allowedUsers="1,10" pm:allowedGroups="20,30" pm:whitelist="${whiteList}">
-      <bpmn:messageEventDefinition id="${eventId}" pm:variableName="${variableName}" />
-    </bpmn:intermediateCatchEvent>`;
-
-    cy.get('[data-test=downloadXMLBtn]').click();
-    cy.window()
-      .its('xml')
-      .then(xml => xml.trim())
-      .should('contain', validXML.trim());
-  });
-
-  it('Message Event Definition Ids are unique on render', function() {
-    const intermediateMessageCatchEventPosition = { x: 200, y: 200 };
-    dragFromSourceToDest(nodeTypes.intermediateMessageCatchEvent, intermediateMessageCatchEventPosition);
-
-    const intermediateMessageCatchEventSecondPosition = { x: 300, y: 300 };
-    dragFromSourceToDest(nodeTypes.intermediateMessageCatchEvent, intermediateMessageCatchEventSecondPosition);
-
-    const validXML =
-      `<bpmn:intermediateCatchEvent id="node_2" name="Intermediate Message Catch Event" pm:allowedUsers="" pm:allowedGroups="" pm:whitelist="">
-      <bpmn:messageEventDefinition id="message_event_1" pm:variableName="message" />
-    </bpmn:intermediateCatchEvent>
-    <bpmn:intermediateCatchEvent id="node_3" name="Intermediate Message Catch Event" pm:allowedUsers="" pm:allowedGroups="" pm:whitelist="">
-      <bpmn:messageEventDefinition id="message_event_2" pm:variableName="message" />
-    </bpmn:intermediateCatchEvent>`;
-
-    cy.get('[data-test=downloadXMLBtn]').click();
-    cy.window()
-      .its('xml')
-      .then(xml => xml.trim())
-      .should('contain', validXML.trim());
-  });
-
-  it('Generates sequential, unique messageEvent IDs', function() {
-    waitToRenderAllShapes();
-
-    const intermediateMessageCatchEventPosition = { x: 200, y: 200 };
-    dragFromSourceToDest(nodeTypes.intermediateMessageCatchEvent, intermediateMessageCatchEventPosition);
-    getElementAtPosition(intermediateMessageCatchEventPosition).click();
-
-    cy.get('[name=eventDefinitionId]').should('have.value', 'message_event_1');
-
-    typeIntoTextInput('[name=eventDefinitionId]', 'message_event_2');
-
-    const intermediateMessageCatchEvent2Position = { x: 250, y: 250 };
-    dragFromSourceToDest(nodeTypes.intermediateMessageCatchEvent, intermediateMessageCatchEvent2Position);
-    getElementAtPosition(intermediateMessageCatchEvent2Position).click();
-
-    cy.get('[name=eventDefinitionId]').should('have.value', 'message_event_3');
-
-    const intermediateMessageCatchEvent3Position = { x: 300, y: 300 };
-    dragFromSourceToDest(nodeTypes.intermediateMessageCatchEvent, intermediateMessageCatchEvent3Position);
-    getElementAtPosition(intermediateMessageCatchEvent3Position).click();
-
-    cy.get('[name=eventDefinitionId]').should('have.value', 'message_event_4');
-
-    uploadXml('messageFlow.xml');
-
-    dragFromSourceToDest(nodeTypes.intermediateMessageCatchEvent, intermediateMessageCatchEventPosition);
-    getElementAtPosition(intermediateMessageCatchEventPosition).click();
-
-    cy.get('[name=eventDefinitionId]').should('have.value', 'message_event_5');
-  });
-});
diff --git a/tests/e2e/specs/IntermediateMessageThrowEvent.spec.js b/tests/e2e/specs/IntermediateMessageThrowEvent.spec.js
index 18faef0f..c45b4a0b 100644
--- a/tests/e2e/specs/IntermediateMessageThrowEvent.spec.js
+++ b/tests/e2e/specs/IntermediateMessageThrowEvent.spec.js
@@ -1,25 +1,109 @@
 import {
   dragFromSourceToDest,
+  getCrownButtonForElement,
   getElementAtPosition,
   removeIndentationAndLinebreaks,
+  typeIntoTextInput,
+  waitToRenderAllShapes,
 } from '../support/utils';
 
 import { nodeTypes } from '../support/constants';
 
+const messageRef = 'node_2_message';
+const messageName = 'awesome message name';
+const eventXMLSnippet = `<bpmn:intermediateThrowEvent id="node_2" name="Intermediate Message Throw Event"><bpmn:messageEventDefinition messageRef="${ messageRef }" /></bpmn:intermediateThrowEvent>`;
+const messageXMLSnippet = `<bpmn:message id="${ messageRef }" name="${ messageName }" />`;
+const intermediateMessageThrowEventPosition = { x: 300, y: 200 };
+
 describe('Intermediate Message Throw Event', () => {
   it('can render an intermediate message throw event', function() {
-    const intermediateMessageThrowEventPosition = { x: 200, y: 200 };
+    dragFromSourceToDest(nodeTypes.intermediateMessageThrowEvent, intermediateMessageThrowEventPosition);
+
+    getElementAtPosition(intermediateMessageThrowEventPosition).click();
+
+    cy.get('[data-test=downloadXMLBtn]').click();
+
+    cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
+      expect(xml).to.contain(eventXMLSnippet);
+    });
+  });
+
+  it('can create a message when intermediate message throw event is dragged on', function() {
+    dragFromSourceToDest(nodeTypes.intermediateMessageThrowEvent, intermediateMessageThrowEventPosition);
 
+    getElementAtPosition(intermediateMessageThrowEventPosition).click();
+
+    typeIntoTextInput('[name=messageName]', messageName);
+
+    cy.get('[data-test=downloadXMLBtn]').click();
+
+    cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
+      expect(xml).to.contain(eventXMLSnippet);
+      expect(xml).to.contain(messageXMLSnippet);
+    });
+  });
+
+  it('can remove the message when intermediate message throw event is deleted', function() {
     dragFromSourceToDest(nodeTypes.intermediateMessageThrowEvent, intermediateMessageThrowEventPosition);
 
     getElementAtPosition(intermediateMessageThrowEventPosition).click();
 
-    const eventXMLSnippet = '<bpmn:intermediateThrowEvent id="node_2" name="Intermediate Message Throw Event"><bpmn:messageEventDefinition /></bpmn:intermediateThrowEvent>';
+    typeIntoTextInput('[name=messageName]', messageName);
+
+    cy.get('[data-test=downloadXMLBtn]').click();
+
+    cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
+      expect(xml).to.contain(messageXMLSnippet);
+    });
+
+    getElementAtPosition(intermediateMessageThrowEventPosition).click().then($intermediateMessageThrowEvent => {
+      getCrownButtonForElement($intermediateMessageThrowEvent, 'delete-button').click();
+    });
+
+    cy.get('[data-test=downloadXMLBtn]').click();
+
+    cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
+      expect(xml).to.not.contain(messageXMLSnippet);
+    });
+  });
+
+  it('retains new message name when clicking off and on intermediate message throw event', function() {
+    const startEventPosition = { x: 150, y: 150 };
+
+    dragFromSourceToDest(nodeTypes.intermediateMessageThrowEvent, intermediateMessageThrowEventPosition);
+
+    getElementAtPosition(intermediateMessageThrowEventPosition).click();
+
+    typeIntoTextInput('[name=messageName]', messageName);
+
+    getElementAtPosition(startEventPosition).click();
+    waitToRenderAllShapes();
+    getElementAtPosition(intermediateMessageThrowEventPosition).click({ force: true });
+    cy.get('[name=messageName]').should('have.value', messageName);
+  });
+
+  it('can associate and rename message on intermediate message catch event', function() {
+    const intermediateMessageCatchEventPosition = { x: 200, y: 300 };
+    const catchEventXMLSnippet = `<bpmn:intermediateCatchEvent id="node_3" name="Intermediate Message Catch Event"><bpmn:messageEventDefinition messageRef="${ messageRef }" /></bpmn:intermediateCatchEvent>`;
+
+    dragFromSourceToDest(nodeTypes.intermediateMessageThrowEvent, intermediateMessageThrowEventPosition);
+    dragFromSourceToDest(nodeTypes.intermediateMessageCatchEvent, intermediateMessageCatchEventPosition);
+
+    getElementAtPosition(intermediateMessageCatchEventPosition).click();
+    cy.get('[name=messageRef]').select(messageRef);
+
+    getElementAtPosition(intermediateMessageThrowEventPosition).click();
+    typeIntoTextInput('[name=messageName]', messageName);
+
+    getElementAtPosition(intermediateMessageCatchEventPosition).click();
+    cy.get('[name=messageRef]').should('contain', messageName);
 
     cy.get('[data-test=downloadXMLBtn]').click();
 
     cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
       expect(xml).to.contain(eventXMLSnippet);
+      expect(xml).to.contain(catchEventXMLSnippet);
+      expect(xml).to.contain(messageXMLSnippet);
     });
   });
 });
diff --git a/tests/unit/intermediateMessageCatchEventUtils.spec.js b/tests/unit/intermediateMessageCatchEventUtils.spec.js
index fe3d2d5b..de6eb944 100644
--- a/tests/unit/intermediateMessageCatchEventUtils.spec.js
+++ b/tests/unit/intermediateMessageCatchEventUtils.spec.js
@@ -1,5 +1,6 @@
 import {
   getMessagesList,
+  getMessage,
 } from '@/components/nodes/intermediateMessageCatchEvent/intermediateMessageCatchEventUtils.js';
 
 function MessageFactory(id, name) {
@@ -43,4 +44,14 @@ describe('intermediateMessageCatchEventUtils', () => {
 
     expect(getMessagesList(store)).toEqual([]);
   });
+
+  it('getMessage returns message', () => {
+    const message = MessageFactory('message_2', 'Message 2');
+    const store = StoreFactory([
+      MessageFactory('message_1', 'Message 1'),
+      message,
+    ]);
+
+    expect(getMessage(store, 'message_2')).toBe(message);
+  });
 });