Repository: boardgameio/boardgame.io
Pull request #865 (2020-12-11 09:22:59)

Add log metadata plugin
Tries to solve the second task in https://github.com/boardgameio/boardgame.io/issues/227
```
2. Custom payload
Allow insertion of custom messages into each log event.
```

I read up on how it used to work in https://github.com/boardgameio/boardgame.io/pull/267 and tried to recreate something similar to that.

Added a Log metadata plugin which can be reached using `ctx.log.setMetadata()` and sets the payload as metadata on the added `LogEntry`.

<img width="319" alt="Screenshot 2020-12-11 at 10 22 18" src="https://user-images.githubusercontent.com/1790340/101885717-c7526400-3b9a-11eb-9528-301e44157f1f.png">


I a bit unsure about the naming of `metadata`, depends what you feel the terminology should be. I tried to find any mismatches and the closest I got was that there is something called action metadata for the bots, but it shouldn't collide from what I could see.

I don't usually write Typescript or Svelte, so if I missed something please point it out and I'll fix it. 🚀 

#### Checklist

* [x] Use a separate branch in your local repo (not `master`).
* [x] Test coverage is 100% (or you have a story for why it's ok).


TomasVidhall: Forgot to add reviewers, labels etc and now I can't edit them, please help! 🤦 


TomasVidhall: Thanks for the great review @delucis. I've updated the PR with the suggested changes. Feel free to give me more feedback.
delucis: Changes look good @TomasVidhall! Will run a few local tests when I’m back at my computer, but I think this looks ready to merge.

Patch:
diff --git a/src/client/debug/log/CustomPayload.svelte b/src/client/debug/log/CustomPayload.svelte
deleted file mode 100644
index 4aae8c0..0000000
--- a/src/client/debug/log/CustomPayload.svelte
+++ /dev/null
@@ -1,8 +0,0 @@
-<script>
-  export let payload;
-  const custompayload =
-    payload !== undefined ? JSON.stringify(payload, null, 4) : '';
-</script>
-
-<div>{custompayload}</div>
-
diff --git a/src/client/debug/log/Log.svelte b/src/client/debug/log/Log.svelte
index d9a70d8..e005d64 100644
--- a/src/client/debug/log/Log.svelte
+++ b/src/client/debug/log/Log.svelte
@@ -138,7 +138,7 @@
     {/if}
   {/each}
 
-  {#each renderedLogEntries as { action, payload }, i}
+  {#each renderedLogEntries as { action, metadata }, i}
     <LogEvent
       pinned={i === pinned}
       logIndex={i}
@@ -146,7 +146,7 @@
       on:mouseenter={OnMouseEnter}
       on:mouseleave={OnMouseLeave}
       {action}
-      {payload} />
+      {metadata} />
   {/each}
 
   {#each renderedLogEntries as { phase }, i}
diff --git a/src/client/debug/log/LogEvent.svelte b/src/client/debug/log/LogEvent.svelte
index ee8d72a..a917e02 100644
--- a/src/client/debug/log/LogEvent.svelte
+++ b/src/client/debug/log/LogEvent.svelte
@@ -2,10 +2,10 @@
   export let logIndex;
   export let action;
   export let pinned;
-  export let payload;
-  export let payloadComponent;
+  export let metadata;
+  export let metadataComponent;
 
-  import CustomPayload from './CustomPayload.svelte';
+  import LogMetadata from './LogMetadata.svelte';
   import { createEventDispatcher } from 'svelte';
 
   const dispatch = createEventDispatcher();
@@ -13,7 +13,7 @@
   const args = action.payload.args;
   const renderedArgs = typeof args === 'string' ? args : (args || []).join(',');
   const playerID = action.payload.playerID;
-  let actionType; 
+  let actionType;
   switch (action.type) {
     case 'UNDO':
       actionType = 'undo';
@@ -134,10 +134,9 @@
   on:blur={() => dispatch('mouseleave')}
 >
   <div>{actionType}({renderedArgs})</div>
-
-  {#if payloadComponent}
-    <svelte:component this={payloadComponent} {payload} />
+  {#if metadataComponent}
+    <svelte:component this={metadataComponent} {metadata} />
   {:else}
-    <CustomPayload {payload} />
+    <LogMetadata {metadata} />
   {/if}
 </button>
diff --git a/src/client/debug/log/LogMetadata.svelte b/src/client/debug/log/LogMetadata.svelte
new file mode 100644
index 0000000..1fb6bcf
--- /dev/null
+++ b/src/client/debug/log/LogMetadata.svelte
@@ -0,0 +1,7 @@
+<script>
+  export let metadata;
+  const renderedMetadata =
+    metadata !== undefined ? JSON.stringify(metadata, null, 4) : '';
+</script>
+
+<div>{renderedMetadata}</div>
diff --git a/src/core/reducer.ts b/src/core/reducer.ts
index f3099cf..bdda36d 100644
--- a/src/core/reducer.ts
+++ b/src/core/reducer.ts
@@ -105,6 +105,11 @@ function initializeDeltalog(
     phase: state.ctx.phase,
   };
 
+  const pluginLogMetadata = state.plugins.log.data.metadata;
+  if (pluginLogMetadata !== undefined) {
+    logEntry.metadata = pluginLogMetadata;
+  }
+
   if (typeof move === 'object' && move.redact === true) {
     logEntry.redact = true;
   }
diff --git a/src/plugins/main.ts b/src/plugins/main.ts
index 048aeab..f4303f3 100644
--- a/src/plugins/main.ts
+++ b/src/plugins/main.ts
@@ -9,6 +9,7 @@
 import PluginImmer from './plugin-immer';
 import PluginRandom from './plugin-random';
 import PluginEvents from './plugin-events';
+import PluginLog from './plugin-log';
 import {
   AnyFn,
   PartialGameState,
@@ -28,7 +29,7 @@ interface PluginOpts {
 /**
  * List of plugins that are always added.
  */
-const DEFAULT_PLUGINS = [PluginImmer, PluginRandom, PluginEvents];
+const DEFAULT_PLUGINS = [PluginImmer, PluginRandom, PluginEvents, PluginLog];
 
 /**
  * Allow plugins to intercept actions and process them.
diff --git a/src/plugins/plugin-log.ts b/src/plugins/plugin-log.ts
new file mode 100644
index 0000000..ca94b8e
--- /dev/null
+++ b/src/plugins/plugin-log.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 The boardgame.io Authors
+ *
+ * Use of this source code is governed by a MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+import { Plugin } from '../types';
+
+interface LogMetadataData {
+  metadata?: any;
+}
+
+interface LogMetadataAPI {
+  setMetadata(metadata: any): void;
+}
+
+/**
+ * Plugin that makes it possible to add metadata to log entries.
+ * During a move, you can set metadata using ctx.log.setMetadata and it will be
+ * available on the log entry for that move.
+ */
+const LogPlugin: Plugin<LogMetadataAPI, LogMetadataData> = {
+  name: 'log',
+
+  flush: () => ({}),
+
+  api: ({ data }) => {
+    return {
+      setMetadata: metadata => {
+        data.metadata = metadata;
+      },
+    };
+  },
+
+  setup: () => ({}),
+};
+
+export default LogPlugin;
diff --git a/src/types.ts b/src/types.ts
index e8dacf8..732010e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -92,6 +92,7 @@ export interface LogEntry {
   phase: string;
   redact?: boolean;
   automatic?: boolean;
+  metadata?: any;
 }
 
 interface PluginContext<


Test patch:
diff --git a/src/plugins/plugin-log.test.ts b/src/plugins/plugin-log.test.ts
new file mode 100644
index 0000000..436ece0
--- /dev/null
+++ b/src/plugins/plugin-log.test.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 The boardgame.io Authors
+ *
+ * Use of this source code is governed by a MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+import { Client } from '../client/client';
+
+describe('log-metadata', () => {
+  test('It sets metadata in a move and then clears the metadata', () => {
+    const game = {
+      moves: {
+        setMetadataMove: (G, ctx) => {
+          ctx.log.setMetadata({
+            message: 'test',
+          });
+        },
+        doNothing: G => G,
+      },
+    };
+    const client = Client({ game });
+    client.moves.setMetadataMove();
+
+    expect(client.getState().plugins.log.data).toEqual({});
+    expect(client.getState().log[0].metadata).toEqual({
+      message: 'test',
+    });
+
+    client.moves.doNothing();
+
+    expect(client.getState().plugins.log.data).toEqual({});
+    expect(client.getState().log[1].metadata).toEqual(undefined);
+  });
+});