Bug 1371721 - Create a MESSAGES_ADD action to add batches of messages at once. r=nchevobbe draft
authorbgrins <bgrinstead@mozilla.com>
Fri, 15 Sep 2017 11:10:11 +0200
changeset 668428 3284f209b96bb02ee1c3c669979c68fa66cda4bf
parent 668025 47f7b6c64265bc7bdd22eef7ab71abc97cf3f8bf
child 668429 655a33bfaf9ddb112234137c800bca3989796ff5
child 668433 8eb00f24307e061b3f0b9b058a70e50ee95ea9d4
push id81043
push userbmo:poirot.alex@gmail.com
push dateThu, 21 Sep 2017 16:57:41 +0000
reviewersnchevobbe
bugs1371721
milestone57.0a1
Bug 1371721 - Create a MESSAGES_ADD action to add batches of messages at once. r=nchevobbe MozReview-Commit-ID: GUp3lQpNxfn
devtools/client/webconsole/new-console-output/actions/messages.js
devtools/client/webconsole/new-console-output/constants.js
devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
devtools/client/webconsole/new-console-output/reducers/messages.js
--- a/devtools/client/webconsole/new-console-output/actions/messages.js
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -9,27 +9,53 @@
 const {
   prepareMessage
 } = require("devtools/client/webconsole/new-console-output/utils/messages");
 const { IdGenerator } = require("devtools/client/webconsole/new-console-output/utils/id-generator");
 const { batchActions } = require("devtools/client/shared/redux/middleware/debounce");
 
 const {
   MESSAGE_ADD,
+  MESSAGES_ADD,
   NETWORK_MESSAGE_UPDATE,
   NETWORK_UPDATE_REQUEST,
   MESSAGES_CLEAR,
   MESSAGE_OPEN,
   MESSAGE_CLOSE,
   MESSAGE_TYPE,
   MESSAGE_TABLE_RECEIVE,
 } = require("../constants");
 
 const defaultIdGenerator = new IdGenerator();
 
+function messagesAdd(packets, idGenerator = null) {
+  if (idGenerator == null) {
+    idGenerator = defaultIdGenerator;
+  }
+  let messages = packets.map(packet => prepareMessage(packet, idGenerator));
+  for (let i = messages.length - 1; i >= 0; i--) {
+    if (messages[i].type === MESSAGE_TYPE.CLEAR) {
+      return batchActions([
+        messagesClear(),
+        {
+          type: MESSAGES_ADD,
+          messages: messages.slice(i),
+        }
+      ]);
+    }
+  }
+
+  // When this is used for non-cached messages then handle clear message and
+  // split up into batches
+  return {
+    type: MESSAGES_ADD,
+    messages
+  };
+}
+
 function messageAdd(packet, idGenerator = null) {
   if (idGenerator == null) {
     idGenerator = defaultIdGenerator;
   }
   let message = prepareMessage(packet, idGenerator);
   const addMessageAction = {
     type: MESSAGE_ADD,
     message
@@ -112,16 +138,17 @@ function networkUpdateRequest(id, data) 
     type: NETWORK_UPDATE_REQUEST,
     id,
     data,
   };
 }
 
 module.exports = {
   messageAdd,
+  messagesAdd,
   messagesClear,
   messageOpen,
   messageClose,
   messageTableDataGet,
   networkMessageUpdate,
   networkUpdateRequest,
   // for test purpose only.
   messageTableDataReceive,
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const actionTypes = {
   BATCH_ACTIONS: "BATCH_ACTIONS",
   MESSAGE_ADD: "MESSAGE_ADD",
+  MESSAGES_ADD: "MESSAGES_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
   MESSAGE_OPEN: "MESSAGE_OPEN",
   MESSAGE_CLOSE: "MESSAGE_CLOSE",
   NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
   NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
   MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
   REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
   TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -4,26 +4,27 @@
 "use strict";
 
 // React & Redux
 const React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 const actions = require("devtools/client/webconsole/new-console-output/actions/index");
-const { batchActions } = require("devtools/client/shared/redux/middleware/debounce");
 const { createContextMenu } = require("devtools/client/webconsole/new-console-output/utils/context-menu");
 const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
 
 const EventEmitter = require("devtools/shared/old-event-emitter");
 const ConsoleOutput = React.createFactory(require("devtools/client/webconsole/new-console-output/components/console-output"));
 const FilterBar = React.createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
 
 let store = null;
-let queuedActions = [];
+let queuedMessageAdds = [];
+let queuedMessageUpdates = [];
+let queuedRequestUpdates = [];
 let throttledDispatchTimeout = false;
 
 function NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, document) {
   EventEmitter.decorate(this);
 
   this.parentNode = parentNode;
   this.jsterm = jsterm;
   this.toolbox = toolbox;
@@ -176,67 +177,65 @@ NewConsoleOutputWrapper.prototype = {
         filterBar,
         childComponent
     ));
     this.body = ReactDOM.render(provider, this.parentNode);
 
     this.jsterm.focus();
   },
   dispatchMessageAdd: function (message, waitForResponse) {
-    let action = actions.messageAdd(message);
-    batchedMessageAdd(action);
+    batchedMessagesAdd(message);
     // Wait for the message to render to resolve with the DOM node.
     // This is just for backwards compatibility with old tests, and should
     // be removed once it's not needed anymore.
     // Can only wait for response if the action contains a valid message.
-    if (waitForResponse && action.message) {
-      let messageId = action.message.id;
+    if (waitForResponse) {
+      let {timeStamp} = message;
       return new Promise(resolve => {
         let jsterm = this.jsterm;
         jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
           for (let m of messages) {
-            if (m.messageId === messageId) {
+            if (m.timeStamp === timeStamp) {
               resolve(m.node);
               jsterm.hud.off("new-messages", onThisMessage);
               return;
             }
           }
         });
       });
     }
 
     return Promise.resolve();
   },
 
   dispatchMessagesAdd: function (messages) {
-    const batchedActions = messages.map(message => actions.messageAdd(message));
-    store.dispatch(batchActions(batchedActions));
+    store.dispatch(actions.messagesAdd(messages));
   },
 
   dispatchMessagesClear: function () {
     store.dispatch(actions.messagesClear());
   },
 
   dispatchTimestampsToggle: function (enabled) {
     store.dispatch(actions.timestampsToggle(enabled));
   },
 
   dispatchMessageUpdate: function (message, res) {
     // network-message-updated will emit when all the update message arrives.
     // Since we can't ensure the order of the network update, we check
     // that networkInfo.updates has all we need.
     const NUMBER_OF_NETWORK_UPDATE = 8;
     if (res.networkInfo.updates.length === NUMBER_OF_NETWORK_UPDATE) {
-      batchedMessageAdd(actions.networkMessageUpdate(message));
+      batchedMessageUpdates(message);
       this.jsterm.hud.emit("network-message-updated", res);
     }
   },
 
   dispatchRequestUpdate: function (id, data) {
-    batchedMessageAdd(actions.networkUpdateRequest(id, data));
+    batchedRequestUpdates({ id, data });
 
     // Fire an event indicating that all data fetched from
     // the backend has been received. This is based on
     // 'FirefoxDataProvider.isQueuePayloadReady', see more
     // comments in that method.
     // (netmonitor/src/connector/firefox-data-provider).
     // This event might be utilized in tests to find the right
     // time when to finish.
@@ -244,21 +243,43 @@ NewConsoleOutputWrapper.prototype = {
   },
 
   // Should be used for test purpose only.
   getStore: function () {
     return store;
   }
 };
 
-function batchedMessageAdd(action) {
-  queuedActions.push(action);
+function setTimeoutIfNeeded() {
   if (!throttledDispatchTimeout) {
     throttledDispatchTimeout = setTimeout(() => {
-      store.dispatch(batchActions(queuedActions));
-      queuedActions = [];
+      store.dispatch(actions.messagesAdd(queuedMessageAdds));
+      queuedMessageUpdates.forEach(message => {
+        actions.networkMessageUpdate(message);
+      });
+      queuedRequestUpdates.forEach(({ id, data}) => {
+        actions.networkUpdateRequest(id, data);
+      });
+      queuedMessageAdds = [];
+      queuedMessageUpdates = [];
+      queuedRequestUpdates = [];
       throttledDispatchTimeout = null;
     }, 50);
   }
 }
 
+function batchedMessageUpdates(message) {
+  queuedMessageUpdates.push(message);
+  setTimeoutIfNeeded();
+}
+
+function batchedRequestUpdates(message) {
+  queuedRequestUpdates.push(message);
+  setTimeoutIfNeeded();
+}
+
+function batchedMessagesAdd(message) {
+  queuedMessageAdds.push(message);
+  setTimeoutIfNeeded();
+}
+
 // Exports from this module
 module.exports = NewConsoleOutputWrapper;
--- a/devtools/client/webconsole/new-console-output/reducers/messages.js
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -49,105 +49,136 @@ const MessageState = Immutable.Record({
   removedActors: [],
   // Map of the form {messageId : numberOfRepeat}
   repeatById: {},
   // Map of the form {messageId : networkInformation}
   // `networkInformation` holds request, response, totalTime, ...
   networkMessagesUpdateById: {},
 });
 
-function messages(state = new MessageState(), action, filtersState, prefsState) {
+function addMessage(state, filtersState, prefsState, newMessage) {
   const {
     messagesById,
     messagesUiById,
-    messagesTableDataById,
-    networkMessagesUpdateById,
     groupsById,
     currentGroup,
     repeatById,
     visibleMessages,
     filteredMessagesCount,
   } = state;
 
+  if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
+    // When the message has a NULL type, we don't add it.
+    return state;
+  }
+
+  if (newMessage.type === constants.MESSAGE_TYPE.END_GROUP) {
+    // Compute the new current group.
+    return state.set("currentGroup", getNewCurrentGroup(currentGroup, groupsById));
+  }
+
+  if (newMessage.allowRepeating && messagesById.size > 0) {
+    let lastMessage = messagesById.last();
+    if (
+      lastMessage.repeatId === newMessage.repeatId
+      && lastMessage.groupId === currentGroup
+    ) {
+      return state.set(
+        "repeatById",
+        Object.assign({}, repeatById, {
+          [lastMessage.id]: (repeatById[lastMessage.id] || 1) + 1
+        })
+      );
+    }
+  }
+
+  return state.withMutations(function (record) {
+    // Add the new message with a reference to the parent group.
+    let parentGroups = getParentGroups(currentGroup, groupsById);
+    newMessage.groupId = currentGroup;
+    newMessage.indent = parentGroups.length;
+
+    const addedMessage = Object.freeze(newMessage);
+    record.set(
+      "messagesById",
+      messagesById.set(newMessage.id, addedMessage)
+    );
+
+    if (newMessage.type === "trace") {
+      // We want the stacktrace to be open by default.
+      record.set("messagesUiById", messagesUiById.push(newMessage.id));
+    } else if (isGroupType(newMessage.type)) {
+      record.set("currentGroup", newMessage.id);
+      record.set("groupsById", groupsById.set(newMessage.id, parentGroups));
+
+      if (newMessage.type === constants.MESSAGE_TYPE.START_GROUP) {
+        // We want the group to be open by default.
+        record.set("messagesUiById", messagesUiById.push(newMessage.id));
+      }
+    }
+
+    const {
+      visible,
+      cause
+    } = getMessageVisibility(addedMessage, record, filtersState);
+
+    if (visible) {
+      record.set("visibleMessages", [...visibleMessages, newMessage.id]);
+    } else if (DEFAULT_FILTERS.includes(cause)) {
+      record.set("filteredMessagesCount", Object.assign({}, filteredMessagesCount, {
+        global: filteredMessagesCount.global + 1,
+        [cause]: filteredMessagesCount[cause] + 1
+      }));
+    }
+  });
+}
+
+function messages(state = new MessageState(), action, filtersState, prefsState) {
+  const {
+    messagesById,
+    messagesUiById,
+    messagesTableDataById,
+    networkMessagesUpdateById,
+    groupsById,
+    visibleMessages,
+  } = state;
+
   const {logLimit} = prefsState;
 
+  let newState;
   switch (action.type) {
-    case constants.MESSAGE_ADD:
-      let newMessage = action.message;
-
-      if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
-        // When the message has a NULL type, we don't add it.
-        return state;
-      }
+    case constants.MESSAGES_ADD:
+      newState = state;
 
-      if (newMessage.type === constants.MESSAGE_TYPE.END_GROUP) {
-        // Compute the new current group.
-        return state.set("currentGroup", getNewCurrentGroup(currentGroup, groupsById));
-      }
-
-      if (newMessage.allowRepeating && messagesById.size > 0) {
-        let lastMessage = messagesById.last();
-        if (
-          lastMessage.repeatId === newMessage.repeatId
-          && lastMessage.groupId === currentGroup
-        ) {
-          return state.set(
-            "repeatById",
-            Object.assign({}, repeatById, {
-              [lastMessage.id]: (repeatById[lastMessage.id] || 1) + 1
-            })
-          );
+      // Preemptively remove messages that will never be rendered
+      let list = [];
+      let prunableCount = 0;
+      for (let i = action.messages.length - 1; i >= 0; i--) {
+        if (!action.messages[i].groupId && !isGroupType(action.messages[i].type) &&
+            action.messages[i].type !== MESSAGE_TYPE.END_GROUP) {
+          prunableCount++;
+          // Once we've added the max number of messages that can be added stop.
+          // TODO: what about repeats?
+          if (prunableCount <= logLimit) {
+            list.unshift(action.messages[i]);
+          }
+        } else {
+          list.unshift(action.messages[i]);
         }
       }
 
-      return state.withMutations(function (record) {
-        // Add the new message with a reference to the parent group.
-        let parentGroups = getParentGroups(currentGroup, groupsById);
-        newMessage.groupId = currentGroup;
-        newMessage.indent = parentGroups.length;
-
-        const addedMessage = Object.freeze(newMessage);
-        record.set(
-          "messagesById",
-          messagesById.set(newMessage.id, addedMessage)
-        );
-
-        if (newMessage.type === "trace") {
-          // We want the stacktrace to be open by default.
-          record.set("messagesUiById", messagesUiById.push(newMessage.id));
-        } else if (isGroupType(newMessage.type)) {
-          record.set("currentGroup", newMessage.id);
-          record.set("groupsById", groupsById.set(newMessage.id, parentGroups));
+      list.forEach(message => {
+        newState = addMessage(newState, filtersState, prefsState, message);
+      });
 
-          if (newMessage.type === constants.MESSAGE_TYPE.START_GROUP) {
-            // We want the group to be open by default.
-            record.set("messagesUiById", messagesUiById.push(newMessage.id));
-          }
-        }
-
-        const {
-          visible,
-          cause
-        } = getMessageVisibility(addedMessage, record, filtersState);
+      return limitTopLevelMessageCount(newState, logLimit);
 
-        if (visible) {
-          record.set("visibleMessages", [...visibleMessages, newMessage.id]);
-        } else if (DEFAULT_FILTERS.includes(cause)) {
-          record.set("filteredMessagesCount", Object.assign({}, filteredMessagesCount, {
-            global: filteredMessagesCount.global + 1,
-            [cause]: filteredMessagesCount[cause] + 1
-          }));
-        }
-
-        // Remove top level message if the total count of top level messages
-        // exceeds the current limit.
-        if (record.messagesById.size > logLimit) {
-          limitTopLevelMessageCount(state, record, logLimit);
-        }
-      });
+    case constants.MESSAGE_ADD:
+      newState = addMessage(state, filtersState, prefsState, action.message);
+      return limitTopLevelMessageCount(newState, logLimit);
 
     case constants.MESSAGES_CLEAR:
       return new MessageState({
         // Store all actors from removed messages. This array is used by
         // `releaseActorsEnhancer` to release all of those backend actors.
         "removedActors": [...state.messagesById].reduce((res, [id, msg]) => {
           res.push(...getAllActorsInMessage(msg, state));
           return res;
@@ -261,17 +292,17 @@ function messages(state = new MessageSta
                 headers: [],
                 headersSize: 0,
               };
               break;
           }
         }
       }
 
-      let newState = state.set(
+      newState = state.set(
         "networkMessagesUpdateById",
         Object.assign({}, networkMessagesUpdateById, {
           [action.id]: Object.assign({}, request, values)
         })
       );
 
       return newState;
     }
@@ -336,109 +367,109 @@ function getParentGroups(currentGroup, g
   return groups;
 }
 
 /**
  * Remove all top level messages that exceeds message limit.
  * Also populate an array of all backend actors associated with these
  * messages so they can be released.
  */
-function limitTopLevelMessageCount(state, record, logLimit) {
-  let topLevelCount = record.groupsById.size === 0
-    ? record.messagesById.size
-    : getToplevelMessageCount(record);
-
-  if (topLevelCount <= logLimit) {
-    return record;
-  }
-
-  const removedMessagesId = [];
-  const removedActors = [];
-  let visibleMessages = [...record.visibleMessages];
-
-  let cleaningGroup = false;
-  record.messagesById.forEach((message, id) => {
-    // If we were cleaning a group and the current message does not have
-    // a groupId, we're done cleaning.
-    if (cleaningGroup === true && !message.groupId) {
-      cleaningGroup = false;
-    }
+function limitTopLevelMessageCount(state, logLimit) {
+  return state.withMutations(function (record) {
+    let topLevelCount = record.groupsById.size === 0
+      ? record.messagesById.size
+      : getToplevelMessageCount(record);
 
-    // If we're not cleaning a group and the message count is below the logLimit,
-    // we exit the forEach iteration.
-    if (cleaningGroup === false && topLevelCount <= logLimit) {
-      return false;
-    }
-
-    // If we're not currently cleaning a group, and the current message is identified
-    // as a group, set the cleaning flag to true.
-    if (cleaningGroup === false && record.groupsById.has(id)) {
-      cleaningGroup = true;
-    }
-
-    if (!message.groupId) {
-      topLevelCount--;
-    }
-
-    removedMessagesId.push(id);
-    removedActors.push(...getAllActorsInMessage(message, record));
-
-    const index = visibleMessages.indexOf(id);
-    if (index > -1) {
-      visibleMessages.splice(index, 1);
+    if (topLevelCount <= logLimit) {
+      return;
     }
 
-    return true;
-  });
+    const removedMessagesId = [];
+    const removedActors = [];
+    let visibleMessages = [...record.visibleMessages];
 
-  if (removedActors.length > 0) {
-    record.set("removedActors", record.removedActors.concat(removedActors));
-  }
-
-  if (record.visibleMessages.length > visibleMessages.length) {
-    record.set("visibleMessages", visibleMessages);
-  }
+    let cleaningGroup = false;
+    record.messagesById.forEach((message, id) => {
+      // If we were cleaning a group and the current message does not have
+      // a groupId, we're done cleaning.
+      if (cleaningGroup === true && !message.groupId) {
+        cleaningGroup = false;
+      }
 
-  const isInRemovedId = id => removedMessagesId.includes(id);
-  const mapHasRemovedIdKey = map => map.findKey((value, id) => isInRemovedId(id));
-  const objectHasRemovedIdKey = obj => Object.keys(obj).findIndex(isInRemovedId) !== -1;
-  const cleanUpCollection = map => removedMessagesId.forEach(id => map.remove(id));
-  const cleanUpList = list => list.filter(id => {
-    return isInRemovedId(id) === false;
-  });
-  const cleanUpObject = object => [...Object.entries(object)]
-    .reduce((res, [id, value]) => {
-      if (!isInRemovedId(id)) {
-        res[id] = value;
+      // If we're not cleaning a group and the message count is below the logLimit,
+      // we exit the forEach iteration.
+      if (cleaningGroup === false && topLevelCount <= logLimit) {
+        return false;
+      }
+
+      // If we're not currently cleaning a group, and the current message is identified
+      // as a group, set the cleaning flag to true.
+      if (cleaningGroup === false && record.groupsById.has(id)) {
+        cleaningGroup = true;
+      }
+
+      if (!message.groupId) {
+        topLevelCount--;
+      }
+
+      removedMessagesId.push(id);
+      removedActors.push(...getAllActorsInMessage(message, record));
+
+      const index = visibleMessages.indexOf(id);
+      if (index > -1) {
+        visibleMessages.splice(index, 1);
       }
-      return res;
-    }, {});
+
+      return true;
+    });
 
-  record.set("messagesById", record.messagesById.withMutations(cleanUpCollection));
+    if (removedActors.length > 0) {
+      record.set("removedActors", record.removedActors.concat(removedActors));
+    }
+
+    if (record.visibleMessages.length > visibleMessages.length) {
+      record.set("visibleMessages", visibleMessages);
+    }
 
-  if (record.messagesUiById.find(isInRemovedId)) {
-    record.set("messagesUiById", cleanUpList(record.messagesUiById));
-  }
-  if (mapHasRemovedIdKey(record.messagesTableDataById)) {
-    record.set("messagesTableDataById",
-      record.messagesTableDataById.withMutations(cleanUpCollection));
-  }
-  if (mapHasRemovedIdKey(record.groupsById)) {
-    record.set("groupsById", record.groupsById.withMutations(cleanUpCollection));
-  }
-  if (objectHasRemovedIdKey(record.repeatById)) {
-    record.set("repeatById", cleanUpObject(record.repeatById));
-  }
+    const isInRemovedId = id => removedMessagesId.includes(id);
+    const mapHasRemovedIdKey = map => map.findKey((value, id) => isInRemovedId(id));
+    const objectHasRemovedIdKey = obj => Object.keys(obj).findIndex(isInRemovedId) !== -1;
+    const cleanUpCollection = map => removedMessagesId.forEach(id => map.remove(id));
+    const cleanUpList = list => list.filter(id => {
+      return isInRemovedId(id) === false;
+    });
+    const cleanUpObject = object => [...Object.entries(object)]
+      .reduce((res, [id, value]) => {
+        if (!isInRemovedId(id)) {
+          res[id] = value;
+        }
+        return res;
+      }, {});
+
+    record.set("messagesById", record.messagesById.withMutations(cleanUpCollection));
 
-  if (objectHasRemovedIdKey(record.networkMessagesUpdateById)) {
-    record.set("networkMessagesUpdateById",
-      cleanUpObject(record.networkMessagesUpdateById));
-  }
+    if (record.messagesUiById.find(isInRemovedId)) {
+      record.set("messagesUiById", cleanUpList(record.messagesUiById));
+    }
+    if (mapHasRemovedIdKey(record.messagesTableDataById)) {
+      record.set("messagesTableDataById",
+        record.messagesTableDataById.withMutations(cleanUpCollection));
+    }
+    if (mapHasRemovedIdKey(record.groupsById)) {
+      record.set("groupsById", record.groupsById.withMutations(cleanUpCollection));
+    }
+    if (objectHasRemovedIdKey(record.repeatById)) {
+      record.set("repeatById", cleanUpObject(record.repeatById));
+    }
 
-  return record;
+    if (objectHasRemovedIdKey(record.networkMessagesUpdateById)) {
+      record.set("networkMessagesUpdateById",
+        cleanUpObject(record.networkMessagesUpdateById));
+    }
+  });
 }
 
 /**
  * Get an array of all the actors logged in a specific message.
  *
  * @param {Message} message: The message to get actors from.
  * @param {Record} state: The redux state.
  * @return {Array} An array containing all the actors logged in a message.