Repository: mercurius-js/mercurius
Pull request #963 (2023-02-15 21:24:03)

feat: include subscription id in onSubscriptionEnd hook
This is related to #959, it will be needed in the gateway to identify when a subscription has ended and which subscription was that.


Patch:
diff --git a/docs/contribute.md b/docs/contribute.md
new file mode 100644
index 0000000..32ff6d6
--- /dev/null
+++ b/docs/contribute.md
@@ -0,0 +1,44 @@
+# Contribute to Mercurius
+
+Mercurius is a growing and friendly community, and would be lucky to have your contributions! 🙇‍♂️
+
+Contributions are always welcome, we only ask you follow the Contribution Guidelines and the Code Of Conduct.
+
+If you don't know where to start you can have a look at the list of good first issues below.
+
+## Good First Issues
+
+<div id="issues">
+  <div v-if="loading" class="spinner"></div>
+  <div v-if="error" class="message is-danger">
+    <div class="message-header">
+      <p>Error</p>
+    </div>
+    <div class="message-body">{{ error }}</div>
+  </div>
+  <div v-if="issues.length > 0" class="issues">
+    <div v-for="issue in filteredIssues" class="good-issue">
+      <div class="card">
+        <div class="card-header">
+            <div class="subtitle">
+                <a :href="issue.project.url">{{ issue.project.name }}</a>
+            </div>
+        </div>
+        <div class="card-content">
+            <h3 class="title">
+              <a :href="issue.url">{{ issue.title }}</a>
+            </h3>
+            <div class="issue-labels">
+                <div v-for="label in issue.labels" >
+                     <span class="issue-label">{{ label }}</span>
+                </div>
+            </div>
+            <div class="issue-comments">
+              <strong>{{ issue.comments }}</strong>
+              <span> Comments</span>
+            </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/docs/hooks.md b/docs/hooks.md
index 64fef38..614e478 100644
--- a/docs/hooks.md
+++ b/docs/hooks.md
@@ -181,7 +181,7 @@ fastify.graphql.addHook('onSubscriptionResolution', async (execution, context) =
 This hook will be triggered when a subscription ends.
 
 ```js
-fastify.graphql.addHook('onSubscriptionEnd', async (context) => {
+fastify.graphql.addHook('onSubscriptionEnd', async (context, id) => {
   await asyncMethod()
 })
 ```
diff --git a/docsify/sidebar.md b/docsify/sidebar.md
index bdcc313..ac50b6b 100644
--- a/docsify/sidebar.md
+++ b/docsify/sidebar.md
@@ -38,3 +38,4 @@
   * [mercurius-apollo-tracing](/docs/plugins#mercurius-apollo-tracing)
 * [Development](/docs/development)
 * [Faq](/docs/faq)
+* [Contribute](/docs/contribute)
diff --git a/examples/hooks-subscription.js b/examples/hooks-subscription.js
index 032181b..4ba21bc 100644
--- a/examples/hooks-subscription.js
+++ b/examples/hooks-subscription.js
@@ -70,7 +70,7 @@ async function start () {
     console.log('onSubscriptionResolution called')
   })
 
-  app.graphql.addHook('onSubscriptionEnd', async function (context) {
+  app.graphql.addHook('onSubscriptionEnd', async function (context, id) {
     console.log('onSubscriptionEnd called')
   })
 
diff --git a/index.d.ts b/index.d.ts
index ad198ca..0fbcd36 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -167,6 +167,7 @@ export interface onSubscriptionResolutionHookHandler<TData extends Record<string
 export interface onSubscriptionEndHookHandler<TContext = MercuriusContext> {
   (
     context: TContext,
+    id: string | number,
   ): Promise<void> | void;
 }
 
diff --git a/index.html b/index.html
index 6277292..1c4383e 100644
--- a/index.html
+++ b/index.html
@@ -29,11 +29,11 @@
         --sidebar-name-padding: 0;
         --code-font-size: 0.9em;
       }
-      .sidebar > h1 {
+    .sidebar > h1 {
         margin-bottom: -0.75em;
         margin-top: 0.75em;
       }
-      .sidebar > h1 img {
+    .sidebar > h1 img {
         height: 4em;
       }
       .markdown-section a code {
@@ -42,6 +42,89 @@
       .markdown-section code:not([class*='lang-']):not([class*='language-']) {
         white-space: unset;
       }
+
+      #issues {
+        display: flex;
+        flex-direction: column;
+      }
+
+
+      .good-issue {
+        margin: 0 0 1em 0;
+      }
+
+      .good-issue .card {
+        border-radius: 5px;
+        -webkit-box-shadow: 3px 3px 10px 4px rgba(40, 40, 40, 0.99);
+        -moz-box-shadow: 3px 3px 10px 4px rgba(40, 40, 40, 0.99);
+        -o-box-shadow: 3px 3px 10px 4px rgba(40, 40, 40, 0.99);
+        box-shadow: 3px 3px 10px 4px rgba(40, 40, 40, 0.99);
+      }
+
+      .good-issue .card .card-content {
+        display: flex;
+        flex-direction: column;
+        padding: 0 1em .5em 1em;
+      }
+
+      .good-issue .card .card-content .title{
+        font-size: 24px;
+        margin-bottom: 0.5em;
+      }
+
+      .good-issue .card .card-content .issue-labels {
+        display: flex;
+        flex-direction: row;
+      }
+
+      .good-issue .card .card-content .issue-label{
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        font-size: 15px;
+        background-color: #111111;
+        padding: .1em 1em;
+        margin-right: .3em;
+        margin-bottom: .3em;
+        border-radius: 15px;
+      }
+
+      .good-issue .card .card-content .issue-comments{
+        margin-left: .3em;
+        font-size: 15px;
+      }
+      .good-issue .card .card-header{
+        display: flex;
+        justify-content: right;
+        padding: 0 .5em;
+      }
+
+      .good-issue .card .card-header .subtitle{
+        font-size: 18px;
+      }
+
+      .error-result,
+      .no-issues {
+        padding: 1rem;
+        width: 100%;
+        text-align: center;
+        border-top: 1px solid #ccc;
+      }
+
+      .spinner {
+        animation: rotate360 1s linear infinite;
+        transform: translateZ(0);
+
+        border-top: 3px solid #04625C;
+        border-right: 3px solid #04625C;
+        border-bottom: 3px solid #04625C;
+        border-left: 3px solid #333;
+        background: transparent;
+        width: 24px;
+        height: 24px;
+        border-radius: 50%;
+        margin: 0 auto;
+      }
     </style>
   </head>
   <body>
@@ -51,7 +134,64 @@
         loadSidebar: 'docsify/sidebar.md',
         name: 'Mercurius',
         repo: 'https://github.com/mercurius-js/mercurius',
-        auto2top: true
+      auto2top: true,
+      vueMounts: {
+        '#issues': {
+          data: function () {
+            return {
+              loading: false,
+              error: null,
+              issues: [],
+              projects: {},
+              filteredIssues: []
+            }
+          },
+          created() {
+            this.loading = true
+            fetch('https://goodfirstissue.fastify.io/api/find-issues?org=mercurius-js')
+              .then(resp => resp.json())
+              .then(data => {
+                this.issues = data.results
+                this.filteredIssues = data.results
+                this.projects = data.results.reduce((acc, curr) => {
+                  if (!acc[curr.project.name]) {
+                    acc[curr.project.name] = { count: 0, selected: true }
+                  }
+                  acc[curr.project.name].count += 1
+                  return acc
+                }, {})
+              })
+              .catch(err => {
+                this.error = err.message
+              })
+              .finally(() => {
+                this.loading = false
+              })
+          },
+          methods: {
+            toggle_toggleProject(name, selected) {
+              const projects = { ...this.projects }
+              if (projects[name]) {
+                projects[name].selected = typeof selected === 'undefined' ? !projects[name].selected : selected
+              }
+              this.filteredIssues = this.issues.filter(issue => {
+                return projects[issue.project.name].selected
+              })
+              this.projects = projects
+            },
+            _toggleProjects(selected) {
+              const projects = { ...this.projects }
+              for (let name in projects) {
+                projects[name].selected = selected
+              }
+              this.filteredIssues = this.issues.filter(issue => {
+                return projects[issue.project.name].selected
+              })
+              this.projects = projects
+            }
+          }
+        },
+      },
       }
     </script>
     <script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
@@ -59,5 +199,6 @@
     <script src="//cdn.jsdelivr.net/npm/docsify-copy-code"></script>
     <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-typescript.min.js"></script>
+    <script src="//cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js"></script>
   </body>
 </html>
diff --git a/lib/hooks.js b/lib/hooks.js
index b685666..cf8274c 100644
--- a/lib/hooks.js
+++ b/lib/hooks.js
@@ -113,7 +113,7 @@ function onResolutionHookRunner (fn, request) {
 }
 
 function onEndHookRunner (fn, request) {
-  return fn(request.context)
+  return fn(request.context, request.id)
 }
 
 module.exports = {
diff --git a/lib/subscription-connection.js b/lib/subscription-connection.js
index 3987025..1740fa0 100644
--- a/lib/subscription-connection.js
+++ b/lib/subscription-connection.js
@@ -278,6 +278,7 @@ module.exports = class SubscriptionConnection {
         get __currentQuery () {
           return print(document)
         },
+        id,
         pubsub: sc,
         lruGatewayResolvers: this.lruGatewayResolvers,
         reply: {
@@ -343,7 +344,7 @@ module.exports = class SubscriptionConnection {
   async handleGQLStop (data) {
     if (this.context.onSubscriptionEnd) {
       try {
-        await onSubscriptionEndHandler({ context: this.context })
+        await onSubscriptionEndHandler({ context: this.context, id: data.id })
       } catch (error) {
         this.fastify.log.error(error)
         return this.handleConnectionClose()


Test patch:
diff --git a/test/subscription-hooks.js b/test/subscription-hooks.js
index b0db343..fc158c8 100644
--- a/test/subscription-hooks.js
+++ b/test/subscription-hooks.js
@@ -319,11 +319,12 @@ test('subscription - should handle onSubscriptionResolution hook errors', async
 // onSubscriptionEnd
 // -----------------
 test('subscription - should call onSubscriptionEnd when subscription ends', async t => {
-  t.plan(4)
+  t.plan(5)
   const app = await createTestServer(t)
 
-  app.graphql.addHook('onSubscriptionEnd', async (context) => {
+  app.graphql.addHook('onSubscriptionEnd', async (context, id) => {
     t.type(context, 'object')
+    t.equal(id, 1)
     t.ok('onSubscriptionEnd called')
   })
 
@@ -364,7 +365,7 @@ test('subscription - should handle onSubscriptionEnd hook errors', async t => {
   t.plan(2)
   const app = await createTestServer(t)
 
-  app.graphql.addHook('onSubscriptionEnd', async (context) => {
+  app.graphql.addHook('onSubscriptionEnd', async (context, id) => {
     throw new Error('kaboom')
   })
 
diff --git a/test/types/index.ts b/test/types/index.ts
index ac9eb24..f81bd1c 100644
--- a/test/types/index.ts
+++ b/test/types/index.ts
@@ -540,12 +540,14 @@ app.graphql.addHook('onSubscriptionResolution', function (execution, context) {
   expectAssignable<MercuriusContext>(context)
 })
 
-app.graphql.addHook('onSubscriptionEnd', async function (context) {
+app.graphql.addHook('onSubscriptionEnd', async function (context, id) {
   expectAssignable<MercuriusContext>(context)
+  expectAssignable<string | number>(id)
 })
 
-app.graphql.addHook('onSubscriptionEnd', function (context) {
+app.graphql.addHook('onSubscriptionEnd', function (context, id) {
   expectAssignable<MercuriusContext>(context)
+  expectAssignable<string | number>(id)
 })
 
 expectError(() => {