refactor: fix pagination with stable resolveKey, use reactive default… (#4951)

* refactor: fix pagination with stable resolveKey, use reactive defaultRoles, and remove email permission check

* Changes from call

* More changes from call

* WIP fixing team composite cursor

* paginated items fix

* minor rename

* composite cursor tools improved

* fe undoing debugging stuff

* extra fixes

* invitable collabs fix

---------

Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
This commit is contained in:
andrewwallacespeckle
2025-06-19 09:28:31 +02:00
committed by GitHub
parent 794bd7c7e9
commit c89fe339ec
20 changed files with 228 additions and 151 deletions
@@ -31,7 +31,7 @@
}
]"
:items="members"
:loading="loading"
:loading="isVeryFirstLoading"
:empty-message="
search.length || seatTypeFilter || roleFilter
? 'No results'
@@ -79,7 +79,7 @@
<template #email="{ item }">
<div class="flex">
<span class="text-foreground-2 truncate">
{{ canReadMemberEmail ? item.email : '-' }}
{{ item.email }}
</span>
</div>
</template>
@@ -156,26 +156,29 @@ const targetUser = ref<SettingsWorkspacesMembersActionsMenu_UserFragment | undef
undefined
)
const defaultRoles = shallowRef([Roles.Workspace.Admin, Roles.Workspace.Member])
const {
identifier,
onInfiniteLoad,
query: { result: membersResult, loading }
query: { result: membersResult },
isVeryFirstLoading
} = usePaginatedQuery({
query: settingsWorkspacesMembersSearchQuery,
baseVariables: computed(() => ({
query: search.value?.length ? search.value : null,
limit: 10,
slug: props.workspaceSlug,
filter: {
search: search.value,
roles: roleFilter.value
? [roleFilter.value]
: [Roles.Workspace.Admin, Roles.Workspace.Member],
roles: roleFilter.value ? [roleFilter.value] : defaultRoles.value,
seatType: seatTypeFilter.value
},
cursor: null as Nullable<string>
})),
resolveKey: (vars) => [vars.query || ''],
resolveKey: (vars) => ({
slug: vars.slug,
filter: vars.filter
}),
resolveCurrentResult: (res) => res?.workspaceBySlug.team,
resolveNextPageVariables: (baseVars, cursor) => ({
...baseVars,
@@ -185,8 +188,5 @@ const {
})
const workspace = computed(() => result.value?.workspaceBySlug)
const canReadMemberEmail = computed(
() => workspace.value?.permissions.canReadMemberEmail.authorized
)
const members = computed(() => membersResult.value?.workspaceBySlug.team.items)
</script>
@@ -68,6 +68,9 @@ type SerializableObject = {
| SerializableObject
| SerializableValue[]
| SerializableObject[]
| Array<SerializableValue | SerializableObject>
| undefined
| null
}
type BasicCursorContainer = {
@@ -106,6 +109,8 @@ export const usePaginatedQuery = <
| SerializableObject
| SerializableValue[]
| SerializableObject[]
| Array<SerializableValue | SerializableObject>
/**
* Predicate for resolving the current paginated result from the query result. Return undefined
* if query hasn't finished loading yet.
@@ -323,7 +323,7 @@ function createCache(): InMemoryCache {
merge: (_existing, incoming) => incoming
},
team: {
keyArgs: ['filter', 'limit'],
keyArgs: ['limit', 'filter', ['roles', 'search', 'seatType']],
merge: buildAbstractCollectionMergeFunction(
'WorkspaceCollaboratorCollection'
)
+4 -5
View File
@@ -24,7 +24,6 @@ const buildSourceMaps = ['1', 'true', true, 1].includes(BUILD_SOURCEMAPS)
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
// ssr: false, // for debugging set to false (prod should always be true)
...(buildSourceMaps ? { sourcemap: true } : {}),
modulesDir: ['./node_modules'],
typescript: {
@@ -56,18 +55,18 @@ export default defineNuxtConfig({
redisUrl: '',
public: {
...featureFlags,
apiOrigin: 'UNDEFINED',
apiOrigin: '',
backendApiOrigin: '',
baseUrl: '',
mixpanelApiHost: 'UNDEFINED',
mixpanelTokenId: 'UNDEFINED',
mixpanelApiHost: '',
mixpanelTokenId: '',
logLevel: NUXT_PUBLIC_LOG_LEVEL,
logPretty: isLogPretty,
logCsrEmitProps: false,
logClientApiToken: '',
logClientApiEndpoint: '',
speckleServerVersion: SPECKLE_SERVER_VERSION || 'unknown',
serverName: 'UNDEFINED',
serverName: 'unknown',
viewerDebug: false,
debugCoreWebVitals: false,
datadogAppId: '',
@@ -276,13 +276,11 @@ export const getModelUploadsItemsFactory =
(deps: { db: Knex }): GetModelUploadsItems =>
async (params) => {
const limit = clamp(params.limit || 0, 0, 100)
const { filterByCursor, resolveNewCursor } = getCursorTools()
const { applyCursorSortAndFilter, resolveNewCursor } = getCursorTools()
const q = getModelUploadsBaseQueryFactory(deps)(params)
.orderBy(FileUploads.col.convertedLastUpdate, 'desc')
.limit(limit)
const q = getModelUploadsBaseQueryFactory(deps)(params).limit(limit)
filterByCursor({
applyCursorSortAndFilter({
query: q,
cursor: params.cursor
})
@@ -76,14 +76,22 @@ export const decodeCompositeCursor = <C extends object>(
return null
}
// This is to allow custom column/alias support for compositeCursorTools() - we don't want
// to force the user to pass in the entire schema config, just the data we need
type LimitedSchemaConfig = Pick<SchemaConfig<any, any, any>, 'col'>
/**
* Simplifies working with composite cursors in SQL queries. Composite cursors are better because they
* allow duplicate values (e.g. updatedAt date) in different rows
*/
export const compositeCursorTools = <
Config extends SchemaConfig<any, any, any>,
Config extends LimitedSchemaConfig,
SelectedCols extends Array<keyof Config['col']>
>(args: {
/**
* Db table schema config OR in case of aliased columns - manual column mapping between final aliases
* as keys and table-prefixed column names as values
*/
schema: Config
/**
* Order of columns matters - put the primary ordering column first (e.g. updatedAt), then the secondary
@@ -107,9 +115,10 @@ export const compositeCursorTools = <
)
/**
* Invoke this on the knex querybuilder to filter the query by the cursor
* Invoke this on the knex querybuilder to filter the query by the cursor and apply
* appropriate ordering
*/
const filterByCursor = <Query extends Knex.QueryBuilder>(params: {
const applyCursorSortAndFilter = <Query extends Knex.QueryBuilder>(params: {
query: Query
/**
* If falsy, filter will be skipped
@@ -121,9 +130,16 @@ export const compositeCursorTools = <
sort?: 'desc' | 'asc'
}) => {
const { query, sort = 'desc' } = params
// Apply orderBy for each cursor column w/ proper sort direction
args.cols.forEach((col) => {
query.orderBy(args.schema.col[col], sort)
})
const cursor = isString(params.cursor) ? decode(params.cursor) : params.cursor
if (!cursor) return query
// Apply cursor filter
const colCount = args.cols.length
const sql = `(${times(colCount, () => '??').join(', ')}) ${
@@ -161,7 +177,7 @@ export const compositeCursorTools = <
return {
encode,
decode,
filterByCursor,
applyCursorSortAndFilter,
resolveNewCursor
}
}
@@ -1,42 +1,24 @@
import {
decodeIsoDateCursor,
encodeIsoDateCursor,
Collection
} from '@/modules/shared/helpers/graphqlHelper'
import { Collection } from '@/modules/shared/helpers/graphqlHelper'
import { MaybeNullOrUndefined } from '@speckle/shared'
type GetPaginatedItemsArgs = {
limit: number
cursor?: string
cursor?: MaybeNullOrUndefined<string>
}
export const getPaginatedItemsFactory =
<TArgs extends GetPaginatedItemsArgs, T extends { createdAt: Date }>({
<TArgs extends GetPaginatedItemsArgs, T>({
getItems,
getTotalCount
}: {
getItems: (args: TArgs) => Promise<T[]>
getItems: (args: TArgs) => Promise<{ items: T[]; cursor: string | null }>
getTotalCount: (args: Omit<TArgs, 'cursor' | 'limit'>) => Promise<number>
}) =>
async (args: TArgs): Promise<Collection<T>> => {
const totalCount = await getTotalCount(args)
if (args.limit === 0) {
return {
cursor: null,
items: [],
totalCount
}
}
const maybeDecodedCursor = args.cursor ? decodeIsoDateCursor(args.cursor) : null
const items = await getItems({
...args,
cursor: maybeDecodedCursor ?? undefined
})
let cursor = null
if (items.length === args.limit) {
const lastItem = items.at(-1)
cursor = lastItem ? encodeIsoDateCursor(lastItem.createdAt) : null
}
const [totalCount, { items, cursor }] = await Promise.all([
getTotalCount(args),
args.limit === 0 ? { cursor: null, items: [] } : getItems(args)
])
return {
items,
@@ -1,9 +0,0 @@
describe('paginatedItems @shared', () => {
describe('getPaginatedItemsFactory creates a function, that', () => {
it('converts undefined cursors to null')
it(
'calculates a new cursor from createdAt if there could be more items to be queried'
)
it('returns items and count from dependencies')
})
})
@@ -128,7 +128,9 @@ export type QueryWorkspacesArgs = CountWorkspacesArgs & {
limit: number
cursor?: string
}
export type QueryWorkspaces = (args: QueryWorkspacesArgs) => Promise<Workspace[]>
export type QueryWorkspaces = (
args: QueryWorkspacesArgs
) => Promise<{ items: Workspace[]; cursor: string | null }>
export type CountWorkspaces = (args: CountWorkspacesArgs) => Promise<number>
export type GetProjectWorkspace = (args: {
projectId: string
@@ -136,10 +138,8 @@ export type GetProjectWorkspace = (args: {
/** Workspace Roles */
export type GetWorkspaceCollaboratorsArgs = {
export type GetWorkspaceCollaboratorsBaseArgs = {
workspaceId: string
limit: number
cursor?: string
filter?: {
/**
* Optionally filter by workspace role(s)
@@ -158,16 +158,17 @@ export type GetWorkspaceCollaboratorsArgs = {
hasAccessToEmail?: boolean
}
export type GetWorkspaceCollaborators = (
args: GetWorkspaceCollaboratorsArgs
) => Promise<WorkspaceTeam>
type GetWorkspaceCollaboratorsTotalCountArgs = {
workspaceId: string
export type GetWorkspaceCollaboratorsArgs = GetWorkspaceCollaboratorsBaseArgs & {
limit: number
cursor?: string
}
export type GetWorkspaceCollaborators = (
args: GetWorkspaceCollaboratorsArgs
) => Promise<{ items: WorkspaceTeam; cursor: string | null }>
export type GetWorkspaceCollaboratorsTotalCount = (
args: GetWorkspaceCollaboratorsTotalCountArgs
args: GetWorkspaceCollaboratorsBaseArgs
) => Promise<number>
type DeleteWorkspaceRoleArgs = {
@@ -250,7 +250,9 @@ export const onWorkspaceRoleDeletedFactory =
updatedByUserId: string
}) => {
// Resolve a fallback admin
const [admin] = await deps.getWorkspaceCollaborators({
const {
items: [admin]
} = await deps.getWorkspaceCollaborators({
workspaceId,
limit: 1,
filter: {
@@ -330,7 +332,9 @@ export const onWorkspaceSeatUpdatedFactory =
})
// Resolve a fallback admin
const [admin] = await deps.getWorkspaceCollaborators({
const {
items: [admin]
} = await deps.getWorkspaceCollaborators({
workspaceId,
limit: 1,
filter: {
@@ -415,7 +419,9 @@ export const onWorkspaceRoleUpdatedFactory =
if (!seatType) return
// Resolve a fallback admin
const [admin] = await deps.getWorkspaceCollaborators({
const {
items: [admin]
} = await deps.getWorkspaceCollaborators({
workspaceId,
limit: 1,
filter: {
@@ -197,8 +197,6 @@ import { getProjectFactory } from '@/modules/core/repositories/projects'
import { getProjectRegionKey } from '@/modules/multiregion/utils/regionSelector'
import { scheduleJob } from '@/modules/multiregion/services/queue'
import { updateWorkspacePlanFactory } from '@/modules/gatekeeper/services/workspacePlans'
import { GetWorkspaceCollaboratorsArgs } from '@/modules/workspaces/domain/operations'
import { WorkspaceTeamMember } from '@/modules/workspaces/domain/types'
import { UsersMeta } from '@/modules/core/dbSchema'
import { setUserActiveWorkspaceFactory } from '@/modules/workspaces/repositories/users'
import { getGenericRedis } from '@/modules/shared/redis/redis'
@@ -2086,10 +2084,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
},
LimitedWorkspace: {
team: async (parent, args) => {
const team = await getPaginatedItemsFactory<
Pick<GetWorkspaceCollaboratorsArgs, 'workspaceId' | 'limit' | 'cursor'>,
WorkspaceTeamMember
>({
const team = await getPaginatedItemsFactory({
getItems: getWorkspaceCollaboratorsFactory({ db }),
getTotalCount: getWorkspaceCollaboratorsTotalCountFactory({ db })
})({
@@ -2107,7 +2102,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
roles: [Roles.Workspace.Admin]
}
})
return team
return team.items
}
},
ActiveUserMutations: {
@@ -10,6 +10,7 @@ import { metaHelpers } from '@/modules/core/helpers/meta'
import { StreamAclRecord, UserRecord } from '@/modules/core/helpers/types'
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
import { formatJsonArrayRecords } from '@/modules/shared/helpers/dbHelper'
import { compositeCursorTools } from '@/modules/shared/helpers/graphqlHelper'
import { SetUserActiveWorkspace } from '@/modules/workspaces/domain/operations'
import { WorkspaceTeamMember } from '@/modules/workspaces/domain/types'
import { WorkspaceAcl as WorkspaceAclRecord } from '@/modules/workspacesCore/domain/types'
@@ -45,6 +46,12 @@ const buildInvitableCollaboratorsByProjectIdQueryFactory =
}) => {
const query = tables
.users(db)
.select([
...Users.cols,
WorkspaceAcl.groupArray('workspaceAcl'),
ServerAcl.groupArray('serverAcl'),
UserEmails.groupArray('emails')
])
.join(WorkspaceAcl.name, WorkspaceAcl.col.userId, Users.col.id)
.join(Streams.name, Streams.col.workspaceId, WorkspaceAcl.col.workspaceId)
.join(ServerAcl.name, ServerAcl.col.userId, Users.col.id)
@@ -81,25 +88,27 @@ export const getInvitableCollaboratorsByProjectIdFactory =
}
cursor?: string
limit: number
}): Promise<WorkspaceTeamMember[]> => {
}): Promise<{ items: WorkspaceTeamMember[]; cursor: string | null }> => {
const { workspaceId, projectId, search } = filter
const query = buildInvitableCollaboratorsByProjectIdQueryFactory({ db })({
workspaceId,
projectId,
search
})
if (cursor) {
query.andWhere(Users.col.createdAt, '<', cursor)
}
const { applyCursorSortAndFilter, resolveNewCursor } = compositeCursorTools({
schema: Users,
cols: ['createdAt', 'id']
})
applyCursorSortAndFilter({
query,
cursor
})
query.limit(limit)
const res = await query
.orderBy(Users.col.createdAt, 'desc')
.limit(limit)
.select([
...Users.cols,
WorkspaceAcl.groupArray('workspaceAcl'),
ServerAcl.groupArray('serverAcl'),
UserEmails.groupArray('emails')
])
const nextCursor = resolveNewCursor(res)
const formattedRes = res.map((row) => {
const workspaceAcl = formatJsonArrayRecords(
@@ -119,7 +128,7 @@ export const getInvitableCollaboratorsByProjectIdFactory =
}
})
return formattedRes
return { items: formattedRes, cursor: nextCursor }
}
export const countInvitableCollaboratorsByProjectIdFactory =
@@ -134,11 +143,16 @@ export const countInvitableCollaboratorsByProjectIdFactory =
}
}) => {
const { workspaceId, projectId, search } = filter
const query = buildInvitableCollaboratorsByProjectIdQueryFactory({ db })({
workspaceId,
projectId,
search
})
const [res] = await query.count()
const query = db
.from(
buildInvitableCollaboratorsByProjectIdQueryFactory({ db })({
workspaceId,
projectId,
search
}).as('sq1')
)
.count()
const [res] = await query
return parseInt(res?.count?.toString() ?? '0')
}
@@ -1,4 +1,5 @@
import { UserEmails } from '@/modules/core/dbSchema'
import { compositeCursorTools } from '@/modules/shared/helpers/graphqlHelper'
import {
CreateWorkspaceJoinRequest,
GetWorkspaceJoinRequest,
@@ -96,18 +97,30 @@ export const getAdminWorkspaceJoinRequestsFactory =
cursor?: string
limit: number
}) => {
const { applyCursorSortAndFilter, resolveNewCursor } = compositeCursorTools({
schema: WorkspaceJoinRequests,
cols: ['createdAt', 'userId']
})
const query = adminWorkspaceJoinRequestsBaseQueryFactory(db)(filter)
applyCursorSortAndFilter({
query,
cursor
})
if (cursor) {
query.andWhere(WorkspaceJoinRequests.col.createdAt, '<', cursor)
}
return await query
query
.select<WorkspaceJoinRequest[]>([
...WorkspaceJoinRequests.cols,
UserEmails.col.email
])
.orderBy(WorkspaceJoinRequests.col.createdAt, 'desc')
.limit(limit)
const items = await query
const newCursor = resolveNewCursor(items)
return {
items,
cursor: newCursor
}
}
export const countAdminWorkspaceJoinRequestsFactory =
@@ -138,7 +151,7 @@ const workspaceJoinRequestsBaseQueryFactory =
export const getWorkspaceJoinRequestsFactory =
({ db }: { db: Knex }) =>
({
async ({
filter,
cursor,
limit
@@ -148,14 +161,24 @@ export const getWorkspaceJoinRequestsFactory =
limit: number
}) => {
const query = workspaceJoinRequestsBaseQueryFactory(db)(filter)
const { applyCursorSortAndFilter, resolveNewCursor } = compositeCursorTools({
schema: WorkspaceJoinRequests,
cols: ['createdAt', 'userId']
})
applyCursorSortAndFilter({
query,
cursor
})
if (cursor) {
query.andWhere(WorkspaceJoinRequests.col.createdAt, '<', cursor)
query.select<WorkspaceJoinRequest[]>(WorkspaceJoinRequests.cols).limit(limit)
const items = await query
const newCursor = resolveNewCursor(items)
return {
items,
cursor: newCursor
}
return query
.select<WorkspaceJoinRequest[]>(WorkspaceJoinRequests.cols)
.orderBy(WorkspaceJoinRequests.col.createdAt, 'desc')
.limit(limit)
}
export const countWorkspaceJoinRequestsFactory =
@@ -26,6 +26,7 @@ import {
GetWorkspaceBySlug,
GetWorkspaceBySlugOrId,
GetWorkspaceCollaborators,
GetWorkspaceCollaboratorsBaseArgs,
GetWorkspaceCollaboratorsTotalCount,
GetWorkspaceCreationState,
GetWorkspaceDomains,
@@ -70,7 +71,7 @@ import {
UserEmails,
Users
} from '@/modules/core/dbSchema'
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
import { removePrivateFields, UserRecord } from '@/modules/core/helpers/userHelper'
import { clamp, has, isObjectLike } from 'lodash'
import {
@@ -78,6 +79,7 @@ import {
WorkspaceTeamMember
} from '@/modules/workspaces/domain/types'
import {
compositeCursorTools,
decodeCompositeCursor,
decodeCursor,
encodeCompositeCursor,
@@ -86,6 +88,7 @@ import {
import { adminOverrideEnabled } from '@/modules/shared/helpers/envHelper'
const tables = {
users: (db: Knex) => db<UserRecord>(Users.name),
branches: (db: Knex) => db<BranchRecord>('branches'),
streams: (db: Knex) => db<StreamRecord>('streams'),
streamAcl: (db: Knex) => db<StreamAclRecord>('stream_acl'),
@@ -311,15 +314,24 @@ const buildWorkspacesQuery = ({ db, search }: { db: Knex; search?: string }) =>
export const queryWorkspacesFactory =
({ db }: { db: Knex }): QueryWorkspaces =>
async ({ limit, cursor, filter }) => {
const { applyCursorSortAndFilter, resolveNewCursor } = compositeCursorTools({
schema: Workspaces,
cols: ['createdAt', 'id']
})
const query = buildWorkspacesQuery({ db, search: filter?.search })
.select()
.orderBy('createdAt', 'desc')
.limit(limit)
if (cursor) {
query.andWhere('createdAt', '<', cursor)
}
return await query
applyCursorSortAndFilter({
query,
cursor
})
const res = await query
const newCursor = resolveNewCursor(res)
return { items: res, cursor: newCursor }
}
export const countWorkspacesFactory =
@@ -445,19 +457,12 @@ export const upsertWorkspaceRoleFactory =
.merge(['role'])
}
export const getWorkspaceCollaboratorsTotalCountFactory =
({ db }: { db: Knex }): GetWorkspaceCollaboratorsTotalCount =>
async ({ workspaceId }) => {
const [res] = await DbWorkspaceAcl.knex(db).where({ workspaceId }).count()
const count = parseInt(res.count)
return count || 0
}
const getWorkspaceCollaboratorsBaseQuery =
(deps: { db: Knex }) => (params: GetWorkspaceCollaboratorsBaseArgs) => {
const { workspaceId, filter = {} } = params
export const getWorkspaceCollaboratorsFactory =
({ db }: { db: Knex }): GetWorkspaceCollaborators =>
async ({ workspaceId, filter = {}, cursor, limit = 25, hasAccessToEmail }) => {
const query = db
.from(Users.name)
const query = tables
.users(deps.db)
.select<Array<WorkspaceTeamMember & { workspaceRoleCreatedAt: Date }>>(
...Users.cols,
ServerAcl.col.role,
@@ -474,7 +479,6 @@ export const getWorkspaceCollaboratorsFactory =
// it will not be surfaced by this query
//
.andWhere(UserEmails.col.primary, true)
.orderBy('workspaceRoleCreatedAt', 'desc')
const { search, roles, seatType, excludeUserIds } = filter || {}
@@ -505,9 +509,40 @@ export const getWorkspaceCollaboratorsFactory =
})
}
if (cursor) {
query.andWhere(DbWorkspaceAcl.col.createdAt, '<', cursor)
}
return query
}
export const getWorkspaceCollaboratorsTotalCountFactory =
({ db }: { db: Knex }): GetWorkspaceCollaboratorsTotalCount =>
async (params) => {
const q = db
.from(getWorkspaceCollaboratorsBaseQuery({ db })(params).as('t1'))
.count()
const [res] = await q
const count = parseInt(res.count + '')
return count || 0
}
export const getWorkspaceCollaboratorsFactory =
({ db }: { db: Knex }): GetWorkspaceCollaborators =>
async (params) => {
const { limit = 25, hasAccessToEmail } = params
const query = getWorkspaceCollaboratorsBaseQuery({ db })(params)
const { applyCursorSortAndFilter, resolveNewCursor } = compositeCursorTools({
schema: {
col: {
workspaceRoleCreatedAt: DbWorkspaceAcl.col.createdAt,
id: Users.col.id
}
},
cols: ['workspaceRoleCreatedAt', 'id']
})
applyCursorSortAndFilter({
query,
cursor: params.cursor
})
if (limit) {
query.limit(clamp(limit, 0, 100))
@@ -521,8 +556,9 @@ export const getWorkspaceCollaboratorsFactory =
workspaceId: i.workspaceId,
role: i.role
}))
const newCursor = resolveNewCursor(items)
return items
return { items, cursor: newCursor }
}
export const storeWorkspaceDomainFactory =
@@ -77,7 +77,7 @@ export const sendWorkspaceJoinRequestReceivedEmailFactory =
}): SendWorkspaceJoinRequestReceivedEmail =>
async (args) => {
const { requester, workspace } = args
const [serverInfo, workspaceAdmins] = await Promise.all([
const [serverInfo, { items: workspaceAdmins }] = await Promise.all([
getServerInfo(),
getWorkspaceCollaborators({
workspaceId: workspace.id,
@@ -90,7 +90,9 @@ export const requestToJoinWorkspaceFactory =
}
if (workspace.discoverabilityAutoJoinEnabled) {
const [workspaceAdmin] = await getWorkspaceTeam({
const {
items: [workspaceAdmin]
} = await getWorkspaceTeam({
workspaceId,
limit: 1,
filter: {
@@ -250,7 +250,7 @@ describe('Workspace repositories', () => {
})
it('returns all workspace members', async () => {
const team = await getWorkspaceCollaborators({
const { items: team } = await getWorkspaceCollaborators({
workspaceId: testWorkspace.id,
limit: 50
})
@@ -298,7 +298,7 @@ describe('Workspace repositories', () => {
})
it('limits search results to specified workspace', async () => {
const result = await getWorkspaceCollaborators({
const { items: result } = await getWorkspaceCollaborators({
workspaceId: testWorkspaces[2].id,
limit: 50,
filter: { search: 'John' }
@@ -86,7 +86,7 @@ describe('Workspace repositories', () => {
})
it('should return all workspace collaborators not members of the project', async () => {
const invitable = await getInvitableCollaboratorsByProjectId({
const { items: invitable } = await getInvitableCollaboratorsByProjectId({
filter: {
workspaceId: testWorkspace.id,
projectId: testProject.id
@@ -100,7 +100,7 @@ describe('Workspace repositories', () => {
])
})
it('should should filter by user name', async () => {
const invitable = await getInvitableCollaboratorsByProjectId({
const { items: invitable } = await getInvitableCollaboratorsByProjectId({
filter: {
workspaceId: testWorkspace.id,
projectId: testProject.id,
@@ -114,7 +114,7 @@ describe('Workspace repositories', () => {
])
})
it('should should filter by user email', async () => {
const invitable = await getInvitableCollaboratorsByProjectId({
const { items: invitable } = await getInvitableCollaboratorsByProjectId({
filter: {
workspaceId: testWorkspace.id,
projectId: testProject.id,
@@ -128,7 +128,7 @@ describe('Workspace repositories', () => {
])
})
it('should should filter by user name and email', async () => {
const invitable = await getInvitableCollaboratorsByProjectId({
const { items: invitable } = await getInvitableCollaboratorsByProjectId({
filter: {
workspaceId: testWorkspace.id,
projectId: testProject.id,
@@ -133,7 +133,7 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
getWorkspaceWithDomains: async () => null,
getUserEmails: async () => [],
addOrUpdateWorkspaceRole: async () => {},
getWorkspaceTeam: async () => []
getWorkspaceTeam: async () => ({ items: [], cursor: null })
})({ workspaceId: createRandomString(), userId: createRandomString() })
)
@@ -150,7 +150,7 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
getWorkspaceWithDomains: async () => null,
getUserEmails: async () => [],
addOrUpdateWorkspaceRole: async () => {},
getWorkspaceTeam: async () => []
getWorkspaceTeam: async () => ({ items: [], cursor: null })
})({ workspaceId: createRandomString(), userId: createRandomString() })
)
@@ -185,7 +185,7 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
workspace as unknown as WorkspaceWithDomains,
getUserEmails: async () => [],
addOrUpdateWorkspaceRole: async () => {},
getWorkspaceTeam: async () => []
getWorkspaceTeam: async () => ({ items: [], cursor: null })
})({ workspaceId: createRandomString(), userId: createRandomString() })
)
@@ -243,7 +243,7 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
getUserEmails: async () =>
[{ email: user.email, verified: true }] as unknown as UserEmail[],
addOrUpdateWorkspaceRole: async () => {},
getWorkspaceTeam: async () => []
getWorkspaceTeam: async () => ({ items: [], cursor: null })
})({ workspaceId: workspace.id, userId: user.id })
).to.equal(true)
@@ -314,7 +314,7 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
getUserEmails: async () =>
[{ email: user.email, verified: true }] as unknown as UserEmail[],
addOrUpdateWorkspaceRole: async () => {},
getWorkspaceTeam: async () => []
getWorkspaceTeam: async () => ({ items: [], cursor: null })
})
expect(
@@ -463,6 +463,11 @@ describe('Workspaces GQL CRUD', () => {
limit: 10,
cursor: resA.data?.workspace.team.cursor
})
const resC = await largeWorkspaceApollo.execute(GetWorkspaceTeamDocument, {
workspaceId: largeWorkspace.id,
limit: 10,
cursor: resB.data?.workspace.team.cursor
})
expect(resA).to.not.haveGraphQLErrors()
expect(resA.data?.workspace.team.items.length).to.equal(2)
@@ -475,7 +480,11 @@ describe('Workspaces GQL CRUD', () => {
expect(resB).to.not.haveGraphQLErrors()
expect(resB.data?.workspace.team.items.length).to.equal(4)
expect(resB.data?.workspace.team.cursor).to.be.null
expect(resB.data?.workspace.team.cursor).to.be.not.null
expect(resC).to.not.haveGraphQLErrors()
expect(resC.data?.workspace.team.items.length).to.equal(0)
expect(resC.data?.workspace.team.cursor).to.be.null
})
it('should return correct total count', async () => {