Przeglądaj źródła

Improve allCategoriesFeaturedVideos query performance (#32)

Bartosz Dryl 2 lat temu
rodzic
commit
48c597af47

+ 2 - 2
schema.graphql

@@ -4,8 +4,8 @@
 # -----------------------------------------------
 
 type CategoryFeaturedVideos {
+  categoryFeaturedVideos: [FeaturedVideo!]!
   categoryId: ID!
-  videos: [FeaturedVideo!]!
 }
 
 type ChannelFollowsInfo {
@@ -43,7 +43,7 @@ type Mutation {
 
 type Query {
   """Get featured videos for all categories"""
-  allCategoriesFeaturedVideos: [CategoryFeaturedVideos!]!
+  allCategoriesFeaturedVideos(videosLimit: Int!): [CategoryFeaturedVideos!]!
 
   """Get featured videos for a given video category"""
   categoryFeaturedVideos(categoryId: ID!): [FeaturedVideo!]!

+ 1 - 1
src/entities/CategoryFeaturedVideos.ts

@@ -7,5 +7,5 @@ export class CategoryFeaturedVideos {
   categoryId!: string
 
   @Field(() => [FeaturedVideo])
-  videos!: FeaturedVideo[]
+  categoryFeaturedVideos!: FeaturedVideo[]
 }

+ 3 - 3
src/resolvers/featuredContent.ts

@@ -1,4 +1,4 @@
-import { Arg, Args, ArgsType, Authorized, Field, ID, InputType, Mutation, Query, Resolver } from 'type-graphql'
+import { Arg, Args, ArgsType, Authorized, Field, ID, InputType, Int, Mutation, Query, Resolver } from 'type-graphql'
 import { CategoryFeaturedVideos } from '../entities/CategoryFeaturedVideos'
 import { FeaturedVideo, getFeaturedContentDoc, VideoHero } from '../models/FeaturedContent'
 
@@ -44,14 +44,14 @@ export class FeaturedContentResolver {
   }
 
   @Query(() => [CategoryFeaturedVideos], { nullable: false, description: 'Get featured videos for all categories' })
-  async allCategoriesFeaturedVideos() {
+  async allCategoriesFeaturedVideos(@Arg('videosLimit', () => Int) videosLimit: number) {
     const featuredContent = await getFeaturedContentDoc()
 
     const categoriesList: CategoryFeaturedVideos[] = []
     featuredContent.featuredVideosPerCategory.forEach((videos, categoryId) => {
       categoriesList.push({
         categoryId,
-        videos,
+        categoryFeaturedVideos: videos.slice(0, videosLimit),
       })
     })
 

+ 48 - 2
src/resolvers/queryNodeStitchingResolvers/featuredContentResolvers.ts

@@ -1,6 +1,10 @@
 import type { IResolvers } from '@graphql-tools/utils'
-import { GraphQLSchema } from 'graphql'
+import { GraphQLSchema, SelectionSetNode } from 'graphql'
 import { createResolver } from './helpers'
+import { delegateToSchema } from '@graphql-tools/delegate'
+import { WrapQuery } from '@graphql-tools/wrap'
+import { Video } from '../../types'
+import { FeaturedVideo } from '../../models/FeaturedContent'
 
 export const featuredContentResolvers = (queryNodeSchema: GraphQLSchema): IResolvers => ({
   VideoHero: {
@@ -20,8 +24,10 @@ export const featuredContentResolvers = (queryNodeSchema: GraphQLSchema): IResol
   },
   FeaturedVideo: {
     video: {
-      selectionSet: '{ videoId }',
       resolve: async (parent, args, context, info) => {
+        if (parent.video) {
+          return parent.video
+        }
         const videoResolver = createResolver(queryNodeSchema, 'videoByUniqueInput')
         return videoResolver(
           parent,
@@ -37,6 +43,46 @@ export const featuredContentResolvers = (queryNodeSchema: GraphQLSchema): IResol
     },
   },
   CategoryFeaturedVideos: {
+    categoryFeaturedVideos: {
+      selectionSet: '{ categoryFeaturedVideos { videoId } }',
+      resolve: async (parent, args, context, info) => {
+        const videosIds = parent.categoryFeaturedVideos?.map((video: FeaturedVideo) => video.videoId)
+        const videoResolver = () =>
+          delegateToSchema({
+            schema: queryNodeSchema,
+            operation: 'query',
+            fieldName: 'videos',
+            args: {
+              where: {
+                id_in: videosIds,
+              },
+            },
+            context,
+            info,
+            transforms: [
+              new WrapQuery(
+                ['videos'],
+                (subtree: SelectionSetNode) => {
+                  const videoSubTree = subtree.selections.find(
+                    (selection) => selection.kind === 'Field' && selection.name.value === 'video'
+                  )
+                  if (videoSubTree?.kind === 'Field' && videoSubTree.selectionSet) {
+                    return videoSubTree.selectionSet
+                  }
+                  return subtree
+                },
+                (result) => result && result
+              ),
+            ],
+          })
+
+        const videos = await videoResolver()
+        return parent.categoryFeaturedVideos.map((v: FeaturedVideo) => ({
+          ...v,
+          video: videos?.find((video: Video) => video.id === v.videoId),
+        }))
+      },
+    },
     category: {
       selectionSet: '{ categoryId }',
       resolve: async (parent, args, context, info) => {

+ 1 - 1
src/server.ts

@@ -34,7 +34,7 @@ export const createServer = async (mongoose: Mongoose, aggregates: Aggregates, q
 
   const queryNodeSchemaExtension = await loadSchema('./queryNodeSchemaExtension.graphql', {
     loaders: [new GraphQLFileLoader()],
-    schemas: [remoteQueryNodeSchema, orionSchema],
+    schemas: [orionSchema, remoteQueryNodeSchema],
     resolvers: queryNodeStitchingResolvers(remoteQueryNodeSchema),
   })
 

+ 8 - 6
tests/featuredContent.test.ts

@@ -17,6 +17,7 @@ import {
   SetCategoryFeaturedVideosArgs,
   SetVideoHero,
   SetVideoHeroArgs,
+  GetAllCategoriesFeaturedVideosArgs,
 } from './queries/featuredContent'
 import {
   DEFAULT_FEATURED_CONTENT_DOC,
@@ -64,9 +65,10 @@ describe('Featured content resolver', () => {
     return result.data?.categoryFeaturedVideos
   }
 
-  const getAllCategoriesFeaturedVideos = async () => {
-    const result = await query<GetAllCategoriesFeaturedVideos>({
+  const getAllCategoriesFeaturedVideos = async (videosLimit: number) => {
+    const result = await query<GetAllCategoriesFeaturedVideos, GetAllCategoriesFeaturedVideosArgs>({
       query: GET_ALL_CATEGORIES_FEATURED_VIDEOS,
+      variables: { videosLimit },
     })
     expect(result.errors).toBeUndefined()
     return result.data?.allCategoriesFeaturedVideos
@@ -83,7 +85,7 @@ describe('Featured content resolver', () => {
   })
 
   it('should return empty array for list of all categories with featured videos', async () => {
-    const allCategoriesFeaturedVideos = await getAllCategoriesFeaturedVideos()
+    const allCategoriesFeaturedVideos = await getAllCategoriesFeaturedVideos(3)
     expect(allCategoriesFeaturedVideos).toHaveLength(0)
   })
 
@@ -135,15 +137,15 @@ describe('Featured content resolver', () => {
       variables: { categoryId: '2', videos: category2FeaturedVideos },
     })
 
-    const allCategoriesFeaturedVideos = await getAllCategoriesFeaturedVideos()
+    const allCategoriesFeaturedVideos = await getAllCategoriesFeaturedVideos(3)
     expect(allCategoriesFeaturedVideos).toEqual([
       {
         categoryId: '1',
-        videos: category1FeaturedVideos,
+        categoryFeaturedVideos: category1FeaturedVideos,
       },
       {
         categoryId: '2',
-        videos: category2FeaturedVideos,
+        categoryFeaturedVideos: category2FeaturedVideos,
       },
     ])
   })

+ 7 - 3
tests/queries/featuredContent.ts

@@ -34,11 +34,15 @@ export type GetCategoryFeaturedVideosArgs = {
   categoryId: string
 }
 
+export type GetAllCategoriesFeaturedVideosArgs = {
+  videosLimit: number
+}
+
 export const GET_ALL_CATEGORIES_FEATURED_VIDEOS = gql`
-  query GetAllCategoriesFeaturedVideos {
-    allCategoriesFeaturedVideos {
+  query GetAllCategoriesFeaturedVideos($videosLimit: Int!) {
+    allCategoriesFeaturedVideos(videosLimit: $videosLimit) {
       categoryId
-      videos {
+      categoryFeaturedVideos {
         videoId
         videoCutUrl
       }