feat(dashboards): query dashboards by/on project (#5704)
This commit is contained in:
@@ -6,6 +6,28 @@ extend type Mutation {
|
||||
dashboardMutations: DashboardMutations! @hasServerRole(role: SERVER_GUEST)
|
||||
}
|
||||
|
||||
type DashboardMutations {
|
||||
create(workspace: WorkspaceIdentifier!, input: DashboardCreateInput!): Dashboard!
|
||||
delete(id: String!): Boolean!
|
||||
update(input: DashboardUpdateInput!): Dashboard!
|
||||
}
|
||||
|
||||
extend type Workspace {
|
||||
dashboards(
|
||||
limit: Int! = 50
|
||||
cursor: String
|
||||
filter: WorkspaceDashboardsFilter
|
||||
): DashboardCollection!
|
||||
}
|
||||
|
||||
extend type Project {
|
||||
dashboards(
|
||||
limit: Int! = 50
|
||||
cursor: String
|
||||
filter: ProjectDashboardsFilter
|
||||
): DashboardCollection!
|
||||
}
|
||||
|
||||
type Dashboard {
|
||||
id: String!
|
||||
name: String!
|
||||
@@ -25,14 +47,13 @@ type DashboardCollection {
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
extend type Workspace {
|
||||
dashboards(limit: Int! = 50, cursor: String): DashboardCollection!
|
||||
input WorkspaceDashboardsFilter {
|
||||
projectIds: [String!]
|
||||
search: String
|
||||
}
|
||||
|
||||
type DashboardMutations {
|
||||
create(workspace: WorkspaceIdentifier!, input: DashboardCreateInput!): Dashboard!
|
||||
delete(id: String!): Boolean!
|
||||
update(input: DashboardUpdateInput!): Dashboard!
|
||||
input ProjectDashboardsFilter {
|
||||
search: String
|
||||
}
|
||||
|
||||
input DashboardCreateInput {
|
||||
|
||||
@@ -2656,6 +2656,7 @@ export type Project = {
|
||||
commentThreads: ProjectCommentCollection;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
dashboardTokens: DashboardTokenCollection;
|
||||
dashboards: DashboardCollection;
|
||||
description?: Maybe<Scalars['String']['output']>;
|
||||
/** Public project-level configuration for embedded viewer */
|
||||
embedOptions: ProjectEmbedOptions;
|
||||
@@ -2774,6 +2775,13 @@ export type ProjectDashboardTokensArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type ProjectDashboardsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<ProjectDashboardsFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectEmbedTokensArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
@@ -3091,6 +3099,10 @@ export type ProjectCreateInput = {
|
||||
visibility?: InputMaybe<ProjectVisibility>;
|
||||
};
|
||||
|
||||
export type ProjectDashboardsFilter = {
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type ProjectEmbedOptions = {
|
||||
__typename?: 'ProjectEmbedOptions';
|
||||
hideSpeckleBranding: Scalars['Boolean']['output'];
|
||||
@@ -5679,6 +5691,7 @@ export type WorkspaceAutomateFunctionsArgs = {
|
||||
|
||||
export type WorkspaceDashboardsArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<WorkspaceDashboardsFilter>;
|
||||
limit?: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
@@ -5777,6 +5790,11 @@ export type WorkspaceCreationStateInput = {
|
||||
workspaceId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
export type WorkspaceDashboardsFilter = {
|
||||
projectIds?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type WorkspaceDismissInput = {
|
||||
workspaceId: Scalars['ID']['input'];
|
||||
};
|
||||
@@ -6623,6 +6641,7 @@ export type ResolversTypes = {
|
||||
ProjectCommentsUpdatedMessage: ResolverTypeWrapper<Omit<ProjectCommentsUpdatedMessage, 'comment'> & { comment?: Maybe<ResolversTypes['Comment']> }>;
|
||||
ProjectCommentsUpdatedMessageType: ProjectCommentsUpdatedMessageType;
|
||||
ProjectCreateInput: ProjectCreateInput;
|
||||
ProjectDashboardsFilter: ProjectDashboardsFilter;
|
||||
ProjectEmbedOptions: ResolverTypeWrapper<ProjectEmbedOptions>;
|
||||
ProjectFileImportUpdatedMessage: ResolverTypeWrapper<Omit<ProjectFileImportUpdatedMessage, 'upload'> & { upload: ResolversTypes['FileUpload'] }>;
|
||||
ProjectFileImportUpdatedMessageType: ProjectFileImportUpdatedMessageType;
|
||||
@@ -6776,6 +6795,7 @@ export type ResolversTypes = {
|
||||
WorkspaceCreateInput: WorkspaceCreateInput;
|
||||
WorkspaceCreationState: ResolverTypeWrapper<WorkspaceCreationState>;
|
||||
WorkspaceCreationStateInput: WorkspaceCreationStateInput;
|
||||
WorkspaceDashboardsFilter: WorkspaceDashboardsFilter;
|
||||
WorkspaceDismissInput: WorkspaceDismissInput;
|
||||
WorkspaceDomain: ResolverTypeWrapper<WorkspaceDomain>;
|
||||
WorkspaceDomainDeleteInput: WorkspaceDomainDeleteInput;
|
||||
@@ -7033,6 +7053,7 @@ export type ResolversParentTypes = {
|
||||
ProjectCommentsFilter: ProjectCommentsFilter;
|
||||
ProjectCommentsUpdatedMessage: Omit<ProjectCommentsUpdatedMessage, 'comment'> & { comment?: Maybe<ResolversParentTypes['Comment']> };
|
||||
ProjectCreateInput: ProjectCreateInput;
|
||||
ProjectDashboardsFilter: ProjectDashboardsFilter;
|
||||
ProjectEmbedOptions: ProjectEmbedOptions;
|
||||
ProjectFileImportUpdatedMessage: Omit<ProjectFileImportUpdatedMessage, 'upload'> & { upload: ResolversParentTypes['FileUpload'] };
|
||||
ProjectInviteCreateInput: ProjectInviteCreateInput;
|
||||
@@ -7167,6 +7188,7 @@ export type ResolversParentTypes = {
|
||||
WorkspaceCreateInput: WorkspaceCreateInput;
|
||||
WorkspaceCreationState: WorkspaceCreationState;
|
||||
WorkspaceCreationStateInput: WorkspaceCreationStateInput;
|
||||
WorkspaceDashboardsFilter: WorkspaceDashboardsFilter;
|
||||
WorkspaceDismissInput: WorkspaceDismissInput;
|
||||
WorkspaceDomain: WorkspaceDomain;
|
||||
WorkspaceDomainDeleteInput: WorkspaceDomainDeleteInput;
|
||||
@@ -8277,6 +8299,7 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
|
||||
commentThreads?: Resolver<ResolversTypes['ProjectCommentCollection'], ParentType, ContextType, Partial<ProjectCommentThreadsArgs>>;
|
||||
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
|
||||
dashboardTokens?: Resolver<ResolversTypes['DashboardTokenCollection'], ParentType, ContextType, Partial<ProjectDashboardTokensArgs>>;
|
||||
dashboards?: Resolver<ResolversTypes['DashboardCollection'], ParentType, ContextType, RequireFields<ProjectDashboardsArgs, 'limit'>>;
|
||||
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
embedOptions?: Resolver<ResolversTypes['ProjectEmbedOptions'], ParentType, ContextType>;
|
||||
embedTokens?: Resolver<ResolversTypes['EmbedTokenCollection'], ParentType, ContextType, Partial<ProjectEmbedTokensArgs>>;
|
||||
|
||||
@@ -14,9 +14,17 @@ export type UpsertDashboardRecord = <T extends Exact<Dashboard, T>>(
|
||||
export type ListDashboardRecords = (args: {
|
||||
workspaceId: string
|
||||
filter?: {
|
||||
projectIds: string[] | null
|
||||
search: string | null
|
||||
updatedBefore: string | null
|
||||
limit: number | null
|
||||
}
|
||||
}) => Promise<Dashboard[]>
|
||||
|
||||
export type CountDashboardRecords = (args: { workspaceId: string }) => Promise<number>
|
||||
export type CountDashboardRecords = (args: {
|
||||
workspaceId: string
|
||||
filter?: {
|
||||
projectIds: string[] | null
|
||||
search: string | null
|
||||
}
|
||||
}) => Promise<number>
|
||||
|
||||
@@ -82,7 +82,35 @@ const resolvers: Resolvers = {
|
||||
workspaceId: parent.id,
|
||||
filter: {
|
||||
limit: args.limit,
|
||||
cursor: args.cursor ?? null
|
||||
cursor: args.cursor ?? null,
|
||||
projectIds: args.filter?.projectIds ?? [],
|
||||
search: args.filter?.search ?? null
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
Project: {
|
||||
dashboards: async (parent, args, context) => {
|
||||
const authResult = await context.authPolicies.workspace.canListDashboards({
|
||||
userId: context.userId,
|
||||
workspaceId: parent.id
|
||||
})
|
||||
throwIfAuthNotOk(authResult)
|
||||
|
||||
if (!parent.workspaceId) {
|
||||
throw new WorkspaceNotFoundError()
|
||||
}
|
||||
|
||||
return await getPaginatedDashboardsFactory({
|
||||
listDashboards: listDashboardsFactory({ db }),
|
||||
countDashboards: countDashboardsFactory({ db })
|
||||
})({
|
||||
workspaceId: parent.workspaceId,
|
||||
filter: {
|
||||
limit: args.limit,
|
||||
cursor: args.cursor ?? null,
|
||||
projectIds: [parent.id],
|
||||
search: args.filter?.search ?? null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,15 +61,33 @@ export const listDashboardsFactory =
|
||||
q.andWhere(Dashboards.col.updatedAt, '<', filter.updatedBefore)
|
||||
}
|
||||
|
||||
if (filter?.projectIds?.length) {
|
||||
q.andWhereRaw('?? && ?', [Dashboards.col.projectIds, filter.projectIds])
|
||||
}
|
||||
|
||||
if (filter?.search) {
|
||||
q.andWhereILike(Dashboards.col.name, filter.search)
|
||||
}
|
||||
|
||||
return await q
|
||||
}
|
||||
|
||||
export const countDashboardsFactory =
|
||||
(deps: { db: Knex }): CountDashboardRecords =>
|
||||
async ({ workspaceId }) => {
|
||||
const [{ count }] = await tables
|
||||
.dashboards(deps.db)
|
||||
.where(Dashboards.col.workspaceId, workspaceId)
|
||||
.count()
|
||||
async ({ workspaceId, filter = {} }) => {
|
||||
const { projectIds, search } = filter
|
||||
|
||||
const q = tables.dashboards(deps.db).where(Dashboards.col.workspaceId, workspaceId)
|
||||
|
||||
if (projectIds?.length) {
|
||||
q.andWhereRaw('?? && ?', [Dashboards.col.projectIds, projectIds])
|
||||
}
|
||||
|
||||
if (search) {
|
||||
q.andWhereILike(Dashboards.col.name, search)
|
||||
}
|
||||
|
||||
const [{ count }] = await q.count()
|
||||
|
||||
return Number.parseInt(count as string)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
StoreTokenResourceAccessDefinitions
|
||||
} from '@/modules/core/domain/tokens/operations'
|
||||
import { TokenResourceIdentifierType } from '@/modules/core/domain/tokens/types'
|
||||
import { clamp } from 'lodash-es'
|
||||
|
||||
export type CreateDashboard = (params: {
|
||||
name: string
|
||||
@@ -150,6 +151,8 @@ export type GetPaginatedDashboards = (params: {
|
||||
filter?: {
|
||||
limit: number | null
|
||||
cursor: string | null
|
||||
projectIds: string[] | null
|
||||
search: string | null
|
||||
}
|
||||
}) => Promise<Collection<Dashboard>>
|
||||
|
||||
@@ -160,16 +163,26 @@ export const getPaginatedDashboardsFactory =
|
||||
}): GetPaginatedDashboards =>
|
||||
async ({ workspaceId, filter }) => {
|
||||
const cursor = filter?.cursor ? decodeIsoDateCursor(filter.cursor) : null
|
||||
const projectIds = filter?.projectIds ?? []
|
||||
const search = filter?.search ?? null
|
||||
|
||||
const [items, totalCount] = await Promise.all([
|
||||
deps.listDashboards({
|
||||
workspaceId,
|
||||
filter: {
|
||||
updatedBefore: cursor,
|
||||
limit: filter?.limit ?? null
|
||||
limit: clamp(filter?.limit ?? 50, 1, 200),
|
||||
projectIds,
|
||||
search
|
||||
}
|
||||
}),
|
||||
deps.countDashboards({ workspaceId })
|
||||
deps.countDashboards({
|
||||
workspaceId,
|
||||
filter: {
|
||||
projectIds,
|
||||
search
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
const lastItem = items.at(-1)
|
||||
|
||||
Reference in New Issue
Block a user