Browse Source

query node + cli + tests - active video counters improvements

ondratra 3 years ago
parent
commit
464ebd8ba1

+ 2 - 7
cli/src/Api.ts

@@ -1,5 +1,5 @@
 import BN from 'bn.js'
-import { createType, types } from '@joystream/types/'
+import { createType, types } from '@joystream/types'
 import { ApiPromise, WsProvider } from '@polkadot/api'
 import { AugmentedQuery, SubmittableExtrinsic } from '@polkadot/api/types'
 import { formatBalance } from '@polkadot/util'
@@ -208,7 +208,7 @@ export default class Api {
     return this._api.query[module]
   }
 
-  protected async membershipById(memberId: MemberId): Promise<Membership | null> {
+  async membershipById(memberId: MemberId): Promise<Membership | null> {
     const profile = await this._api.query.members.membershipById(memberId)
 
     // Can't just use profile.isEmpty because profile.suspended is Bool (which isEmpty method always returns false)
@@ -484,11 +484,6 @@ export default class Api {
     }
   }
 
-  async getMemberIdsByControllerAccount(address: string): Promise<MemberId[]> {
-    const ids = await this._api.query.members.memberIdsByControllerAccountId(address)
-    return ids.toArray()
-  }
-
   async workerExitRationaleConstraint(group: WorkingGroups): Promise<InputValidationLengthConstraint> {
     return await this.workingGroupApiQuery(group).workerExitRationaleText()
   }

+ 16 - 4
cli/src/base/AccountsCommandBase.ts

@@ -355,7 +355,7 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
     return this.selectedMember
   }
 
-  async getKnownMembers(allowedIds?: MemberId[]): Promise<ISelectedMember[]> {
+  private async getKnownMembers(allowedIds?: MemberId[]): Promise<ISelectedMember[]> {
     const membersEntries = allowedIds
       ? await this.getApi().memberEntriesByIds(allowedIds)
       : await this.getApi().allMemberEntries()
@@ -382,6 +382,20 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
     return availableMemberships[memberIndex]
   }
 
+  private async initSelectedMember(): Promise<void> {
+    const memberIdString = this.getPreservedState().selectedMemberId
+
+    const memberId = this.createType('MemberId', memberIdString)
+    const member = await this.getApi().membershipById(memberId)
+
+    // ensure selected member exists
+    if (!member) {
+      return
+    }
+
+    this.selectedMember = [memberId, member]
+  }
+
   async init(): Promise<void> {
     await super.init()
     try {
@@ -391,8 +405,6 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
     }
     await this.initKeyring()
 
-    const availableMemberships = await this.getKnownMembers()
-    const memberId = this.getPreservedState().selectedMemberId
-    this.selectedMember = availableMemberships.find((item) => item[0].toString() === memberId) || undefined
+    await this.initSelectedMember()
   }
 }

+ 3 - 0
cli/src/base/ApiCommandBase.ts

@@ -89,6 +89,9 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
       if (this.requiresQueryNode && !queryNodeUri) {
         this.warn('Query node endpoint uri is required in order to run this command!')
         queryNodeUri = await this.promptForQueryNodeUri(true)
+      } else if (queryNodeUri === undefined) {
+        this.warn("You haven't provided a Joystream query node uri for the CLI to connect to yet!")
+        queryNodeUri = await this.promptForQueryNodeUri()
       }
 
       const { metadataCache } = this.getPreservedState()

+ 29 - 15
cli/src/commands/account/chooseMember.ts

@@ -3,38 +3,52 @@ import chalk from 'chalk'
 import { flags } from '@oclif/command'
 import ExitCodes from '../../ExitCodes'
 
-export default class AccountChoose extends AccountsCommandBase {
-  static description = 'Choose default account to use in the CLI'
+export default class AccountChooseMember extends AccountsCommandBase {
+  static description = 'Choose default member to use in the CLI'
   static flags = {
-    address: flags.string({
-      description: 'Select account by address (if available)',
-      char: 'a',
+    memberId: flags.string({
+      description: 'Select member (if available)',
+      char: 'm',
       required: false,
     }),
   }
 
   async run() {
-    const { address } = this.parse(AccountChoose).flags
+    const { memberId } = this.parse(AccountChooseMember).flags
 
-    const memberData = address
-      ? await this.selectKnownMember(address)
+    const memberData = memberId
+      ? await this.selectKnownMember(memberId)
       : await this.getRequiredMemberContext(undefined, false)
 
     await this.setSelectedMember(memberData)
 
-    this.log(chalk.greenBright(`\nAccount switched to ${chalk.magentaBright(address)} (MemberId: ${memberData[0]})!`))
+    this.log(
+      chalk.greenBright(
+        `\nMember to id ${chalk.magentaBright(
+          memberData[0]
+        )} (account: ${memberData[1].controller_account.toString()})!`
+      )
+    )
   }
 
-  async selectKnownMember(address: string): Promise<ISelectedMember> {
-    const knownMembersData = await this.getKnownMembers()
-    const memberData = knownMembersData.find(([, member]) => member.controller_account.toString() === address)
+  async selectKnownMember(memberIdString: string): Promise<ISelectedMember> {
+    const memberId = this.createType('MemberId', memberIdString)
+    const member = await this.getApi().membershipById(memberId)
 
-    if (!memberData) {
-      this.error(`Selected account address not found among known members!`, {
+    if (!member) {
+      this.error(`Selected member id not found among known members!`, {
         exit: ExitCodes.AccessDenied,
       })
     }
 
-    return memberData
+    const selectedMember = [memberId, member] as ISelectedMember
+
+    if (!this.isKeyAvailable(member.controller_account)) {
+      this.error(`Selected member's account is not imported to CLI!`, {
+        exit: ExitCodes.AccessDenied,
+      })
+    }
+
+    return selectedMember
   }
 }

+ 9 - 25
query-node/mappings/content/utils.ts

@@ -549,19 +549,8 @@ export interface IVideoActiveStatus {
 }
 
 export function getVideoActiveStatus(video: Video): IVideoActiveStatus {
-  const productionEnv = () => {
-    const isFullyActive =
-      !!video.isPublic && !video.isCensored && !!video.thumbnailPhoto?.isAccepted && !!video.media?.isAccepted
-
-    return isFullyActive
-  }
-  const testEnv = () => {
-    const isFullyActive = !!video.isPublic && !video.isCensored
-
-    return isFullyActive
-  }
-
-  const isFullyActive = process.env.QN_TEST_ENV ? testEnv() : productionEnv()
+  const isFullyActive =
+    !!video.isPublic && !video.isCensored && !!video.thumbnailPhoto?.isAccepted && !!video.media?.isAccepted
 
   const videoCategory = video.category
   const channel = video.channel
@@ -581,18 +570,13 @@ export async function updateVideoActiveCounters(
   initialActiveStatus: IVideoActiveStatus | null | undefined,
   activeStatus: IVideoActiveStatus | null | undefined
 ): Promise<void> {
-  // definition of generic type for Hydra DatabaseManager's methods
-  type EntityType<T> = {
-    new (...args: any[]): T
-  }
-
   async function updateSingleEntity<Entity extends VideoCategory | Channel>(
     entity: Entity,
     counterChange: number
   ): Promise<void> {
     entity.activeVideosCounter += counterChange
 
-    await store.save<EntityType<Entity>>(entity)
+    await store.save(entity)
   }
 
   async function reflectUpdate<Entity extends VideoCategory | Channel>(
@@ -624,16 +608,16 @@ export async function updateVideoActiveCounters(
 
     // didEntityChange === true
 
-    if (oldEntity) {
-      // if video was fully active before, prepare to decrease counter; increase counter otherwise
-      const counterChange = initFullyActive ? -1 : 1
+    if (oldEntity && initFullyActive) {
+      // if video was fully active before, prepare to decrease counter
+      const counterChange = -1
 
       await updateSingleEntity(oldEntity, counterChange)
     }
 
-    if (newEntity) {
-      // if video is fully active now, prepare to increase counter; decrease counter otherwise
-      const counterChange = nowFullyActive ? 1 : -1
+    if (newEntity && nowFullyActive) {
+      // if video is fully active now, prepare to increase counter
+      const counterChange = 1
 
       await updateSingleEntity(newEntity, counterChange)
     }

+ 8 - 13
query-node/mappings/storage/index.ts

@@ -273,9 +273,7 @@ export async function storage_PendingDataObjectsAccepted({ event, store }: Event
     performance issues. In that case, a unit test for this mapping will be required.
   */
   // load relevant videos one by one and update related active-video-counters
-  await initialActiveStates.reduce(async (accPromise, initialActiveState) => {
-    await accPromise
-
+  for (const initialActiveState of initialActiveStates) {
     // load refreshed version of videos and related entities (channel, channel category, category)
 
     const video = (await store.get(Video, {
@@ -284,7 +282,7 @@ export async function storage_PendingDataObjectsAccepted({ event, store }: Event
     })) as Video
 
     await updateVideoActiveCounters(store, initialActiveState, getVideoActiveStatus(video))
-  }, Promise.resolve())
+  }
 }
 
 export async function storage_DataObjectsMoved({ event, store }: EventContext & StoreContext): Promise<void> {
@@ -311,19 +309,16 @@ export async function storage_DataObjectsDeleted({ event, store }: EventContext
   await Promise.all(
     dataObjects.map(async (dataObject) => {
       // remember if video is fully active before update
-      const initialVideoActiveStatusThumbnail = dataObject.videoThumbnail
-        ? getVideoActiveStatus(dataObject.videoThumbnail)
-        : null
-      const initialVideoActiveStatusMedia = dataObject.videoMedia ? getVideoActiveStatus(dataObject.videoMedia) : null
+      const initialVideoActiveStatus =
+        (dataObject.videoThumbnail && getVideoActiveStatus(dataObject.videoThumbnail)) ||
+        (dataObject.videoMedia && getVideoActiveStatus(dataObject.videoMedia)) ||
+        null
 
       await unsetAssetRelations(store, dataObject)
 
       // update video active counters
-      if (initialVideoActiveStatusThumbnail) {
-        await updateVideoActiveCounters(store, initialVideoActiveStatusThumbnail, undefined)
-      }
-      if (initialVideoActiveStatusMedia) {
-        await updateVideoActiveCounters(store, initialVideoActiveStatusMedia, undefined)
+      if (initialVideoActiveStatus) {
+        await updateVideoActiveCounters(store, initialVideoActiveStatus, undefined)
       }
     })
   )

+ 0 - 8
tests/network-tests/src/Api.ts

@@ -909,14 +909,6 @@ export class Api {
     }
   }
 
-  public findStorageBucketCreated(events: EventRecord[]): DataObjectId | undefined {
-    const record = this.findEventRecord(events, 'storage', 'StorageBucketCreated')
-
-    if (record) {
-      return (record.event.data[0] as unknown) as DataObjectId
-    }
-  }
-
   // Subscribe to system events, resolves to an InvertedPromise or rejects if subscription fails.
   // The inverted promise wraps a promise which resolves when the Proposal with id specified
   // is executed.

+ 18 - 10
tests/network-tests/src/CliApi.ts

@@ -3,12 +3,15 @@ import { spawnSync } from 'child_process'
 import * as fs from 'fs'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { v4 as uuid } from 'uuid'
+import { MemberId } from '@joystream/types/members'
 
 export interface ICreatedVideoData {
   videoId: number
   assetContentIds: string[]
 }
 
+const jsonEndodeIndent = 2
+
 /**
   Adapter for calling CLI commands from integration tests.
 */
@@ -55,6 +58,12 @@ export class CliApi {
     }
   }
 
+  private saveToTempFileEncode(content: unknown) {
+    const encodedContent = JSON.stringify(content, null, jsonEndodeIndent)
+
+    this.saveToTempFile(encodedContent)
+  }
+
   /**
     Parses `id` of newly created content entity from CLI's stdout.
   */
@@ -102,8 +111,7 @@ export class CliApi {
     Imports an account from Polkadot's keyring keypair to CLI.
   */
   async importAccount(keyringPair: KeyringPair, password = ''): Promise<void> {
-    const importableAccount = JSON.stringify(keyringPair.toJson(password))
-    this.saveToTempFile(importableAccount)
+    this.saveToTempFileEncode(keyringPair.toJson(password))
 
     const { stderr } = this.runCommand([
       'account:import',
@@ -120,8 +128,8 @@ export class CliApi {
     }
   }
 
-  async chooseMemberAccount(accountAddress: string) {
-    const { stderr } = this.runCommand(['account:chooseMember', '--address', accountAddress])
+  async chooseMemberAccount(memberId: MemberId) {
+    const { stderr } = this.runCommand(['account:chooseMember', '--memberId', memberId.toString()])
 
     if (stderr) {
       throw new Error(`Unexpected CLI failure on choosing account: "${stderr}"`)
@@ -132,7 +140,7 @@ export class CliApi {
     Creates a new channel.
   */
   async createChannel(channel: unknown): Promise<number> {
-    this.saveToTempFile(JSON.stringify(channel))
+    this.saveToTempFileEncode(channel)
 
     const { stdout, stderr } = this.runCommand(
       ['content:createChannel', '--input', this.tmpFilePath, '--context', 'Member'],
@@ -151,7 +159,7 @@ export class CliApi {
     Creates a new channel category.
   */
   async createChannelCategory(channelCategory: unknown): Promise<number> {
-    this.saveToTempFile(JSON.stringify(channelCategory))
+    this.saveToTempFileEncode(channelCategory)
 
     const { stdout, stderr } = this.runCommand(
       ['content:createChannelCategory', '--input', this.tmpFilePath, '--context', 'Lead'],
@@ -169,7 +177,7 @@ export class CliApi {
     Creates a new video.
   */
   async createVideo(channelId: number, video: unknown): Promise<ICreatedVideoData> {
-    this.saveToTempFile(JSON.stringify(video))
+    this.saveToTempFileEncode(video)
 
     const { stdout, stderr } = this.runCommand(
       ['content:createVideo', '--input', this.tmpFilePath, '--channelId', channelId.toString()],
@@ -194,7 +202,7 @@ export class CliApi {
     Creates a new video category.
   */
   async createVideoCategory(videoCategory: unknown): Promise<number> {
-    this.saveToTempFile(JSON.stringify(videoCategory))
+    this.saveToTempFileEncode(videoCategory)
 
     const { stdout, stderr } = this.runCommand(
       ['content:createVideoCategory', '--input', this.tmpFilePath, '--context', 'Lead'],
@@ -212,7 +220,7 @@ export class CliApi {
     Updates an existing video.
   */
   async updateVideo(videoId: number, video: unknown): Promise<void> {
-    this.saveToTempFile(JSON.stringify(video))
+    this.saveToTempFileEncode(video)
 
     const { stdout, stderr } = this.runCommand(
       ['content:updateVideo', '--input', this.tmpFilePath, videoId.toString()],
@@ -229,7 +237,7 @@ export class CliApi {
     Updates a channel.
   */
   async updateChannel(channelId: number, channel: unknown): Promise<void> {
-    this.saveToTempFile(JSON.stringify(channel))
+    this.saveToTempFileEncode(channel)
 
     const { stdout, stderr } = this.runCommand(
       ['content:updateChannel', '--input', this.tmpFilePath, channelId.toString()],

+ 2 - 2
tests/network-tests/src/fixtures/content/activeVideoCounters.ts

@@ -118,7 +118,7 @@ export class ActiveVideoCountersFixture extends BaseQueryNodeFixture {
 
     this.debug(`Choosing content working group lead's account`)
     // this expects lead account to be already imported into CLI
-    await this.cli.chooseMemberAccount(contentLeader.role_account_id.toString())
+    await this.cli.chooseMemberAccount(contentLeader.member_id)
 
     this.debug('Creating channel categories')
     const channelCategoryIds = await this.createChannelCategories(channelCategoryCount)
@@ -130,7 +130,7 @@ export class ActiveVideoCountersFixture extends BaseQueryNodeFixture {
 
     this.debug(`Importing author's account`)
     await this.cli.importAccount(author.keyringPair)
-    await this.cli.chooseMemberAccount(author.keyringPair.address)
+    await this.cli.chooseMemberAccount(author.memberId)
 
     // create content entities