From 4a604e4b7e668901df84e36a73d0140a8dc973db Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Thu, 26 Sep 2024 16:10:50 +0300 Subject: [PATCH] chore(server): core IoC 7 - getProjectTopLevelModelsTreeFactory --- .../core/domain/branches/operations.ts | 39 +++- .../modules/core/graph/resolvers/models.ts | 10 +- .../modules/core/repositories/branches.ts | 203 ++++++++++-------- .../modules/core/services/branch/retrieval.ts | 80 +++---- 4 files changed, 199 insertions(+), 133 deletions(-) diff --git a/packages/server/modules/core/domain/branches/operations.ts b/packages/server/modules/core/domain/branches/operations.ts index 7031eadf8..72e1b5914 100644 --- a/packages/server/modules/core/domain/branches/operations.ts +++ b/packages/server/modules/core/domain/branches/operations.ts @@ -1,7 +1,13 @@ import { Branch, ModelTreeItem } from '@/modules/core/domain/branches/types' import { BranchLatestCommit } from '@/modules/core/domain/commits/types' -import { ProjectModelsArgs } from '@/modules/core/graph/generated/graphql' +import { + ModelsTreeItemCollection, + ProjectModelsArgs, + ProjectModelsTreeArgs +} from '@/modules/core/graph/generated/graphql' +import { ModelsTreeItemGraphQLReturn } from '@/modules/core/helpers/graphTypes' import { Nullable, Optional } from '@speckle/shared' +import { Merge } from 'type-fest' export type GenerateBranchId = () => string @@ -63,3 +69,34 @@ export type GetPaginatedProjectModels = ( items: Branch[] cursor: string | null }> + +export type GetModelTreeItemsFiltered = ( + projectId: string, + args: ProjectModelsTreeArgs, + options?: Partial<{ + filterOutEmptyMain: boolean + }> +) => Promise + +export type GetModelTreeItemsFilteredTotalCount = ( + projectId: string, + args: ProjectModelsTreeArgs, + options?: Partial<{ + filterOutEmptyMain: boolean + }> +) => Promise + +export type GetProjectTopLevelModelsTree = ( + projectId: string, + args: ProjectModelsTreeArgs, + options?: Partial<{ + filterOutEmptyMain: boolean + }> +) => Promise< + Merge< + ModelsTreeItemCollection, + { + items: ModelsTreeItemGraphQLReturn[] + } + > +> diff --git a/packages/server/modules/core/graph/resolvers/models.ts b/packages/server/modules/core/graph/resolvers/models.ts index 4de1dad23..11d15534a 100644 --- a/packages/server/modules/core/graph/resolvers/models.ts +++ b/packages/server/modules/core/graph/resolvers/models.ts @@ -7,7 +7,7 @@ import { } from '@/modules/core/services/branch/management' import { getPaginatedProjectModelsFactory, - getProjectTopLevelModelsTree + getProjectTopLevelModelsTreeFactory } from '@/modules/core/services/branch/retrieval' import { authorizeResolver } from '@/modules/shared' import { getServerOrigin } from '@/modules/shared/helpers/envHelper' @@ -25,6 +25,8 @@ import { import { getBranchLatestCommitsFactory, getModelTreeItems, + getModelTreeItemsFilteredFactory, + getModelTreeItemsFilteredTotalCountFactory, getPaginatedProjectModelsItemsFactory, getPaginatedProjectModelsTotalCountFactory, getStreamBranchesByNameFactory @@ -52,6 +54,12 @@ const getPaginatedProjectModels = getPaginatedProjectModelsFactory({ db }) }) +const getProjectTopLevelModelsTree = getProjectTopLevelModelsTreeFactory({ + getModelTreeItemsFiltered: getModelTreeItemsFilteredFactory({ db }), + getModelTreeItemsFilteredTotalCount: getModelTreeItemsFilteredTotalCountFactory({ + db + }) +}) export = { User: { diff --git a/packages/server/modules/core/repositories/branches.ts b/packages/server/modules/core/repositories/branches.ts index 606f2dfe1..5fc4724e6 100644 --- a/packages/server/modules/core/repositories/branches.ts +++ b/packages/server/modules/core/repositories/branches.ts @@ -20,6 +20,8 @@ import { GetBranchById, GetBranchesByIds, GetBranchLatestCommits, + GetModelTreeItemsFiltered, + GetModelTreeItemsFilteredTotalCount, GetPaginatedProjectModelsItems, GetPaginatedProjectModelsTotalCount, GetStreamBranchByName, @@ -360,114 +362,125 @@ export const getPaginatedProjectModelsTotalCountFactory = return parseInt(res?.count || '0') } -function getModelTreeItemsFilteredBaseQuery( - projectId: string, - args: ProjectModelsTreeArgs, - options?: Partial<{ filterOutEmptyMain: boolean }> -) { - const search = args.filter?.search || '' - const sourceApps = args.filter?.sourceApps || [] - const contributors = args.filter?.contributors || [] - const isFiltering = search.length || sourceApps.length || contributors.length - const filterOutEmptyMain = !isFiltering && (options?.filterOutEmptyMain ?? true) +const getModelTreeItemsFilteredBaseQueryFactory = + (deps: { db: Knex }) => + ( + projectId: string, + args: ProjectModelsTreeArgs, + options?: Partial<{ filterOutEmptyMain: boolean }> + ) => { + const search = args.filter?.search || '' + const sourceApps = args.filter?.sourceApps || [] + const contributors = args.filter?.contributors || [] + const isFiltering = search.length || sourceApps.length || contributors.length + const filterOutEmptyMain = !isFiltering && (options?.filterOutEmptyMain ?? true) - const BranchesJoin = Branches.with({ withCustomTablePrefix: 'b2' }) + const BranchesJoin = Branches.with({ withCustomTablePrefix: 'b2' }) - const q = Branches.knex() - .select>([ - Branches.col.name, - Branches.col.updatedAt, - knex.raw(`COUNT(??) > 0 as "hasChildren"`, [BranchesJoin.col.id]) - ]) - .leftJoin(BranchesJoin.name, (lj) => { - lj.on( - BranchesJoin.col.name, - 'ilike', - knex.raw(`(?? || '/%')`, [Branches.col.name]) - ) - .andOn(BranchesJoin.col.name, '!=', Branches.col.name) - .andOn(BranchesJoin.col.streamId, '=', Branches.col.streamId) - }) - .where(Branches.col.streamId, projectId) - .groupBy(Branches.col.id) - .orderBy(Branches.col.updatedAt, 'desc') + const q = tables + .branches(deps.db) + .select>([ + Branches.col.name, + Branches.col.updatedAt, + knex.raw(`COUNT(??) > 0 as "hasChildren"`, [BranchesJoin.col.id]) + ]) + .leftJoin(BranchesJoin.name, (lj) => { + lj.on( + BranchesJoin.col.name, + 'ilike', + knex.raw(`(?? || '/%')`, [Branches.col.name]) + ) + .andOn(BranchesJoin.col.name, '!=', Branches.col.name) + .andOn(BranchesJoin.col.streamId, '=', Branches.col.streamId) + }) + .where(Branches.col.streamId, projectId) + .groupBy(Branches.col.id) + .orderBy(Branches.col.updatedAt, 'desc') - if (filterOutEmptyMain) { - q.andWhere((w) => { - w.whereNot(Branches.col.name, 'main').orWhere( - 0, - '<', - BranchCommits.knex() - .count() - .where(BranchCommits.col.branchId, knex.raw(Branches.col.id)) - ) - }) - } - - if (search.length) { - q.andWhereILike(Branches.col.name, `%${search}%`) - } - - if (sourceApps.length || contributors.length) { - q.leftJoin( - BranchCommits.name, - BranchCommits.col.branchId, - Branches.col.id - ).leftJoin(Commits.name, Commits.col.id, BranchCommits.col.commitId) - - if (contributors.length) { - q.whereIn(Commits.col.author, contributors) + if (filterOutEmptyMain) { + q.andWhere((w) => { + w.whereNot(Branches.col.name, 'main').orWhere( + 0, + '<', + BranchCommits.knex() + .count() + .where(BranchCommits.col.branchId, knex.raw(Branches.col.id)) + ) + }) } - if (sourceApps.length) { - q.whereRaw( - knex.raw(`?? ~* ?`, [Commits.col.sourceApplication, sourceApps.join('|')]) - ) + if (search.length) { + q.andWhereILike(Branches.col.name, `%${search}%`) } + + if (sourceApps.length || contributors.length) { + q.leftJoin( + BranchCommits.name, + BranchCommits.col.branchId, + Branches.col.id + ).leftJoin(Commits.name, Commits.col.id, BranchCommits.col.commitId) + + if (contributors.length) { + q.whereIn(Commits.col.author, contributors) + } + + if (sourceApps.length) { + q.whereRaw( + knex.raw(`?? ~* ?`, [Commits.col.sourceApplication, sourceApps.join('|')]) + ) + } + } + + return q } - return q -} +export const getModelTreeItemsFilteredFactory = + (deps: { db: Knex }): GetModelTreeItemsFiltered => + async ( + projectId: string, + args: ProjectModelsTreeArgs, + options?: Partial<{ filterOutEmptyMain: boolean }> + ) => { + const limit = clamp(args.limit || 25, 0, 100) + const q = getModelTreeItemsFilteredBaseQueryFactory(deps)(projectId, args, options) + q.limit(limit) -export async function getModelTreeItemsFiltered( - projectId: string, - args: ProjectModelsTreeArgs, - options?: Partial<{ filterOutEmptyMain: boolean }> -) { - const limit = clamp(args.limit || 25, 0, 100) - const q = getModelTreeItemsFilteredBaseQuery(projectId, args, options) - q.limit(limit) + if (args.cursor) { + q.andWhere(Branches.col.updatedAt, '<', args.cursor) + } - if (args.cursor) { - q.andWhere(Branches.col.updatedAt, '<', args.cursor) + const res = await q + const items = res.map((i): ModelsTreeItemGraphQLReturn => { + const displayName = last(i.name.split('/')) as string + return { + id: `${projectId}-${i.name}`, + projectId, + name: displayName, + fullName: i.name, + updatedAt: i.updatedAt, + hasChildren: i.hasChildren + } + }) + + return items } - const res = await q - const items = res.map((i): ModelsTreeItemGraphQLReturn => { - const displayName = last(i.name.split('/')) as string - return { - id: `${projectId}-${i.name}`, +export const getModelTreeItemsFilteredTotalCountFactory = + (deps: { db: Knex }): GetModelTreeItemsFilteredTotalCount => + async ( + projectId: string, + args: ProjectModelsTreeArgs, + options?: Partial<{ filterOutEmptyMain: boolean }> + ) => { + const baseQ = getModelTreeItemsFilteredBaseQueryFactory(deps)( projectId, - name: displayName, - fullName: i.name, - updatedAt: i.updatedAt, - hasChildren: i.hasChildren - } - }) - - return items -} - -export async function getModelTreeItemsFilteredTotalCount( - projectId: string, - args: ProjectModelsTreeArgs, - options?: Partial<{ filterOutEmptyMain: boolean }> -) { - const baseQ = getModelTreeItemsFilteredBaseQuery(projectId, args, options) - const q = knex.count<{ count: string }[]>().from(baseQ.as('sq1')) - const [row] = await q - return parseInt(row.count || '0') -} + args, + options + ) + const q = deps.db().count<{ count: string }[]>().from(baseQ.as('sq1')) + const [row] = await q + return parseInt(row.count || '0') + } function getModelTreeItemsBaseQuery( projectId: string, diff --git a/packages/server/modules/core/services/branch/retrieval.ts b/packages/server/modules/core/services/branch/retrieval.ts index df0c975c7..5b803679a 100644 --- a/packages/server/modules/core/services/branch/retrieval.ts +++ b/packages/server/modules/core/services/branch/retrieval.ts @@ -6,9 +6,7 @@ import { } from '@/modules/core/graph/generated/graphql' import { getBranchesByStreamId } from '@/modules/core/services/branches' import { - getModelTreeItemsFiltered, getModelTreeItems, - getModelTreeItemsFilteredTotalCount, getModelTreeItemsTotalCount } from '@/modules/core/repositories/branches' import { last } from 'lodash' @@ -17,8 +15,11 @@ import { ModelsTreeItemGraphQLReturn } from '@/modules/core/helpers/graphTypes' import { getMaximumProjectModelsPerPage } from '@/modules/shared/helpers/envHelper' import { BadRequestError } from '@/modules/shared/errors' import { + GetModelTreeItemsFiltered, + GetModelTreeItemsFilteredTotalCount, GetPaginatedProjectModelsItems, - GetPaginatedProjectModelsTotalCount + GetPaginatedProjectModelsTotalCount, + GetProjectTopLevelModelsTree } from '@/modules/core/domain/branches/operations' export async function getPaginatedStreamBranches( @@ -56,40 +57,47 @@ export const getPaginatedProjectModelsFactory = } } -export async function getProjectTopLevelModelsTree( - projectId: string, - args: ProjectModelsTreeArgs, - options?: Partial<{ filterOutEmptyMain: boolean }> -): Promise> { - let items: ModelsTreeItemGraphQLReturn[] = [] - let totalCount = 0 +export const getProjectTopLevelModelsTreeFactory = + (deps: { + getModelTreeItemsFiltered: GetModelTreeItemsFiltered + getModelTreeItemsFilteredTotalCount: GetModelTreeItemsFilteredTotalCount + }): GetProjectTopLevelModelsTree => + async ( + projectId: string, + args: ProjectModelsTreeArgs, + options?: Partial<{ filterOutEmptyMain: boolean }> + ): Promise< + Merge + > => { + let items: ModelsTreeItemGraphQLReturn[] = [] + let totalCount = 0 - if ( - args.filter?.search || - args.filter?.contributors?.length || - args.filter?.sourceApps?.length - ) { - const [filteredItems, filteredTotalCount] = await Promise.all([ - getModelTreeItemsFiltered(projectId, args, options), - getModelTreeItemsFilteredTotalCount(projectId, args, options) - ]) + if ( + args.filter?.search || + args.filter?.contributors?.length || + args.filter?.sourceApps?.length + ) { + const [filteredItems, filteredTotalCount] = await Promise.all([ + deps.getModelTreeItemsFiltered(projectId, args, options), + deps.getModelTreeItemsFilteredTotalCount(projectId, args, options) + ]) - items = filteredItems - totalCount = filteredTotalCount - } else { - const [unfilteredItems, unfilteredTotalCount] = await Promise.all([ - getModelTreeItems(projectId, args, options), - getModelTreeItemsTotalCount(projectId, options) - ]) + items = filteredItems + totalCount = filteredTotalCount + } else { + const [unfilteredItems, unfilteredTotalCount] = await Promise.all([ + getModelTreeItems(projectId, args, options), + getModelTreeItemsTotalCount(projectId, options) + ]) - items = unfilteredItems - totalCount = unfilteredTotalCount + items = unfilteredItems + totalCount = unfilteredTotalCount + } + + const lastItem = last(items) + return { + items, + totalCount, + cursor: lastItem ? lastItem.updatedAt.toISOString() : null + } } - - const lastItem = last(items) - return { - items, - totalCount, - cursor: lastItem ? lastItem.updatedAt.toISOString() : null - } -}