chore(server): core IoC 7 - getProjectTopLevelModelsTreeFactory

This commit is contained in:
Kristaps Fabians Geikins
2024-09-26 16:10:50 +03:00
parent 76f2e096b5
commit 4a604e4b7e
4 changed files with 199 additions and 133 deletions
@@ -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<ModelsTreeItemGraphQLReturn[]>
export type GetModelTreeItemsFilteredTotalCount = (
projectId: string,
args: ProjectModelsTreeArgs,
options?: Partial<{
filterOutEmptyMain: boolean
}>
) => Promise<number>
export type GetProjectTopLevelModelsTree = (
projectId: string,
args: ProjectModelsTreeArgs,
options?: Partial<{
filterOutEmptyMain: boolean
}>
) => Promise<
Merge<
ModelsTreeItemCollection,
{
items: ModelsTreeItemGraphQLReturn[]
}
>
>
@@ -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: {
@@ -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<Array<{ name: string; updatedAt: Date; hasChildren: boolean }>>([
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<Array<{ name: string; updatedAt: Date; hasChildren: boolean }>>([
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,
@@ -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<Merge<ModelsTreeItemCollection, { items: ModelsTreeItemGraphQLReturn[] }>> {
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<ModelsTreeItemCollection, { items: ModelsTreeItemGraphQLReturn[] }>
> => {
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
}
}