Browse Source

SetStickyThreads

Leszek Wiesner 3 years ago
parent
commit
a0f8b3cc39

+ 38 - 4
query-node/mappings/forum.ts

@@ -32,10 +32,12 @@ import {
   PostAddedEvent,
   PostStatusLocked,
   PostOriginThreadReply,
+  CategoryStickyThreadUpdateEvent,
 } from 'query-node/dist/model'
 import { Forum } from './generated/types'
 import { PrivilegedActor } from '@joystream/types/augment/all'
 import { ForumPostMetadata } from '@joystream/metadata-protobuf'
+import { Not } from 'typeorm'
 
 async function getCategory(db: DatabaseManager, categoryId: string): Promise<ForumCategory> {
   const category = await db.get(ForumCategory, { where: { id: categoryId } })
@@ -353,6 +355,42 @@ export async function forum_PostAdded(db: DatabaseManager, event_: SubstrateEven
   await db.save<ForumPost>(post)
 }
 
+export async function forum_CategoryStickyThreadUpdate(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  const [categoryId, newStickyThreadsIdsVec, privilegedActor] = new Forum.CategoryStickyThreadUpdateEvent(event_).params
+  const eventTime = new Date(event_.blockTimestamp)
+  const actorWorker = await getActorWorker(db, privilegedActor)
+  const newStickyThreadsIds = newStickyThreadsIdsVec.map((id) => id.toString())
+  const threadsToSetSticky = await db.getMany(ForumThread, {
+    where: { category: { id: categoryId.toString() }, id: newStickyThreadsIds },
+  })
+  const threadsToUnsetSticky = await db.getMany(ForumThread, {
+    where: { category: { id: categoryId.toString() }, isSticky: true, id: Not(newStickyThreadsIds) },
+  })
+
+  const setStickyUpdates = (threadsToSetSticky || []).map(async (t) => {
+    t.updatedAt = eventTime
+    t.isSticky = true
+    await db.save<ForumThread>(t)
+  })
+
+  const unsetStickyUpdates = (threadsToUnsetSticky || []).map(async (t) => {
+    t.updatedAt = eventTime
+    t.isSticky = false
+    await db.save<ForumThread>(t)
+  })
+
+  await Promise.all(setStickyUpdates.concat(unsetStickyUpdates))
+
+  const categoryStickyThreadUpdateEvent = new CategoryStickyThreadUpdateEvent({
+    ...genericEventFields(event_),
+    actor: actorWorker,
+    category: new ForumCategory({ id: categoryId.toString() }),
+    newStickyThreads: threadsToSetSticky,
+  })
+
+  await db.save<CategoryStickyThreadUpdateEvent>(categoryStickyThreadUpdateEvent)
+}
+
 export async function forum_PostModerated(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
   // TODO
 }
@@ -369,10 +407,6 @@ export async function forum_PostReacted(db: DatabaseManager, event_: SubstrateEv
   // TODO
 }
 
-export async function forum_CategoryStickyThreadUpdate(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
-  // TODO
-}
-
 export async function forum_CategoryMembershipOfModeratorUpdated(
   db: DatabaseManager,
   event_: SubstrateEvent

+ 14 - 0
tests/integration-tests/src/QueryNodeApi.ts

@@ -223,6 +223,10 @@ import {
   GetThreadMovedEventsByEventIdsQuery,
   GetThreadMovedEventsByEventIdsQueryVariables,
   GetThreadMovedEventsByEventIds,
+  CategoryStickyThreadUpdateEventFieldsFragment,
+  GetCategoryStickyThreadUpdateEventsByEventIdsQuery,
+  GetCategoryStickyThreadUpdateEventsByEventIdsQueryVariables,
+  GetCategoryStickyThreadUpdateEventsByEventIds,
 } from './graphql/generated/queries'
 import { Maybe } from './graphql/generated/schema'
 import { OperationDefinitionNode } from 'graphql'
@@ -825,4 +829,14 @@ export class QueryNodeApi {
       GetThreadMovedEventsByEventIdsQueryVariables
     >(GetThreadMovedEventsByEventIds, { eventIds }, 'threadMovedEvents')
   }
+
+  public async getCategoryStickyThreadUpdateEvents(
+    events: EventDetails[]
+  ): Promise<CategoryStickyThreadUpdateEventFieldsFragment[]> {
+    const eventIds = events.map((e) => this.getQueryNodeEventId(e.blockNumber, e.indexInBlock))
+    return this.multipleEntitiesQuery<
+      GetCategoryStickyThreadUpdateEventsByEventIdsQuery,
+      GetCategoryStickyThreadUpdateEventsByEventIdsQueryVariables
+    >(GetCategoryStickyThreadUpdateEventsByEventIds, { eventIds }, 'categoryStickyThreadUpdateEvents')
+  }
 }

+ 85 - 0
tests/integration-tests/src/fixtures/forum/SetStickyThreadsFixture.ts

@@ -0,0 +1,85 @@
+import { Api } from '../../Api'
+import { QueryNodeApi } from '../../QueryNodeApi'
+import { EventDetails } from '../../types'
+import { SubmittableExtrinsic } from '@polkadot/api/types'
+import { Utils } from '../../utils'
+import { ISubmittableResult } from '@polkadot/types/types/'
+import {
+  CategoryStickyThreadUpdateEventFieldsFragment,
+  ForumCategoryFieldsFragment,
+} from '../../graphql/generated/queries'
+import { assert } from 'chai'
+import { CategoryId } from '@joystream/types/forum'
+import { ThreadId } from '@joystream/types/common'
+import { WorkerId } from '@joystream/types/working-group'
+import { WithForumWorkersFixture } from './WithForumWorkersFixture'
+import _ from 'lodash'
+
+export type StickyThreadsParams = {
+  categoryId: CategoryId
+  stickyTreads: ThreadId[]
+  asWorker?: WorkerId
+}
+
+export class SetStickyThreadsFixture extends WithForumWorkersFixture {
+  protected events: EventDetails[] = []
+
+  protected stickyThreadsParams: StickyThreadsParams[]
+
+  public constructor(api: Api, query: QueryNodeApi, stickyThreadsParams: StickyThreadsParams[]) {
+    super(api, query)
+    this.stickyThreadsParams = stickyThreadsParams
+  }
+
+  protected async getSignerAccountOrAccounts(): Promise<string[]> {
+    return this.getSignersFromInput(this.stickyThreadsParams)
+  }
+
+  protected async getExtrinsics(): Promise<SubmittableExtrinsic<'promise'>[]> {
+    return this.stickyThreadsParams.map((p) =>
+      this.api.tx.forum.setStickiedThreads(
+        p.asWorker ? { Moderator: p.asWorker } : { Lead: null },
+        p.categoryId,
+        p.stickyTreads
+      )
+    )
+  }
+
+  protected async getEventFromResult(result: ISubmittableResult): Promise<EventDetails> {
+    return this.api.retrieveForumEventDetails(result, 'CategoryStickyThreadUpdate')
+  }
+
+  protected assertQueriedCategoriesAreValid(qCategories: ForumCategoryFieldsFragment[]): void {
+    _.uniqBy([...this.stickyThreadsParams.reverse()], (v) => v.categoryId).forEach((params) => {
+      const qCategory = qCategories.find((c) => c.id === params.categoryId.toString())
+      Utils.assert(qCategory, 'Query node: Category not found')
+      assert.sameDeepMembers(
+        qCategory.threads.filter((t) => t.isSticky).map((t) => t.id),
+        params.stickyTreads.map((threadId) => threadId.toString())
+      )
+    })
+  }
+
+  protected assertQueryNodeEventIsValid(qEvent: CategoryStickyThreadUpdateEventFieldsFragment, i: number): void {
+    const { categoryId, stickyTreads, asWorker } = this.stickyThreadsParams[i]
+    assert.equal(qEvent.category.id, categoryId.toString())
+    assert.sameDeepMembers(
+      qEvent.newStickyThreads.map((t) => t.id),
+      stickyTreads.map((threadId) => threadId.toString())
+    )
+    assert.equal(qEvent.actor.id, `forumWorkingGroup-${asWorker ? asWorker.toString() : this.forumLeadId!.toString()}`)
+  }
+
+  async runQueryNodeChecks(): Promise<void> {
+    await super.runQueryNodeChecks()
+    // Query the events
+    await this.query.tryQueryWithTimeout(
+      () => this.query.getCategoryStickyThreadUpdateEvents(this.events),
+      (qEvents) => this.assertQueryNodeEventsAreValid(qEvents)
+    )
+
+    // Query the categories
+    const qCategories = await this.query.getCategoriesByIds(this.stickyThreadsParams.map((e) => e.categoryId))
+    this.assertQueriedCategoriesAreValid(qCategories)
+  }
+}

+ 1 - 0
tests/integration-tests/src/fixtures/forum/index.ts

@@ -7,3 +7,4 @@ export { VoteOnPollFixture, VoteParams } from './VoteOnPollFixture'
 export { AddPostsFixture, PostParams } from './AddPostsFixture'
 export { UpdateThreadTitlesFixture, ThreadTitleUpdate } from './UpdateThreadTitlesFixture'
 export { MoveThreadsFixture, MoveThreadParams } from './MoveThreadsFixture'
+export { SetStickyThreadsFixture, StickyThreadsParams } from './SetStickyThreadsFixture'

+ 23 - 1
tests/integration-tests/src/flows/forum/threads.ts

@@ -7,12 +7,16 @@ import {
   DeleteThreadsFixture,
   MoveThreadParams,
   MoveThreadsFixture,
+  SetStickyThreadsFixture,
+  StickyThreadsParams,
   ThreadRemovalInput,
   ThreadTitleUpdate,
   UpdateThreadTitlesFixture,
 } from '../../fixtures/forum'
 import { CreateThreadsFixture, ThreadParams } from '../../fixtures/forum/CreateThreadsFixture'
 import { BuyMembershipHappyCaseFixture } from '../../fixtures/membership'
+import _ from 'lodash'
+import { ThreadId } from '@joystream/types/common'
 
 export default async function threads({ api, query }: FlowProps): Promise<void> {
   const debug = Debugger(`flow:threads`)
@@ -53,7 +57,7 @@ export default async function threads({ api, query }: FlowProps): Promise<void>
   await createThreadsRunner.runWithQueryNodeChecks()
   const threadIds = createThreadsFixture.getCreatedThreadsIds()
 
-  // Update categories
+  // Move threads
   const threadCategoryUpdates: MoveThreadParams[] = threadIds.map((threadId, i) => ({
     threadId,
     categoryId: threads[i].categoryId,
@@ -64,6 +68,23 @@ export default async function threads({ api, query }: FlowProps): Promise<void>
   const moveThreadsRunner = new FixtureRunner(moveThreadsFixture)
   await moveThreadsRunner.run()
   const threadCategories = threadCategoryUpdates.map((u) => u.newCategoryId)
+  const threadIdsByCategoryId = threadIds.reduce((ids, id, i) => {
+    const categoryId = threadCategories[i].toString()
+    return { ...ids, [categoryId]: [...(ids[categoryId] || []), id] }
+  }, {} as Record<string, ThreadId[]>)
+
+  // Set threads as sticky (2 per category)
+  const stickyThreadsParams: StickyThreadsParams[] = categoryIds.reduce((paramsArr, categoryId, i) => {
+    const threadIds = threadIdsByCategoryId[categoryId.toString()]
+    return paramsArr.concat([
+      { categoryId, stickyTreads: [threadIds[0], threadIds[1]] },
+      { categoryId, stickyTreads: [threadIds[1], threadIds[2]] },
+    ])
+  }, [] as StickyThreadsParams[])
+
+  const setStickyThreadsFixture = new SetStickyThreadsFixture(api, query, stickyThreadsParams)
+  const setStickyThreadsRunner = new FixtureRunner(setStickyThreadsFixture)
+  await setStickyThreadsRunner.run()
 
   // Update titles
   const titleUpdates = threadIds.reduce(
@@ -80,6 +101,7 @@ export default async function threads({ api, query }: FlowProps): Promise<void>
   await updateThreadTitlesRunner.run()
 
   // Remove threads
+  // TODO: Should removing / moving threads also "unstick" them?
   const threadRemovals: ThreadRemovalInput[] = threadIds.map((threadId, i) => ({
     threadId,
     categoryId: threadCategories[i],

+ 47 - 0
tests/integration-tests/src/graphql/generated/queries.ts

@@ -241,6 +241,26 @@ export type GetThreadMovedEventsByEventIdsQueryVariables = Types.Exact<{
 
 export type GetThreadMovedEventsByEventIdsQuery = { threadMovedEvents: Array<ThreadMovedEventFieldsFragment> }
 
+export type CategoryStickyThreadUpdateEventFieldsFragment = {
+  id: string
+  createdAt: any
+  inBlock: number
+  network: Types.Network
+  inExtrinsic?: Types.Maybe<string>
+  indexInBlock: number
+  category: { id: string }
+  newStickyThreads: Array<{ id: string }>
+  actor: { id: string }
+}
+
+export type GetCategoryStickyThreadUpdateEventsByEventIdsQueryVariables = Types.Exact<{
+  eventIds?: Types.Maybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>
+}>
+
+export type GetCategoryStickyThreadUpdateEventsByEventIdsQuery = {
+  categoryStickyThreadUpdateEvents: Array<CategoryStickyThreadUpdateEventFieldsFragment>
+}
+
 export type MemberMetadataFieldsFragment = { name?: Types.Maybe<string>; about?: Types.Maybe<string> }
 
 export type MembershipFieldsFragment = {
@@ -1381,6 +1401,25 @@ export const ThreadMovedEventFields = gql`
     }
   }
 `
+export const CategoryStickyThreadUpdateEventFields = gql`
+  fragment CategoryStickyThreadUpdateEventFields on CategoryStickyThreadUpdateEvent {
+    id
+    createdAt
+    inBlock
+    network
+    inExtrinsic
+    indexInBlock
+    category {
+      id
+    }
+    newStickyThreads {
+      id
+    }
+    actor {
+      id
+    }
+  }
+`
 export const MemberMetadataFields = gql`
   fragment MemberMetadataFields on MemberMetadata {
     name
@@ -2276,6 +2315,14 @@ export const GetThreadMovedEventsByEventIds = gql`
   }
   ${ThreadMovedEventFields}
 `
+export const GetCategoryStickyThreadUpdateEventsByEventIds = gql`
+  query getCategoryStickyThreadUpdateEventsByEventIds($eventIds: [ID!]) {
+    categoryStickyThreadUpdateEvents(where: { id_in: $eventIds }) {
+      ...CategoryStickyThreadUpdateEventFields
+    }
+  }
+  ${CategoryStickyThreadUpdateEventFields}
+`
 export const GetMemberById = gql`
   query getMemberById($id: ID!) {
     membershipByUniqueInput(where: { id: $id }) {

+ 1 - 0
tests/integration-tests/src/graphql/queries/forum.graphql

@@ -9,6 +9,7 @@ fragment ForumCategoryFields on ForumCategory {
   description
   threads {
     id
+    isSticky
   }
   moderators {
     id

+ 24 - 0
tests/integration-tests/src/graphql/queries/forumEvents.graphql

@@ -188,3 +188,27 @@ query getThreadMovedEventsByEventIds($eventIds: [ID!]) {
     ...ThreadMovedEventFields
   }
 }
+
+fragment CategoryStickyThreadUpdateEventFields on CategoryStickyThreadUpdateEvent {
+  id
+  createdAt
+  inBlock
+  network
+  inExtrinsic
+  indexInBlock
+  category {
+    id
+  }
+  newStickyThreads {
+    id
+  }
+  actor {
+    id
+  }
+}
+
+query getCategoryStickyThreadUpdateEventsByEventIds($eventIds: [ID!]) {
+  categoryStickyThreadUpdateEvents(where: { id_in: $eventIds }) {
+    ...CategoryStickyThreadUpdateEventFields
+  }
+}