* feat(webhook-service): learn to speak multi region

* feat(fileimport-service): talk multi region to me

* feat(fileuploads, blobs): multi region

* feat(workspaces): update delet workspace with billing and regions

* fix typo

* feat(workspaces): remove old billing resolvers

* test(workspaces): fix tests

* fix(workspaces): remove unused schema
This commit is contained in:
Gergő Jedlicska 2024-11-19 15:45:03 +01:00 коммит произвёл GitHub
Родитель 381c4e2a85
Коммит e30b8c83b9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
15 изменённых файлов: 143 добавлений и 593 удалений

Просмотреть файл

@ -226,60 +226,6 @@ input PendingWorkspaceCollaboratorsFilter {
search: String
}
type WorkspaceVersionsCount {
"""
Total number of versions of all projects in the workspace
"""
current: Int!
"""
Maximum number of version of all projects in the workspace with no additional cost
"""
max: Int!
}
enum Currency {
GBP
USD
EUR
}
type WorkspaceCostItem {
count: Int!
name: String!
cost: Float!
label: String!
}
type WorkspaceCostDiscount {
name: String!
amount: Float!
}
type WorkspaceCost {
"""
Estimated cost of the workspace with no discount applied
"""
subTotal: Float!
"""
Currency of the price
"""
currency: Currency!
items: [WorkspaceCostItem!]!
"""
Discount applied to the total
"""
discount: WorkspaceCostDiscount
"""
Total cost with discount applied
"""
total: Float!
}
type WorkspaceBilling {
versionsCount: WorkspaceVersionsCount!
cost: WorkspaceCost!
}
type Workspace {
id: ID!
name: String!
@ -324,10 +270,6 @@ type Workspace {
filter: WorkspaceProjectsFilter
): ProjectCollection!
"""
Billing data for Workspaces beta
"""
billing: WorkspaceBilling @hasWorkspaceRole(role: MEMBER)
"""
Information about the workspace's SSO configuration and the current user's SSO session, if present
"""
sso: WorkspaceSso

Просмотреть файл

@ -56,7 +56,6 @@ generates:
ProjectAutomationsUpdatedMessage: '@/modules/automate/helpers/graphTypes#ProjectAutomationsUpdatedMessageGraphQLReturn'
UserAutomateInfo: '@/modules/automate/helpers/graphTypes#UserAutomateInfoGraphQLReturn'
Workspace: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceGraphQLReturn'
WorkspaceBilling: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceBillingGraphQLReturn'
WorkspaceSso: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceSsoGraphQLReturn'
WorkspaceMutations: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceMutationsGraphQLReturn'
WorkspaceInviteMutations: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceInviteMutationsGraphQLReturn'

Просмотреть файл

@ -5,7 +5,7 @@ import { CommentReplyAuthorCollectionGraphQLReturn, CommentGraphQLReturn } from
import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/helpers/graphTypes';
import { FileUploadGraphQLReturn } from '@/modules/fileuploads/helpers/types';
import { AutomateFunctionGraphQLReturn, AutomateFunctionReleaseGraphQLReturn, AutomationGraphQLReturn, AutomationRevisionGraphQLReturn, AutomationRevisionFunctionGraphQLReturn, AutomateRunGraphQLReturn, AutomationRunTriggerGraphQLReturn, AutomationRevisionTriggerDefinitionGraphQLReturn, AutomateFunctionRunGraphQLReturn, TriggeredAutomationsStatusGraphQLReturn, ProjectAutomationMutationsGraphQLReturn, ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn, ProjectAutomationsUpdatedMessageGraphQLReturn, UserAutomateInfoGraphQLReturn } from '@/modules/automate/helpers/graphTypes';
import { WorkspaceGraphQLReturn, WorkspaceBillingGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, ProjectRoleGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes';
import { WorkspaceGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, ProjectRoleGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes';
import { WorkspaceBillingMutationsGraphQLReturn } from '@/modules/gatekeeper/helpers/graphTypes';
import { WebhookGraphQLReturn } from '@/modules/webhooks/helpers/graphTypes';
import { SmartTextEditorValueGraphQLReturn } from '@/modules/core/services/richTextEditorService';
@ -4029,8 +4029,6 @@ export type Workspace = {
__typename?: 'Workspace';
/** Regions available to the workspace for project data residency */
availableRegions: Array<ServerRegionItem>;
/** Billing data for Workspaces beta */
billing?: Maybe<WorkspaceBilling>;
createdAt: Scalars['DateTime']['output'];
customerPortalUrl?: Maybe<Scalars['String']['output']>;
/** Selected fallback when `logo` not set */
@ -4092,12 +4090,6 @@ export type WorkspaceTeamArgs = {
limit?: Scalars['Int']['input'];
};
export type WorkspaceBilling = {
__typename?: 'WorkspaceBilling';
cost: WorkspaceCost;
versionsCount: WorkspaceVersionsCount;
};
export type WorkspaceBillingMutations = {
__typename?: 'WorkspaceBillingMutations';
cancelCheckoutSession: Scalars['Boolean']['output'];
@ -4792,7 +4784,6 @@ export type ResolversTypes = {
WebhookEventCollection: ResolverTypeWrapper<WebhookEventCollection>;
WebhookUpdateInput: WebhookUpdateInput;
Workspace: ResolverTypeWrapper<WorkspaceGraphQLReturn>;
WorkspaceBilling: ResolverTypeWrapper<WorkspaceBillingGraphQLReturn>;
WorkspaceBillingMutations: ResolverTypeWrapper<WorkspaceBillingMutationsGraphQLReturn>;
WorkspaceCollaborator: ResolverTypeWrapper<WorkspaceCollaboratorGraphQLReturn>;
WorkspaceCollaboratorCollection: ResolverTypeWrapper<Omit<WorkspaceCollaboratorCollection, 'items'> & { items: Array<ResolversTypes['WorkspaceCollaborator']> }>;
@ -5053,7 +5044,6 @@ export type ResolversParentTypes = {
WebhookEventCollection: WebhookEventCollection;
WebhookUpdateInput: WebhookUpdateInput;
Workspace: WorkspaceGraphQLReturn;
WorkspaceBilling: WorkspaceBillingGraphQLReturn;
WorkspaceBillingMutations: WorkspaceBillingMutationsGraphQLReturn;
WorkspaceCollaborator: WorkspaceCollaboratorGraphQLReturn;
WorkspaceCollaboratorCollection: Omit<WorkspaceCollaboratorCollection, 'items'> & { items: Array<ResolversParentTypes['WorkspaceCollaborator']> };
@ -6467,7 +6457,6 @@ export type WebhookEventCollectionResolvers<ContextType = GraphQLContext, Parent
export type WorkspaceResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['Workspace'] = ResolversParentTypes['Workspace']> = {
availableRegions?: Resolver<Array<ResolversTypes['ServerRegionItem']>, ParentType, ContextType>;
billing?: Resolver<Maybe<ResolversTypes['WorkspaceBilling']>, ParentType, ContextType>;
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
customerPortalUrl?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
defaultLogoIndex?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
@ -6493,12 +6482,6 @@ export type WorkspaceResolvers<ContextType = GraphQLContext, ParentType extends
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type WorkspaceBillingResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceBilling'] = ResolversParentTypes['WorkspaceBilling']> = {
cost?: Resolver<ResolversTypes['WorkspaceCost'], ParentType, ContextType>;
versionsCount?: Resolver<ResolversTypes['WorkspaceVersionsCount'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type WorkspaceBillingMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceBillingMutations'] = ResolversParentTypes['WorkspaceBillingMutations']> = {
cancelCheckoutSession?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<WorkspaceBillingMutationsCancelCheckoutSessionArgs, 'input'>>;
createCheckoutSession?: Resolver<ResolversTypes['CheckoutSession'], ParentType, ContextType, RequireFields<WorkspaceBillingMutationsCreateCheckoutSessionArgs, 'input'>>;
@ -6763,7 +6746,6 @@ export type Resolvers<ContextType = GraphQLContext> = {
WebhookEvent?: WebhookEventResolvers<ContextType>;
WebhookEventCollection?: WebhookEventCollectionResolvers<ContextType>;
Workspace?: WorkspaceResolvers<ContextType>;
WorkspaceBilling?: WorkspaceBillingResolvers<ContextType>;
WorkspaceBillingMutations?: WorkspaceBillingMutationsResolvers<ContextType>;
WorkspaceCollaborator?: WorkspaceCollaboratorResolvers<ContextType>;
WorkspaceCollaboratorCollection?: WorkspaceCollaboratorCollectionResolvers<ContextType>;

Просмотреть файл

@ -4010,8 +4010,6 @@ export type Workspace = {
__typename?: 'Workspace';
/** Regions available to the workspace for project data residency */
availableRegions: Array<ServerRegionItem>;
/** Billing data for Workspaces beta */
billing?: Maybe<WorkspaceBilling>;
createdAt: Scalars['DateTime']['output'];
customerPortalUrl?: Maybe<Scalars['String']['output']>;
/** Selected fallback when `logo` not set */
@ -4073,12 +4071,6 @@ export type WorkspaceTeamArgs = {
limit?: Scalars['Int']['input'];
};
export type WorkspaceBilling = {
__typename?: 'WorkspaceBilling';
cost: WorkspaceCost;
versionsCount: WorkspaceVersionsCount;
};
export type WorkspaceBillingMutations = {
__typename?: 'WorkspaceBillingMutations';
cancelCheckoutSession: Scalars['Boolean']['output'];

Просмотреть файл

@ -1 +0,0 @@
export const WORKSPACE_MAX_PROJECTS_VERSIONS = 500

Просмотреть файл

@ -30,7 +30,7 @@ import {
deleteInviteFactory as deleteInviteFromDbFactory,
queryAllUserResourceInvitesFactory,
queryAllResourceInvitesFactory,
markInviteUpdatedfactory,
markInviteUpdatedFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
@ -384,7 +384,7 @@ export = {
}),
findUserByTarget: findUserByTargetFactory({ db }),
findInvite: findInviteFactory({ db }),
markInviteUpdated: markInviteUpdatedfactory({ db }),
markInviteUpdated: markInviteUpdatedFactory({ db }),
getUser,
getServerInfo
})

Просмотреть файл

@ -501,7 +501,7 @@ export const findInviteByTokenFactory =
return (await q) || null
}
export const markInviteUpdatedfactory =
export const markInviteUpdatedFactory =
({ db }: { db: Knex }): MarkInviteUpdated =>
async ({ inviteId }) => {
const cols = ServerInvites.with({ withoutTablePrefix: true }).col

Просмотреть файл

@ -239,10 +239,6 @@ export type EmitWorkspaceEvent = <TEvent extends WorkspaceEvents>(args: {
payload: EventBusPayloads[TEvent]
}) => Promise<void>
export type CountProjectsVersionsByWorkspaceId = (args: {
workspaceId: string
}) => Promise<number>
export type CountWorkspaceRoleWithOptionalProjectRole = (args: {
workspaceId: string
workspaceRole: WorkspaceRoles

Просмотреть файл

@ -113,3 +113,9 @@ export class WorkspaceDomainsInvalidState extends BaseError {
static code = 'WORKSPACE_NO_VERIFIED_DOMAINS'
static statusCode = 500
}
export class WorkspacePaidPlanActiveError extends BaseError {
static defaultMessage = 'Workspace has an active paid plan, cancel it first'
static code = 'WORKSPACE_PAID_PLAN_ACTIVE'
static statusCode = 400
}

Просмотреть файл

@ -24,7 +24,7 @@ import {
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
markInviteUpdatedfactory,
markInviteUpdatedFactory,
queryAllResourceInvitesFactory,
queryAllUserResourceInvitesFactory,
updateAllInviteTargetsFactory
@ -49,6 +49,7 @@ import {
WorkspaceInvalidRoleError,
WorkspaceJoinNotAllowedError,
WorkspaceNotFoundError,
WorkspacePaidPlanActiveError,
WorkspacesNotAuthorizedError,
WorkspacesNotYetImplementedError
} from '@/modules/workspaces/errors/workspace'
@ -68,11 +69,9 @@ import {
getWorkspaceDomainsFactory,
getUserDiscoverableWorkspacesFactory,
getWorkspaceWithDomainsFactory,
countProjectsVersionsByWorkspaceIdFactory,
countWorkspaceRoleWithOptionalProjectRoleFactory,
getUserIdsWithRoleInWorkspaceFactory,
getWorkspaceRoleForUserFactory,
getWorkspaceBySlugFactory
getWorkspaceBySlugFactory,
countDomainsByWorkspaceIdFactory
} from '@/modules/workspaces/repositories/workspaces'
import {
buildWorkspaceInviteEmailContentsFactory,
@ -106,7 +105,12 @@ import {
getPaginatedWorkspaceTeamFactory,
getWorkspacesForUserFactory
} from '@/modules/workspaces/services/retrieval'
import { Roles, WorkspaceRoles, removeNullOrUndefinedKeys } from '@speckle/shared'
import {
Roles,
WorkspaceRoles,
removeNullOrUndefinedKeys,
throwUncoveredError
} from '@speckle/shared'
import { chunk } from 'lodash'
import {
findEmailsByUserIdFactory,
@ -118,11 +122,6 @@ import {
import { joinWorkspaceFactory } from '@/modules/workspaces/services/join'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { WORKSPACE_MAX_PROJECTS_VERSIONS } from '@/modules/gatekeeper/domain/constants'
import {
getWorkspaceCostFactory,
getWorkspaceCostItemsFactory
} from '@/modules/workspaces/services/cost'
import {
deleteWorkspaceDomainFactory,
isUserWorkspaceDomainPolicyCompliantFactory
@ -162,7 +161,7 @@ import {
} from '@/modules/core/services/ratelimiter'
import { RateLimitError } from '@/modules/core/errors/ratelimit'
import { ProjectsEmitter } from '@/modules/core/events/projectsEmitter'
import { getDb } from '@/modules/multiregion/dbSelector'
import { getDb, getRegionDb } from '@/modules/multiregion/dbSelector'
import { createNewProjectFactory } from '@/modules/core/services/projects'
import {
deleteProjectFactory,
@ -184,6 +183,8 @@ import {
import { getDecryptor } from '@/modules/workspaces/helpers/sso'
import { getDefaultRegionFactory } from '@/modules/workspaces/repositories/regions'
import { storeModelFactory } from '@/modules/core/repositories/models'
import { getWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing'
import { Knex } from 'knex'
const eventBus = getEventBus()
const getServerInfo = getServerInfoFactory({ db })
@ -241,7 +242,6 @@ const buildCreateAndSendWorkspaceInvite = () =>
getUser,
getServerInfo
})
const deleteStream = deleteStreamFactory({ db })
const saveActivity = saveActivityFactory({ db })
const validateStreamAccess = validateStreamAccessFactory({ authorizeResolver })
const isStreamCollaborator = isStreamCollaboratorFactory({
@ -448,18 +448,51 @@ export = FF_WORKSPACES_MODULE_ENABLED
context.resourceAccessRules
)
// Delete workspace and associated resources (i.e. invites)
const deleteWorkspace = deleteWorkspaceFactory({
deleteWorkspace: repoDeleteWorkspaceFactory({ db }),
deleteProject: deleteStream,
deleteAllResourceInvites: deleteAllResourceInvitesFactory({ db }),
queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({
getStreams: legacyGetStreamsFactory({ db })
}),
deleteSsoProvider: deleteSsoProviderFactory({ db })
})
const workspacePlan = await getWorkspacePlanFactory({ db })({ workspaceId })
if (workspacePlan) {
switch (workspacePlan.name) {
case 'team':
case 'pro':
case 'business':
switch (workspacePlan.status) {
case 'cancelationScheduled':
case 'valid':
case 'paymentFailed':
throw new WorkspacePaidPlanActiveError()
case 'canceled':
case 'trial':
case 'expired':
break
default:
throwUncoveredError(workspacePlan)
}
case 'unlimited':
case 'academia':
break
default:
throwUncoveredError(workspacePlan)
}
}
await deleteWorkspace({ workspaceId })
const deleteWorkspaceFrom = (db: Knex) =>
deleteWorkspaceFactory({
deleteWorkspace: repoDeleteWorkspaceFactory({ db }),
deleteProject: deleteStreamFactory({ db }),
deleteAllResourceInvites: deleteAllResourceInvitesFactory({ db }),
queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({
getStreams: legacyGetStreamsFactory({ db })
}),
deleteSsoProvider: deleteSsoProviderFactory({ db })
})
// this should be turned into a get all regions and map over the regions...
const region = await getDefaultRegionFactory({ db })({ workspaceId })
if (region) {
const regionDb = await getRegionDb({ regionKey: region.key })
await deleteWorkspaceFrom(regionDb)({ workspaceId })
}
await deleteWorkspaceFrom(db)({ workspaceId })
return true
},
@ -570,7 +603,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
)
await deleteWorkspaceDomainFactory({
deleteWorkspaceDomain: repoDeleteWorkspaceDomainFactory({ db }),
countDomainsByWorkspaceId: countProjectsVersionsByWorkspaceIdFactory({
countDomainsByWorkspaceId: countDomainsByWorkspaceIdFactory({
db
}),
updateWorkspace: updateWorkspaceFactory({
@ -656,7 +689,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
db,
filterQuery: workspaceInviteValidityFilter
}),
markInviteUpdated: markInviteUpdatedfactory({ db }),
markInviteUpdated: markInviteUpdatedFactory({ db }),
getUser,
getServerInfo
})
@ -956,35 +989,12 @@ export = FF_WORKSPACES_MODULE_ENABLED
domains: async (parent) => {
return await getWorkspaceDomainsFactory({ db })({ workspaceIds: [parent.id] })
},
billing: (parent) => ({ parent }),
sso: async (parent) => {
return await getWorkspaceSsoProviderRecordFactory({ db })({
workspaceId: parent.id
})
}
},
WorkspaceBilling: {
versionsCount: async ({ parent }) => {
const workspaceId = parent.id
return {
current: await countProjectsVersionsByWorkspaceIdFactory({ db })({
workspaceId
}),
max: WORKSPACE_MAX_PROJECTS_VERSIONS
}
},
cost: async ({ parent }) => {
const workspaceId = parent.id
return getWorkspaceCostFactory({
getWorkspaceCostItems: getWorkspaceCostItemsFactory({
countRole: countWorkspaceRoleWithOptionalProjectRoleFactory({ db }),
getUserIdsWithRoleInWorkspace: getUserIdsWithRoleInWorkspaceFactory({
db
})
})
})({ workspaceId })
}
},
WorkspaceSso: {
provider: async ({ workspaceId }) => {
const provider = await getWorkspaceSsoProviderFactory({

Просмотреть файл

@ -6,7 +6,6 @@ import {
} from '@/modules/workspacesCore/domain/types'
import {
CountDomainsByWorkspaceId,
CountProjectsVersionsByWorkspaceId,
CountWorkspaceRoleWithOptionalProjectRole,
DeleteWorkspace,
DeleteWorkspaceDomain,
@ -42,7 +41,6 @@ import {
ServerAcl,
ServerInvites,
StreamAcl,
StreamCommits,
Streams,
Users
} from '@/modules/core/dbSchema'
@ -388,18 +386,6 @@ export const getWorkspaceWithDomainsFactory =
} as Workspace & { domains: WorkspaceDomain[] }
}
export const countProjectsVersionsByWorkspaceIdFactory =
({ db }: { db: Knex }): CountProjectsVersionsByWorkspaceId =>
async ({ workspaceId }) => {
const [res] = await tables
.streams(db)
.join(StreamCommits.name, StreamCommits.col.streamId, Streams.col.id)
.where({ workspaceId })
.count(StreamCommits.col.commitId)
return parseInt(res.count.toString())
}
export const getUserIdsWithRoleInWorkspaceFactory =
({ db }: { db: Knex }): GetUserIdsWithRoleInWorkspace =>
async ({ workspaceId, workspaceRole }, options) => {

Просмотреть файл

@ -1,168 +0,0 @@
import {
CountWorkspaceRoleWithOptionalProjectRole,
GetUserIdsWithRoleInWorkspace
} from '@/modules/workspaces/domain/operations'
import { Roles, throwUncoveredError } from '@speckle/shared'
type KnownWorkspaceCostItemNames =
| 'workspace-members'
| 'free-guests'
| 'read-write-guests'
| 'read-only-guests'
type KnownCurrencies = 'GBP'
type WorkspaceCostItem = {
name: KnownWorkspaceCostItemNames
description: string
count: number
cost: number
label: string
}
const getWorkspaceCostItemCost = ({
name
}: {
name: KnownWorkspaceCostItemNames
currency?: KnownCurrencies
}): number => {
switch (name) {
case 'workspace-members':
return 49
case 'free-guests':
return 0
case 'read-write-guests':
return 15
case 'read-only-guests':
return 5
default:
throwUncoveredError(name)
}
}
type GetWorkspaceCostItems = (args: {
workspaceId: string
}) => Promise<WorkspaceCostItem[]>
export const getWorkspaceCostItemsFactory =
({
countRole,
getUserIdsWithRoleInWorkspace
}: {
countRole: CountWorkspaceRoleWithOptionalProjectRole
getUserIdsWithRoleInWorkspace: GetUserIdsWithRoleInWorkspace
}): GetWorkspaceCostItems =>
async ({ workspaceId }) => {
const freeGuestsIds = await getUserIdsWithRoleInWorkspace(
{
workspaceId,
workspaceRole: Roles.Workspace.Guest
},
{ limit: 10 }
)
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,
skipUserIds: freeGuestsIds
}),
countRole({
workspaceId,
workspaceRole: Roles.Workspace.Guest,
projectRole: Roles.Stream.Reviewer,
skipUserIds: freeGuestsIds
})
])
const workspaceCostItems: WorkspaceCostItem[] = []
const workspaceMembersCount = adminCount + memberCount
const freeGuestsCount = freeGuestsIds.length
workspaceCostItems.push({
name: 'workspace-members',
description: 'General workspace member',
count: workspaceMembersCount,
cost: getWorkspaceCostItemCost({ name: 'workspace-members' }),
label: `${workspaceMembersCount} workspace ${
workspaceMembersCount === 1 ? 'member' : 'members'
}`
})
workspaceCostItems.push({
name: 'free-guests',
description: 'The first 10 workspace guests are free',
count: freeGuestsCount,
cost: getWorkspaceCostItemCost({ name: 'free-guests' }),
label: `${freeGuestsCount}/10 free ${freeGuestsCount === 1 ? 'guest' : 'guests'}`
})
workspaceCostItems.push({
name: 'read-write-guests',
description: 'Workspace guests with write access to minimum 1 workspace project',
count: writeGuestCount,
cost: getWorkspaceCostItemCost({ name: 'read-write-guests' }),
label: `${writeGuestCount} read/write ${
writeGuestCount === 1 ? 'guest' : 'guests'
}`
})
workspaceCostItems.push({
name: 'read-only-guests',
description: 'Workspace guests with only read access to some workspace projects',
count: readGuestCount,
cost: getWorkspaceCostItemCost({ name: 'read-only-guests' }),
label: `${readGuestCount} read only ${readGuestCount === 1 ? 'guest' : 'guests'}`
})
return workspaceCostItems
}
type WorkspaceDiscount = {
name: string
amount: number
}
type WorkspaceCost = {
subTotal: number
currency: KnownCurrencies
items: WorkspaceCostItem[]
total: number
discount?: WorkspaceDiscount
}
export const calculateWorkspaceTotalCost = ({
subTotal,
discount
}: Pick<WorkspaceCost, 'subTotal' | 'discount'>) => {
if (!discount) {
return subTotal
}
return subTotal * discount?.amount
}
export const getWorkspaceCostFactory =
({
getWorkspaceCostItems,
discount
}: {
getWorkspaceCostItems: GetWorkspaceCostItems
discount?: WorkspaceDiscount
}) =>
async ({ workspaceId }: { workspaceId: string }): Promise<WorkspaceCost> => {
const items = await getWorkspaceCostItems({ workspaceId })
const subTotal = items.reduce((acc, { cost, count }) => acc + cost * count, 0)
return {
currency: 'GBP',
items,
subTotal,
discount,
total: calculateWorkspaceTotalCost({
subTotal,
discount
})
}
}

Просмотреть файл

@ -31,31 +31,6 @@ export const basicPendingWorkspaceCollaboratorFragment = gql`
}
`
export const workspaceBillingFragment = gql`
fragment WorkspaceBilling on Workspace {
billing {
versionsCount {
current
max
}
cost {
subTotal
currency
items {
count
name
cost
label
}
discount {
name
amount
}
total
}
}
}
`
export const workspaceProjectsFragment = gql`
fragment WorkspaceProjects on ProjectCollection {
items {
@ -122,17 +97,6 @@ export const getWorkspaceWithTeamQuery = gql`
${basicPendingWorkspaceCollaboratorFragment}
`
export const getWorkspaceWithBillingQuery = gql`
query GetWorkspaceWithBilling($workspaceId: String!) {
workspace(id: $workspaceId) {
...BasicWorkspace
...WorkspaceBilling
}
}
${basicWorkspaceFragment}
${workspaceBillingFragment}
`
export const getWorkspaceWithProjectsQuery = gql`
query GetWorkspaceWithProjects($workspaceId: String!) {
workspace(id: $workspaceId) {

Просмотреть файл

@ -23,9 +23,6 @@ import {
UpdateWorkspaceDocument,
UpdateWorkspaceRoleDocument,
ActiveUserLeaveWorkspaceDocument,
GetWorkspaceWithBillingDocument,
CreateObjectDocument,
CreateProjectVersionDocument,
GetWorkspaceWithProjectsDocument,
AddWorkspaceDomainDocument,
DeleteWorkspaceDomainDocument,
@ -50,62 +47,9 @@ import {
} from '@/modules/core/helpers/testHelpers'
import { getWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces'
import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams'
import { getPaginatedStreamBranchesFactory } from '@/modules/core/services/branch/retrieval'
import {
getPaginatedStreamBranchesPageFactory,
getStreamBranchCountFactory
} from '@/modules/core/repositories/branches'
const getBranchesByStreamId = getPaginatedStreamBranchesFactory({
getPaginatedStreamBranchesPage: getPaginatedStreamBranchesPageFactory({ db }),
getStreamBranchCount: getStreamBranchCountFactory({ db })
})
const grantStreamPermissions = grantStreamPermissionsFactory({ db })
const createProjectWithVersions =
({ apollo }: { apollo: TestApolloServer }) =>
async ({
workspaceId,
versionsCount
}: {
workspaceId: string
versionsCount: number
}) => {
const resProject1 = await apollo.execute(CreateWorkspaceProjectDocument, {
input: {
name: createRandomPassword(),
workspaceId
}
})
expect(resProject1).to.not.haveGraphQLErrors()
const project1Id = resProject1.data!.workspaceMutations.projects.create.id
const {
items: [model1]
} = await getBranchesByStreamId(project1Id, { limit: 1, cursor: null })
expect(model1).to.exist
const resObj1 = await apollo.execute(CreateObjectDocument, {
input: {
streamId: project1Id,
objects: [{ some: 'obj' }]
}
})
expect(resObj1).to.not.haveGraphQLErrors()
await Promise.all(
new Array(versionsCount).fill(0).map(async () => {
const res = await apollo.execute(CreateProjectVersionDocument, {
input: {
projectId: project1Id,
modelId: model1.id,
objectId: resObj1.data!.objectCreate[0]
}
})
expect(res).to.not.haveGraphQLErrors()
})
)
}
describe('Workspaces GQL CRUD', () => {
let apollo: TestApolloServer
@ -544,159 +488,53 @@ describe('Workspaces GQL CRUD', () => {
})
})
describe('query workspace.billing', () => {
it('should return workspace version limits', async () => {
await createProjectWithVersions({ apollo })({
workspaceId: workspace.id,
versionsCount: 3
})
await createProjectWithVersions({ apollo })({
workspaceId: workspace.id,
versionsCount: 2
})
const res = await apollo.execute(GetWorkspaceWithBillingDocument, {
workspaceId: workspace.id
})
expect(res).to.not.haveGraphQLErrors()
expect(res.data?.workspace.billing?.versionsCount).to.deep.equal({
current: 5,
max: 500
})
})
it('should return workspace cost', async () => {
const createRes = await apollo.execute(CreateWorkspaceDocument, {
input: {
name: createRandomString(),
slug: cryptoRandomString({ length: 10 })
}
})
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 freeGuests = new Array(10).fill(0).map(() => ({
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()
}
// first 10 users
await createTestUsers(freeGuests)
for (const guest of freeGuests) {
await assignToWorkspace(workspace, guest, Roles.Workspace.Guest)
}
await Promise.all([
createTestUser(member),
createTestUser(guestWithWritePermission),
createTestUser(viewer),
createTestUser(viewer2)
])
await assignToWorkspace(workspace, member, Roles.Workspace.Member)
await assignToWorkspace(
workspace,
guestWithWritePermission,
Roles.Workspace.Guest
)
await assignToWorkspace(workspace, viewer, Roles.Workspace.Guest)
await assignToWorkspace(workspace, viewer2, Roles.Workspace.Guest)
const resProject1 = await apollo.execute(CreateWorkspaceProjectDocument, {
input: {
name: createRandomPassword(),
workspaceId
}
})
expect(resProject1).to.not.haveGraphQLErrors()
const project1Id = resProject1.data!.workspaceMutations.projects.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, total, discount } =
res.data?.workspace.billing?.cost || {}
expect(subTotal).to.equal(49 + 49 + 15 + 2 * 5)
expect(currency).to.equal('GBP')
expect(items).to.deep.equal([
{
name: 'workspace-members',
count: 2,
cost: 49,
label: '2 workspace members'
},
{
name: 'free-guests',
count: 10,
cost: 0,
label: '10/10 free guests'
},
{
name: 'read-write-guests',
count: 1,
cost: 15,
label: '1 read/write guest'
},
{
name: 'read-only-guests',
count: 2,
cost: 5,
label: '2 read only guests'
}
])
expect(discount).to.deep.equal(null)
expect(total).to.equal(123)
})
})
describe('query activeUser.workspaces', () => {
it('should return all workspaces for a user', async () => {
const res = await apollo.execute(GetActiveUserWorkspacesDocument, {})
const testUser: BasicTestUser = {
id: '',
name: 'John Speckle',
email: 'foobar@example.org',
role: Roles.Server.Admin,
verified: true
}
await createTestUser(testUser)
const testApollo: TestApolloServer = await testApolloServer({
context: await createTestContext({
auth: true,
userId: testUser.id,
token: '',
role: testUser.role,
scopes: AllScopes
})
})
const workspace1: BasicTestWorkspace = {
id: '',
ownerId: '',
name: 'Workspace A',
slug: cryptoRandomString({ length: 10 })
}
const workspace2: BasicTestWorkspace = {
id: '',
ownerId: '',
name: 'Workspace A',
slug: cryptoRandomString({ length: 10 })
}
const workspace3: BasicTestWorkspace = {
id: '',
ownerId: '',
name: 'Workspace A',
slug: cryptoRandomString({ length: 10 })
}
await createTestWorkspace(workspace1, testUser)
await createTestWorkspace(workspace2, testUser)
await createTestWorkspace(workspace3, testUser)
const res = await testApollo.execute(GetActiveUserWorkspacesDocument, {})
expect(res).to.not.haveGraphQLErrors()
// TODO: this test depends on the previous tests
expect(res.data?.activeUser?.workspaces?.items?.length).to.equal(3)
@ -786,7 +624,7 @@ describe('Workspaces GQL CRUD', () => {
}
before(async () => {
await createTestWorkspace(workspace, testAdminUser)
await createTestWorkspace(workspace, testAdminUser, { addPlan: false })
workspaceProject.workspaceId = workspace.id
@ -820,6 +658,22 @@ describe('Workspaces GQL CRUD', () => {
})
})
it('should fail to delete a paid workspace', async () => {
const paidWorkspace = {
id: '',
name: 'test ws',
slug: cryptoRandomString({ length: 10 }),
ownerId: ''
}
await createTestWorkspace(paidWorkspace, testAdminUser, { addPlan: true })
const deleteRes = await apollo.execute(DeleteWorkspaceDocument, {
workspaceId: paidWorkspace.id
})
expect(deleteRes).to.haveGraphQLErrors('Workspace has an active paid plan')
})
it('should delete the workspace', async () => {
const deleteRes = await apollo.execute(DeleteWorkspaceDocument, {
workspaceId: workspace.id
@ -833,18 +687,25 @@ describe('Workspaces GQL CRUD', () => {
})
it('should throw if non-workspace-admin triggers delete', async () => {
const memberApollo: TestApolloServer = (apollo = await testApolloServer({
const nonPaidWorkspace = {
id: '',
name: 'test ws',
slug: cryptoRandomString({ length: 10 }),
ownerId: ''
}
await createTestWorkspace(nonPaidWorkspace, testAdminUser, { addPlan: false })
const memberApollo: TestApolloServer = await testApolloServer({
context: await createTestContext({
auth: true,
userId: testAdminUser.id,
userId: testMemberUser.id,
token: '',
role: testAdminUser.role,
role: testMemberUser.role,
scopes: AllScopes
})
}))
})
const res = await memberApollo.execute(DeleteWorkspaceDocument, {
workspaceId: workspace.id
workspaceId: nonPaidWorkspace.id
})
expect(res).to.haveGraphQLErrors('not authorized')

Просмотреть файл

@ -4011,8 +4011,6 @@ export type Workspace = {
__typename?: 'Workspace';
/** Regions available to the workspace for project data residency */
availableRegions: Array<ServerRegionItem>;
/** Billing data for Workspaces beta */
billing?: Maybe<WorkspaceBilling>;
createdAt: Scalars['DateTime']['output'];
customerPortalUrl?: Maybe<Scalars['String']['output']>;
/** Selected fallback when `logo` not set */
@ -4074,12 +4072,6 @@ export type WorkspaceTeamArgs = {
limit?: Scalars['Int']['input'];
};
export type WorkspaceBilling = {
__typename?: 'WorkspaceBilling';
cost: WorkspaceCost;
versionsCount: WorkspaceVersionsCount;
};
export type WorkspaceBillingMutations = {
__typename?: 'WorkspaceBillingMutations';
cancelCheckoutSession: Scalars['Boolean']['output'];
@ -4467,8 +4459,6 @@ 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 }, cost: { __typename?: 'WorkspaceCost', subTotal: number, currency: Currency, total: number, items: Array<{ __typename?: 'WorkspaceCostItem', count: number, name: string, cost: number, label: string }>, discount?: { __typename?: 'WorkspaceCostDiscount', name: string, amount: number } | null } } | null };
export type WorkspaceProjectsFragment = { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string }> };
export type CreateWorkspaceInviteMutationVariables = Exact<{
@ -4494,13 +4484,6 @@ export type GetWorkspaceWithTeamQueryVariables = Exact<{
export type GetWorkspaceWithTeamQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, slug: string, updatedAt: string, createdAt: string, role?: string | null, invitedTeam?: Array<{ __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 }> | null } };
export type GetWorkspaceWithBillingQueryVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type GetWorkspaceWithBillingQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, slug: 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, total: number, items: Array<{ __typename?: 'WorkspaceCostItem', count: number, name: string, cost: number, label: string }>, discount?: { __typename?: 'WorkspaceCostDiscount', name: string, amount: number } | null } } | null } };
export type GetWorkspaceWithProjectsQueryVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
@ -5246,7 +5229,6 @@ export type MoveProjectToWorkspaceMutation = { __typename?: 'Mutation', workspac
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":"slug"}},{"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>;
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<WorkspaceProjectsFragment, unknown>;
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<BasicStreamAccessRequestFieldsFragment, unknown>;
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<TestAutomateFunctionFragment, unknown>;
@ -5268,7 +5250,6 @@ 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":"slug"}},{"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<CreateWorkspaceInviteMutation, CreateWorkspaceInviteMutationVariables>;
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":"slug"}},{"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<BatchCreateWorkspaceInvitesMutation, BatchCreateWorkspaceInvitesMutationVariables>;
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":"slug"}},{"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<GetWorkspaceWithTeamQuery, GetWorkspaceWithTeamQueryVariables>;
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":"slug"}},{"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"}},{"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<GetWorkspaceWithBillingQuery, GetWorkspaceWithBillingQueryVariables>;
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":"slug"}},{"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<GetWorkspaceWithProjectsQuery, GetWorkspaceWithProjectsQueryVariables>;
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":"slug"}},{"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<CancelWorkspaceInviteMutation, CancelWorkspaceInviteMutationVariables>;
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<UseWorkspaceInviteMutation, UseWorkspaceInviteMutationVariables>;