From f25beb2e537f0b7d80c711c15f08a5a9959b9f79 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Thu, 26 Sep 2024 16:21:43 +0300 Subject: [PATCH 1/2] chore(server): core IoC 8 - getProjectTopLevelModelsTreeFactory --- .../core/domain/branches/operations.ts | 17 ++ .../modules/core/graph/resolvers/models.ts | 8 +- .../modules/core/repositories/branches.ts | 227 +++++++++--------- .../modules/core/services/branch/retrieval.ts | 12 +- 4 files changed, 149 insertions(+), 115 deletions(-) diff --git a/packages/server/modules/core/domain/branches/operations.ts b/packages/server/modules/core/domain/branches/operations.ts index 72e1b5914..a0d74a77f 100644 --- a/packages/server/modules/core/domain/branches/operations.ts +++ b/packages/server/modules/core/domain/branches/operations.ts @@ -100,3 +100,20 @@ export type GetProjectTopLevelModelsTree = ( } > > + +export type GetModelTreeItems = ( + projectId: string, + args: Omit, + options?: Partial<{ + filterOutEmptyMain: boolean + parentModelName: string + }> +) => Promise + +export type GetModelTreeItemsTotalCount = ( + projectId: string, + options?: Partial<{ + filterOutEmptyMain: boolean + parentModelName: string + }> +) => Promise diff --git a/packages/server/modules/core/graph/resolvers/models.ts b/packages/server/modules/core/graph/resolvers/models.ts index 11d15534a..1faa3bc34 100644 --- a/packages/server/modules/core/graph/resolvers/models.ts +++ b/packages/server/modules/core/graph/resolvers/models.ts @@ -24,9 +24,10 @@ import { } from '@/modules/shared/utils/subscriptions' import { getBranchLatestCommitsFactory, - getModelTreeItems, + getModelTreeItemsFactory, getModelTreeItemsFilteredFactory, getModelTreeItemsFilteredTotalCountFactory, + getModelTreeItemsTotalCountFactory, getPaginatedProjectModelsItemsFactory, getPaginatedProjectModelsTotalCountFactory, getStreamBranchesByNameFactory @@ -54,11 +55,14 @@ const getPaginatedProjectModels = getPaginatedProjectModelsFactory({ db }) }) +const getModelTreeItems = getModelTreeItemsFactory({ db }) const getProjectTopLevelModelsTree = getProjectTopLevelModelsTreeFactory({ getModelTreeItemsFiltered: getModelTreeItemsFilteredFactory({ db }), getModelTreeItemsFilteredTotalCount: getModelTreeItemsFilteredTotalCountFactory({ db - }) + }), + getModelTreeItems, + getModelTreeItemsTotalCount: getModelTreeItemsTotalCountFactory({ db }) }) export = { diff --git a/packages/server/modules/core/repositories/branches.ts b/packages/server/modules/core/repositories/branches.ts index 5fc4724e6..e465e293c 100644 --- a/packages/server/modules/core/repositories/branches.ts +++ b/packages/server/modules/core/repositories/branches.ts @@ -20,8 +20,10 @@ import { GetBranchById, GetBranchesByIds, GetBranchLatestCommits, + GetModelTreeItems, GetModelTreeItemsFiltered, GetModelTreeItemsFilteredTotalCount, + GetModelTreeItemsTotalCount, GetPaginatedProjectModelsItems, GetPaginatedProjectModelsTotalCount, GetStreamBranchByName, @@ -482,125 +484,136 @@ export const getModelTreeItemsFilteredTotalCountFactory = return parseInt(row.count || '0') } -function getModelTreeItemsBaseQuery( - projectId: string, - options?: Partial<{ filterOutEmptyMain: boolean; parentModelName: string }> -) { - const cleanInput = ( - input: string | null | undefined, - options?: Partial<{ escapeRegexp: boolean }> +const getModelTreeItemsBaseQueryFactory = + (deps: { db: Knex }) => + ( + projectId: string, + options?: Partial<{ filterOutEmptyMain: boolean; parentModelName: string }> ) => { - let clean = (input || '').toLowerCase() - clean = trim(trim(clean), '/') - clean = options?.escapeRegexp ? clean.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : clean - return clean - } + const cleanInput = ( + input: string | null | undefined, + options?: Partial<{ escapeRegexp: boolean }> + ) => { + let clean = (input || '').toLowerCase() + clean = trim(trim(clean), '/') + clean = options?.escapeRegexp + ? clean.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + : clean + return clean + } - const { filterOutEmptyMain = true, parentModelName } = options || {} - const cleanModelName = cleanInput(parentModelName, { escapeRegexp: true }) - const branchPartPattern = `[^/]+` // regexp for each branch part between slashes + const { filterOutEmptyMain = true, parentModelName } = options || {} + const cleanModelName = cleanInput(parentModelName, { escapeRegexp: true }) + const branchPartPattern = `[^/]+` // regexp for each branch part between slashes - const regExp = cleanModelName.length - ? // only direct children of parentModelName - `^${cleanModelName}\\/(${branchPartPattern})` - : // only first branch part (top level item) - `^${branchPartPattern}` + const regExp = cleanModelName.length + ? // only direct children of parentModelName + `^${cleanModelName}\\/(${branchPartPattern})` + : // only first branch part (top level item) + `^${branchPartPattern}` - const branchPartMatcher = `(regexp_match("branches"."name", '${regExp}', 'i'))[1]` + const branchPartMatcher = `(regexp_match("branches"."name", '${regExp}', 'i'))[1]` - const baseQuery = Branches.knex() - .select<{ updatedAt: Date; branchPart: string; longestFullName: string }[]>([ - // for sorting by updatedAt (of any of the children) - knex.raw('MAX(??) as "updatedAt"', [Branches.col.updatedAt]), - // for allowing filtering by parent branch name - knex.raw(`${branchPartMatcher} as "branchPart"`), - // for checking for children + checking if returned row represents 'main' if filterOutEmptyMain - knex.raw(`MAX(??) as "longestFullName"`, [Branches.col.name]) - ]) - .where(Branches.col.streamId, projectId) - .andWhere(knex.raw(`${branchPartMatcher} IS NOT NULL`)) - .groupBy('branchPart') - .orderBy('updatedAt', 'desc') + const baseQuery = tables + .branches(deps.db) + .select<{ updatedAt: Date; branchPart: string; longestFullName: string }[]>([ + // for sorting by updatedAt (of any of the children) + knex.raw('MAX(??) as "updatedAt"', [Branches.col.updatedAt]), + // for allowing filtering by parent branch name + knex.raw(`${branchPartMatcher} as "branchPart"`), + // for checking for children + checking if returned row represents 'main' if filterOutEmptyMain + knex.raw(`MAX(??) as "longestFullName"`, [Branches.col.name]) + ]) + .where(Branches.col.streamId, projectId) + .andWhere(knex.raw(`${branchPartMatcher} IS NOT NULL`)) + .groupBy('branchPart') + .orderBy('updatedAt', 'desc') - let finalQuery = baseQuery - if (filterOutEmptyMain) { - // Select branch id to check for attached commits - // And count to ensure that there's only "main" and no children - baseQuery.select([ - knex.raw(`(ARRAY_AGG(??))[1] as "lastId"`, [Branches.col.id]), - knex.raw(`COUNT(??) as "branchCount"`, [Branches.col.id]) - ]) + let finalQuery = baseQuery + if (filterOutEmptyMain) { + // Select branch id to check for attached commits + // And count to ensure that there's only "main" and no children + baseQuery.select([ + knex.raw(`(ARRAY_AGG(??))[1] as "lastId"`, [Branches.col.id]), + knex.raw(`COUNT(??) as "branchCount"`, [Branches.col.id]) + ]) - // Wrap base query and filter out empty main - finalQuery = knex - .select<{ updatedAt: Date; branchPart: string; longestFullName: string }[]>('*') - .from(baseQuery.as('sq1')) - .where((w1) => { - // Either there are children branches, or the branch name isn't 'main' - // Or the branch has more than 0 commits - w1.where((w2) => { - w2.whereNot('branchCount', 1) - .orWhereNot('branchPart', knex.raw('"longestFullName"')) - .orWhereNot('longestFullName', 'main') - }).orWhere( - knex.raw( - `(SELECT COUNT(*) FROM "branch_commits" WHERE "branch_commits"."branchId" = "lastId")` - ), - '>', - 0 - ) - }) - } - - return { - query: finalQuery, - parentModelName: cleanInput(parentModelName) - } -} - -export async function getModelTreeItems( - projectId: string, - args: Omit, - options?: Partial<{ filterOutEmptyMain: boolean; parentModelName: string }> -) { - const limit = clamp(args.limit || 25, 0, 100) - const { query, parentModelName } = getModelTreeItemsBaseQuery(projectId, options) - - const finalQuery = knex.from(query.as('sq1')) - finalQuery.limit(limit) - - if (args.cursor) { - finalQuery.andWhere('updatedAt', '<', args.cursor) - } - - const res = await finalQuery - const items = res.map((i): ModelsTreeItemGraphQLReturn => { - const fullName = parentModelName - ? `${parentModelName}/${i.branchPart}` - : i.branchPart + // Wrap base query and filter out empty main + finalQuery = (deps.db().from(baseQuery.as('sq1')) as typeof baseQuery) + .select<{ updatedAt: Date; branchPart: string; longestFullName: string }[]>('*') + .where((w1) => { + // Either there are children branches, or the branch name isn't 'main' + // Or the branch has more than 0 commits + w1.where((w2) => { + w2.whereNot('branchCount', 1) + .orWhereNot('branchPart', knex.raw('"longestFullName"')) + .orWhereNot('longestFullName', 'main') + }).orWhere( + knex.raw( + `(SELECT COUNT(*) FROM "branch_commits" WHERE "branch_commits"."branchId" = "lastId")` + ), + '>', + 0 + ) + }) + } return { - id: `${projectId}-${fullName}`, - projectId, - name: i.branchPart, - fullName, - updatedAt: i.updatedAt, - hasChildren: fullName.length < i.longestFullName.length + query: finalQuery, + parentModelName: cleanInput(parentModelName) } - }) + } - return items -} +export const getModelTreeItemsFactory = + (deps: { db: Knex }): GetModelTreeItems => + async ( + projectId: string, + args: Omit, + options?: Partial<{ filterOutEmptyMain: boolean; parentModelName: string }> + ) => { + const limit = clamp(args.limit || 25, 0, 100) + const { query, parentModelName } = getModelTreeItemsBaseQueryFactory(deps)( + projectId, + options + ) -export async function getModelTreeItemsTotalCount( - projectId: string, - options?: Partial<{ filterOutEmptyMain: boolean; parentModelName: string }> -) { - const { query } = getModelTreeItemsBaseQuery(projectId, options) - const q = knex.count<{ count: string }[]>().from(query.as('sq1')) - const [row] = await q - return parseInt(row.count || '0') -} + const finalQuery = knex.from(query.as('sq1')) + finalQuery.limit(limit) + + if (args.cursor) { + finalQuery.andWhere('updatedAt', '<', args.cursor) + } + + const res = await finalQuery + const items = res.map((i): ModelsTreeItemGraphQLReturn => { + const fullName = parentModelName + ? `${parentModelName}/${i.branchPart}` + : i.branchPart + + return { + id: `${projectId}-${fullName}`, + projectId, + name: i.branchPart, + fullName, + updatedAt: i.updatedAt, + hasChildren: fullName.length < i.longestFullName.length + } + }) + + return items + } + +export const getModelTreeItemsTotalCountFactory = + (deps: { db: Knex }): GetModelTreeItemsTotalCount => + async ( + projectId: string, + options?: Partial<{ filterOutEmptyMain: boolean; parentModelName: string }> + ) => { + const { query } = getModelTreeItemsBaseQueryFactory(deps)(projectId, options) + const q = knex.count<{ count: string }[]>().from(query.as('sq1')) + const [row] = await q + return parseInt(row.count || '0') + } export const validateBranchName = (name: string) => { name = (name || '').trim() diff --git a/packages/server/modules/core/services/branch/retrieval.ts b/packages/server/modules/core/services/branch/retrieval.ts index 5b803679a..1a40d7e26 100644 --- a/packages/server/modules/core/services/branch/retrieval.ts +++ b/packages/server/modules/core/services/branch/retrieval.ts @@ -5,18 +5,16 @@ import { StreamBranchesArgs } from '@/modules/core/graph/generated/graphql' import { getBranchesByStreamId } from '@/modules/core/services/branches' -import { - getModelTreeItems, - getModelTreeItemsTotalCount -} from '@/modules/core/repositories/branches' import { last } from 'lodash' import { Merge } from 'type-fest' import { ModelsTreeItemGraphQLReturn } from '@/modules/core/helpers/graphTypes' import { getMaximumProjectModelsPerPage } from '@/modules/shared/helpers/envHelper' import { BadRequestError } from '@/modules/shared/errors' import { + GetModelTreeItems, GetModelTreeItemsFiltered, GetModelTreeItemsFilteredTotalCount, + GetModelTreeItemsTotalCount, GetPaginatedProjectModelsItems, GetPaginatedProjectModelsTotalCount, GetProjectTopLevelModelsTree @@ -61,6 +59,8 @@ export const getProjectTopLevelModelsTreeFactory = (deps: { getModelTreeItemsFiltered: GetModelTreeItemsFiltered getModelTreeItemsFilteredTotalCount: GetModelTreeItemsFilteredTotalCount + getModelTreeItems: GetModelTreeItems + getModelTreeItemsTotalCount: GetModelTreeItemsTotalCount }): GetProjectTopLevelModelsTree => async ( projectId: string, @@ -86,8 +86,8 @@ export const getProjectTopLevelModelsTreeFactory = totalCount = filteredTotalCount } else { const [unfilteredItems, unfilteredTotalCount] = await Promise.all([ - getModelTreeItems(projectId, args, options), - getModelTreeItemsTotalCount(projectId, options) + deps.getModelTreeItems(projectId, args, options), + deps.getModelTreeItemsTotalCount(projectId, options) ]) items = unfilteredItems From 5cee4f3d45028659a3e4bcd3927e5dfe56acb9ea Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Thu, 26 Sep 2024 16:36:11 +0300 Subject: [PATCH 2/2] cr fix --- packages/server/modules/core/repositories/branches.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/modules/core/repositories/branches.ts b/packages/server/modules/core/repositories/branches.ts index e465e293c..7a61193cf 100644 --- a/packages/server/modules/core/repositories/branches.ts +++ b/packages/server/modules/core/repositories/branches.ts @@ -610,7 +610,7 @@ export const getModelTreeItemsTotalCountFactory = options?: Partial<{ filterOutEmptyMain: boolean; parentModelName: string }> ) => { const { query } = getModelTreeItemsBaseQueryFactory(deps)(projectId, options) - const q = knex.count<{ count: string }[]>().from(query.as('sq1')) + const q = deps.db().count<{ count: string }[]>().from(query.as('sq1')) const [row] = await q return parseInt(row.count || '0') }