feat(workspaces): move project to workspace (#2992)
* feat(workspaces): move project, like this * fix(workspaces): use new event * fix(workspaces): add resolver again after merge * chore(workspaces): lint * fix(workspaces): works but is a bit illegal * fix(workspaces): use service update * chore(workspaces): add unit tests * fix(workspaces): use transaction --------- Co-authored-by: Gergő Jedlicska <gergo@jedlicska.com>
This commit is contained in:
Родитель
ce55e5474b
Коммит
56d392424d
|
@ -122,14 +122,14 @@ type WorkspaceMutations {
|
|||
deleteDomain(input: WorkspaceDomainDeleteInput!): Workspace!
|
||||
@hasScope(scope: "workspace:update")
|
||||
invites: WorkspaceInviteMutations!
|
||||
projects: WorkspaceProjectMutations!
|
||||
projects: WorkspaceProjectMutations! @hasServerRole(role: SERVER_USER)
|
||||
}
|
||||
|
||||
type WorkspaceProjectMutations {
|
||||
updateRole(input: ProjectUpdateRoleInput!): Project!
|
||||
@hasServerRole(role: SERVER_USER)
|
||||
@hasStreamRole(role: STREAM_OWNER)
|
||||
@hasWorkspaceRole(role: MEMBER)
|
||||
moveToWorkspace(projectId: String!, workspaceId: String!): Project!
|
||||
}
|
||||
|
||||
input WorkspaceDomainDeleteInput {
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
import { ProjectTeamMember } from '@/modules/core/domain/projects/types'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { StreamRoles } from '@speckle/shared'
|
||||
|
||||
export type GetProject = (args: { projectId: string }) => Promise<StreamRecord>
|
||||
|
||||
export type GetProjectCollaborators = (args: {
|
||||
projectId: string
|
||||
}) => Promise<ProjectTeamMember[]>
|
||||
|
||||
export type UpdateProject = (args: {
|
||||
projectUpdate: Pick<StreamRecord, 'id' | 'workspaceId'>
|
||||
}) => Promise<StreamRecord>
|
||||
|
||||
export type UpsertProjectRole = (args: {
|
||||
projectId: string
|
||||
userId: string
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import { LimitedUserRecord, UserWithRole } from '@/modules/core/helpers/types'
|
||||
import { StreamRoles } from '@speckle/shared'
|
||||
|
||||
export type ProjectTeamMember = UserWithRole<LimitedUserRecord> & {
|
||||
streamRole: StreamRoles
|
||||
}
|
|
@ -4132,10 +4132,17 @@ export type WorkspaceProjectInviteCreateInput = {
|
|||
|
||||
export type WorkspaceProjectMutations = {
|
||||
__typename?: 'WorkspaceProjectMutations';
|
||||
moveToWorkspace: Project;
|
||||
updateRole: Project;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectMutationsMoveToWorkspaceArgs = {
|
||||
projectId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectMutationsUpdateRoleArgs = {
|
||||
input: ProjectUpdateRoleInput;
|
||||
};
|
||||
|
@ -6203,6 +6210,7 @@ export type WorkspaceMutationsResolvers<ContextType = GraphQLContext, ParentType
|
|||
};
|
||||
|
||||
export type WorkspaceProjectMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceProjectMutations'] = ResolversParentTypes['WorkspaceProjectMutations']> = {
|
||||
moveToWorkspace?: Resolver<ResolversTypes['Project'], ParentType, ContextType, RequireFields<WorkspaceProjectMutationsMoveToWorkspaceArgs, 'projectId' | 'workspaceId'>>;
|
||||
updateRole?: Resolver<ResolversTypes['Project'], ParentType, ContextType, RequireFields<WorkspaceProjectMutationsUpdateRoleArgs, 'input'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
|
|
@ -50,12 +50,19 @@ import dayjs from 'dayjs'
|
|||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { Knex } from 'knex'
|
||||
import { isProjectCreateInput } from '@/modules/core/helpers/stream'
|
||||
import { StreamAccessUpdateError } from '@/modules/core/errors/stream'
|
||||
import {
|
||||
StreamAccessUpdateError,
|
||||
StreamNotFoundError,
|
||||
StreamUpdateError
|
||||
} from '@/modules/core/errors/stream'
|
||||
import { metaHelpers } from '@/modules/core/helpers/meta'
|
||||
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
|
||||
import { db as defaultKnexInstance } from '@/db/knex'
|
||||
import {
|
||||
DeleteProjectRole,
|
||||
GetProject,
|
||||
GetProjectCollaborators,
|
||||
UpdateProject,
|
||||
GetRolesByUserId,
|
||||
UpsertProjectRole
|
||||
} from '@/modules/core/domain/projects/operations'
|
||||
|
@ -168,6 +175,19 @@ export async function getStream(
|
|||
return <Optional<StreamWithOptionalRole>>streams[0]
|
||||
}
|
||||
|
||||
// TODO: Inject db
|
||||
export const getProjectFactory =
|
||||
(): GetProject =>
|
||||
async ({ projectId }) => {
|
||||
const project = await getStream({ streamId: projectId })
|
||||
|
||||
if (!project) {
|
||||
throw new StreamNotFoundError()
|
||||
}
|
||||
|
||||
return project
|
||||
}
|
||||
|
||||
export type StreamWithCommitId = StreamWithOptionalRole & { commitId: string }
|
||||
|
||||
export async function getCommitStreams(params: {
|
||||
|
@ -645,6 +665,13 @@ export async function getStreamCollaborators(streamId: string, type?: StreamRole
|
|||
return items
|
||||
}
|
||||
|
||||
// TODO: Inject db
|
||||
export const getProjectCollaboratorsFactory =
|
||||
(): GetProjectCollaborators =>
|
||||
async ({ projectId }) => {
|
||||
return await getStreamCollaborators(projectId)
|
||||
}
|
||||
|
||||
type BaseUserStreamsQueryParams = {
|
||||
/**
|
||||
* User whose streams we wish to find
|
||||
|
@ -904,7 +931,11 @@ const isProjectUpdateInput = (
|
|||
i: StreamUpdateInput | ProjectUpdateInput
|
||||
): i is ProjectUpdateInput => has(i, 'visibility')
|
||||
|
||||
export async function updateStream(update: StreamUpdateInput | ProjectUpdateInput) {
|
||||
/** @deprecated Replace all calls with `updateProjectFacotry` */
|
||||
export async function updateStream(
|
||||
update: StreamUpdateInput | ProjectUpdateInput,
|
||||
db?: Knex
|
||||
) {
|
||||
const { id: streamId } = update
|
||||
|
||||
if (!update.name) update.name = null // to prevent saving name ''
|
||||
|
@ -936,7 +967,8 @@ export async function updateStream(update: StreamUpdateInput | ProjectUpdateInpu
|
|||
|
||||
if (!Object.keys(validUpdate).length) return null
|
||||
|
||||
const [updatedStream] = await Streams.knex()
|
||||
const [updatedStream] = await tables
|
||||
.streams(db ?? knex)
|
||||
.returning('*')
|
||||
.where({ id: streamId })
|
||||
.update<StreamRecord[]>({
|
||||
|
@ -947,6 +979,18 @@ export async function updateStream(update: StreamUpdateInput | ProjectUpdateInpu
|
|||
return updatedStream
|
||||
}
|
||||
|
||||
export const updateProjectFactory =
|
||||
({ db }: { db: Knex }): UpdateProject =>
|
||||
async ({ projectUpdate }) => {
|
||||
const updatedStream = await updateStream(projectUpdate, db)
|
||||
|
||||
if (!updatedStream) {
|
||||
throw new StreamUpdateError()
|
||||
}
|
||||
|
||||
return updatedStream
|
||||
}
|
||||
|
||||
export async function markBranchStreamUpdated(branchId: string) {
|
||||
const q = Streams.knex()
|
||||
.whereIn(Streams.col.id, (w) => {
|
||||
|
|
|
@ -4118,10 +4118,17 @@ export type WorkspaceProjectInviteCreateInput = {
|
|||
|
||||
export type WorkspaceProjectMutations = {
|
||||
__typename?: 'WorkspaceProjectMutations';
|
||||
moveToWorkspace: Project;
|
||||
updateRole: Project;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectMutationsMoveToWorkspaceArgs = {
|
||||
projectId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectMutationsUpdateRoleArgs = {
|
||||
input: ProjectUpdateRoleInput;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { UserRoleData } from '@/modules/shared/domain/rolesAndScopes/types'
|
||||
import { AvailableRoles } from '@speckle/shared'
|
||||
import { isUndefined } from 'lodash'
|
||||
|
||||
export const orderByWeight = <T extends AvailableRoles>(
|
||||
roles: T[],
|
||||
definitions: UserRoleData<T>[]
|
||||
): UserRoleData<T>[] => {
|
||||
const roleDefinitions = roles
|
||||
.map((role) => definitions.find((definition) => definition.name === role))
|
||||
.filter((definition): definition is UserRoleData<T> => !isUndefined(definition))
|
||||
|
||||
return roleDefinitions.sort((a, b) => b.weight - a.weight)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { orderByWeight } from '@/modules/shared/domain/rolesAndScopes/logic'
|
||||
import coreUserRoles from '@/modules/core/roles'
|
||||
import { workspaceRoles } from '@/modules/workspaces/roles'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
|
||||
describe('orderByWeight', () => {
|
||||
it('should return the highest weighted server role first', () => {
|
||||
const result = orderByWeight(
|
||||
[Roles.Server.Guest, Roles.Server.User, Roles.Server.Admin],
|
||||
coreUserRoles
|
||||
)
|
||||
expect(result[0].name).to.equal(Roles.Server.Admin)
|
||||
})
|
||||
|
||||
it('should return the highest weighted stream role first', () => {
|
||||
const result = orderByWeight(
|
||||
[Roles.Stream.Reviewer, Roles.Stream.Contributor, Roles.Stream.Owner],
|
||||
coreUserRoles
|
||||
)
|
||||
expect(result[0].name).to.equal(Roles.Stream.Owner)
|
||||
})
|
||||
|
||||
it('should return the highest weighted workspace role first', () => {
|
||||
const result = orderByWeight(
|
||||
[Roles.Workspace.Guest, Roles.Workspace.Member, Roles.Workspace.Admin],
|
||||
workspaceRoles
|
||||
)
|
||||
expect(result[0].name).to.equal(Roles.Workspace.Admin)
|
||||
})
|
||||
})
|
|
@ -132,8 +132,23 @@ export type GetWorkspaceRolesForUser = (
|
|||
options?: GetWorkspaceRolesForUserOptions
|
||||
) => Promise<WorkspaceAcl[]>
|
||||
|
||||
/** Repository-level change to workspace acl record */
|
||||
export type UpsertWorkspaceRole = (args: WorkspaceAcl) => Promise<void>
|
||||
|
||||
/** Service-level change with protection against invalid role changes */
|
||||
export type UpdateWorkspaceRole = (
|
||||
args: Pick<WorkspaceAcl, 'userId' | 'workspaceId' | 'role'> & {
|
||||
/**
|
||||
* If this gets triggered from a project role update, we don't want to override that project's role to the default one
|
||||
*/
|
||||
skipProjectRoleUpdatesFor?: string[]
|
||||
/**
|
||||
* Only add or upgrade role, prevent downgrades
|
||||
*/
|
||||
preventRoleDowngrade?: boolean
|
||||
}
|
||||
) => Promise<void>
|
||||
|
||||
export type GetWorkspaceRoleToDefaultProjectRoleMapping = (args: {
|
||||
workspaceId: string
|
||||
}) => Promise<WorkspaceRoleToDefaultProjectRoleMapping>
|
||||
|
|
|
@ -2,9 +2,13 @@ import { db } from '@/db/knex'
|
|||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
|
||||
import {
|
||||
getProjectCollaboratorsFactory,
|
||||
getProjectFactory,
|
||||
getStream,
|
||||
getUserStreams,
|
||||
getUserStreamsCount,
|
||||
updateProjectFactory,
|
||||
upsertProjectRoleFactory,
|
||||
getRolesByUserIdFactory
|
||||
} from '@/modules/core/repositories/streams'
|
||||
import { getUser, getUsers } from '@/modules/core/repositories/users'
|
||||
|
@ -91,6 +95,8 @@ import {
|
|||
} from '@/modules/workspaces/services/management'
|
||||
import {
|
||||
getWorkspaceProjectsFactory,
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory,
|
||||
moveProjectToWorkspaceFactory,
|
||||
queryAllWorkspaceProjectsFactory
|
||||
} from '@/modules/workspaces/services/projects'
|
||||
import {
|
||||
|
@ -653,6 +659,50 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
|||
context.userId!,
|
||||
context.resourceAccessRules
|
||||
)
|
||||
},
|
||||
moveToWorkspace: async (_parent, args, context) => {
|
||||
const { projectId, workspaceId } = args
|
||||
|
||||
await authorizeResolver(
|
||||
context.userId,
|
||||
projectId,
|
||||
Roles.Stream.Owner,
|
||||
context.resourceAccessRules
|
||||
)
|
||||
await authorizeResolver(
|
||||
context.userId,
|
||||
workspaceId,
|
||||
Roles.Workspace.Admin,
|
||||
context.resourceAccessRules
|
||||
)
|
||||
|
||||
const trx = await db.transaction()
|
||||
|
||||
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
|
||||
getProject: getProjectFactory(),
|
||||
updateProject: updateProjectFactory({ db: trx }),
|
||||
upsertProjectRole: upsertProjectRoleFactory({ db: trx }),
|
||||
getProjectCollaborators: getProjectCollaboratorsFactory(),
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }),
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping:
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory({
|
||||
getWorkspace: getWorkspaceFactory({ db })
|
||||
}),
|
||||
updateWorkspaceRole: updateWorkspaceRoleFactory({
|
||||
getWorkspaceRoles: getWorkspaceRolesFactory({ db: trx }),
|
||||
getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({ db: trx }),
|
||||
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({
|
||||
db: trx
|
||||
}),
|
||||
upsertWorkspaceRole: upsertWorkspaceRoleFactory({ db: trx }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
})
|
||||
})
|
||||
|
||||
return await withTransaction(
|
||||
moveProjectToWorkspace({ projectId, workspaceId }),
|
||||
trx
|
||||
)
|
||||
}
|
||||
},
|
||||
Workspace: {
|
||||
|
|
|
@ -9,7 +9,8 @@ import {
|
|||
UpsertWorkspaceRole,
|
||||
GetWorkspaceWithDomains,
|
||||
GetWorkspaceDomains,
|
||||
UpdateWorkspace
|
||||
UpdateWorkspace,
|
||||
UpdateWorkspaceRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import {
|
||||
Workspace,
|
||||
|
@ -312,22 +313,13 @@ export const updateWorkspaceRoleFactory =
|
|||
findVerifiedEmailsByUserId: FindVerifiedEmailsByUserId
|
||||
upsertWorkspaceRole: UpsertWorkspaceRole
|
||||
emitWorkspaceEvent: EmitWorkspaceEvent
|
||||
}) =>
|
||||
}): UpdateWorkspaceRole =>
|
||||
async ({
|
||||
workspaceId,
|
||||
userId,
|
||||
role: nextWorkspaceRole,
|
||||
skipProjectRoleUpdatesFor,
|
||||
preventRoleDowngrade
|
||||
}: Pick<WorkspaceAcl, 'userId' | 'workspaceId' | 'role'> & {
|
||||
/**
|
||||
* If this gets triggered from a project role update, we don't want to override that project's role to the default one
|
||||
*/
|
||||
skipProjectRoleUpdatesFor?: string[]
|
||||
/**
|
||||
* Only add or upgrade role, prevent downgrades
|
||||
*/
|
||||
preventRoleDowngrade?: boolean
|
||||
}): Promise<void> => {
|
||||
const workspaceRoles = await getWorkspaceRoles({ workspaceId })
|
||||
|
||||
|
|
|
@ -3,14 +3,26 @@ import { getStreams as serviceGetStreams } from '@/modules/core/services/streams
|
|||
import { getUserStreams } from '@/modules/core/repositories/streams'
|
||||
import {
|
||||
GetWorkspace,
|
||||
GetWorkspaceRoles,
|
||||
GetWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
QueryAllWorkspaceProjects
|
||||
QueryAllWorkspaceProjects,
|
||||
UpdateWorkspaceRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import {
|
||||
WorkspaceInvalidProjectError,
|
||||
WorkspaceNotFoundError,
|
||||
WorkspaceQueryError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import {
|
||||
GetProject,
|
||||
GetProjectCollaborators,
|
||||
UpdateProject,
|
||||
UpsertProjectRole
|
||||
} from '@/modules/core/domain/projects/operations'
|
||||
import { chunk } from 'lodash'
|
||||
import { Roles, StreamRoles } from '@speckle/shared'
|
||||
import { orderByWeight } from '@/modules/shared/domain/rolesAndScopes/logic'
|
||||
import coreUserRoles from '@/modules/core/roles'
|
||||
|
||||
export const queryAllWorkspaceProjectsFactory = ({
|
||||
getStreams
|
||||
|
@ -82,6 +94,89 @@ export const getWorkspaceProjectsFactory =
|
|||
}
|
||||
}
|
||||
|
||||
type MoveProjectToWorkspaceArgs = {
|
||||
projectId: string
|
||||
workspaceId: string
|
||||
}
|
||||
|
||||
export const moveProjectToWorkspaceFactory =
|
||||
({
|
||||
getProject,
|
||||
updateProject,
|
||||
upsertProjectRole,
|
||||
getProjectCollaborators,
|
||||
getWorkspaceRoles,
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
updateWorkspaceRole
|
||||
}: {
|
||||
getProject: GetProject
|
||||
updateProject: UpdateProject
|
||||
upsertProjectRole: UpsertProjectRole
|
||||
getProjectCollaborators: GetProjectCollaborators
|
||||
getWorkspaceRoles: GetWorkspaceRoles
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: GetWorkspaceRoleToDefaultProjectRoleMapping
|
||||
updateWorkspaceRole: UpdateWorkspaceRole
|
||||
}) =>
|
||||
async ({
|
||||
projectId,
|
||||
workspaceId
|
||||
}: MoveProjectToWorkspaceArgs): Promise<StreamRecord> => {
|
||||
const project = await getProject({ projectId })
|
||||
|
||||
if (project.workspaceId?.length) {
|
||||
// We do not currently support moving projects between workspaces
|
||||
throw new WorkspaceInvalidProjectError(
|
||||
'Specified project already belongs to a workspace. Moving between workspaces is not yet supported.'
|
||||
)
|
||||
}
|
||||
|
||||
// Update roles for current project members
|
||||
const projectTeam = await getProjectCollaborators({ projectId })
|
||||
const workspaceTeam = await getWorkspaceRoles({ workspaceId })
|
||||
const defaultProjectRoleMapping = await getWorkspaceRoleToDefaultProjectRoleMapping(
|
||||
{ workspaceId }
|
||||
)
|
||||
|
||||
for (const projectMembers of chunk(projectTeam, 5)) {
|
||||
await Promise.all(
|
||||
projectMembers.map(
|
||||
async ({ id: userId, role: serverRole, streamRole: currentProjectRole }) => {
|
||||
// Update workspace role. Prefer existing workspace role if there is one.
|
||||
const currentWorkspaceRole = workspaceTeam.find(
|
||||
(role) => role.userId === userId
|
||||
)
|
||||
const nextWorkspaceRole = currentWorkspaceRole ?? {
|
||||
userId,
|
||||
workspaceId,
|
||||
role:
|
||||
serverRole === Roles.Server.Guest
|
||||
? Roles.Workspace.Guest
|
||||
: Roles.Workspace.Member,
|
||||
createdAt: new Date()
|
||||
}
|
||||
await updateWorkspaceRole(nextWorkspaceRole)
|
||||
|
||||
// Update project role. Prefer default workspace project role if more permissive.
|
||||
const defaultProjectRole =
|
||||
defaultProjectRoleMapping[nextWorkspaceRole.role] ?? Roles.Stream.Reviewer
|
||||
const nextProjectRole = orderByWeight(
|
||||
[currentProjectRole, defaultProjectRole],
|
||||
coreUserRoles
|
||||
)[0]
|
||||
await upsertProjectRole({
|
||||
userId,
|
||||
projectId,
|
||||
role: nextProjectRole.name as StreamRoles
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Assign project to workspace
|
||||
return await updateProject({ projectUpdate: { id: projectId, workspaceId } })
|
||||
}
|
||||
|
||||
export const getWorkspaceRoleToDefaultProjectRoleMappingFactory =
|
||||
({
|
||||
getWorkspace
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { AllScopes } from '@/modules/core/helpers/mainConstants'
|
||||
import { grantStreamPermissions } from '@/modules/core/repositories/streams'
|
||||
import {
|
||||
BasicTestWorkspace,
|
||||
createTestWorkspace
|
||||
|
@ -11,7 +12,9 @@ import {
|
|||
import {
|
||||
ActiveUserProjectsWorkspaceDocument,
|
||||
CreateWorkspaceProjectDocument,
|
||||
GetWorkspaceProjectsDocument
|
||||
GetWorkspaceProjectsDocument,
|
||||
GetWorkspaceTeamDocument,
|
||||
MoveProjectToWorkspaceDocument
|
||||
} from '@/test/graphql/generated/graphql'
|
||||
import {
|
||||
createTestContext,
|
||||
|
@ -19,6 +22,7 @@ import {
|
|||
TestApolloServer
|
||||
} from '@/test/graphqlHelper'
|
||||
import { beforeEachContext } from '@/test/hooks'
|
||||
import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
@ -32,14 +36,14 @@ describe('Workspace project GQL CRUD', () => {
|
|||
name: 'My Test Workspace'
|
||||
}
|
||||
|
||||
const testUser: BasicTestUser = {
|
||||
const serverAdminUser: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'John Speckle',
|
||||
email: 'john-speckle-workspace-project-admin@example.org',
|
||||
role: Roles.Server.Admin
|
||||
}
|
||||
|
||||
const testNonWorkspaceMemberUser: BasicTestUser = {
|
||||
const serverMemberUser: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'John Nobody',
|
||||
email: 'john-nobody@example.org',
|
||||
|
@ -48,19 +52,19 @@ describe('Workspace project GQL CRUD', () => {
|
|||
|
||||
before(async () => {
|
||||
await beforeEachContext()
|
||||
await createTestUsers([testUser, testNonWorkspaceMemberUser])
|
||||
const token = await createAuthTokenForUser(testUser.id, AllScopes)
|
||||
await createTestUsers([serverAdminUser, serverMemberUser])
|
||||
const token = await createAuthTokenForUser(serverAdminUser.id, AllScopes)
|
||||
apollo = await testApolloServer({
|
||||
context: createTestContext({
|
||||
auth: true,
|
||||
userId: testUser.id,
|
||||
userId: serverAdminUser.id,
|
||||
token,
|
||||
role: testUser.role,
|
||||
role: serverAdminUser.role,
|
||||
scopes: AllScopes
|
||||
})
|
||||
})
|
||||
|
||||
await createTestWorkspace(workspace, testUser)
|
||||
await createTestWorkspace(workspace, serverAdminUser)
|
||||
|
||||
const workspaceProjects = [
|
||||
{ name: 'Workspace Project A', workspaceId: workspace.id },
|
||||
|
@ -169,4 +173,97 @@ describe('Workspace project GQL CRUD', () => {
|
|||
.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('when moving a project to a workspace', () => {
|
||||
const testProject: BasicTestStream = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: 'Test Project',
|
||||
isPublic: false
|
||||
}
|
||||
|
||||
const targetWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
name: 'Target Workspace'
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
await createTestWorkspace(targetWorkspace, serverAdminUser)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await createTestStream(testProject, serverAdminUser)
|
||||
await grantStreamPermissions({
|
||||
streamId: testProject.id,
|
||||
userId: serverMemberUser.id,
|
||||
role: Roles.Stream.Contributor
|
||||
})
|
||||
})
|
||||
|
||||
it('should move the project to the target workspace', async () => {
|
||||
const res = await apollo.execute(MoveProjectToWorkspaceDocument, {
|
||||
projectId: testProject.id,
|
||||
workspaceId: targetWorkspace.id
|
||||
})
|
||||
|
||||
const { workspaceId } =
|
||||
res.data?.workspaceMutations.projects.moveToWorkspace ?? {}
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(workspaceId).to.equal(targetWorkspace.id)
|
||||
})
|
||||
|
||||
it('should preserve project roles for project members', async () => {
|
||||
const res = await apollo.execute(MoveProjectToWorkspaceDocument, {
|
||||
projectId: testProject.id,
|
||||
workspaceId: targetWorkspace.id
|
||||
})
|
||||
|
||||
const { team } = res.data?.workspaceMutations.projects.moveToWorkspace ?? {}
|
||||
|
||||
const adminProjectRole = team?.find((role) => role.id === serverAdminUser.id)
|
||||
const memberProjectRole = team?.find((role) => role.id === serverMemberUser.id)
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(adminProjectRole?.role).to.equal(Roles.Stream.Owner)
|
||||
expect(memberProjectRole?.role).to.equal(Roles.Stream.Contributor)
|
||||
})
|
||||
|
||||
it('should grant workspace roles to project members that are not already in the target workspace', async () => {
|
||||
const resA = await apollo.execute(MoveProjectToWorkspaceDocument, {
|
||||
projectId: testProject.id,
|
||||
workspaceId: targetWorkspace.id
|
||||
})
|
||||
const resB = await apollo.execute(GetWorkspaceTeamDocument, {
|
||||
workspaceId: targetWorkspace.id
|
||||
})
|
||||
|
||||
const memberWorkspaceRole = resB.data?.workspace.team.items.find(
|
||||
(role) => role.id === serverMemberUser.id
|
||||
)
|
||||
|
||||
expect(resA).to.not.haveGraphQLErrors()
|
||||
expect(resB).to.not.haveGraphQLErrors()
|
||||
expect(memberWorkspaceRole?.role).to.equal(Roles.Workspace.Member)
|
||||
})
|
||||
|
||||
it('should preserve workspace roles for project members that are already in the target workspace', async () => {
|
||||
const resA = await apollo.execute(MoveProjectToWorkspaceDocument, {
|
||||
projectId: testProject.id,
|
||||
workspaceId: targetWorkspace.id
|
||||
})
|
||||
const resB = await apollo.execute(GetWorkspaceTeamDocument, {
|
||||
workspaceId: targetWorkspace.id
|
||||
})
|
||||
|
||||
const adminWorkspaceRole = resB.data?.workspace.team.items.find(
|
||||
(role) => role.id === serverAdminUser.id
|
||||
)
|
||||
|
||||
expect(resA).to.not.haveGraphQLErrors()
|
||||
expect(resB).to.not.haveGraphQLErrors()
|
||||
expect(adminWorkspaceRole?.role).to.equal(Roles.Workspace.Admin)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,8 +1,21 @@
|
|||
import { StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { queryAllWorkspaceProjectsFactory } from '@/modules/workspaces/services/projects'
|
||||
import { ProjectTeamMember } from '@/modules/core/domain/projects/types'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import {
|
||||
moveProjectToWorkspaceFactory,
|
||||
queryAllWorkspaceProjectsFactory
|
||||
} from '@/modules/workspaces/services/projects'
|
||||
import { WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
||||
import { expectToThrow } from '@/test/assertionHelper'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
||||
const getWorkspaceRoleToDefaultProjectRoleMapping = async () => ({
|
||||
'workspace:admin': Roles.Stream.Owner,
|
||||
'workspace:guest': null,
|
||||
'workspace:member': Roles.Stream.Contributor
|
||||
})
|
||||
|
||||
describe('Project retrieval services', () => {
|
||||
describe('queryAllWorkspaceProjectFactory returns a generator, that', () => {
|
||||
it('returns all streams for a workspace', async () => {
|
||||
|
@ -75,3 +88,309 @@ describe('Project retrieval services', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Project management services', () => {
|
||||
describe('moveProjectToWorkspaceFactory returns a function, that', () => {
|
||||
it('should throw if attempting to move a project already in a workspace', async () => {
|
||||
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
|
||||
getProject: async () => {
|
||||
return {
|
||||
workspaceId: cryptoRandomString({ length: 6 })
|
||||
} as StreamRecord
|
||||
},
|
||||
updateProject: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertProjectRole: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getProjectCollaborators: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
updateWorkspaceRole: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})
|
||||
|
||||
await expectToThrow(() =>
|
||||
moveProjectToWorkspace({
|
||||
projectId: cryptoRandomString({ length: 6 }),
|
||||
workspaceId: cryptoRandomString({ length: 6 })
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should preserve existing workspace roles in target workspace', async () => {
|
||||
const userId = cryptoRandomString({ length: 6 })
|
||||
const projectId = cryptoRandomString({ length: 6 })
|
||||
const workspaceId = cryptoRandomString({ length: 6 })
|
||||
|
||||
const updatedRoles: Partial<WorkspaceAcl>[] = []
|
||||
|
||||
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
|
||||
getProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
updateProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
upsertProjectRole: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
getProjectCollaborators: async () => {
|
||||
return [
|
||||
{
|
||||
id: userId,
|
||||
streamRole: Roles.Stream.Contributor
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return [
|
||||
{
|
||||
userId,
|
||||
role: Roles.Workspace.Admin,
|
||||
workspaceId,
|
||||
createdAt: new Date()
|
||||
}
|
||||
]
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
'workspace:admin': Roles.Stream.Owner,
|
||||
'workspace:guest': null,
|
||||
'workspace:member': Roles.Stream.Contributor
|
||||
}),
|
||||
updateWorkspaceRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Workspace.Admin)
|
||||
})
|
||||
|
||||
it('should set project members as workspace members in target workspace', async () => {
|
||||
const userId = cryptoRandomString({ length: 6 })
|
||||
const projectId = cryptoRandomString({ length: 6 })
|
||||
const workspaceId = cryptoRandomString({ length: 6 })
|
||||
|
||||
const updatedRoles: Partial<WorkspaceAcl>[] = []
|
||||
|
||||
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
|
||||
getProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
updateProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
upsertProjectRole: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
getProjectCollaborators: async () => {
|
||||
return [
|
||||
{
|
||||
id: userId,
|
||||
streamRole: Roles.Stream.Contributor
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return []
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
updateWorkspaceRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Workspace.Member)
|
||||
})
|
||||
|
||||
it('should set project members that are server guests as workspace guests in target workspace', async () => {
|
||||
const userId = cryptoRandomString({ length: 6 })
|
||||
const projectId = cryptoRandomString({ length: 6 })
|
||||
const workspaceId = cryptoRandomString({ length: 6 })
|
||||
|
||||
const updatedRoles: Partial<WorkspaceAcl>[] = []
|
||||
|
||||
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
|
||||
getProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
updateProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
upsertProjectRole: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
getProjectCollaborators: async () => {
|
||||
return [
|
||||
{
|
||||
id: userId,
|
||||
role: Roles.Server.Guest,
|
||||
streamRole: Roles.Stream.Contributor
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return []
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
updateWorkspaceRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Workspace.Guest)
|
||||
})
|
||||
|
||||
it('should preserve project roles for project members', async () => {
|
||||
const userId = cryptoRandomString({ length: 6 })
|
||||
const projectId = cryptoRandomString({ length: 6 })
|
||||
const workspaceId = cryptoRandomString({ length: 6 })
|
||||
|
||||
const updatedRoles: Partial<StreamAclRecord>[] = []
|
||||
|
||||
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
|
||||
getProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
updateProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
upsertProjectRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
return {} as StreamRecord
|
||||
},
|
||||
getProjectCollaborators: async () => {
|
||||
return [
|
||||
{
|
||||
id: userId,
|
||||
role: Roles.Server.User,
|
||||
streamRole: Roles.Stream.Owner
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return []
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
updateWorkspaceRole: async () => {}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Stream.Owner)
|
||||
})
|
||||
|
||||
it('should guarantee that target workspace members get at least the default workspace project role', async () => {
|
||||
const userId = cryptoRandomString({ length: 6 })
|
||||
const projectId = cryptoRandomString({ length: 6 })
|
||||
const workspaceId = cryptoRandomString({ length: 6 })
|
||||
|
||||
const updatedRoles: Partial<StreamAclRecord>[] = []
|
||||
|
||||
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
|
||||
getProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
updateProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
upsertProjectRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
return {} as StreamRecord
|
||||
},
|
||||
getProjectCollaborators: async () => {
|
||||
return [
|
||||
{
|
||||
id: userId,
|
||||
role: Roles.Server.User,
|
||||
streamRole: Roles.Stream.Reviewer
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return []
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Guest]: null,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner
|
||||
}),
|
||||
updateWorkspaceRole: async () => {}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Stream.Contributor)
|
||||
})
|
||||
|
||||
it('should guarantee that target workspace admins become project owners', async () => {
|
||||
const userId = cryptoRandomString({ length: 6 })
|
||||
const projectId = cryptoRandomString({ length: 6 })
|
||||
const workspaceId = cryptoRandomString({ length: 6 })
|
||||
|
||||
const updatedRoles: Partial<StreamAclRecord>[] = []
|
||||
|
||||
const moveProjectToWorkspace = moveProjectToWorkspaceFactory({
|
||||
getProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
updateProject: async () => {
|
||||
return {} as StreamRecord
|
||||
},
|
||||
upsertProjectRole: async (role) => {
|
||||
updatedRoles.push(role)
|
||||
return {} as StreamRecord
|
||||
},
|
||||
getProjectCollaborators: async () => {
|
||||
return [
|
||||
{
|
||||
id: userId,
|
||||
role: Roles.Server.User,
|
||||
streamRole: Roles.Stream.Reviewer
|
||||
} as unknown as ProjectTeamMember
|
||||
]
|
||||
},
|
||||
getWorkspaceRoles: async () => {
|
||||
return [
|
||||
{
|
||||
userId,
|
||||
workspaceId,
|
||||
role: Roles.Workspace.Admin,
|
||||
createdAt: new Date()
|
||||
}
|
||||
]
|
||||
},
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: async () => ({
|
||||
[Roles.Workspace.Guest]: null,
|
||||
[Roles.Workspace.Member]: Roles.Stream.Contributor,
|
||||
[Roles.Workspace.Admin]: Roles.Stream.Owner
|
||||
}),
|
||||
updateWorkspaceRole: async () => {}
|
||||
})
|
||||
|
||||
await moveProjectToWorkspace({ projectId, workspaceId })
|
||||
|
||||
expect(updatedRoles.length).to.equal(1)
|
||||
expect(updatedRoles[0].role).to.equal(Roles.Stream.Owner)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -4119,10 +4119,17 @@ export type WorkspaceProjectInviteCreateInput = {
|
|||
|
||||
export type WorkspaceProjectMutations = {
|
||||
__typename?: 'WorkspaceProjectMutations';
|
||||
moveToWorkspace: Project;
|
||||
updateRole: Project;
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectMutationsMoveToWorkspaceArgs = {
|
||||
projectId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkspaceProjectMutationsUpdateRoleArgs = {
|
||||
input: ProjectUpdateRoleInput;
|
||||
};
|
||||
|
@ -4883,6 +4890,14 @@ export type ActiveUserProjectsWorkspaceQueryVariables = Exact<{ [key: string]: n
|
|||
|
||||
export type ActiveUserProjectsWorkspaceQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', projects: { __typename?: 'ProjectCollection', items: Array<{ __typename?: 'Project', id: string, workspace?: { __typename?: 'Workspace', id: string, name: string } | null }> } } | null };
|
||||
|
||||
export type MoveProjectToWorkspaceMutationVariables = Exact<{
|
||||
projectId: Scalars['String']['input'];
|
||||
workspaceId: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type MoveProjectToWorkspaceMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', projects: { __typename?: 'WorkspaceProjectMutations', moveToWorkspace: { __typename?: 'Project', id: string, workspaceId?: string | null, team: Array<{ __typename?: 'ProjectCollaborator', id: string, role: string }> } } } };
|
||||
|
||||
export const BasicWorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]} as unknown as DocumentNode<BasicWorkspaceFragment, unknown>;
|
||||
export const BasicPendingWorkspaceCollaboratorFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]} as unknown as DocumentNode<BasicPendingWorkspaceCollaboratorFragment, unknown>;
|
||||
export const WorkspaceBillingFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceBilling"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionsCount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"current"}},{"kind":"Field","name":{"kind":"Name","value":"max"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cost"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subTotal"}},{"kind":"Field","name":{"kind":"Name","value":"currency"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"cost"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}},{"kind":"Field","name":{"kind":"Name","value":"discount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]} as unknown as DocumentNode<WorkspaceBillingFragment, unknown>;
|
||||
|
@ -4993,4 +5008,5 @@ export const CreateWorkspaceProjectDocument = {"kind":"Document","definitions":[
|
|||
export const GetWorkspaceProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceProjectsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceProject"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceProjectsQuery, GetWorkspaceProjectsQueryVariables>;
|
||||
export const GetWorkspaceTeamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceTeam"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceTeamFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode<GetWorkspaceTeamQuery, GetWorkspaceTeamQueryVariables>;
|
||||
export const ActiveUserLeaveWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ActiveUserLeaveWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"leave"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]}}]} as unknown as DocumentNode<ActiveUserLeaveWorkspaceMutation, ActiveUserLeaveWorkspaceMutationVariables>;
|
||||
export const ActiveUserProjectsWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUserProjectsWorkspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<ActiveUserProjectsWorkspaceQuery, ActiveUserProjectsWorkspaceQueryVariables>;
|
||||
export const ActiveUserProjectsWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUserProjectsWorkspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<ActiveUserProjectsWorkspaceQuery, ActiveUserProjectsWorkspaceQueryVariables>;
|
||||
export const MoveProjectToWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MoveProjectToWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"moveToWorkspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}},{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<MoveProjectToWorkspaceMutation, MoveProjectToWorkspaceMutationVariables>;
|
|
@ -200,3 +200,20 @@ export const getProjectWorkspaceQuery = gql`
|
|||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const moveProjectToWorkspaceMutation = gql`
|
||||
mutation MoveProjectToWorkspace($projectId: String!, $workspaceId: String!) {
|
||||
workspaceMutations {
|
||||
projects {
|
||||
moveToWorkspace(projectId: $projectId, workspaceId: $workspaceId) {
|
||||
id
|
||||
workspaceId
|
||||
team {
|
||||
id
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
Загрузка…
Ссылка в новой задаче