diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index 6347d7ffb..3623487f7 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -825,6 +825,12 @@ export type CreateVersionInput = { totalChildrenCount?: InputMaybe; }; +export enum Currency { + Eur = 'EUR', + Gbp = 'GBP', + Usd = 'USD' +} + export type DeleteModelInput = { id: Scalars['ID']['input']; projectId: Scalars['ID']['input']; @@ -3894,7 +3900,8 @@ export type WorkspaceTeamArgs = { export type WorkspaceBilling = { __typename?: 'WorkspaceBilling'; - versionsCount?: Maybe; + cost: WorkspaceCost; + versionsCount: WorkspaceVersionsCount; }; export type WorkspaceCollaborator = { @@ -3911,6 +3918,22 @@ export type WorkspaceCollection = { totalCount: Scalars['Int']['output']; }; +export type WorkspaceCost = { + __typename?: 'WorkspaceCost'; + /** Currency of the price */ + currency: Currency; + items: Array; + /** Estimated cost of the workspace with no discount applied */ + subTotal: Scalars['Float']['output']; +}; + +export type WorkspaceCostItem = { + __typename?: 'WorkspaceCostItem'; + cost: Scalars['Float']['output']; + count: Scalars['Int']['output']; + name: Scalars['String']['output']; +}; + export type WorkspaceCreateInput = { defaultLogoIndex?: InputMaybe; description?: InputMaybe; diff --git a/packages/server/assets/workspacesCore/typedefs/workspaces.graphql b/packages/server/assets/workspacesCore/typedefs/workspaces.graphql index c09c7f4d6..ce659ef17 100644 --- a/packages/server/assets/workspacesCore/typedefs/workspaces.graphql +++ b/packages/server/assets/workspacesCore/typedefs/workspaces.graphql @@ -193,8 +193,33 @@ type WorkspaceVersionsCount { max: Int! } +enum Currency { + GBP + USD + EUR +} + +type WorkspaceCostItem { + count: Int! + name: String! + cost: Float! +} + +type WorkspaceCost { + """ + Estimated cost of the workspace with no discount applied + """ + subTotal: Float! + """ + Currency of the price + """ + currency: Currency! + items: [WorkspaceCostItem!]! +} + type WorkspaceBilling { - versionsCount: WorkspaceVersionsCount + versionsCount: WorkspaceVersionsCount! + cost: WorkspaceCost! } type Workspace { diff --git a/packages/server/modules/core/events/projectsEmitter.ts b/packages/server/modules/core/events/projectsEmitter.ts index dd55491ef..89ecbfc86 100644 --- a/packages/server/modules/core/events/projectsEmitter.ts +++ b/packages/server/modules/core/events/projectsEmitter.ts @@ -8,7 +8,7 @@ export const ProjectEvents = { export type ProjectEvents = (typeof ProjectEvents)[keyof typeof ProjectEvents] export type ProjectEventsPayloads = { - [ProjectEvents.Created]: { project: StreamRecord } + [ProjectEvents.Created]: { project: StreamRecord; ownerId: string } } const { emit, listen } = initializeModuleEventEmitter({ diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index 2b956d388..6c9bc0e2b 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -839,6 +839,12 @@ export type CreateVersionInput = { totalChildrenCount?: InputMaybe; }; +export enum Currency { + Eur = 'EUR', + Gbp = 'GBP', + Usd = 'USD' +} + export type DeleteModelInput = { id: Scalars['ID']['input']; projectId: Scalars['ID']['input']; @@ -3908,7 +3914,8 @@ export type WorkspaceTeamArgs = { export type WorkspaceBilling = { __typename?: 'WorkspaceBilling'; - versionsCount?: Maybe; + cost: WorkspaceCost; + versionsCount: WorkspaceVersionsCount; }; export type WorkspaceCollaborator = { @@ -3925,6 +3932,22 @@ export type WorkspaceCollection = { totalCount: Scalars['Int']['output']; }; +export type WorkspaceCost = { + __typename?: 'WorkspaceCost'; + /** Currency of the price */ + currency: Currency; + items: Array; + /** Estimated cost of the workspace with no discount applied */ + subTotal: Scalars['Float']['output']; +}; + +export type WorkspaceCostItem = { + __typename?: 'WorkspaceCostItem'; + cost: Scalars['Float']['output']; + count: Scalars['Int']['output']; + name: Scalars['String']['output']; +}; + export type WorkspaceCreateInput = { defaultLogoIndex?: InputMaybe; description?: InputMaybe; @@ -4270,6 +4293,7 @@ export type ResolversTypes = { CreateModelInput: CreateModelInput; CreateUserEmailInput: CreateUserEmailInput; CreateVersionInput: CreateVersionInput; + Currency: Currency; DateTime: ResolverTypeWrapper; DeleteModelInput: DeleteModelInput; DeleteUserEmailInput: DeleteUserEmailInput; @@ -4428,6 +4452,8 @@ export type ResolversTypes = { WorkspaceBilling: ResolverTypeWrapper; WorkspaceCollaborator: ResolverTypeWrapper; WorkspaceCollection: ResolverTypeWrapper & { items: Array }>; + WorkspaceCost: ResolverTypeWrapper; + WorkspaceCostItem: ResolverTypeWrapper; WorkspaceCreateInput: WorkspaceCreateInput; WorkspaceDomain: ResolverTypeWrapper; WorkspaceDomainDeleteInput: WorkspaceDomainDeleteInput; @@ -4662,6 +4688,8 @@ export type ResolversParentTypes = { WorkspaceBilling: WorkspaceBilling; WorkspaceCollaborator: WorkspaceCollaboratorGraphQLReturn; WorkspaceCollection: Omit & { items: Array }; + WorkspaceCost: WorkspaceCost; + WorkspaceCostItem: WorkspaceCostItem; WorkspaceCreateInput: WorkspaceCreateInput; WorkspaceDomain: WorkspaceDomain; WorkspaceDomainDeleteInput: WorkspaceDomainDeleteInput; @@ -6034,7 +6062,8 @@ export type WorkspaceResolvers = { - versionsCount?: Resolver, ParentType, ContextType>; + cost?: Resolver; + versionsCount?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }; @@ -6052,6 +6081,20 @@ export type WorkspaceCollectionResolvers; }; +export type WorkspaceCostResolvers = { + currency?: Resolver; + items?: Resolver, ParentType, ContextType>; + subTotal?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type WorkspaceCostItemResolvers = { + cost?: Resolver; + count?: Resolver; + name?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type WorkspaceDomainResolvers = { domain?: Resolver; id?: Resolver; @@ -6218,6 +6261,8 @@ export type Resolvers = { WorkspaceBilling?: WorkspaceBillingResolvers; WorkspaceCollaborator?: WorkspaceCollaboratorResolvers; WorkspaceCollection?: WorkspaceCollectionResolvers; + WorkspaceCost?: WorkspaceCostResolvers; + WorkspaceCostItem?: WorkspaceCostItemResolvers; WorkspaceDomain?: WorkspaceDomainResolvers; WorkspaceInviteMutations?: WorkspaceInviteMutationsResolvers; WorkspaceMutations?: WorkspaceMutationsResolvers; diff --git a/packages/server/modules/core/services/streams/management.ts b/packages/server/modules/core/services/streams/management.ts index d83dcb73c..44e3b2514 100644 --- a/packages/server/modules/core/services/streams/management.ts +++ b/packages/server/modules/core/services/streams/management.ts @@ -120,7 +120,7 @@ export async function createStreamReturnRecord( }) } - await ProjectsEmitter.emit(ProjectEvents.Created, { project: stream }) + await ProjectsEmitter.emit(ProjectEvents.Created, { project: stream, ownerId }) return stream } diff --git a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts index e82bbb88a..e7501dc3c 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -828,6 +828,12 @@ export type CreateVersionInput = { totalChildrenCount?: InputMaybe; }; +export enum Currency { + Eur = 'EUR', + Gbp = 'GBP', + Usd = 'USD' +} + export type DeleteModelInput = { id: Scalars['ID']['input']; projectId: Scalars['ID']['input']; @@ -3897,7 +3903,8 @@ export type WorkspaceTeamArgs = { export type WorkspaceBilling = { __typename?: 'WorkspaceBilling'; - versionsCount?: Maybe; + cost: WorkspaceCost; + versionsCount: WorkspaceVersionsCount; }; export type WorkspaceCollaborator = { @@ -3914,6 +3921,22 @@ export type WorkspaceCollection = { totalCount: Scalars['Int']['output']; }; +export type WorkspaceCost = { + __typename?: 'WorkspaceCost'; + /** Currency of the price */ + currency: Currency; + items: Array; + /** Estimated cost of the workspace with no discount applied */ + subTotal: Scalars['Float']['output']; +}; + +export type WorkspaceCostItem = { + __typename?: 'WorkspaceCostItem'; + cost: Scalars['Float']['output']; + count: Scalars['Int']['output']; + name: Scalars['String']['output']; +}; + export type WorkspaceCreateInput = { defaultLogoIndex?: InputMaybe; description?: InputMaybe; diff --git a/packages/server/modules/workspaces/domain/constants.ts b/packages/server/modules/workspaces/domain/constants.ts index 909fd3673..fc14076e2 100644 --- a/packages/server/modules/workspaces/domain/constants.ts +++ b/packages/server/modules/workspaces/domain/constants.ts @@ -1 +1,6 @@ export const WorkspaceInviteResourceType = 'workspace' + +export const WORKSPACE_COST_ADMIN = 70 +export const WORKSPACE_COST_MEMBER = 50 +export const WORKSPACE_COST_GUEST = 10 +export const WORKSPACE_COST_VIEWER = 0 diff --git a/packages/server/modules/workspaces/domain/operations.ts b/packages/server/modules/workspaces/domain/operations.ts index 207f8abd0..6a5a03a19 100644 --- a/packages/server/modules/workspaces/domain/operations.ts +++ b/packages/server/modules/workspaces/domain/operations.ts @@ -8,7 +8,7 @@ import { WorkspaceWithOptionalRole } from '@/modules/workspacesCore/domain/types' import { EventBusPayloads } from '@/modules/shared/services/eventBus' -import { WorkspaceRoles } from '@speckle/shared' +import { StreamRoles, WorkspaceRoles } from '@speckle/shared' import { UserWithRole } from '@/modules/core/repositories/users' /** Workspace */ @@ -147,8 +147,12 @@ export type EmitWorkspaceEvent = (args: { payload: EventBusPayloads[TEvent] }) => Promise -export type CountProjectsVersionsByWorkspaceId = ({ - workspaceId -}: { +export type CountProjectsVersionsByWorkspaceId = (args: { workspaceId: string }) => Promise + +export type CountWorkspaceRoleWithOptionalProjectRole = (args: { + workspaceId: string + workspaceRole: WorkspaceRoles + projectRole?: StreamRoles | undefined +}) => Promise diff --git a/packages/server/modules/workspaces/events/eventListener.ts b/packages/server/modules/workspaces/events/eventListener.ts index 53320719f..db00bee0c 100644 --- a/packages/server/modules/workspaces/events/eventListener.ts +++ b/packages/server/modules/workspaces/events/eventListener.ts @@ -42,6 +42,8 @@ export const onProjectCreatedFactory = await Promise.all( workspaceMembers.map(({ userId, role: workspaceRole }) => { + // we do not need to assign new roles to the project owner + if (userId === payload.ownerId) return // Guests do not get roles on project create if (workspaceRole === Roles.Workspace.Guest) return diff --git a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts index ed84aa519..f80d441a8 100644 --- a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts +++ b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts @@ -1,5 +1,9 @@ import { db } from '@/db/knex' -import { Resolvers } from '@/modules/core/graph/generated/graphql' +import { + ResolverTypeWrapper, + Resolvers, + WorkspaceBilling +} from '@/modules/core/graph/generated/graphql' import { removePrivateFields } from '@/modules/core/helpers/userHelper' import { getStream, @@ -66,7 +70,8 @@ import { getWorkspaceDomainsFactory, getUserDiscoverableWorkspacesFactory, getWorkspaceWithDomainsFactory, - countProjectsVersionsByWorkspaceIdFactory + countProjectsVersionsByWorkspaceIdFactory, + countWorkspaceRoleWithOptionalProjectRoleFactory } from '@/modules/workspaces/repositories/workspaces' import { buildWorkspaceInviteEmailContentsFactory, @@ -109,6 +114,10 @@ import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userE import { requestNewEmailVerification } from '@/modules/emails/services/verification/request' import { Workspace } from '@/modules/workspacesCore/domain/types' import { WORKSPACE_MAX_PROJECTS_VERSIONS } from '@/modules/gatekeeper/domain/constants' +import { + getWorkspaceCostFactory, + getWorkspaceCostItemsFactory +} from '@/modules/workspaces/services/cost' import { isUserWorkspaceDomainPolicyCompliantFactory } from '@/modules/workspaces/services/domains' const buildCreateAndSendServerOrProjectInvite = () => @@ -639,16 +648,26 @@ export = FF_WORKSPACES_MODULE_ENABLED domains: async (parent) => { return await getWorkspaceDomainsFactory({ db })({ workspaceIds: [parent.id] }) }, - billing: (parent) => ({ parent }) + billing: (parent) => + ({ parent } as { parent: Workspace } & ResolverTypeWrapper) }, WorkspaceBilling: { versionsCount: async (parent) => { + const workspaceId = (parent as unknown as { parent: Workspace }).parent.id return { current: await countProjectsVersionsByWorkspaceIdFactory({ db })({ - workspaceId: (parent as { parent: Workspace }).parent.id + workspaceId }), max: WORKSPACE_MAX_PROJECTS_VERSIONS } + }, + cost: async (parent) => { + const workspaceId = (parent as unknown as { parent: Workspace }).parent.id + return getWorkspaceCostFactory({ + getWorkspaceCostItems: getWorkspaceCostItemsFactory({ + countRole: countWorkspaceRoleWithOptionalProjectRoleFactory({ db }) + }) + })({ workspaceId }) } }, WorkspaceCollaborator: { diff --git a/packages/server/modules/workspaces/repositories/workspaces.ts b/packages/server/modules/workspaces/repositories/workspaces.ts index 7e072c80c..b46ecfeb6 100644 --- a/packages/server/modules/workspaces/repositories/workspaces.ts +++ b/packages/server/modules/workspaces/repositories/workspaces.ts @@ -6,6 +6,7 @@ import { } from '@/modules/workspacesCore/domain/types' import { CountProjectsVersionsByWorkspaceId, + CountWorkspaceRoleWithOptionalProjectRole, DeleteWorkspace, DeleteWorkspaceDomain, DeleteWorkspaceRole, @@ -35,6 +36,7 @@ import { knex, ServerAcl, ServerInvites, + StreamAcl, StreamCommits, Streams, Users @@ -322,3 +324,30 @@ export const countProjectsVersionsByWorkspaceIdFactory = return parseInt(res.count.toString()) } + +export const countWorkspaceRoleWithOptionalProjectRoleFactory = + ({ db }: { db: Knex }): CountWorkspaceRoleWithOptionalProjectRole => + async ({ workspaceId, workspaceRole, projectRole }) => { + if (projectRole) { + const query = tables + .streams(db) + .join(StreamAcl.name, StreamAcl.col.resourceId, Streams.col.id) + .join(DbWorkspaceAcl.name, DbWorkspaceAcl.col.userId, StreamAcl.col.userId) + .where(Streams.col.workspaceId, workspaceId) + .andWhere(DbWorkspaceAcl.col.role, workspaceRole) + .andWhere(StreamAcl.col.role, projectRole) + .countDistinct(DbWorkspaceAcl.col.userId) + + const [res] = await query + return parseInt(res.count.toString()) + } else { + const query = tables + .workspacesAcl(db) + .where(DbWorkspaceAcl.col.workspaceId, workspaceId) + .where(DbWorkspaceAcl.col.role, workspaceRole) + .count() + + const [res] = await query + return parseInt(res.count.toString()) + } + } diff --git a/packages/server/modules/workspaces/services/cost.ts b/packages/server/modules/workspaces/services/cost.ts new file mode 100644 index 000000000..f545f7592 --- /dev/null +++ b/packages/server/modules/workspaces/services/cost.ts @@ -0,0 +1,118 @@ +import { CountWorkspaceRoleWithOptionalProjectRole } from '@/modules/workspaces/domain/operations' +import { Roles, throwUncoveredError } from '@speckle/shared' + +type KnownWorkspaceCostItemNames = + | 'workspace admin' + | 'workspace member' + | 'read/write guest' + | 'read only guest' + +type KnownCurrencies = 'GBP' + +type WorkspaceCostItem = { + name: KnownWorkspaceCostItemNames + description: string + count: number + cost: number +} + +const getWorkspaceCostItemCost = ({ + name +}: { + name: KnownWorkspaceCostItemNames + currency?: KnownCurrencies +}): number => { + switch (name) { + case 'workspace admin': + return 70 + case 'workspace member': + return 50 + case 'read/write guest': + return 10 + case 'read only guest': + return 0 + default: + throwUncoveredError(name) + } +} + +type GetWorkspaceCostItems = (args: { + workspaceId: string +}) => Promise + +export const getWorkspaceCostItemsFactory = + ({ + countRole + }: { + countRole: CountWorkspaceRoleWithOptionalProjectRole + }): GetWorkspaceCostItems => + async ({ workspaceId }) => { + const [adminCount, memberCount, writeGuestCount, readGuestCount] = + await Promise.all([ + countRole({ workspaceId, workspaceRole: Roles.Workspace.Admin }), + countRole({ workspaceId, workspaceRole: Roles.Workspace.Member }), + countRole({ + workspaceId, + workspaceRole: Roles.Workspace.Guest, + projectRole: Roles.Stream.Contributor + }), + countRole({ + workspaceId, + workspaceRole: Roles.Workspace.Guest, + projectRole: Roles.Stream.Reviewer + }) + ]) + + const workspaceCostItems: WorkspaceCostItem[] = [] + + if (adminCount) + workspaceCostItems.push({ + name: 'workspace admin', + description: 'Workspace administrator with all the powers', + count: adminCount, + cost: getWorkspaceCostItemCost({ name: 'workspace admin' }) + }) + if (memberCount) + workspaceCostItems.push({ + name: 'workspace member', + description: 'General workspace member', + count: memberCount, + cost: getWorkspaceCostItemCost({ name: 'workspace member' }) + }) + if (writeGuestCount) + workspaceCostItems.push({ + name: 'read/write guest', + description: 'Workspace guest with write access to minimum 1 workspace project', + count: writeGuestCount, + cost: getWorkspaceCostItemCost({ name: 'read/write guest' }) + }) + if (readGuestCount) + workspaceCostItems.push({ + name: 'read only guest', + description: 'Workspace guest with only read access to some workspace projects', + count: readGuestCount, + cost: getWorkspaceCostItemCost({ name: 'read only guest' }) + }) + + return workspaceCostItems + } + +type WorkspaceCost = { + subTotal: number + currency: KnownCurrencies + items: WorkspaceCostItem[] +} + +export const getWorkspaceCostFactory = + ({ getWorkspaceCostItems }: { getWorkspaceCostItems: GetWorkspaceCostItems }) => + async ({ workspaceId }: { workspaceId: string }): Promise => { + const items = await getWorkspaceCostItems({ workspaceId }) + + const subTotal = items.reduce((acc, { cost, count }) => acc + cost * count, 0) + + return { + currency: 'GBP', + items, + subTotal + } + } diff --git a/packages/server/modules/workspaces/tests/helpers/graphql.ts b/packages/server/modules/workspaces/tests/helpers/graphql.ts index eb603e68d..f9d253c93 100644 --- a/packages/server/modules/workspaces/tests/helpers/graphql.ts +++ b/packages/server/modules/workspaces/tests/helpers/graphql.ts @@ -37,6 +37,15 @@ export const workspaceBillingFragment = gql` current max } + cost { + subTotal + currency + items { + count + name + cost + } + } } } ` diff --git a/packages/server/modules/workspaces/tests/integration/repositories.spec.ts b/packages/server/modules/workspaces/tests/integration/repositories.spec.ts index 1ed473019..54521ac88 100644 --- a/packages/server/modules/workspaces/tests/integration/repositories.spec.ts +++ b/packages/server/modules/workspaces/tests/integration/repositories.spec.ts @@ -9,7 +9,8 @@ import { deleteWorkspaceFactory, storeWorkspaceDomainFactory, getUserDiscoverableWorkspacesFactory, - getWorkspaceWithDomainsFactory + getWorkspaceWithDomainsFactory, + countWorkspaceRoleWithOptionalProjectRoleFactory } from '@/modules/workspaces/repositories/workspaces' import db from '@/db/knex' import cryptoRandomString from 'crypto-random-string' @@ -19,6 +20,7 @@ import { expectToThrow } from '@/test/assertionHelper' import { BasicTestUser, createTestUser } from '@/test/authHelper' import { BasicTestWorkspace, + assignToWorkspace, createTestWorkspace } from '@/modules/workspaces/tests/helpers/creation' import { @@ -26,8 +28,14 @@ import { updateUserEmailFactory } from '@/modules/core/repositories/userEmails' import { Roles } from '@speckle/shared' -import { createRandomPassword } from '@/modules/core/helpers/testHelpers' +import { + createRandomEmail, + createRandomPassword, + createRandomString +} from '@/modules/core/helpers/testHelpers' import { truncateTables } from '@/test/hooks' +import { createTestStream } from '@/test/speckle-helpers/streamHelper' +import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams' const getWorkspace = getWorkspaceFactory({ db }) const upsertWorkspace = upsertWorkspaceFactory({ db }) @@ -41,6 +49,7 @@ const storeWorkspaceDomain = storeWorkspaceDomainFactory({ db }) const createUserEmail = createUserEmailFactory({ db }) const updateUserEmail = updateUserEmailFactory({ db }) const getUserDiscoverableWorkspaces = getUserDiscoverableWorkspacesFactory({ db }) +const grantStreamPermissions = grantStreamPermissionsFactory({ db }) const createAndStoreTestUser = async (): Promise => { const testId = cryptoRandomString({ length: 6 }) @@ -640,4 +649,204 @@ describe('Workspace repositories', () => { expect(workspaceWithDomains?.domains.length).to.eq(1) }) }) + + describe('countWorkspaceRoleWithOptionalProjectRoleFactory returns a function, that', () => { + it('counts workspace roles by userId', async () => { + const admin = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(admin) + const workspace = { + id: createRandomPassword(), + name: 'my workspace', + ownerId: admin.id + } + await createTestWorkspace(workspace, admin) + + // just another workspace, for testing if workspaceId filter works + const workspace2 = { + id: createRandomPassword(), + name: 'my workspace', + ownerId: admin.id + } + await createTestWorkspace(workspace2, admin) + + const admin2 = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(admin2) + await assignToWorkspace(workspace, admin2, Roles.Workspace.Admin) + + const member = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(member) + await assignToWorkspace(workspace, member, Roles.Workspace.Member) + let count = await countWorkspaceRoleWithOptionalProjectRoleFactory({ db })({ + workspaceId: workspace.id, + workspaceRole: Roles.Workspace.Admin + }) + expect(count).to.equal(2) + + count = await countWorkspaceRoleWithOptionalProjectRoleFactory({ db })({ + workspaceId: workspace.id, + workspaceRole: Roles.Workspace.Member + }) + expect(count).to.equal(1) + }) + it('counts workspace roles with a project role filter', async () => { + const admin = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(admin) + const workspace = { + id: createRandomPassword(), + name: 'my workspace', + ownerId: admin.id + } + + await createTestWorkspace(workspace, admin) + + const member = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(member) + await assignToWorkspace(workspace, member, Roles.Workspace.Member) + + const member2 = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(member2) + await assignToWorkspace(workspace, member2, Roles.Workspace.Member) + + const project1 = { + id: createRandomString(), + name: 'test stream', + isPublic: true, + ownerId: admin.id, + workspaceId: workspace.id + } + const project2 = { + id: createRandomString(), + name: 'test stream 2', + isPublic: true, + ownerId: member.id, + workspaceId: workspace.id + } + + const project3 = { + id: createRandomString(), + name: 'test stream 3', + isPublic: true, + ownerId: member.id, + workspaceId: workspace.id + } + await createTestStream(project1, admin) + await createTestStream(project2, member) + await createTestStream(project3, member2) + + let count = await countWorkspaceRoleWithOptionalProjectRoleFactory({ db })({ + workspaceId: workspace.id, + workspaceRole: Roles.Workspace.Admin, + projectRole: Roles.Stream.Owner + }) + expect(count).to.equal(1) + + count = await countWorkspaceRoleWithOptionalProjectRoleFactory({ db })({ + workspaceId: workspace.id, + workspaceRole: Roles.Workspace.Member, + projectRole: Roles.Stream.Owner + }) + expect(count).to.equal(2) + }) + it('does not count project roles, that are not in the workspace', async () => { + const admin = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(admin) + const workspace = { + id: createRandomPassword(), + name: 'my workspace', + ownerId: admin.id + } + await createTestWorkspace(workspace, admin) + + const guest = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(guest) + await assignToWorkspace(workspace, guest, Roles.Workspace.Guest) + + const guest2 = { + id: createRandomPassword(), + name: createRandomPassword(), + email: createRandomEmail() + } + await createTestUser(guest2) + await assignToWorkspace(workspace, guest2, Roles.Workspace.Guest) + + // only project 1 is in the workspace + const project1 = { + id: createRandomString(), + name: 'test stream', + isPublic: true, + ownerId: admin.id, + workspaceId: workspace.id + } + // this is not in the workspace, roles here should not count + const project2 = { + id: createRandomString(), + name: 'test stream 2', + isPublic: true, + ownerId: guest.id + } + + await createTestStream(project1, admin) + await createTestStream(project2, guest) + + // adding project roles to guests + await grantStreamPermissions({ + role: Roles.Stream.Contributor, + streamId: project1.id, + userId: guest.id + }) + + await grantStreamPermissions({ + role: Roles.Stream.Reviewer, + streamId: project1.id, + userId: guest2.id + }) + + // adding contributor to guest 2 on project 2 + await grantStreamPermissions({ + role: Roles.Stream.Contributor, + streamId: project2.id, + userId: guest2.id + }) + + const count = await countWorkspaceRoleWithOptionalProjectRoleFactory({ db })({ + workspaceId: workspace.id, + workspaceRole: Roles.Workspace.Guest, + projectRole: Roles.Stream.Contributor + }) + // checking that the non workspace project doesn't leak into the counts + expect(count).to.equal(1) + }) + }) }) diff --git a/packages/server/modules/workspaces/tests/integration/workspaces.graph.spec.ts b/packages/server/modules/workspaces/tests/integration/workspaces.graph.spec.ts index 7d3e64e90..632e89465 100644 --- a/packages/server/modules/workspaces/tests/integration/workspaces.graph.spec.ts +++ b/packages/server/modules/workspaces/tests/integration/workspaces.graph.spec.ts @@ -38,12 +38,15 @@ import { } from '@/modules/workspaces/tests/helpers/creation' import { BasicTestCommit, createTestCommit } from '@/test/speckle-helpers/commitHelper' import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper' -import knex from '@/db/knex' +import knex, { db } from '@/db/knex' import { createRandomPassword, - createRandomEmail + createRandomEmail, + createRandomString } from '@/modules/core/helpers/testHelpers' import { getBranchesByStreamId } from '@/modules/core/services/branches' +import { grantStreamPermissions } from '@/modules/core/repositories/streams' +import { getWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces' const createProjectWithVersions = ({ apollo }: { apollo: TestApolloServer }) => @@ -230,13 +233,117 @@ describe('Workspaces GQL CRUD', () => { max: 500 }) }) + it('should return workspace cost', async () => { + const createRes = await apollo.execute(CreateWorkspaceDocument, { + input: { name: createRandomString() } + }) + expect(createRes).to.not.haveGraphQLErrors() + const workspaceId = createRes.data!.workspaceMutations.create.id + const workspace = (await getWorkspaceFactory({ db })({ + workspaceId + })) as unknown as BasicTestWorkspace + + const member = { + id: createRandomString(), + name: createRandomPassword(), + email: createRandomEmail() + } + const guestWithWritePermission = { + id: createRandomString(), + name: createRandomPassword(), + email: createRandomEmail() + } + const viewer = { + id: createRandomString(), + name: createRandomPassword(), + email: createRandomEmail() + } + const viewer2 = { + id: createRandomString(), + name: createRandomPassword(), + email: createRandomEmail() + } + + await Promise.all([ + createTestUser(member), + createTestUser(guestWithWritePermission), + createTestUser(viewer), + createTestUser(viewer2) + ]) + + await Promise.all([ + assignToWorkspace(workspace, member, Roles.Workspace.Member), + assignToWorkspace(workspace, guestWithWritePermission, Roles.Workspace.Guest), + assignToWorkspace(workspace, viewer, Roles.Workspace.Guest), + assignToWorkspace(workspace, viewer2, Roles.Workspace.Guest) + ]) + + const resProject1 = await apollo.execute(CreateProjectDocument, { + input: { + name: createRandomPassword(), + workspaceId + } + }) + expect(resProject1).to.not.haveGraphQLErrors() + const project1Id = resProject1.data!.projectMutations.create.id + + await Promise.all([ + grantStreamPermissions({ + streamId: project1Id, + userId: guestWithWritePermission.id, + role: Roles.Stream.Contributor + }), + grantStreamPermissions({ + streamId: project1Id, + userId: viewer.id, + role: Roles.Stream.Reviewer + }), + grantStreamPermissions({ + streamId: project1Id, + userId: viewer2.id, + role: Roles.Stream.Reviewer + }) + ]) + + const res = await apollo.execute(GetWorkspaceWithBillingDocument, { + workspaceId + }) + + expect(res).to.not.haveGraphQLErrors() + const { subTotal, currency, items } = res.data!.workspace.billing.cost + expect(subTotal).to.equal(70 + 50 + 10) + expect(currency).to.equal('GBP') + expect(items).to.deep.equal([ + { + name: 'workspace admin', + count: 1, + cost: 70 + }, + { + name: 'workspace member', + count: 1, + cost: 50 + }, + { + name: 'read/write guest', + count: 1, + cost: 10 + }, + { + name: 'read only guest', + count: 2, + cost: 0 + } + ]) + }) }) describe('query activeUser.workspaces', () => { it('should return all workspaces for a user', async () => { const res = await apollo.execute(GetActiveUserWorkspacesDocument, {}) expect(res).to.not.haveGraphQLErrors() - expect(res.data?.activeUser?.workspaces?.items?.length).to.equal(1) + // TODO: this test depends on the previous tests + expect(res.data?.activeUser?.workspaces?.items?.length).to.equal(2) }) }) diff --git a/packages/server/modules/workspaces/tests/unit/events/eventListener.spec.ts b/packages/server/modules/workspaces/tests/unit/events/eventListener.spec.ts index 11381eb25..758f2b0c4 100644 --- a/packages/server/modules/workspaces/tests/unit/events/eventListener.spec.ts +++ b/packages/server/modules/workspaces/tests/unit/events/eventListener.spec.ts @@ -45,7 +45,8 @@ describe('Event handlers', () => { }) await onProjectCreated({ - project: { workspaceId, id: projectId } as StreamRecord + project: { workspaceId, id: projectId } as StreamRecord, + ownerId: cryptoRandomString({ length: 10 }) }) expect(projectRoles.length).to.equal(2) diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index 7fc061f00..44b1b4c59 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -829,6 +829,12 @@ export type CreateVersionInput = { totalChildrenCount?: InputMaybe; }; +export enum Currency { + Eur = 'EUR', + Gbp = 'GBP', + Usd = 'USD' +} + export type DeleteModelInput = { id: Scalars['ID']['input']; projectId: Scalars['ID']['input']; @@ -3898,7 +3904,8 @@ export type WorkspaceTeamArgs = { export type WorkspaceBilling = { __typename?: 'WorkspaceBilling'; - versionsCount?: Maybe; + cost: WorkspaceCost; + versionsCount: WorkspaceVersionsCount; }; export type WorkspaceCollaborator = { @@ -3915,6 +3922,22 @@ export type WorkspaceCollection = { totalCount: Scalars['Int']['output']; }; +export type WorkspaceCost = { + __typename?: 'WorkspaceCost'; + /** Currency of the price */ + currency: Currency; + items: Array; + /** Estimated cost of the workspace with no discount applied */ + subTotal: Scalars['Float']['output']; +}; + +export type WorkspaceCostItem = { + __typename?: 'WorkspaceCostItem'; + cost: Scalars['Float']['output']; + count: Scalars['Int']['output']; + name: Scalars['String']['output']; +}; + export type WorkspaceCreateInput = { defaultLogoIndex?: InputMaybe; description?: InputMaybe; @@ -4123,7 +4146,7 @@ export type BasicWorkspaceFragment = { __typename?: 'Workspace', id: string, nam export type BasicPendingWorkspaceCollaboratorFragment = { __typename?: 'PendingWorkspaceCollaborator', id: string, inviteId: string, workspaceId: string, workspaceName: string, title: string, role: string, token?: string | null, invitedBy: { __typename?: 'LimitedUser', id: string, name: string }, user?: { __typename?: 'LimitedUser', id: string, name: string } | null }; -export type WorkspaceBillingFragment = { __typename?: 'Workspace', billing: { __typename?: 'WorkspaceBilling', versionsCount?: { __typename?: 'WorkspaceVersionsCount', current: number, max: number } | null } }; +export type WorkspaceBillingFragment = { __typename?: 'Workspace', billing: { __typename?: 'WorkspaceBilling', versionsCount: { __typename?: 'WorkspaceVersionsCount', current: number, max: number }, cost: { __typename?: 'WorkspaceCost', subTotal: number, currency: Currency, items: Array<{ __typename?: 'WorkspaceCostItem', count: number, name: string, cost: number }> } } }; export type WorkspaceProjectsFragment = { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string }> }; @@ -4155,7 +4178,7 @@ export type GetWorkspaceWithBillingQueryVariables = Exact<{ }>; -export type GetWorkspaceWithBillingQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, updatedAt: string, createdAt: string, role?: string | null, billing: { __typename?: 'WorkspaceBilling', versionsCount?: { __typename?: 'WorkspaceVersionsCount', current: number, max: number } | null } } }; +export type GetWorkspaceWithBillingQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, updatedAt: string, createdAt: string, role?: string | null, billing: { __typename?: 'WorkspaceBilling', versionsCount: { __typename?: 'WorkspaceVersionsCount', current: number, max: number }, cost: { __typename?: 'WorkspaceCost', subTotal: number, currency: Currency, items: Array<{ __typename?: 'WorkspaceCostItem', count: number, name: string, cost: number }> } } } }; export type GetWorkspaceWithProjectsQueryVariables = Exact<{ workspaceId: Scalars['String']['input']; @@ -4805,7 +4828,7 @@ export type ActiveUserProjectsWorkspaceQuery = { __typename?: 'Query', activeUse 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; 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; -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"}}]}}]}}]}}]} as unknown as DocumentNode; +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"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const WorkspaceProjectsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjects"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"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":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]} as unknown as DocumentNode; export const BasicStreamAccessRequestFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; export const TestAutomateFunctionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestAutomateFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"5"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"versionTag"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"inputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"commitId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"automationCount"}},{"kind":"Field","name":{"kind":"Name","value":"supportedSourceApps"}},{"kind":"Field","name":{"kind":"Name","value":"tags"}}]}}]} as unknown as DocumentNode; @@ -4827,7 +4850,7 @@ export const CreateObjectDocument = {"kind":"Document","definitions":[{"kind":"O export const CreateWorkspaceInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateWorkspaceInvite"},"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":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceInviteCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"invites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"}}]}}]}}]}}]}}]}},{"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"}}]}},{"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; export const BatchCreateWorkspaceInvitesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BatchCreateWorkspaceInvites"},"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":"input"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceInviteCreateInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"invites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"batchCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"}}]}}]}}]}}]}}]}},{"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"}}]}},{"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; export const GetWorkspaceWithTeamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceWithTeam"},"variableDefinitions":[{"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":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"}}]}}]}}]}},{"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"}}]}},{"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; -export const GetWorkspaceWithBillingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceWithBilling"},"variableDefinitions":[{"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":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBilling"}}]}}]}},{"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"}}]}},{"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"}}]}}]}}]}}]} as unknown as DocumentNode; +export const GetWorkspaceWithBillingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceWithBilling"},"variableDefinitions":[{"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":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBilling"}}]}}]}},{"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"}}]}},{"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"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetWorkspaceWithProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceWithProjects"},"variableDefinitions":[{"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":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjects"}}]}}]}}]}},{"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"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjects"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"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":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]} as unknown as DocumentNode; export const CancelWorkspaceInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CancelWorkspaceInvite"},"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":"inviteId"}},"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":"invites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cancel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"inviteId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"inviteId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"}}]}}]}}]}}]}}]}},{"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"}}]}},{"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; export const UseWorkspaceInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UseWorkspaceInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceInviteUseInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"invites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"use"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]}}]} as unknown as DocumentNode;