From 90e94d9cd7e18074bb47bc217bcfcf951707c5c1 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Wed, 22 Jan 2025 15:41:45 +0200 Subject: [PATCH 01/78] chore(server): refactor activityStream invocations - batch #2 - accessRequest --- .../events/accessRequestListeners.ts | 128 ++++++++++++++++++ .../server/modules/activitystream/index.ts | 36 ++--- .../services/accessRequestActivity.ts | 45 ------ .../activitystream/services/eventListener.ts | 57 +------- 4 files changed, 137 insertions(+), 129 deletions(-) create mode 100644 packages/server/modules/activitystream/events/accessRequestListeners.ts delete mode 100644 packages/server/modules/activitystream/services/accessRequestActivity.ts diff --git a/packages/server/modules/activitystream/events/accessRequestListeners.ts b/packages/server/modules/activitystream/events/accessRequestListeners.ts new file mode 100644 index 000000000..7237afc3b --- /dev/null +++ b/packages/server/modules/activitystream/events/accessRequestListeners.ts @@ -0,0 +1,128 @@ +import { EventBusListen, EventPayload } from '@/modules/shared/services/eventBus' +import { + AddStreamAccessRequestDeclinedActivity, + AddStreamAccessRequestedActivity, + SaveActivity +} from '@/modules/activitystream/domain/operations' +import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' +import { AccessRequestEvents } from '@/modules/accessrequests/domain/events' +import { + AccessRequestType, + isStreamAccessRequest +} from '@/modules/accessrequests/repositories' + +/** + * Save a "stream access requested" activity + */ +const addStreamAccessRequestedActivityFactory = + ({ + saveActivity + }: { + saveActivity: SaveActivity + }): AddStreamAccessRequestedActivity => + async (params: { streamId: string; requesterId: string }) => { + const { streamId, requesterId } = params + await saveActivity({ + streamId, + resourceType: ResourceTypes.Stream, + resourceId: streamId, + userId: requesterId, + actionType: ActionTypes.Stream.AccessRequestSent, + message: `User ${requesterId} has requested access to stream ${streamId}`, + info: { requesterId } + }) + } + +/** + * Save a "stream acccess request declined/denied" activity + */ +const addStreamAccessRequestDeclinedActivityFactory = + ({ + saveActivity + }: { + saveActivity: SaveActivity + }): AddStreamAccessRequestDeclinedActivity => + async (params: { streamId: string; requesterId: string; declinerId: string }) => { + const { streamId, requesterId, declinerId } = params + await saveActivity({ + streamId, + resourceType: ResourceTypes.Stream, + resourceId: streamId, + userId: declinerId, + actionType: ActionTypes.Stream.AccessRequestDeclined, + message: `User ${declinerId} declined access to stream ${streamId} for user ${requesterId}`, + info: { requesterId, declinerId } + }) + } + +const onServerAccessRequestCreatedFactory = + ({ + addStreamAccessRequestedActivity + }: { + addStreamAccessRequestedActivity: AddStreamAccessRequestedActivity + }) => + async (payload: EventPayload) => { + const { + request: { resourceId, requesterId } + } = payload.payload + if (!isStreamAccessRequest(payload.payload.request)) return + if (!resourceId) return + + await addStreamAccessRequestedActivity({ + streamId: resourceId, + requesterId + }) + } + +const onServerAccessRequestFinalizedFactory = + ({ + addStreamAccessRequestDeclinedActivity + }: { + addStreamAccessRequestDeclinedActivity: AddStreamAccessRequestDeclinedActivity + }) => + async (payload: EventPayload) => { + const { + approved, + finalizedBy, + request: { resourceId, resourceType, requesterId } + } = payload.payload + if (!resourceId) return + + if (resourceType === AccessRequestType.Stream) { + // If user was added to stream, an activity stream item was already added from 'addOrUpdateStreamCollaborator' + if (approved) return + + await addStreamAccessRequestDeclinedActivity({ + streamId: resourceId, + requesterId, + declinerId: finalizedBy + }) + } + } + +export const reportAccessRequestActivityFactory = + (deps: { eventListen: EventBusListen; saveActivity: SaveActivity }) => () => { + const addStreamAccessRequestedActivity = + addStreamAccessRequestedActivityFactory(deps) + const addStreamAccessRequestDeclinedActivity = + addStreamAccessRequestDeclinedActivityFactory(deps) + const onServerAccessRequestCreated = onServerAccessRequestCreatedFactory({ + addStreamAccessRequestedActivity + }) + const onServerAccessRequestFinalized = onServerAccessRequestFinalizedFactory({ + addStreamAccessRequestDeclinedActivity + }) + + const quitters = [ + deps.eventListen(AccessRequestEvents.Created, async (payload) => { + if (!isStreamAccessRequest(payload.payload.request)) return + return await onServerAccessRequestCreated(payload) + }), + deps.eventListen(AccessRequestEvents.Finalized, async (payload) => { + if (!isStreamAccessRequest(payload.payload.request)) return + await onServerAccessRequestFinalized(payload) + }) + ] + + return () => quitters.forEach((quit) => quit()) + } diff --git a/packages/server/modules/activitystream/index.ts b/packages/server/modules/activitystream/index.ts index 1aebbc331..820d99d42 100644 --- a/packages/server/modules/activitystream/index.ts +++ b/packages/server/modules/activitystream/index.ts @@ -14,10 +14,6 @@ import { addStreamInviteSentOutActivityFactory } from '@/modules/activitystream/services/streamActivity' import { getStreamFactory } from '@/modules/core/repositories/streams' -import { - addStreamAccessRequestDeclinedActivityFactory, - addStreamAccessRequestedActivityFactory -} from '@/modules/activitystream/services/accessRequestActivity' import { ScheduleExecution } from '@/modules/core/domain/scheduledTasks/operations' import { scheduleExecutionFactory } from '@/modules/core/services/taskScheduler' import { @@ -25,18 +21,13 @@ import { releaseTaskLockFactory } from '@/modules/core/repositories/scheduledTasks' import { Knex } from 'knex' -import { - onServerAccessRequestCreatedFactory, - onServerAccessRequestFinalizedFactory, - onServerInviteCreatedFactory -} from '@/modules/activitystream/services/eventListener' +import { onServerInviteCreatedFactory } from '@/modules/activitystream/services/eventListener' import { isProjectResourceTarget } from '@/modules/serverinvites/helpers/core' import { publish } from '@/modules/shared/utils/subscriptions' -import { isStreamAccessRequest } from '@/modules/accessrequests/repositories' import { ServerInvitesEvents } from '@/modules/serverinvites/domain/events' import { ProjectEvents } from '@/modules/core/domain/projects/events' -import { AccessRequestEvents } from '@/modules/accessrequests/domain/events' import { reportUserActivityFactory } from '@/modules/activitystream/events/userListeners' +import { reportAccessRequestActivityFactory } from '@/modules/activitystream/events/accessRequestListeners' let scheduledTask: ReturnType | null = null let quitEventListeners: Optional<() => void> = undefined @@ -57,25 +48,14 @@ const initializeEventListeners = ({ eventListen: eventBus.listen, saveActivity }) + const reportAccessRequestActivity = reportAccessRequestActivityFactory({ + eventListen: eventBus.listen, + saveActivity + }) + const quitCbs = [ reportUserActivity(), - eventBus.listen(AccessRequestEvents.Created, async (payload) => { - if (!isStreamAccessRequest(payload.payload.request)) return - return await onServerAccessRequestCreatedFactory({ - addStreamAccessRequestedActivity: addStreamAccessRequestedActivityFactory({ - saveActivity: saveActivityFactory({ db }) - }) - })(payload) - }), - eventBus.listen(AccessRequestEvents.Finalized, async (payload) => { - if (!isStreamAccessRequest(payload.payload.request)) return - await onServerAccessRequestFinalizedFactory({ - addStreamAccessRequestDeclinedActivity: - addStreamAccessRequestDeclinedActivityFactory({ - saveActivity: saveActivityFactory({ db }) - }) - })(payload) - }), + reportAccessRequestActivity(), eventBus.listen(ServerInvitesEvents.Created, async ({ payload }) => { if (!isProjectResourceTarget(payload.invite.resource)) return await onServerInviteCreatedFactory({ diff --git a/packages/server/modules/activitystream/services/accessRequestActivity.ts b/packages/server/modules/activitystream/services/accessRequestActivity.ts deleted file mode 100644 index 3963d7c9a..000000000 --- a/packages/server/modules/activitystream/services/accessRequestActivity.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - AddStreamAccessRequestedActivity, - SaveActivity -} from '@/modules/activitystream/domain/operations' -import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' - -/** - * Save a "stream access requested" activity - */ -export const addStreamAccessRequestedActivityFactory = - ({ - saveActivity - }: { - saveActivity: SaveActivity - }): AddStreamAccessRequestedActivity => - async (params: { streamId: string; requesterId: string }) => { - const { streamId, requesterId } = params - await saveActivity({ - streamId, - resourceType: ResourceTypes.Stream, - resourceId: streamId, - userId: requesterId, - actionType: ActionTypes.Stream.AccessRequestSent, - message: `User ${requesterId} has requested access to stream ${streamId}`, - info: { requesterId } - }) - } - -/** - * Save a "stream acccess request declined/denied" activity - */ -export const addStreamAccessRequestDeclinedActivityFactory = - ({ saveActivity }: { saveActivity: SaveActivity }) => - async (params: { streamId: string; requesterId: string; declinerId: string }) => { - const { streamId, requesterId, declinerId } = params - await saveActivity({ - streamId, - resourceType: ResourceTypes.Stream, - resourceId: streamId, - userId: declinerId, - actionType: ActionTypes.Stream.AccessRequestDeclined, - message: `User ${declinerId} declined access to stream ${streamId} for user ${requesterId}`, - info: { requesterId, declinerId } - }) - } diff --git a/packages/server/modules/activitystream/services/eventListener.ts b/packages/server/modules/activitystream/services/eventListener.ts index 3dd878b91..fa7595b1b 100644 --- a/packages/server/modules/activitystream/services/eventListener.ts +++ b/packages/server/modules/activitystream/services/eventListener.ts @@ -1,14 +1,5 @@ import { Logger } from '@/logging/logging' -import { AccessRequestEvents } from '@/modules/accessrequests/domain/events' -import { - AccessRequestType, - isStreamAccessRequest -} from '@/modules/accessrequests/repositories' -import { - AddStreamAccessRequestDeclinedActivity, - AddStreamAccessRequestedActivity, - AddStreamInviteSentOutActivity -} from '@/modules/activitystream/domain/operations' +import { AddStreamInviteSentOutActivity } from '@/modules/activitystream/domain/operations' import { GetStream } from '@/modules/core/domain/streams/operations' import { ServerInvitesEvents, @@ -18,52 +9,6 @@ import { isProjectResourceTarget, resolveTarget } from '@/modules/serverinvites/helpers/core' -import { EventPayload } from '@/modules/shared/services/eventBus' - -export const onServerAccessRequestCreatedFactory = - ({ - addStreamAccessRequestedActivity - }: { - addStreamAccessRequestedActivity: AddStreamAccessRequestedActivity - }) => - async (payload: EventPayload) => { - const { - request: { resourceId, requesterId } - } = payload.payload - if (!isStreamAccessRequest(payload.payload.request)) return - if (!resourceId) return - - await addStreamAccessRequestedActivity({ - streamId: resourceId, - requesterId - }) - } - -export const onServerAccessRequestFinalizedFactory = - ({ - addStreamAccessRequestDeclinedActivity - }: { - addStreamAccessRequestDeclinedActivity: AddStreamAccessRequestDeclinedActivity - }) => - async (payload: EventPayload) => { - const { - approved, - finalizedBy, - request: { resourceId, resourceType, requesterId } - } = payload.payload - if (!resourceId) return - - if (resourceType === AccessRequestType.Stream) { - // If user was added to stream, an activity stream item was already added from 'addOrUpdateStreamCollaborator' - if (approved) return - - await addStreamAccessRequestDeclinedActivity({ - streamId: resourceId, - requesterId, - declinerId: finalizedBy - }) - } - } export const onServerInviteCreatedFactory = ({ From 8ff0ae91ca77406b2c50e5b37f1af98d8c128c35 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Thu, 23 Jan 2025 12:06:54 +0200 Subject: [PATCH 02/78] chore(server): refactor activityStream invocations - batch #3 - branches --- .../activitystream/events/branchListeners.ts | 97 +++++++++++++ .../server/modules/activitystream/index.ts | 6 + .../activitystream/services/branchActivity.ts | 128 ------------------ .../modules/cli/commands/download/project.ts | 7 +- .../modules/core/domain/branches/events.ts | 25 +++- .../modules/core/graph/resolvers/branches.ts | 24 +--- .../modules/core/graph/resolvers/models.ts | 23 +--- .../core/services/branch/management.ts | 93 ++++++++++--- .../modules/core/tests/branches.spec.ts | 15 +- .../server/modules/core/tests/commits.spec.ts | 7 +- .../core/tests/integration/subs.graph.spec.ts | 15 +- .../server/modules/core/tests/streams.spec.ts | 6 +- .../server/modules/cross-server-sync/index.ts | 7 +- packages/server/modules/fileuploads/index.ts | 11 +- .../fileuploads/services/resultListener.ts | 30 +++- .../test/speckle-helpers/branchHelper.ts | 10 +- 16 files changed, 256 insertions(+), 248 deletions(-) create mode 100644 packages/server/modules/activitystream/events/branchListeners.ts delete mode 100644 packages/server/modules/activitystream/services/branchActivity.ts diff --git a/packages/server/modules/activitystream/events/branchListeners.ts b/packages/server/modules/activitystream/events/branchListeners.ts new file mode 100644 index 000000000..193e9d736 --- /dev/null +++ b/packages/server/modules/activitystream/events/branchListeners.ts @@ -0,0 +1,97 @@ +import { + AddBranchCreatedActivity, + AddBranchDeletedActivity, + AddBranchUpdatedActivity, + SaveActivity +} from '@/modules/activitystream/domain/operations' +import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' +import { ModelEvents } from '@/modules/core/domain/branches/events' +import { isBranchDeleteInput, isBranchUpdateInput } from '@/modules/core/helpers/branch' +import { EventBusListen } from '@/modules/shared/services/eventBus' + +/** + * Save "branch created" activity + */ +const addBranchCreatedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddBranchCreatedActivity => + async (params) => { + const { branch } = params + + await saveActivity({ + streamId: branch.streamId, + resourceType: ResourceTypes.Branch, + resourceId: branch.id, + actionType: ActionTypes.Branch.Create, + userId: branch.authorId, + info: { branch }, + message: `Branch created: ${branch.name} (${branch.id})` + }) + } + +const addBranchUpdatedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddBranchUpdatedActivity => + async (params) => { + const { update, userId, oldBranch } = params + + const streamId = isBranchUpdateInput(update) ? update.streamId : update.projectId + await saveActivity({ + streamId, + resourceType: ResourceTypes.Branch, + resourceId: update.id, + actionType: ActionTypes.Branch.Update, + userId, + info: { old: oldBranch, new: update }, + message: `Branch metadata changed for branch ${update.id}` + }) + } + +const addBranchDeletedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddBranchDeletedActivity => + async (params) => { + const { input, userId, branchName } = params + + const streamId = isBranchDeleteInput(input) ? input.streamId : input.projectId + await Promise.all([ + saveActivity({ + streamId, + resourceType: ResourceTypes.Branch, + resourceId: input.id, + actionType: ActionTypes.Branch.Delete, + userId, + info: { branch: { ...input, name: branchName } }, + message: `Branch deleted: '${branchName}' (${input.id})` + }) + ]) + } + +export const reportBranchActivityFactory = + (deps: { eventListen: EventBusListen; saveActivity: SaveActivity }) => () => { + const addBranchCreatedActivity = addBranchCreatedActivityFactory(deps) + const addBranchUpdatedActivity = addBranchUpdatedActivityFactory(deps) + const addBranchDeletedActivity = addBranchDeletedActivityFactory(deps) + + const quitters = [ + deps.eventListen(ModelEvents.Created, async (payload) => { + await addBranchCreatedActivity({ branch: payload.payload.model }) + }), + deps.eventListen(ModelEvents.Updated, async ({ payload }) => { + await addBranchUpdatedActivity({ + update: payload.update, + userId: payload.userId, + oldBranch: payload.oldModel, + newBranch: payload.newModel + }) + }), + deps.eventListen(ModelEvents.Deleted, async ({ payload }) => { + await addBranchDeletedActivity({ + userId: payload.userId, + input: payload.input, + branchName: payload.model.name + }) + }) + ] + + return () => { + quitters.forEach((quit) => quit()) + } + } diff --git a/packages/server/modules/activitystream/index.ts b/packages/server/modules/activitystream/index.ts index 820d99d42..7d8cc049c 100644 --- a/packages/server/modules/activitystream/index.ts +++ b/packages/server/modules/activitystream/index.ts @@ -28,6 +28,7 @@ import { ServerInvitesEvents } from '@/modules/serverinvites/domain/events' import { ProjectEvents } from '@/modules/core/domain/projects/events' import { reportUserActivityFactory } from '@/modules/activitystream/events/userListeners' import { reportAccessRequestActivityFactory } from '@/modules/activitystream/events/accessRequestListeners' +import { reportBranchActivityFactory } from '@/modules/activitystream/events/branchListeners' let scheduledTask: ReturnType | null = null let quitEventListeners: Optional<() => void> = undefined @@ -52,10 +53,15 @@ const initializeEventListeners = ({ eventListen: eventBus.listen, saveActivity }) + const reportBranchActivity = reportBranchActivityFactory({ + eventListen: eventBus.listen, + saveActivity + }) const quitCbs = [ reportUserActivity(), reportAccessRequestActivity(), + reportBranchActivity(), eventBus.listen(ServerInvitesEvents.Created, async ({ payload }) => { if (!isProjectResourceTarget(payload.invite.resource)) return await onServerInviteCreatedFactory({ diff --git a/packages/server/modules/activitystream/services/branchActivity.ts b/packages/server/modules/activitystream/services/branchActivity.ts deleted file mode 100644 index 22ef8c0a2..000000000 --- a/packages/server/modules/activitystream/services/branchActivity.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' -import { - BranchSubscriptions as BranchPubsubEvents, - PublishSubscription -} from '@/modules/shared/utils/subscriptions' -import { ProjectModelsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' -import { ProjectSubscriptions } from '@/modules/shared/utils/subscriptions' -import { isBranchDeleteInput, isBranchUpdateInput } from '@/modules/core/helpers/branch' -import { - AddBranchCreatedActivity, - AddBranchDeletedActivity, - AddBranchUpdatedActivity, - SaveActivity -} from '@/modules/activitystream/domain/operations' - -/** - * Save "branch created" activity - */ -export const addBranchCreatedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }): AddBranchCreatedActivity => - async (params) => { - const { branch } = params - - await Promise.all([ - saveActivity({ - streamId: branch.streamId, - resourceType: ResourceTypes.Branch, - resourceId: branch.id, - actionType: ActionTypes.Branch.Create, - userId: branch.authorId, - info: { branch }, - message: `Branch created: ${branch.name} (${branch.id})` - }), - publish(BranchPubsubEvents.BranchCreated, { - branchCreated: { ...branch }, - streamId: branch.streamId - }), - publish(ProjectSubscriptions.ProjectModelsUpdated, { - projectId: branch.streamId, - projectModelsUpdated: { - id: branch.id, - type: ProjectModelsUpdatedMessageType.Created, - model: branch - } - }) - ]) - } - -export const addBranchUpdatedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }): AddBranchUpdatedActivity => - async (params) => { - const { update, userId, oldBranch, newBranch } = params - - const streamId = isBranchUpdateInput(update) ? update.streamId : update.projectId - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Branch, - resourceId: update.id, - actionType: ActionTypes.Branch.Update, - userId, - info: { old: oldBranch, new: update }, - message: `Branch metadata changed for branch ${update.id}` - }), - publish(BranchPubsubEvents.BranchUpdated, { - branchUpdated: { ...update }, - streamId, - branchId: update.id - }), - publish(ProjectSubscriptions.ProjectModelsUpdated, { - projectId: streamId, - projectModelsUpdated: { - model: newBranch, - id: newBranch.id, - type: ProjectModelsUpdatedMessageType.Updated - } - }) - ]) - } - -export const addBranchDeletedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }): AddBranchDeletedActivity => - async (params) => { - const { input, userId, branchName } = params - - const streamId = isBranchDeleteInput(input) ? input.streamId : input.projectId - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Branch, - resourceId: input.id, - actionType: ActionTypes.Branch.Delete, - userId, - info: { branch: { ...input, name: branchName } }, - message: `Branch deleted: '${branchName}' (${input.id})` - }), - publish(BranchPubsubEvents.BranchDeleted, { - branchDeleted: input, - streamId - }), - publish(ProjectSubscriptions.ProjectModelsUpdated, { - projectId: streamId, - projectModelsUpdated: { - id: input.id, - type: ProjectModelsUpdatedMessageType.Deleted, - model: null - } - }) - ]) - } diff --git a/packages/server/modules/cli/commands/download/project.ts b/packages/server/modules/cli/commands/download/project.ts index 8789100a2..ae6fd5d74 100644 --- a/packages/server/modules/cli/commands/download/project.ts +++ b/packages/server/modules/cli/commands/download/project.ts @@ -62,7 +62,6 @@ import { publish } from '@/modules/shared/utils/subscriptions' import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { getUserFactory } from '@/modules/core/repositories/users' import { createObjectFactory } from '@/modules/core/services/objects/management' -import { addBranchCreatedActivityFactory } from '@/modules/activitystream/services/branchActivity' import { authorizeResolver } from '@/modules/shared' import { Roles } from '@speckle/shared' import { getDefaultRegionFactory } from '@/modules/workspaces/repositories/regions' @@ -247,10 +246,8 @@ const command: CommandModule< createBranchAndNotify: createBranchAndNotifyFactory({ getStreamBranchByName, createBranch: createBranchFactory({ db: projectDb }), - addBranchCreatedActivity: addBranchCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) }) await downloadProject({ ...argv, regionKey }, { logger: cliLogger }) diff --git a/packages/server/modules/core/domain/branches/events.ts b/packages/server/modules/core/domain/branches/events.ts index a76bc7cda..a1880d32e 100644 --- a/packages/server/modules/core/domain/branches/events.ts +++ b/packages/server/modules/core/domain/branches/events.ts @@ -1,11 +1,32 @@ import { Model } from '@/modules/core/domain/branches/types' +import { + BranchDeleteInput, + BranchUpdateInput, + DeleteModelInput, + UpdateModelInput +} from '@/modules/core/graph/generated/graphql' export const modelEventsNamespace = 'models' as const export const ModelEvents = { - Deleted: `${modelEventsNamespace}.deleted` + Deleted: `${modelEventsNamespace}.deleted`, + Created: `${modelEventsNamespace}.created`, + Updated: `${modelEventsNamespace}.updated` } as const export type ModelEventsPayloads = { - [ModelEvents.Deleted]: { projectId: string; modelId: string; model: Model } + [ModelEvents.Deleted]: { + projectId: string + modelId: string + model: Model + userId: string + input: BranchDeleteInput | DeleteModelInput + } + [ModelEvents.Created]: { projectId: string; model: Model } + [ModelEvents.Updated]: { + update: BranchUpdateInput | UpdateModelInput + userId: string + oldModel: Model + newModel: Model + } } diff --git a/packages/server/modules/core/graph/resolvers/branches.ts b/packages/server/modules/core/graph/resolvers/branches.ts index 1da138c84..770830726 100644 --- a/packages/server/modules/core/graph/resolvers/branches.ts +++ b/packages/server/modules/core/graph/resolvers/branches.ts @@ -4,7 +4,6 @@ import { updateBranchAndNotifyFactory, deleteBranchAndNotifyFactory } from '@/modules/core/services/branch/management' - import { Roles } from '@speckle/shared' import { getBranchByIdFactory, @@ -16,11 +15,6 @@ import { getStreamBranchCountFactory } from '@/modules/core/repositories/branches' import { db } from '@/db/knex' -import { - addBranchCreatedActivityFactory, - addBranchDeletedActivityFactory, - addBranchUpdatedActivityFactory -} from '@/modules/activitystream/services/branchActivity' import { getStreamFactory, markBranchStreamUpdatedFactory @@ -28,7 +22,6 @@ import { import { legacyGetUserFactory } from '@/modules/core/repositories/users' import { Resolvers } from '@/modules/core/graph/generated/graphql' import { getPaginatedStreamBranchesFactory } from '@/modules/core/services/branch/retrieval' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { filteredSubscribe, publish } from '@/modules/shared/utils/subscriptions' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import { getEventBus } from '@/modules/shared/services/eventBus' @@ -89,10 +82,8 @@ export = { const createBranchAndNotify = createBranchAndNotifyFactory({ getStreamBranchByName, createBranch: createBranchFactory({ db: projectDB }), - addBranchCreatedActivity: addBranchCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) const { id } = await createBranchAndNotify(args.branch, context.userId!) @@ -112,10 +103,8 @@ export = { const updateBranchAndNotify = updateBranchAndNotifyFactory({ getBranchById, updateBranch: updateBranchFactory({ db: projectDB }), - addBranchUpdatedActivity: addBranchUpdatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) const newBranch = await updateBranchAndNotify(args.branch, context.userId!) return !!newBranch @@ -137,10 +126,7 @@ export = { getBranchById: getBranchByIdFactory({ db: projectDB }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - addBranchDeletedActivity: addBranchDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db: projectDB }) }) const deleted = await deleteBranchAndNotify(args.branch, context.userId!) diff --git a/packages/server/modules/core/graph/resolvers/models.ts b/packages/server/modules/core/graph/resolvers/models.ts index 0b5081553..84bb4edca 100644 --- a/packages/server/modules/core/graph/resolvers/models.ts +++ b/packages/server/modules/core/graph/resolvers/models.ts @@ -50,16 +50,10 @@ import { legacyGetPaginatedStreamCommitsPageFactory } from '@/modules/core/repositories/commits' import { db } from '@/db/knex' -import { - addBranchCreatedActivityFactory, - addBranchDeletedActivityFactory, - addBranchUpdatedActivityFactory -} from '@/modules/activitystream/services/branchActivity' import { getStreamFactory, markBranchStreamUpdatedFactory } from '@/modules/core/repositories/streams' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { getProjectDbClient, getRegisteredRegionClients @@ -312,10 +306,8 @@ export = { const createBranchAndNotify = createBranchAndNotifyFactory({ getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDB }), createBranch: createBranchFactory({ db: projectDB }), - addBranchCreatedActivity: addBranchCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) return await createBranchAndNotify(args.input, ctx.userId!) }, @@ -330,10 +322,8 @@ export = { const updateBranchAndNotify = updateBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db: projectDB }), updateBranch: updateBranchFactory({ db: projectDB }), - addBranchUpdatedActivity: addBranchUpdatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) return await updateBranchAndNotify(args.input, ctx.userId!) }, @@ -352,10 +342,7 @@ export = { getBranchById: getBranchByIdFactory({ db: projectDB }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - addBranchDeletedActivity: addBranchDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db: projectDB }) }) return await deleteBranchAndNotify(args.input, ctx.userId!) diff --git a/packages/server/modules/core/services/branch/management.ts b/packages/server/modules/core/services/branch/management.ts index 3b7aa7641..1e8c4a7c8 100644 --- a/packages/server/modules/core/services/branch/management.ts +++ b/packages/server/modules/core/services/branch/management.ts @@ -10,6 +10,7 @@ import { BranchUpdateInput, CreateModelInput, DeleteModelInput, + ProjectModelsUpdatedMessageType, UpdateModelInput } from '@/modules/core/graph/generated/graphql' import { BranchRecord } from '@/modules/core/helpers/types' @@ -29,13 +30,13 @@ import { GetStream, MarkBranchStreamUpdated } from '@/modules/core/domain/streams/operations' -import { - AddBranchCreatedActivity, - AddBranchDeletedActivity, - AddBranchUpdatedActivity -} from '@/modules/activitystream/domain/operations' import { EventBusEmit } from '@/modules/shared/services/eventBus' import { ModelEvents } from '@/modules/core/domain/branches/events' +import { + ProjectSubscriptions, + PublishSubscription +} from '@/modules/shared/utils/subscriptions' +import { BranchPubsubEvents } from '@/modules/shared' const isBranchCreateInput = ( i: BranchCreateInput | CreateModelInput @@ -45,7 +46,8 @@ export const createBranchAndNotifyFactory = (deps: { getStreamBranchByName: GetStreamBranchByName createBranch: StoreBranch - addBranchCreatedActivity: AddBranchCreatedActivity + eventEmit: EventBusEmit + publishSub: PublishSubscription }): CreateBranchAndNotify => async (input: BranchCreateInput | CreateModelInput, creatorId: string) => { const streamId = isBranchCreateInput(input) ? input.streamId : input.projectId @@ -60,7 +62,26 @@ export const createBranchAndNotifyFactory = streamId: isBranchCreateInput(input) ? input.streamId : input.projectId, authorId: creatorId }) - await deps.addBranchCreatedActivity({ branch }) + + await Promise.all([ + deps.eventEmit({ + eventName: ModelEvents.Created, + payload: { model: branch, projectId: branch.streamId } + }), + // TODO: Move to event bus listeners + deps.publishSub(BranchPubsubEvents.BranchCreated, { + branchCreated: { ...branch }, + streamId: branch.streamId + }), + deps.publishSub(ProjectSubscriptions.ProjectModelsUpdated, { + projectId: branch.streamId, + projectModelsUpdated: { + id: branch.id, + type: ProjectModelsUpdatedMessageType.Created, + model: branch + } + }) + ]) return branch } @@ -69,7 +90,8 @@ export const updateBranchAndNotifyFactory = (deps: { getBranchById: GetBranchById updateBranch: UpdateBranch - addBranchUpdatedActivity: AddBranchUpdatedActivity + publishSub: PublishSubscription + eventEmit: EventBusEmit }): UpdateBranchAndNotify => async (input: BranchUpdateInput | UpdateModelInput, userId: string) => { const streamId = isBranchUpdateInput(input) ? input.streamId : input.projectId @@ -113,12 +135,31 @@ export const updateBranchAndNotifyFactory = } if (newBranch) { - await deps.addBranchUpdatedActivity({ - update: input, - userId, - oldBranch: existingBranch, - newBranch - }) + await Promise.all([ + deps.eventEmit({ + eventName: ModelEvents.Updated, + payload: { + update: input, + userId, + oldModel: existingBranch, + newModel: newBranch + } + }), + // TODO: Move to event bus listeners + deps.publishSub(BranchPubsubEvents.BranchUpdated, { + branchUpdated: { ...input }, + streamId, + branchId: input.id + }), + deps.publishSub(ProjectSubscriptions.ProjectModelsUpdated, { + projectId: streamId, + projectModelsUpdated: { + model: newBranch, + id: newBranch.id, + type: ProjectModelsUpdatedMessageType.Updated + } + }) + ]) } return newBranch @@ -130,8 +171,8 @@ export const deleteBranchAndNotifyFactory = getBranchById: GetBranchById emitEvent: EventBusEmit markBranchStreamUpdated: MarkBranchStreamUpdated - addBranchDeletedActivity: AddBranchDeletedActivity deleteBranchById: DeleteBranchById + publishSub: PublishSubscription }): DeleteBranchAndNotify => async (input: BranchDeleteInput | DeleteModelInput, userId: string) => { const streamId = isBranchDeleteInput(input) ? input.streamId : input.projectId @@ -167,18 +208,28 @@ export const deleteBranchAndNotifyFactory = const isDeleted = !!(await deps.deleteBranchById(existingBranch.id)) if (isDeleted) { await Promise.all([ - deps.addBranchDeletedActivity({ - input, - userId, - branchName: existingBranch.name - }), deps.markBranchStreamUpdated(input.id), deps.emitEvent({ eventName: ModelEvents.Deleted, payload: { modelId: existingBranch.id, model: existingBranch, - projectId: streamId + projectId: streamId, + input, + userId + } + }), + // TODO: Move to event bus listeners + deps.publishSub(BranchPubsubEvents.BranchDeleted, { + branchDeleted: input, + streamId + }), + deps.publishSub(ProjectSubscriptions.ProjectModelsUpdated, { + projectId: streamId, + projectModelsUpdated: { + id: input.id, + type: ProjectModelsUpdatedMessageType.Deleted, + model: null } }) ]) diff --git a/packages/server/modules/core/tests/branches.spec.ts b/packages/server/modules/core/tests/branches.spec.ts index a0fc7e223..f5717d71a 100644 --- a/packages/server/modules/core/tests/branches.spec.ts +++ b/packages/server/modules/core/tests/branches.spec.ts @@ -23,10 +23,6 @@ import { getPaginatedStreamBranchesPageFactory, getStreamBranchCountFactory } from '@/modules/core/repositories/branches' -import { - addBranchUpdatedActivityFactory, - addBranchDeletedActivityFactory -} from '@/modules/activitystream/services/branchActivity' import { getStreamFactory, createStreamFactory, @@ -104,20 +100,15 @@ const createBranch = createBranchFactory({ db: knex }) const updateBranchAndNotify = updateBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db: knex }), updateBranch: updateBranchFactory({ db: knex }), - addBranchUpdatedActivity: addBranchUpdatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) const deleteBranchAndNotify = deleteBranchAndNotifyFactory({ getStream, getBranchById: getBranchByIdFactory({ db: knex }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - addBranchDeletedActivity: addBranchDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db: knex }) }) diff --git a/packages/server/modules/core/tests/commits.spec.ts b/packages/server/modules/core/tests/commits.spec.ts index 5d22b2605..0622be85a 100644 --- a/packages/server/modules/core/tests/commits.spec.ts +++ b/packages/server/modules/core/tests/commits.spec.ts @@ -91,7 +91,6 @@ import { getPaginatedBranchCommitsItemsByNameFactory } from '@/modules/core/services/commit/retrieval' import { createObjectFactory } from '@/modules/core/services/objects/management' -import { addBranchCreatedActivityFactory } from '@/modules/activitystream/services/branchActivity' import { ensureError } from '@speckle/shared' import { VersionEvents } from '@/modules/core/domain/commits/events' @@ -105,10 +104,8 @@ const createBranch = createBranchFactory({ db }) const createBranchAndNotify = createBranchAndNotifyFactory({ createBranch, getStreamBranchByName: getStreamBranchByNameFactory({ db }), - addBranchCreatedActivity: addBranchCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) const getCommit = getCommitFactory({ db }) const deleteCommitAndNotify = deleteCommitAndNotifyFactory({ diff --git a/packages/server/modules/core/tests/integration/subs.graph.spec.ts b/packages/server/modules/core/tests/integration/subs.graph.spec.ts index 2ea983940..83bb1dd0b 100644 --- a/packages/server/modules/core/tests/integration/subs.graph.spec.ts +++ b/packages/server/modules/core/tests/integration/subs.graph.spec.ts @@ -1,10 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { db } from '@/db/knex' import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { - addBranchDeletedActivityFactory, - addBranchUpdatedActivityFactory -} from '@/modules/activitystream/services/branchActivity' import { addCommitDeletedActivityFactory, addCommitUpdatedActivityFactory @@ -159,10 +155,8 @@ const buildUpdateModel = async (params: { projectId: string }) => { const updateBranchAndNotify = updateBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db: projectDB }), updateBranch: updateBranchFactory({ db: projectDB }), - addBranchUpdatedActivity: addBranchUpdatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) return updateBranchAndNotify } @@ -179,10 +173,7 @@ const buildDeleteModel = async (params: { projectId: string }) => { getBranchById: getBranchByIdFactory({ db: projectDB }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - addBranchDeletedActivity: addBranchDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db: projectDB }) }) return deleteBranchAndNotify diff --git a/packages/server/modules/core/tests/streams.spec.ts b/packages/server/modules/core/tests/streams.spec.ts index 69f3588f4..d864cdd4e 100644 --- a/packages/server/modules/core/tests/streams.spec.ts +++ b/packages/server/modules/core/tests/streams.spec.ts @@ -100,7 +100,6 @@ import { import { changeUserRoleFactory } from '@/modules/core/services/users/management' import { getServerInfoFactory } from '@/modules/core/repositories/server' import { createObjectFactory } from '@/modules/core/services/objects/management' -import { addBranchDeletedActivityFactory } from '@/modules/activitystream/services/branchActivity' const getServerInfo = getServerInfoFactory({ db }) const getUser = getUserFactory({ db }) @@ -115,10 +114,7 @@ const deleteBranchAndNotify = deleteBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - addBranchDeletedActivity: addBranchDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db }) }) diff --git a/packages/server/modules/cross-server-sync/index.ts b/packages/server/modules/cross-server-sync/index.ts index 99c428765..9c33a7a51 100644 --- a/packages/server/modules/cross-server-sync/index.ts +++ b/packages/server/modules/cross-server-sync/index.ts @@ -1,7 +1,6 @@ import { db } from '@/db/knex' import { moduleLogger, crossServerSyncLogger } from '@/logging/logging' import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { addBranchCreatedActivityFactory } from '@/modules/activitystream/services/branchActivity' import { addCommentCreatedActivityFactory, addReplyAddedActivityFactory @@ -196,10 +195,8 @@ const crossServerSyncModule: SpeckleModule = { createBranchAndNotify: createBranchAndNotifyFactory({ createBranch: createBranchFactory({ db }), getStreamBranchByName, - addBranchCreatedActivity: addBranchCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) }), markOnboardingBaseStream diff --git a/packages/server/modules/fileuploads/index.ts b/packages/server/modules/fileuploads/index.ts index 619f2cc2f..0966ec919 100644 --- a/packages/server/modules/fileuploads/index.ts +++ b/packages/server/modules/fileuploads/index.ts @@ -19,11 +19,10 @@ import { streamWritePermissionsPipelineFactory } from '@/modules/shared/authz' import { getRolesFactory } from '@/modules/shared/repositories/roles' import { getStreamBranchByNameFactory } from '@/modules/core/repositories/branches' import { getStreamFactory } from '@/modules/core/repositories/streams' -import { addBranchCreatedActivityFactory } from '@/modules/activitystream/services/branchActivity' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { getPort } from '@/modules/shared/helpers/envHelper' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import { listenFor } from '@/modules/core/utils/dbNotificationListener' +import { getEventBus } from '@/modules/shared/services/eventBus' export const init: SpeckleModule['init'] = async (app, isInitial) => { if (process.env.DISABLE_FILE_UPLOADS) { @@ -120,6 +119,9 @@ export const init: SpeckleModule['init'] = async (app, isInitial) => { } ) + // FIXME: Fix the type issue + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error req.pipe(pipedReq) } ) @@ -133,10 +135,7 @@ export const init: SpeckleModule['init'] = async (app, isInitial) => { getFileInfo: getFileInfoFactory({ db: projectDb }), publish, getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDb }), - addBranchCreatedActivity: addBranchCreatedActivityFactory({ - publish, - saveActivity: saveActivityFactory({ db }) - }) + eventEmit: getEventBus().emit })(parsedMessage) }) listenFor('file_import_started', async (msg) => { diff --git a/packages/server/modules/fileuploads/services/resultListener.ts b/packages/server/modules/fileuploads/services/resultListener.ts index 8dfbad4a8..5e859efde 100644 --- a/packages/server/modules/fileuploads/services/resultListener.ts +++ b/packages/server/modules/fileuploads/services/resultListener.ts @@ -1,22 +1,26 @@ import { FileImportSubscriptions, + ProjectSubscriptions, publish, type PublishSubscription } from '@/modules/shared/utils/subscriptions' import { ProjectFileImportUpdatedMessageType, + ProjectModelsUpdatedMessageType, ProjectPendingModelsUpdatedMessageType, ProjectPendingVersionsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' import { GetFileInfo } from '@/modules/fileuploads/domain/operations' import { GetStreamBranchByName } from '@/modules/core/domain/branches/operations' -import { AddBranchCreatedActivity } from '@/modules/activitystream/domain/operations' +import { EventBusEmit } from '@/modules/shared/services/eventBus' +import { ModelEvents } from '@/modules/core/domain/branches/events' +import { BranchPubsubEvents } from '@/modules/shared' type OnFileImportProcessedDeps = { getFileInfo: GetFileInfo getStreamBranchByName: GetStreamBranchByName publish: PublishSubscription - addBranchCreatedActivity: AddBranchCreatedActivity + eventEmit: EventBusEmit } type ParsedMessage = { @@ -55,7 +59,27 @@ export const onFileImportProcessedFactory = projectId: upload.streamId }) - if (branch) await deps.addBranchCreatedActivity({ branch }) + if (branch) { + await Promise.all([ + deps.eventEmit({ + eventName: ModelEvents.Created, + payload: { model: branch, projectId: branch.streamId } + }), + // TODO: Move to event bus listeners + deps.publish(BranchPubsubEvents.BranchCreated, { + branchCreated: { ...branch }, + streamId: branch.streamId + }), + deps.publish(ProjectSubscriptions.ProjectModelsUpdated, { + projectId: branch.streamId, + projectModelsUpdated: { + id: branch.id, + type: ProjectModelsUpdatedMessageType.Created, + model: branch + } + }) + ]) + } } else { await deps.publish(FileImportSubscriptions.ProjectPendingVersionsUpdated, { projectPendingVersionsUpdated: { diff --git a/packages/server/test/speckle-helpers/branchHelper.ts b/packages/server/test/speckle-helpers/branchHelper.ts index f21f7b9be..5377f1901 100644 --- a/packages/server/test/speckle-helpers/branchHelper.ts +++ b/packages/server/test/speckle-helpers/branchHelper.ts @@ -1,12 +1,10 @@ -import { db } from '@/db/knex' -import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { addBranchCreatedActivityFactory } from '@/modules/activitystream/services/branchActivity' import { createBranchFactory, getStreamBranchByNameFactory } from '@/modules/core/repositories/branches' import { createBranchAndNotifyFactory } from '@/modules/core/services/branch/management' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' +import { getEventBus } from '@/modules/shared/services/eventBus' import { publish } from '@/modules/shared/utils/subscriptions' import { BasicTestUser } from '@/test/authHelper' import { BasicTestStream } from '@/test/speckle-helpers/streamHelper' @@ -44,10 +42,8 @@ export async function createTestBranch(params: { const createBranchAndNotify = createBranchAndNotifyFactory({ getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDb }), createBranch: createBranchFactory({ db: projectDb }), - addBranchCreatedActivity: addBranchCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + eventEmit: getEventBus().emit }) const id = ( From a8c9f1edf1401e90e9bce50c5ac1c33d87d5541e Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Thu, 23 Jan 2025 14:13:58 +0200 Subject: [PATCH 03/78] undo ts check --- packages/server/modules/fileuploads/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/server/modules/fileuploads/index.ts b/packages/server/modules/fileuploads/index.ts index 0966ec919..fed58356d 100644 --- a/packages/server/modules/fileuploads/index.ts +++ b/packages/server/modules/fileuploads/index.ts @@ -119,9 +119,6 @@ export const init: SpeckleModule['init'] = async (app, isInitial) => { } ) - // FIXME: Fix the type issue - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error req.pipe(pipedReq) } ) From d9bdc545992bdf1fbda668ab83776a6b55e2148b Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Fri, 24 Jan 2025 16:49:55 +0200 Subject: [PATCH 04/78] chore(server): moving out branch sub reporting to separate listeners --- .../modules/cli/commands/download/project.ts | 1 - .../core/events/subscriptionListeners.ts | 87 +++++++++++++++++++ .../modules/core/graph/resolvers/branches.ts | 5 +- .../modules/core/graph/resolvers/models.ts | 6 +- packages/server/modules/core/index.ts | 9 ++ .../core/services/branch/management.ts | 79 +++-------------- .../modules/core/tests/branches.spec.ts | 2 - .../server/modules/core/tests/commits.spec.ts | 1 - .../core/tests/integration/subs.graph.spec.ts | 2 - .../server/modules/core/tests/streams.spec.ts | 1 - .../server/modules/cross-server-sync/index.ts | 1 - .../fileuploads/services/resultListener.ts | 26 +----- .../server/modules/shared/helpers/factory.ts | 8 ++ .../test/speckle-helpers/branchHelper.ts | 2 - 14 files changed, 123 insertions(+), 107 deletions(-) create mode 100644 packages/server/modules/core/events/subscriptionListeners.ts create mode 100644 packages/server/modules/shared/helpers/factory.ts diff --git a/packages/server/modules/cli/commands/download/project.ts b/packages/server/modules/cli/commands/download/project.ts index ae6fd5d74..b614137ef 100644 --- a/packages/server/modules/cli/commands/download/project.ts +++ b/packages/server/modules/cli/commands/download/project.ts @@ -246,7 +246,6 @@ const command: CommandModule< createBranchAndNotify: createBranchAndNotifyFactory({ getStreamBranchByName, createBranch: createBranchFactory({ db: projectDb }), - publishSub: publish, eventEmit: getEventBus().emit }) }) diff --git a/packages/server/modules/core/events/subscriptionListeners.ts b/packages/server/modules/core/events/subscriptionListeners.ts new file mode 100644 index 000000000..6c693b11e --- /dev/null +++ b/packages/server/modules/core/events/subscriptionListeners.ts @@ -0,0 +1,87 @@ +import { ModelEvents } from '@/modules/core/domain/branches/events' +import { ProjectModelsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' +import { BranchPubsubEvents } from '@/modules/shared' +import { EventBusListen, EventPayload } from '@/modules/shared/services/eventBus' +import { + ProjectSubscriptions, + PublishSubscription +} from '@/modules/shared/utils/subscriptions' + +const reportModelCreatedFactory = + (deps: { publish: PublishSubscription }) => + async (payload: EventPayload) => { + const { model } = payload.payload + + await Promise.all([ + deps.publish(BranchPubsubEvents.BranchCreated, { + branchCreated: { ...model }, + streamId: model.streamId + }), + deps.publish(ProjectSubscriptions.ProjectModelsUpdated, { + projectId: model.streamId, + projectModelsUpdated: { + id: model.id, + type: ProjectModelsUpdatedMessageType.Created, + model + } + }) + ]) + } + +const reportModelUpdatedFactory = + (deps: { publish: PublishSubscription }) => + async (payload: EventPayload) => { + const { newModel, update } = payload.payload + + await Promise.all([ + deps.publish(BranchPubsubEvents.BranchUpdated, { + branchUpdated: { ...update }, + streamId: newModel.streamId, + branchId: newModel.id + }), + deps.publish(ProjectSubscriptions.ProjectModelsUpdated, { + projectId: newModel.streamId, + projectModelsUpdated: { + model: newModel, + id: newModel.id, + type: ProjectModelsUpdatedMessageType.Updated + } + }) + ]) + } + +const reportModelDeletedFactory = + (deps: { publish: PublishSubscription }) => + async (payload: EventPayload) => { + const { input, projectId } = payload.payload + + await Promise.all([ + deps.publish(BranchPubsubEvents.BranchDeleted, { + branchDeleted: input, + streamId: projectId + }), + deps.publish(ProjectSubscriptions.ProjectModelsUpdated, { + projectId, + projectModelsUpdated: { + id: input.id, + type: ProjectModelsUpdatedMessageType.Deleted, + model: null + } + }) + ]) + } + +export const reportSubscriptionEventsFactory = + (deps: { eventListen: EventBusListen; publish: PublishSubscription }) => () => { + const reportModelCreated = reportModelCreatedFactory(deps) + const reportModelUpdated = reportModelUpdatedFactory(deps) + const reportModelDeleted = reportModelDeletedFactory(deps) + + const quitCbs = [ + deps.eventListen(ModelEvents.Created, reportModelCreated), + deps.eventListen(ModelEvents.Updated, reportModelUpdated), + deps.eventListen(ModelEvents.Deleted, reportModelDeleted) + ] + + return () => quitCbs.forEach((quit) => quit()) + } diff --git a/packages/server/modules/core/graph/resolvers/branches.ts b/packages/server/modules/core/graph/resolvers/branches.ts index 770830726..509c39566 100644 --- a/packages/server/modules/core/graph/resolvers/branches.ts +++ b/packages/server/modules/core/graph/resolvers/branches.ts @@ -22,7 +22,7 @@ import { import { legacyGetUserFactory } from '@/modules/core/repositories/users' import { Resolvers } from '@/modules/core/graph/generated/graphql' import { getPaginatedStreamBranchesFactory } from '@/modules/core/services/branch/retrieval' -import { filteredSubscribe, publish } from '@/modules/shared/utils/subscriptions' +import { filteredSubscribe } from '@/modules/shared/utils/subscriptions' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import { getEventBus } from '@/modules/shared/services/eventBus' @@ -82,7 +82,6 @@ export = { const createBranchAndNotify = createBranchAndNotifyFactory({ getStreamBranchByName, createBranch: createBranchFactory({ db: projectDB }), - publishSub: publish, eventEmit: getEventBus().emit }) const { id } = await createBranchAndNotify(args.branch, context.userId!) @@ -103,7 +102,6 @@ export = { const updateBranchAndNotify = updateBranchAndNotifyFactory({ getBranchById, updateBranch: updateBranchFactory({ db: projectDB }), - publishSub: publish, eventEmit: getEventBus().emit }) const newBranch = await updateBranchAndNotify(args.branch, context.userId!) @@ -126,7 +124,6 @@ export = { getBranchById: getBranchByIdFactory({ db: projectDB }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db: projectDB }) }) const deleted = await deleteBranchAndNotify(args.branch, context.userId!) diff --git a/packages/server/modules/core/graph/resolvers/models.ts b/packages/server/modules/core/graph/resolvers/models.ts index 84bb4edca..20e829db5 100644 --- a/packages/server/modules/core/graph/resolvers/models.ts +++ b/packages/server/modules/core/graph/resolvers/models.ts @@ -20,8 +20,7 @@ import { } from '@/modules/core/services/commit/retrieval' import { filteredSubscribe, - ProjectSubscriptions, - publish + ProjectSubscriptions } from '@/modules/shared/utils/subscriptions' import { createBranchFactory, @@ -306,7 +305,6 @@ export = { const createBranchAndNotify = createBranchAndNotifyFactory({ getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDB }), createBranch: createBranchFactory({ db: projectDB }), - publishSub: publish, eventEmit: getEventBus().emit }) return await createBranchAndNotify(args.input, ctx.userId!) @@ -322,7 +320,6 @@ export = { const updateBranchAndNotify = updateBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db: projectDB }), updateBranch: updateBranchFactory({ db: projectDB }), - publishSub: publish, eventEmit: getEventBus().emit }) return await updateBranchAndNotify(args.input, ctx.userId!) @@ -342,7 +339,6 @@ export = { getBranchById: getBranchByIdFactory({ db: projectDB }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db: projectDB }) }) return await deleteBranchAndNotify(args.input, ctx.userId!) diff --git a/packages/server/modules/core/index.ts b/packages/server/modules/core/index.ts index e45cc5569..700b25009 100644 --- a/packages/server/modules/core/index.ts +++ b/packages/server/modules/core/index.ts @@ -19,6 +19,9 @@ import db from '@/db/knex' import { registerOrUpdateRole } from '@/modules/shared/repositories/roles' import { isTestEnv } from '@/modules/shared/helpers/envHelper' import { HooksConfig, Hook, ExecuteHooks } from '@/modules/core/hooks' +import { reportSubscriptionEventsFactory } from '@/modules/core/events/subscriptionListeners' +import { getEventBus } from '@/modules/shared/services/eventBus' +import { publish } from '@/modules/shared/utils/subscriptions' let stopTestSubs: (() => void) | undefined = undefined @@ -75,6 +78,12 @@ const coreModule: SpeckleModule<{ const { startEmittingTestSubs } = await import('@/test/graphqlHelper') stopTestSubs = await startEmittingTestSubs() } + + // Setup GQL sub emits + reportSubscriptionEventsFactory({ + eventListen: getEventBus().listen, + publish + })() } }, async shutdown() { diff --git a/packages/server/modules/core/services/branch/management.ts b/packages/server/modules/core/services/branch/management.ts index 1e8c4a7c8..144313d1c 100644 --- a/packages/server/modules/core/services/branch/management.ts +++ b/packages/server/modules/core/services/branch/management.ts @@ -10,7 +10,6 @@ import { BranchUpdateInput, CreateModelInput, DeleteModelInput, - ProjectModelsUpdatedMessageType, UpdateModelInput } from '@/modules/core/graph/generated/graphql' import { BranchRecord } from '@/modules/core/helpers/types' @@ -32,11 +31,6 @@ import { } from '@/modules/core/domain/streams/operations' import { EventBusEmit } from '@/modules/shared/services/eventBus' import { ModelEvents } from '@/modules/core/domain/branches/events' -import { - ProjectSubscriptions, - PublishSubscription -} from '@/modules/shared/utils/subscriptions' -import { BranchPubsubEvents } from '@/modules/shared' const isBranchCreateInput = ( i: BranchCreateInput | CreateModelInput @@ -47,7 +41,6 @@ export const createBranchAndNotifyFactory = getStreamBranchByName: GetStreamBranchByName createBranch: StoreBranch eventEmit: EventBusEmit - publishSub: PublishSubscription }): CreateBranchAndNotify => async (input: BranchCreateInput | CreateModelInput, creatorId: string) => { const streamId = isBranchCreateInput(input) ? input.streamId : input.projectId @@ -63,25 +56,10 @@ export const createBranchAndNotifyFactory = authorId: creatorId }) - await Promise.all([ - deps.eventEmit({ - eventName: ModelEvents.Created, - payload: { model: branch, projectId: branch.streamId } - }), - // TODO: Move to event bus listeners - deps.publishSub(BranchPubsubEvents.BranchCreated, { - branchCreated: { ...branch }, - streamId: branch.streamId - }), - deps.publishSub(ProjectSubscriptions.ProjectModelsUpdated, { - projectId: branch.streamId, - projectModelsUpdated: { - id: branch.id, - type: ProjectModelsUpdatedMessageType.Created, - model: branch - } - }) - ]) + await deps.eventEmit({ + eventName: ModelEvents.Created, + payload: { model: branch, projectId: branch.streamId } + }) return branch } @@ -90,7 +68,6 @@ export const updateBranchAndNotifyFactory = (deps: { getBranchById: GetBranchById updateBranch: UpdateBranch - publishSub: PublishSubscription eventEmit: EventBusEmit }): UpdateBranchAndNotify => async (input: BranchUpdateInput | UpdateModelInput, userId: string) => { @@ -135,31 +112,15 @@ export const updateBranchAndNotifyFactory = } if (newBranch) { - await Promise.all([ - deps.eventEmit({ - eventName: ModelEvents.Updated, - payload: { - update: input, - userId, - oldModel: existingBranch, - newModel: newBranch - } - }), - // TODO: Move to event bus listeners - deps.publishSub(BranchPubsubEvents.BranchUpdated, { - branchUpdated: { ...input }, - streamId, - branchId: input.id - }), - deps.publishSub(ProjectSubscriptions.ProjectModelsUpdated, { - projectId: streamId, - projectModelsUpdated: { - model: newBranch, - id: newBranch.id, - type: ProjectModelsUpdatedMessageType.Updated - } - }) - ]) + await deps.eventEmit({ + eventName: ModelEvents.Updated, + payload: { + update: input, + userId, + oldModel: existingBranch, + newModel: newBranch + } + }) } return newBranch @@ -172,7 +133,6 @@ export const deleteBranchAndNotifyFactory = emitEvent: EventBusEmit markBranchStreamUpdated: MarkBranchStreamUpdated deleteBranchById: DeleteBranchById - publishSub: PublishSubscription }): DeleteBranchAndNotify => async (input: BranchDeleteInput | DeleteModelInput, userId: string) => { const streamId = isBranchDeleteInput(input) ? input.streamId : input.projectId @@ -218,19 +178,6 @@ export const deleteBranchAndNotifyFactory = input, userId } - }), - // TODO: Move to event bus listeners - deps.publishSub(BranchPubsubEvents.BranchDeleted, { - branchDeleted: input, - streamId - }), - deps.publishSub(ProjectSubscriptions.ProjectModelsUpdated, { - projectId: streamId, - projectModelsUpdated: { - id: input.id, - type: ProjectModelsUpdatedMessageType.Deleted, - model: null - } }) ]) } diff --git a/packages/server/modules/core/tests/branches.spec.ts b/packages/server/modules/core/tests/branches.spec.ts index f5717d71a..a791822b7 100644 --- a/packages/server/modules/core/tests/branches.spec.ts +++ b/packages/server/modules/core/tests/branches.spec.ts @@ -100,7 +100,6 @@ const createBranch = createBranchFactory({ db: knex }) const updateBranchAndNotify = updateBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db: knex }), updateBranch: updateBranchFactory({ db: knex }), - publishSub: publish, eventEmit: getEventBus().emit }) const deleteBranchAndNotify = deleteBranchAndNotifyFactory({ @@ -108,7 +107,6 @@ const deleteBranchAndNotify = deleteBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db: knex }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db: knex }) }) diff --git a/packages/server/modules/core/tests/commits.spec.ts b/packages/server/modules/core/tests/commits.spec.ts index 0622be85a..d56ad3761 100644 --- a/packages/server/modules/core/tests/commits.spec.ts +++ b/packages/server/modules/core/tests/commits.spec.ts @@ -104,7 +104,6 @@ const createBranch = createBranchFactory({ db }) const createBranchAndNotify = createBranchAndNotifyFactory({ createBranch, getStreamBranchByName: getStreamBranchByNameFactory({ db }), - publishSub: publish, eventEmit: getEventBus().emit }) const getCommit = getCommitFactory({ db }) diff --git a/packages/server/modules/core/tests/integration/subs.graph.spec.ts b/packages/server/modules/core/tests/integration/subs.graph.spec.ts index 83bb1dd0b..29dd504fb 100644 --- a/packages/server/modules/core/tests/integration/subs.graph.spec.ts +++ b/packages/server/modules/core/tests/integration/subs.graph.spec.ts @@ -155,7 +155,6 @@ const buildUpdateModel = async (params: { projectId: string }) => { const updateBranchAndNotify = updateBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db: projectDB }), updateBranch: updateBranchFactory({ db: projectDB }), - publishSub: publish, eventEmit: getEventBus().emit }) return updateBranchAndNotify @@ -173,7 +172,6 @@ const buildDeleteModel = async (params: { projectId: string }) => { getBranchById: getBranchByIdFactory({ db: projectDB }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db: projectDB }) }) return deleteBranchAndNotify diff --git a/packages/server/modules/core/tests/streams.spec.ts b/packages/server/modules/core/tests/streams.spec.ts index d864cdd4e..0b560e925 100644 --- a/packages/server/modules/core/tests/streams.spec.ts +++ b/packages/server/modules/core/tests/streams.spec.ts @@ -114,7 +114,6 @@ const deleteBranchAndNotify = deleteBranchAndNotifyFactory({ getBranchById: getBranchByIdFactory({ db }), emitEvent: getEventBus().emit, markBranchStreamUpdated, - publishSub: publish, deleteBranchById: deleteBranchByIdFactory({ db }) }) diff --git a/packages/server/modules/cross-server-sync/index.ts b/packages/server/modules/cross-server-sync/index.ts index 9c33a7a51..40b990252 100644 --- a/packages/server/modules/cross-server-sync/index.ts +++ b/packages/server/modules/cross-server-sync/index.ts @@ -195,7 +195,6 @@ const crossServerSyncModule: SpeckleModule = { createBranchAndNotify: createBranchAndNotifyFactory({ createBranch: createBranchFactory({ db }), getStreamBranchByName, - publishSub: publish, eventEmit: getEventBus().emit }) }), diff --git a/packages/server/modules/fileuploads/services/resultListener.ts b/packages/server/modules/fileuploads/services/resultListener.ts index 5e859efde..7f3b6aa57 100644 --- a/packages/server/modules/fileuploads/services/resultListener.ts +++ b/packages/server/modules/fileuploads/services/resultListener.ts @@ -1,12 +1,10 @@ import { FileImportSubscriptions, - ProjectSubscriptions, publish, type PublishSubscription } from '@/modules/shared/utils/subscriptions' import { ProjectFileImportUpdatedMessageType, - ProjectModelsUpdatedMessageType, ProjectPendingModelsUpdatedMessageType, ProjectPendingVersionsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' @@ -14,7 +12,6 @@ import { GetFileInfo } from '@/modules/fileuploads/domain/operations' import { GetStreamBranchByName } from '@/modules/core/domain/branches/operations' import { EventBusEmit } from '@/modules/shared/services/eventBus' import { ModelEvents } from '@/modules/core/domain/branches/events' -import { BranchPubsubEvents } from '@/modules/shared' type OnFileImportProcessedDeps = { getFileInfo: GetFileInfo @@ -60,25 +57,10 @@ export const onFileImportProcessedFactory = }) if (branch) { - await Promise.all([ - deps.eventEmit({ - eventName: ModelEvents.Created, - payload: { model: branch, projectId: branch.streamId } - }), - // TODO: Move to event bus listeners - deps.publish(BranchPubsubEvents.BranchCreated, { - branchCreated: { ...branch }, - streamId: branch.streamId - }), - deps.publish(ProjectSubscriptions.ProjectModelsUpdated, { - projectId: branch.streamId, - projectModelsUpdated: { - id: branch.id, - type: ProjectModelsUpdatedMessageType.Created, - model: branch - } - }) - ]) + await deps.eventEmit({ + eventName: ModelEvents.Created, + payload: { model: branch, projectId: branch.streamId } + }) } } else { await deps.publish(FileImportSubscriptions.ProjectPendingVersionsUpdated, { diff --git a/packages/server/modules/shared/helpers/factory.ts b/packages/server/modules/shared/helpers/factory.ts new file mode 100644 index 000000000..fe7dead6d --- /dev/null +++ b/packages/server/modules/shared/helpers/factory.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +type Factory = ( + deps: Deps +) => (...args: Args[]) => ReturnType + +export type DependenciesOf = F extends Factory + ? Deps + : never diff --git a/packages/server/test/speckle-helpers/branchHelper.ts b/packages/server/test/speckle-helpers/branchHelper.ts index 5377f1901..35a673d78 100644 --- a/packages/server/test/speckle-helpers/branchHelper.ts +++ b/packages/server/test/speckle-helpers/branchHelper.ts @@ -5,7 +5,6 @@ import { import { createBranchAndNotifyFactory } from '@/modules/core/services/branch/management' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import { getEventBus } from '@/modules/shared/services/eventBus' -import { publish } from '@/modules/shared/utils/subscriptions' import { BasicTestUser } from '@/test/authHelper' import { BasicTestStream } from '@/test/speckle-helpers/streamHelper' import { omit } from 'lodash' @@ -42,7 +41,6 @@ export async function createTestBranch(params: { const createBranchAndNotify = createBranchAndNotifyFactory({ getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDb }), createBranch: createBranchFactory({ db: projectDb }), - publishSub: publish, eventEmit: getEventBus().emit }) From 5d1a46d541078f452b953d123be20b33cfa2deb2 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Fri, 24 Jan 2025 16:55:11 +0200 Subject: [PATCH 05/78] minor adjustment --- .../modules/core/events/subscriptionListeners.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/server/modules/core/events/subscriptionListeners.ts b/packages/server/modules/core/events/subscriptionListeners.ts index 6c693b11e..68c76c939 100644 --- a/packages/server/modules/core/events/subscriptionListeners.ts +++ b/packages/server/modules/core/events/subscriptionListeners.ts @@ -1,6 +1,7 @@ import { ModelEvents } from '@/modules/core/domain/branches/events' import { ProjectModelsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' import { BranchPubsubEvents } from '@/modules/shared' +import { DependenciesOf } from '@/modules/shared/helpers/factory' import { EventBusListen, EventPayload } from '@/modules/shared/services/eventBus' import { ProjectSubscriptions, @@ -72,7 +73,15 @@ const reportModelDeletedFactory = } export const reportSubscriptionEventsFactory = - (deps: { eventListen: EventBusListen; publish: PublishSubscription }) => () => { + ( + deps: { + eventListen: EventBusListen + publish: PublishSubscription + } & DependenciesOf & + DependenciesOf & + DependenciesOf + ) => + () => { const reportModelCreated = reportModelCreatedFactory(deps) const reportModelUpdated = reportModelUpdatedFactory(deps) const reportModelDeleted = reportModelDeletedFactory(deps) From ac88c503e33349f9358e040b4e6107cb25f5993c Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Thu, 23 Jan 2025 14:10:21 +0200 Subject: [PATCH 06/78] chore(server): refactor activityStream invocations - batch #4 - commits --- .../activitystream/events/commitListeners.ts | 195 ++++++++++++++++ .../server/modules/activitystream/index.ts | 6 + .../activitystream/services/commitActivity.ts | 214 ------------------ .../modules/cli/commands/download/commit.ts | 6 +- .../modules/cli/commands/download/project.ts | 6 +- .../comments/tests/comments.graph.spec.js | 9 +- .../modules/comments/tests/comments.spec.ts | 7 +- .../modules/core/domain/commits/events.ts | 52 ++++- .../modules/core/domain/commits/operations.ts | 6 - .../modules/core/graph/resolvers/commits.ts | 38 +--- .../modules/core/graph/resolvers/versions.ts | 33 +-- .../services/commit/batchCommitActions.ts | 96 +++++--- .../core/services/commit/management.ts | 194 +++++++++------- .../modules/core/tests/branches.spec.ts | 7 +- .../server/modules/core/tests/commits.spec.ts | 23 +- .../core/tests/integration/subs.graph.spec.ts | 16 +- .../server/modules/core/tests/streams.spec.ts | 6 +- .../server/modules/core/tests/users.spec.ts | 7 +- .../server/modules/cross-server-sync/index.ts | 6 +- .../cross-server-sync/services/commit.ts | 23 +- .../server/modules/stats/tests/stats.spec.ts | 7 +- .../test/speckle-helpers/commitHelper.ts | 8 +- 22 files changed, 484 insertions(+), 481 deletions(-) create mode 100644 packages/server/modules/activitystream/events/commitListeners.ts delete mode 100644 packages/server/modules/activitystream/services/commitActivity.ts diff --git a/packages/server/modules/activitystream/events/commitListeners.ts b/packages/server/modules/activitystream/events/commitListeners.ts new file mode 100644 index 000000000..4a96909e9 --- /dev/null +++ b/packages/server/modules/activitystream/events/commitListeners.ts @@ -0,0 +1,195 @@ +import { + AddCommitCreatedActivity, + AddCommitDeletedActivity, + AddCommitUpdatedActivity, + SaveActivity +} from '@/modules/activitystream/domain/operations' +import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' +import { VersionEvents } from '@/modules/core/domain/commits/events' +import { CommitCreateInput } from '@/modules/core/graph/generated/graphql' +import { CommitRecord } from '@/modules/core/helpers/types' +import { EventBusListen } from '@/modules/shared/services/eventBus' +import { MaybeNullOrUndefined } from '@speckle/shared' + +/** + * Save "new commit created" activity item + */ +const addCommitCreatedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddCommitCreatedActivity => + async (params: { + commitId: string + streamId: string + userId: string + input: CommitCreateInput + branchName: string + modelId: string + commit: CommitRecord + }) => { + const { commitId, input, streamId, userId, branchName, commit, modelId } = params + await saveActivity({ + streamId, + resourceType: ResourceTypes.Commit, + resourceId: commitId, + actionType: ActionTypes.Commit.Create, + userId, + info: { + id: commitId, + commit: { + ...input, + projectId: streamId, + modelId, + versionId: commit.id + } + }, + message: `Commit created on branch ${branchName}: ${commitId} (${input.message})` + }) + } + +const addCommitUpdatedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddCommitUpdatedActivity => + async (params) => { + const { commitId, streamId, userId, originalCommit, update } = params + + await saveActivity({ + streamId, + resourceType: ResourceTypes.Commit, + resourceId: commitId, + actionType: ActionTypes.Commit.Update, + userId, + info: { old: originalCommit, new: update }, + message: `Commit updated: ${commitId}` + }) + } + +const addCommitMovedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }) => + async (params: { + commitId: string + streamId: string + userId: string + originalBranchId: string + newBranchId: string + commit: CommitRecord + }) => { + const { commitId, streamId, userId, originalBranchId, newBranchId } = params + await saveActivity({ + streamId, + resourceType: ResourceTypes.Commit, + resourceId: commitId, + actionType: ActionTypes.Commit.Move, + userId, + info: { originalBranchId, newBranchId }, + message: `Commit moved: ${commitId}` + }) + } + +const addCommitDeletedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddCommitDeletedActivity => + async (params: { + commitId: string + streamId: string + userId: string + commit: CommitRecord + branchId: string + }) => { + const { commitId, streamId, userId, commit } = params + await saveActivity({ + streamId, + resourceType: ResourceTypes.Commit, + resourceId: commitId, + actionType: ActionTypes.Commit.Delete, + userId, + info: { commit }, + message: `Commit deleted: ${commitId}` + }) + } + +const addCommitReceivedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }) => + async (params: { + streamId: string + commitId: string + userId: string + sourceApplication: string + message: MaybeNullOrUndefined + }) => { + const { streamId, commitId, userId, sourceApplication, message } = params + await saveActivity({ + streamId, + resourceType: ResourceTypes.Commit, + resourceId: commitId, + actionType: ActionTypes.Commit.Receive, + userId, + info: { + sourceApplication, + message + }, + message: `Commit $commitId} was received by user ${userId}` + }) + } + +export const reportCommitActivityFactory = + (deps: { eventListen: EventBusListen; saveActivity: SaveActivity }) => () => { + const addCommitCreatedActivity = addCommitCreatedActivityFactory(deps) + const addCommitUpdatedActivity = addCommitUpdatedActivityFactory(deps) + const addCommitMovedActivity = addCommitMovedActivityFactory(deps) + const addCommitDeletedActivity = addCommitDeletedActivityFactory(deps) + const addCommitReceivedActivity = addCommitReceivedActivityFactory(deps) + + const quitters = [ + deps.eventListen(VersionEvents.Created, async ({ payload }) => { + await addCommitCreatedActivity({ + commitId: payload.version.id, + streamId: payload.projectId, + userId: payload.userId, + input: payload.input, + branchName: payload.modelName, + modelId: payload.modelId, + commit: payload.version + }) + }), + deps.eventListen(VersionEvents.Updated, async ({ payload }) => { + await addCommitUpdatedActivity({ + commitId: payload.versionId, + streamId: payload.projectId, + userId: payload.userId, + originalCommit: payload.oldVersion, + update: payload.update, + newCommit: payload.newVersion, + branchId: payload.modelId + }) + }), + deps.eventListen(VersionEvents.MovedModel, async ({ payload }) => { + await addCommitMovedActivity({ + commitId: payload.version.id, + streamId: payload.projectId, + userId: payload.userId, + originalBranchId: payload.originalModelId, + newBranchId: payload.newModelId, + commit: payload.version + }) + }), + deps.eventListen(VersionEvents.Deleted, async ({ payload }) => { + await addCommitDeletedActivity({ + commitId: payload.versionId, + streamId: payload.projectId, + userId: payload.userId, + commit: payload.version, + branchId: payload.modelId + }) + }), + deps.eventListen(VersionEvents.Received, async ({ payload }) => { + await addCommitReceivedActivity({ + streamId: payload.projectId, + commitId: payload.versionId, + userId: payload.userId, + sourceApplication: payload.sourceApplication, + message: payload.message + }) + }) + ] + + return () => { + quitters.forEach((q) => q()) + } + } diff --git a/packages/server/modules/activitystream/index.ts b/packages/server/modules/activitystream/index.ts index 7d8cc049c..62596a91b 100644 --- a/packages/server/modules/activitystream/index.ts +++ b/packages/server/modules/activitystream/index.ts @@ -29,6 +29,7 @@ import { ProjectEvents } from '@/modules/core/domain/projects/events' import { reportUserActivityFactory } from '@/modules/activitystream/events/userListeners' import { reportAccessRequestActivityFactory } from '@/modules/activitystream/events/accessRequestListeners' import { reportBranchActivityFactory } from '@/modules/activitystream/events/branchListeners' +import { reportCommitActivityFactory } from '@/modules/activitystream/events/commitListeners' let scheduledTask: ReturnType | null = null let quitEventListeners: Optional<() => void> = undefined @@ -57,11 +58,16 @@ const initializeEventListeners = ({ eventListen: eventBus.listen, saveActivity }) + const reportCommitActivity = reportCommitActivityFactory({ + eventListen: eventBus.listen, + saveActivity + }) const quitCbs = [ reportUserActivity(), reportAccessRequestActivity(), reportBranchActivity(), + reportCommitActivity(), eventBus.listen(ServerInvitesEvents.Created, async ({ payload }) => { if (!isProjectResourceTarget(payload.invite.resource)) return await onServerInviteCreatedFactory({ diff --git a/packages/server/modules/activitystream/services/commitActivity.ts b/packages/server/modules/activitystream/services/commitActivity.ts deleted file mode 100644 index 1415b8d57..000000000 --- a/packages/server/modules/activitystream/services/commitActivity.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' -import { - CommitSubscriptions as CommitPubsubEvents, - PublishSubscription -} from '@/modules/shared/utils/subscriptions' -import { - CommitCreateInput, - CommitUpdateInput, - ProjectVersionsUpdatedMessageType, - UpdateVersionInput -} from '@/modules/core/graph/generated/graphql' -import { CommitRecord } from '@/modules/core/helpers/types' -import { ProjectSubscriptions } from '@/modules/shared/utils/subscriptions' -import { has } from 'lodash' -import { - AddCommitCreatedActivity, - AddCommitDeletedActivity, - AddCommitUpdatedActivity, - SaveActivity -} from '@/modules/activitystream/domain/operations' - -/** - * Save "new commit created" activity item - */ -export const addCommitCreatedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }): AddCommitCreatedActivity => - async (params: { - commitId: string - streamId: string - userId: string - input: CommitCreateInput - branchName: string - modelId: string - commit: CommitRecord - }) => { - const { commitId, input, streamId, userId, branchName, commit, modelId } = params - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Commit, - resourceId: commitId, - actionType: ActionTypes.Commit.Create, - userId, - info: { - id: commitId, - commit: { - ...input, - projectId: streamId, - modelId, - versionId: commit.id - } - }, - message: `Commit created on branch ${branchName}: ${commitId} (${input.message})` - }), - publish(CommitPubsubEvents.CommitCreated, { - commitCreated: { ...input, id: commitId, authorId: userId }, - streamId - }), - publish(ProjectSubscriptions.ProjectVersionsUpdated, { - projectId: streamId, - projectVersionsUpdated: { - id: commit.id, - version: { ...commit, streamId }, - type: ProjectVersionsUpdatedMessageType.Created, - modelId - } - }) - ]) - } - -const isOldVersionUpdateInput = ( - i: CommitUpdateInput | UpdateVersionInput -): i is CommitUpdateInput => has(i, 'streamId') - -export const addCommitUpdatedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }): AddCommitUpdatedActivity => - async (params: { - commitId: string - streamId: string - userId: string - originalCommit: CommitRecord - update: CommitUpdateInput | UpdateVersionInput - newCommit: CommitRecord - branchId: string - }) => { - const { commitId, streamId, userId, originalCommit, update, newCommit, branchId } = - params - const legacyUpdateStruct: CommitUpdateInput = isOldVersionUpdateInput(update) - ? update - : { - id: update.versionId, - message: update.message, - streamId - } - - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Commit, - resourceId: commitId, - actionType: ActionTypes.Commit.Update, - userId, - info: { old: originalCommit, new: update }, - message: `Commit updated: ${commitId}` - }), - publish(CommitPubsubEvents.CommitUpdated, { - commitUpdated: { ...legacyUpdateStruct }, - streamId, - commitId - }), - publish(ProjectSubscriptions.ProjectVersionsUpdated, { - projectId: streamId, - projectVersionsUpdated: { - id: commitId, - version: { ...newCommit, streamId }, - type: ProjectVersionsUpdatedMessageType.Updated, - modelId: branchId - } - }) - ]) - } - -export const addCommitMovedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }) => - async (params: { - commitId: string - streamId: string - userId: string - originalBranchId: string - newBranchId: string - commit: CommitRecord - }) => { - const { commitId, streamId, userId, originalBranchId, newBranchId, commit } = params - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Commit, - resourceId: commitId, - actionType: ActionTypes.Commit.Move, - userId, - info: { originalBranchId, newBranchId }, - message: `Commit moved: ${commitId}` - }), - publish(ProjectSubscriptions.ProjectVersionsUpdated, { - projectId: streamId, - projectVersionsUpdated: { - id: commitId, - version: { ...commit, streamId }, - type: ProjectVersionsUpdatedMessageType.Updated, - modelId: newBranchId - } - }) - ]) - } - -export const addCommitDeletedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }): AddCommitDeletedActivity => - async (params: { - commitId: string - streamId: string - userId: string - commit: CommitRecord - branchId: string - }) => { - const { commitId, streamId, userId, commit, branchId } = params - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Commit, - resourceId: commitId, - actionType: ActionTypes.Commit.Delete, - userId, - info: { commit }, - message: `Commit deleted: ${commitId}` - }), - publish(CommitPubsubEvents.CommitDeleted, { - commitDeleted: { ...commit, streamId, branchId }, - streamId - }), - publish(ProjectSubscriptions.ProjectVersionsUpdated, { - projectId: streamId, - projectVersionsUpdated: { - id: commitId, - type: ProjectVersionsUpdatedMessageType.Deleted, - version: null, - modelId: branchId - } - }) - ]) - } diff --git a/packages/server/modules/cli/commands/download/commit.ts b/packages/server/modules/cli/commands/download/commit.ts index 5e6933220..8caf01f3b 100644 --- a/packages/server/modules/cli/commands/download/commit.ts +++ b/packages/server/modules/cli/commands/download/commit.ts @@ -53,7 +53,6 @@ import { import { validateInputAttachmentsFactory } from '@/modules/comments/services/commentTextService' import { getBlobsFactory } from '@/modules/blobstorage/repositories' import { createCommitByBranchIdFactory } from '@/modules/core/services/commit/management' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' import { getUserFactory } from '@/modules/core/repositories/users' @@ -178,10 +177,7 @@ const command: CommandModule< markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + publishSub: publish }) const createObject = createObjectFactory({ diff --git a/packages/server/modules/cli/commands/download/project.ts b/packages/server/modules/cli/commands/download/project.ts index b614137ef..b379fbfdb 100644 --- a/packages/server/modules/cli/commands/download/project.ts +++ b/packages/server/modules/cli/commands/download/project.ts @@ -59,7 +59,6 @@ import { getBlobsFactory } from '@/modules/blobstorage/repositories' import { validateInputAttachmentsFactory } from '@/modules/comments/services/commentTextService' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { getUserFactory } from '@/modules/core/repositories/users' import { createObjectFactory } from '@/modules/core/services/objects/management' import { authorizeResolver } from '@/modules/shared' @@ -202,10 +201,7 @@ const command: CommandModule< markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + publishSub: publish }) const getUser = getUserFactory({ db }) diff --git a/packages/server/modules/comments/tests/comments.graph.spec.js b/packages/server/modules/comments/tests/comments.graph.spec.js index 6c2bc2629..b217f2cf0 100644 --- a/packages/server/modules/comments/tests/comments.graph.spec.js +++ b/packages/server/modules/comments/tests/comments.graph.spec.js @@ -80,11 +80,7 @@ const { buildCoreInviteEmailContentsFactory } = require('@/modules/serverinvites/services/coreEmailContents') const { getEventBus } = require('@/modules/shared/services/eventBus') -const { saveActivityFactory } = require('@/modules/activitystream/repositories') const { publish } = require('@/modules/shared/utils/subscriptions') -const { - addCommitCreatedActivityFactory -} = require('@/modules/activitystream/services/commitActivity') const { getUsersFactory, getUserFactory, @@ -145,10 +141,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/comments/tests/comments.spec.ts b/packages/server/modules/comments/tests/comments.spec.ts index aa62f4dfe..daeba767f 100644 --- a/packages/server/modules/comments/tests/comments.spec.ts +++ b/packages/server/modules/comments/tests/comments.spec.ts @@ -87,9 +87,7 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { getUsersFactory, getUserFactory, @@ -187,10 +185,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/core/domain/commits/events.ts b/packages/server/modules/core/domain/commits/events.ts index a5534f7a3..696b57bb7 100644 --- a/packages/server/modules/core/domain/commits/events.ts +++ b/packages/server/modules/core/domain/commits/events.ts @@ -1,11 +1,59 @@ import { Version } from '@/modules/core/domain/commits/types' +import { + CommitCreateInput, + CommitUpdateInput, + UpdateVersionInput +} from '@/modules/core/graph/generated/graphql' +import { MaybeNullOrUndefined } from '@speckle/shared' export const versionEventsNamespace = 'versions' as const export const VersionEvents = { - Created: `${versionEventsNamespace}.created` + Created: `${versionEventsNamespace}.created`, + Updated: `${versionEventsNamespace}.updated`, + MovedModel: `${versionEventsNamespace}.movedModel`, + Deleted: `${versionEventsNamespace}.deleted`, + Received: `${versionEventsNamespace}.received` } as const export type VersionEventsPayloads = { - [VersionEvents.Created]: { projectId: string; modelId: string; version: Version } + [VersionEvents.Created]: { + projectId: string + modelId: string + version: Version + userId: string + modelName: string + input: CommitCreateInput + } + [VersionEvents.Updated]: { + projectId: string + modelId: string + versionId: string + newVersion: Version + oldVersion: Version + userId: string + update: CommitUpdateInput | UpdateVersionInput + } + [VersionEvents.MovedModel]: { + projectId: string + versionId: string + userId: string + version: Version + originalModelId: string + newModelId: string + } + [VersionEvents.Deleted]: { + projectId: string + versionId: string + modelId: string + userId: string + version: Version + } + [VersionEvents.Received]: { + projectId: string + versionId: string + userId: string + sourceApplication: string + message: MaybeNullOrUndefined + } } diff --git a/packages/server/modules/core/domain/commits/operations.ts b/packages/server/modules/core/domain/commits/operations.ts index bbb221f64..574bc0254 100644 --- a/packages/server/modules/core/domain/commits/operations.ts +++ b/packages/server/modules/core/domain/commits/operations.ts @@ -70,9 +70,6 @@ export type CreateCommitByBranchId = ( sourceApplication: Nullable totalChildrenCount?: MaybeNullOrUndefined parents: Nullable - }>, - options?: Partial<{ - notify: boolean }> ) => Promise @@ -86,9 +83,6 @@ export type CreateCommitByBranchName = ( sourceApplication: Nullable totalChildrenCount?: MaybeNullOrUndefined parents: Nullable - }>, - options?: Partial<{ - notify: boolean }> ) => Promise diff --git a/packages/server/modules/core/graph/resolvers/commits.ts b/packages/server/modules/core/graph/resolvers/commits.ts index 40da4716c..1ed71d400 100644 --- a/packages/server/modules/core/graph/resolvers/commits.ts +++ b/packages/server/modules/core/graph/resolvers/commits.ts @@ -65,15 +65,8 @@ import { getStreamBranchByNameFactory, createBranchFactory } from '@/modules/core/repositories/branches' -import { - addCommitCreatedActivityFactory, - addCommitUpdatedActivityFactory, - addCommitMovedActivityFactory, - addCommitDeletedActivityFactory -} from '@/modules/activitystream/services/commitActivity' import { getObjectFactory } from '@/modules/core/repositories/objects' import { validateStreamAccessFactory } from '@/modules/core/services/streams/access' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { Resolvers } from '@/modules/core/graph/generated/graphql' import { CommitGraphQLReturn } from '@/modules/core/helpers/graphTypes' import { @@ -350,10 +343,7 @@ export = { markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ @@ -388,10 +378,8 @@ export = { getCommitBranch: getCommitBranchFactory({ db: projectDb }), switchCommitBranch: switchCommitBranchFactory({ db: projectDb }), updateCommit: updateCommitFactory({ db: projectDb }), - addCommitUpdatedActivity: addCommitUpdatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, + emitEvent: getEventBus().emit, markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }) }) @@ -410,7 +398,7 @@ export = { const projectDb = await getProjectDbClient({ projectId: args.input.streamId }) await markCommitReceivedAndNotifyFactory({ getCommit: getCommitFactory({ db: projectDb }), - saveActivity: saveActivityFactory({ db }) + emitEvent: getEventBus().emit })({ input: args.input, userId: context.userId! @@ -433,10 +421,8 @@ export = { markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), deleteCommit: deleteCommitFactory({ db: projectDb }), - addCommitDeletedActivity: addCommitDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + emitEvent: getEventBus().emit }) const deleted = await deleteCommitAndNotify( args.commit.id, @@ -455,10 +441,8 @@ export = { getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDb }), createBranch: createBranchFactory({ db: projectDb }), moveCommitsToBranch: moveCommitsToBranchFactory({ db: projectDb }), - addCommitMovedActivity: addCommitMovedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + emitEvent: getEventBus().emit }) await batchMoveCommits(args.input, ctx.userId!) return true @@ -471,10 +455,8 @@ export = { getCommits: getCommitsFactory({ db: projectDb }), getStreams, deleteCommits: deleteCommitsFactory({ db: projectDb }), - addCommitDeletedActivity: addCommitDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + emitEvent: getEventBus().emit }) await batchDeleteCommits(args.input, ctx.userId!) return true diff --git a/packages/server/modules/core/graph/resolvers/versions.ts b/packages/server/modules/core/graph/resolvers/versions.ts index 4b38bb9c6..3f7203b64 100644 --- a/packages/server/modules/core/graph/resolvers/versions.ts +++ b/packages/server/modules/core/graph/resolvers/versions.ts @@ -34,7 +34,6 @@ import { switchCommitBranchFactory, updateCommitFactory } from '@/modules/core/repositories/commits' -import { db } from '@/db/knex' import { createBranchFactory, getBranchByIdFactory, @@ -47,14 +46,7 @@ import { getStreamsFactory, markCommitStreamUpdatedFactory } from '@/modules/core/repositories/streams' -import { - addCommitCreatedActivityFactory, - addCommitDeletedActivityFactory, - addCommitMovedActivityFactory, - addCommitUpdatedActivityFactory -} from '@/modules/activitystream/services/commitActivity' import { getObjectFactory } from '@/modules/core/repositories/objects' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import coreModule from '@/modules/core' import { getEventBus } from '@/modules/shared/services/eventBus' @@ -113,10 +105,8 @@ export = { getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDb }), createBranch: createBranchFactory({ db: projectDb }), moveCommitsToBranch: moveCommitsToBranchFactory({ db: projectDb }), - addCommitMovedActivity: addCommitMovedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + emitEvent: getEventBus().emit }) return await batchMoveCommits(args.input, ctx.userId!) }, @@ -128,10 +118,8 @@ export = { getCommits: getCommitsFactory({ db: projectDb }), getStreams: getStreamsFactory({ db: projectDb }), deleteCommits: deleteCommitsFactory({ db: projectDb }), - addCommitDeletedActivity: addCommitDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + emitEvent: getEventBus().emit }) await batchDeleteCommits(args.input, ctx.userId!) return true @@ -161,10 +149,8 @@ export = { getCommitBranch: getCommitBranchFactory({ db: projectDb }), switchCommitBranch: switchCommitBranchFactory({ db: projectDb }), updateCommit: updateCommitFactory({ db: projectDb }), - addCommitUpdatedActivity: addCommitUpdatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, + emitEvent: getEventBus().emit, markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }) }) @@ -198,10 +184,7 @@ export = { markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const commit = await createCommitByBranchId({ @@ -228,7 +211,7 @@ export = { await markCommitReceivedAndNotifyFactory({ getCommit: getCommitFactory({ db: projectDb }), - saveActivity: saveActivityFactory({ db }) + emitEvent: getEventBus().emit })({ input: args.input, userId: ctx.userId! diff --git a/packages/server/modules/core/services/commit/batchCommitActions.ts b/packages/server/modules/core/services/commit/batchCommitActions.ts index d7ba95934..e1e2147dd 100644 --- a/packages/server/modules/core/services/commit/batchCommitActions.ts +++ b/packages/server/modules/core/services/commit/batchCommitActions.ts @@ -1,11 +1,8 @@ -import { - AddCommitDeletedActivity, - AddCommitMovedActivity -} from '@/modules/activitystream/domain/operations' import { GetStreamBranchByName, StoreBranch } from '@/modules/core/domain/branches/operations' +import { VersionEvents } from '@/modules/core/domain/commits/events' import { DeleteCommits, GetCommits, @@ -22,10 +19,17 @@ import { CommitsDeleteInput, CommitsMoveInput, DeleteVersionsInput, - MoveVersionsInput + MoveVersionsInput, + ProjectVersionsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' import { Roles } from '@/modules/core/helpers/mainConstants' +import { CommitPubsubEvents } from '@/modules/shared' import { ensureError } from '@/modules/shared/helpers/errorHelper' +import { EventBusEmit } from '@/modules/shared/services/eventBus' +import { + ProjectSubscriptions, + PublishSubscription +} from '@/modules/shared/utils/subscriptions' import { difference, groupBy, has, keyBy } from 'lodash' type OldBatchInput = CommitsMoveInput | CommitsDeleteInput @@ -153,7 +157,8 @@ export const batchMoveCommitsFactory = deps: ValidateCommitsMoveDeps & { createBranch: StoreBranch moveCommitsToBranch: MoveCommitsToBranch - addCommitMovedActivity: AddCommitMovedActivity + emitEvent: EventBusEmit + publishSub: PublishSubscription } ): ValidateAndBatchMoveCommits => async (params: CommitsMoveInput | MoveVersionsInput, userId: string) => { @@ -177,16 +182,31 @@ export const batchMoveCommitsFactory = await deps.moveCommitsToBranch(commitIds, finalBranch.id) await Promise.all( - commitsWithStreams.map(({ commit, stream }) => - deps.addCommitMovedActivity({ - commitId: commit.id, - streamId: stream.id, - userId, - commit, - originalBranchId: commit.branchId, - newBranchId: finalBranch.id - }) - ) + commitsWithStreams.map(async ({ commit, stream }) => { + await Promise.all([ + deps.emitEvent({ + eventName: VersionEvents.MovedModel, + payload: { + versionId: commit.id, + projectId: stream.id, + userId, + originalModelId: commit.branchId, + newModelId: finalBranch.id, + version: commit + } + }), + // TODO: Move to event bus listeners + deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId: stream.id, + projectVersionsUpdated: { + id: commit.id, + version: { ...commit, streamId: stream.id }, + type: ProjectVersionsUpdatedMessageType.Updated, + modelId: finalBranch.id + } + }) + ]) + }) ) return finalBranch } catch (e) { @@ -202,7 +222,8 @@ export const batchDeleteCommitsFactory = ( deps: ValidateBatchBaseRulesDeps & { deleteCommits: DeleteCommits - addCommitDeletedActivity: AddCommitDeletedActivity + emitEvent: EventBusEmit + publishSub: PublishSubscription } ): ValidateAndBatchDeleteCommits => async (params: CommitsDeleteInput | DeleteVersionsInput, userId: string) => { @@ -216,15 +237,38 @@ export const batchDeleteCommitsFactory = try { await deps.deleteCommits(commitIds) await Promise.all( - commitsWithStreams.map(({ commit, stream }) => - deps.addCommitDeletedActivity({ - commitId: commit.id, - streamId: stream.id, - userId, - commit, - branchId: commit.branchId - }) - ) + commitsWithStreams.map(async ({ commit, stream }) => { + await Promise.all([ + deps.emitEvent({ + eventName: VersionEvents.Deleted, + payload: { + projectId: stream.id, + modelId: commit.branchId, + versionId: commit.id, + userId, + version: commit + } + }), + // TODO: Move to event bus listeners + deps.publishSub(CommitPubsubEvents.CommitDeleted, { + commitDeleted: { + ...commit, + streamId: stream.id, + branchId: commit.branchId + }, + streamId: stream.id + }), + deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId: stream.id, + projectVersionsUpdated: { + id: commit.id, + type: ProjectVersionsUpdatedMessageType.Deleted, + version: null, + modelId: commit.branchId + } + }) + ]) + }) ) } catch (e) { const err = ensureError(e) diff --git a/packages/server/modules/core/services/commit/management.ts b/packages/server/modules/core/services/commit/management.ts index 78ca57100..5f08e0490 100644 --- a/packages/server/modules/core/services/commit/management.ts +++ b/packages/server/modules/core/services/commit/management.ts @@ -1,10 +1,3 @@ -import { - AddCommitCreatedActivity, - AddCommitDeletedActivity, - AddCommitUpdatedActivity, - SaveActivity -} from '@/modules/activitystream/domain/operations' -import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' import { GetBranchById, GetStreamBranchByName, @@ -41,15 +34,21 @@ import { CommitReceivedInput, CommitUpdateInput, MarkReceivedVersionInput, + ProjectVersionsUpdatedMessageType, UpdateVersionInput } from '@/modules/core/graph/generated/graphql' import { BranchRecord, CommitRecord } from '@/modules/core/helpers/types' +import { CommitPubsubEvents } from '@/modules/shared' import { EventBusEmit } from '@/modules/shared/services/eventBus' +import { + ProjectSubscriptions, + PublishSubscription +} from '@/modules/shared/utils/subscriptions' import { ensureError, Roles } from '@speckle/shared' import { has } from 'lodash' export const markCommitReceivedAndNotifyFactory = - ({ getCommit, saveActivity }: { getCommit: GetCommit; saveActivity: SaveActivity }) => + ({ getCommit, emitEvent }: { getCommit: GetCommit; emitEvent: EventBusEmit }) => async (params: { input: MarkReceivedVersionInput | CommitReceivedInput userId: string @@ -75,17 +74,15 @@ export const markCommitReceivedAndNotifyFactory = ) } - await saveActivity({ - streamId: oldInput.streamId, - resourceType: ResourceTypes.Commit, - resourceId: oldInput.commitId, - actionType: ActionTypes.Commit.Receive, - userId, - info: { - sourceApplication: input.sourceApplication, - message: input.message - }, - message: `Commit ${oldInput.commitId} was received by user ${userId}` + await emitEvent({ + eventName: VersionEvents.Received, + payload: { + projectId: oldInput.streamId, + versionId: oldInput.commitId, + userId, + sourceApplication: oldInput.sourceApplication, + message: oldInput.message + } }) } @@ -98,10 +95,10 @@ export const createCommitByBranchIdFactory = insertBranchCommits: InsertBranchCommits markCommitStreamUpdated: MarkCommitStreamUpdated markCommitBranchUpdated: MarkCommitBranchUpdated - addCommitCreatedActivity: AddCommitCreatedActivity emitEvent: EventBusEmit + publishSub: PublishSubscription }): CreateCommitByBranchId => - async (params, options) => { + async (params) => { const { streamId, branchId, @@ -111,7 +108,6 @@ export const createCommitByBranchIdFactory = sourceApplication, parents } = params - const { notify = true } = options || {} // If no total children count is passed in, get it from the original object // that this commit references. @@ -148,6 +144,10 @@ export const createCommitByBranchIdFactory = deps.insertStreamCommits([{ streamId, commitId: id }]) ]) + const input = { + ...params, + branchName: branch.name + } await Promise.all([ deps.markCommitStreamUpdated(id), deps.markCommitBranchUpdated(id), @@ -156,27 +156,26 @@ export const createCommitByBranchIdFactory = payload: { projectId: streamId, modelId: branchId, - version: commit + version: commit, + input, + modelName: branch.name, + userId: authorId } }), - ...(notify - ? [ - deps.addCommitCreatedActivity({ - commitId: commit.id, - streamId, - userId: authorId, - branchName: branch.name, - input: { - ...commit, - branchName: branch.name, - objectId, - streamId - }, - modelId: branch.id, - commit - }) - ] - : []) + // TODO: Move to event bus listeners + deps.publishSub(CommitPubsubEvents.CommitCreated, { + commitCreated: { ...input, id, authorId }, + streamId + }), + deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId: streamId, + projectVersionsUpdated: { + id: commit.id, + version: { ...commit, streamId }, + type: ProjectVersionsUpdatedMessageType.Created, + modelId: branchId + } + }) ]) return { ...commit, streamId, branchId } @@ -188,7 +187,7 @@ export const createCommitByBranchNameFactory = getStreamBranchByName: GetStreamBranchByName getBranchById: GetBranchById }): CreateCommitByBranchName => - async (params, options) => { + async (params) => { const { streamId, objectId, @@ -198,9 +197,6 @@ export const createCommitByBranchNameFactory = parents, totalChildrenCount } = params - - const { notify = true } = options || {} - const branchName = params.branchName.toLowerCase() let myBranch = await deps.getStreamBranchByName(streamId, branchName) if (!myBranch) { @@ -215,19 +211,16 @@ export const createCommitByBranchNameFactory = ) } - const commit = await deps.createCommitByBranchId( - { - streamId, - branchId: myBranch.id, - objectId, - authorId, - message, - sourceApplication, - totalChildrenCount, - parents - }, - { notify } - ) + const commit = await deps.createCommitByBranchId({ + streamId, + branchId: myBranch.id, + objectId, + authorId, + message, + sourceApplication, + totalChildrenCount, + parents + }) return commit } @@ -245,9 +238,10 @@ export const updateCommitAndNotifyFactory = getCommitBranch: GetCommitBranch switchCommitBranch: SwitchCommitBranch updateCommit: UpdateCommit - addCommitUpdatedActivity: AddCommitUpdatedActivity markCommitStreamUpdated: MarkCommitStreamUpdated markCommitBranchUpdated: MarkCommitBranchUpdated + emitEvent: EventBusEmit + publishSub: PublishSubscription }): UpdateCommitAndNotify => async (params: CommitUpdateInput | UpdateVersionInput, userId: string) => { const { @@ -323,19 +317,43 @@ export const updateCommitAndNotifyFactory = } if (commit) { - await deps.addCommitUpdatedActivity({ - commitId, - streamId: stream.id, - userId, - originalCommit: commit, - update: params, - newCommit, - branchId: branch!.id - }) - + const legacyUpdateStruct: CommitUpdateInput = isOldVersionUpdateInput(params) + ? params + : { + id: params.versionId, + message: params.message, + streamId: stream.id + } const [updatedBranch] = await Promise.all([ deps.markCommitBranchUpdated(commit.id), - deps.markCommitStreamUpdated(commit.id) + deps.markCommitStreamUpdated(commit.id), + deps.emitEvent({ + eventName: VersionEvents.Updated, + payload: { + projectId: stream.id, + modelId: branch!.id, + versionId: commitId, + newVersion: newCommit, + oldVersion: commit, + userId, + update: params + } + }), + // TODO: Move to event bus listeners + deps.publishSub(CommitPubsubEvents.CommitUpdated, { + commitUpdated: { ...legacyUpdateStruct }, + streamId: stream.id, + commitId + }), + deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId: stream.id, + projectVersionsUpdated: { + id: commitId, + version: { ...newCommit, streamId: stream.id }, + type: ProjectVersionsUpdatedMessageType.Updated, + modelId: branch!.id + } + }) ]) branch = updatedBranch } @@ -349,7 +367,8 @@ export const deleteCommitAndNotifyFactory = markCommitStreamUpdated: MarkCommitStreamUpdated markCommitBranchUpdated: MarkCommitBranchUpdated deleteCommit: DeleteCommit - addCommitDeletedActivity: AddCommitDeletedActivity + emitEvent: EventBusEmit + publishSub: PublishSubscription }): DeleteCommitAndNotify => async (commitId: string, streamId: string, userId: string) => { const commit = await deps.getCommit(commitId) @@ -372,13 +391,32 @@ export const deleteCommitAndNotifyFactory = const isDeleted = await deps.deleteCommit(commitId) if (isDeleted) { - await deps.addCommitDeletedActivity({ - commitId, - streamId, - userId, - commit, - branchId: updatedBranch.id - }) + await Promise.all([ + deps.emitEvent({ + eventName: VersionEvents.Deleted, + payload: { + projectId: streamId, + modelId: updatedBranch.id, + versionId: commitId, + userId, + version: commit + } + }), + // TODO: Move to event bus listeners + deps.publishSub(CommitPubsubEvents.CommitDeleted, { + commitDeleted: { ...commit, streamId, branchId: updatedBranch.id }, + streamId + }), + deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId: streamId, + projectVersionsUpdated: { + id: commitId, + type: ProjectVersionsUpdatedMessageType.Deleted, + version: null, + modelId: updatedBranch.id + } + }) + ]) } return isDeleted diff --git a/packages/server/modules/core/tests/branches.spec.ts b/packages/server/modules/core/tests/branches.spec.ts index a791822b7..370f2fdec 100644 --- a/packages/server/modules/core/tests/branches.spec.ts +++ b/packages/server/modules/core/tests/branches.spec.ts @@ -58,9 +58,7 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { getUsersFactory, getUserFactory, @@ -121,10 +119,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/core/tests/commits.spec.ts b/packages/server/modules/core/tests/commits.spec.ts index d56ad3761..5896a720b 100644 --- a/packages/server/modules/core/tests/commits.spec.ts +++ b/packages/server/modules/core/tests/commits.spec.ts @@ -39,11 +39,6 @@ import { createStreamFactory, markCommitStreamUpdatedFactory } from '@/modules/core/repositories/streams' -import { - addCommitUpdatedActivityFactory, - addCommitDeletedActivityFactory, - addCommitCreatedActivityFactory -} from '@/modules/activitystream/services/commitActivity' import { getObjectFactory, storeSingleObjectIfNotFoundFactory, @@ -64,7 +59,6 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' import { getUsersFactory, @@ -112,10 +106,8 @@ const deleteCommitAndNotify = deleteCommitAndNotifyFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), deleteCommit: deleteCommitFactory({ db }), - addCommitDeletedActivity: addCommitDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + emitEvent: getEventBus().emit }) const getObject = getObjectFactory({ db }) @@ -128,10 +120,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ @@ -148,10 +137,8 @@ const updateCommitAndNotify = updateCommitAndNotifyFactory({ getCommitBranch: getCommitBranchFactory({ db }), switchCommitBranch: switchCommitBranchFactory({ db }), updateCommit: updateCommitFactory({ db }), - addCommitUpdatedActivity: addCommitUpdatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, + emitEvent: getEventBus().emit, markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }) }) diff --git a/packages/server/modules/core/tests/integration/subs.graph.spec.ts b/packages/server/modules/core/tests/integration/subs.graph.spec.ts index 29dd504fb..a14dc399d 100644 --- a/packages/server/modules/core/tests/integration/subs.graph.spec.ts +++ b/packages/server/modules/core/tests/integration/subs.graph.spec.ts @@ -1,10 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { db } from '@/db/knex' import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { - addCommitDeletedActivityFactory, - addCommitUpdatedActivityFactory -} from '@/modules/activitystream/services/commitActivity' import { addStreamDeletedActivityFactory, addStreamInviteAcceptedActivityFactory, @@ -185,10 +181,8 @@ const buildDeleteVersion = async (params: { projectId: string }) => { getCommits: getCommitsFactory({ db: projectDb }), getStreams: getStreamsFactory({ db: projectDb }), deleteCommits: deleteCommitsFactory({ db: projectDb }), - addCommitDeletedActivity: addCommitDeletedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish, + emitEvent: getEventBus().emit }) return batchDeleteCommits } @@ -204,10 +198,8 @@ const buildUpdateVersion = async (params: { projectId: string }) => { getCommitBranch: getCommitBranchFactory({ db: projectDb }), switchCommitBranch: switchCommitBranchFactory({ db: projectDb }), updateCommit: updateCommitFactory({ db: projectDb }), - addCommitUpdatedActivity: addCommitUpdatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), + publishSub: publish, + emitEvent: getEventBus().emit, markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }) }) diff --git a/packages/server/modules/core/tests/streams.spec.ts b/packages/server/modules/core/tests/streams.spec.ts index 0b560e925..19ad556bc 100644 --- a/packages/server/modules/core/tests/streams.spec.ts +++ b/packages/server/modules/core/tests/streams.spec.ts @@ -59,7 +59,6 @@ import { insertBranchCommitsFactory, insertStreamCommitsFactory } from '@/modules/core/repositories/commits' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { getObjectFactory, storeClosuresIfNotFoundFactory, @@ -127,10 +126,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/core/tests/users.spec.ts b/packages/server/modules/core/tests/users.spec.ts index cdf894b20..97edea8ba 100644 --- a/packages/server/modules/core/tests/users.spec.ts +++ b/packages/server/modules/core/tests/users.spec.ts @@ -60,9 +60,7 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { getUsersFactory, getUserFactory, @@ -137,10 +135,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/cross-server-sync/index.ts b/packages/server/modules/cross-server-sync/index.ts index 40b990252..38973f6bd 100644 --- a/packages/server/modules/cross-server-sync/index.ts +++ b/packages/server/modules/cross-server-sync/index.ts @@ -5,7 +5,6 @@ import { addCommentCreatedActivityFactory, addReplyAddedActivityFactory } from '@/modules/activitystream/services/commentActivity' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { getBlobsFactory } from '@/modules/blobstorage/repositories' import { getCommentFactory, @@ -154,10 +153,7 @@ const crossServerSyncModule: SpeckleModule = { markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createObject = createObjectFactory({ diff --git a/packages/server/modules/cross-server-sync/services/commit.ts b/packages/server/modules/cross-server-sync/services/commit.ts index a7fce52de..91ceac49c 100644 --- a/packages/server/modules/cross-server-sync/services/commit.ts +++ b/packages/server/modules/cross-server-sync/services/commit.ts @@ -473,19 +473,16 @@ const saveNewCommitFactory = const sourceApplication = commit.sourceApplication || null const totalChildrenCount = commit.totalChildrenCount - const newCommit = await deps.createCommitByBranchId( - { - streamId, - branchId: targetBranch.id, - objectId, - authorId: owner.id, - message, - sourceApplication, - totalChildrenCount, - parents: parents.length ? parents : null - }, - { notify: true } - ) + const newCommit = await deps.createCommitByBranchId({ + streamId, + branchId: targetBranch.id, + objectId, + authorId: owner.id, + message, + sourceApplication, + totalChildrenCount, + parents: parents.length ? parents : null + }) const id = newCommit.id return id diff --git a/packages/server/modules/stats/tests/stats.spec.ts b/packages/server/modules/stats/tests/stats.spec.ts index f0fd0c17b..b95aa737d 100644 --- a/packages/server/modules/stats/tests/stats.spec.ts +++ b/packages/server/modules/stats/tests/stats.spec.ts @@ -50,9 +50,7 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { countAdminUsersFactory, getUserFactory, @@ -95,10 +93,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/test/speckle-helpers/commitHelper.ts b/packages/server/test/speckle-helpers/commitHelper.ts index 3655623ac..64feac0d6 100644 --- a/packages/server/test/speckle-helpers/commitHelper.ts +++ b/packages/server/test/speckle-helpers/commitHelper.ts @@ -1,6 +1,3 @@ -import { db } from '@/db/knex' -import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { addCommitCreatedActivityFactory } from '@/modules/activitystream/services/commitActivity' import { getBranchByIdFactory, getStreamBranchByNameFactory, @@ -127,10 +124,7 @@ export async function createTestCommits( markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - addCommitCreatedActivity: addCommitCreatedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommitByBranchName = createCommitByBranchNameFactory({ From 9775c488a1ba258300ffc56d7861746081e6d59e Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Fri, 24 Jan 2025 17:52:34 +0200 Subject: [PATCH 07/78] chore(server): moving out version sub reporting to separate listeners --- .../modules/cli/commands/download/commit.ts | 3 +- .../modules/cli/commands/download/project.ts | 3 +- .../comments/tests/comments.graph.spec.js | 4 +- .../modules/comments/tests/comments.spec.ts | 4 +- .../core/events/subscriptionListeners.ts | 124 +++++++++++++++++- .../modules/core/graph/resolvers/commits.ts | 10 +- .../modules/core/graph/resolvers/versions.ts | 9 +- .../services/commit/batchCommitActions.ts | 60 ++------- .../core/services/commit/management.ts | 81 ++---------- .../modules/core/tests/branches.spec.ts | 4 +- .../server/modules/core/tests/commits.spec.ts | 6 +- .../core/tests/integration/subs.graph.spec.ts | 2 - .../server/modules/core/tests/streams.spec.ts | 3 +- .../server/modules/core/tests/users.spec.ts | 4 +- .../server/modules/cross-server-sync/index.ts | 3 +- .../server/modules/stats/tests/stats.spec.ts | 4 +- .../test/speckle-helpers/commitHelper.ts | 4 +- 17 files changed, 156 insertions(+), 172 deletions(-) diff --git a/packages/server/modules/cli/commands/download/commit.ts b/packages/server/modules/cli/commands/download/commit.ts index 8caf01f3b..5640670d0 100644 --- a/packages/server/modules/cli/commands/download/commit.ts +++ b/packages/server/modules/cli/commands/download/commit.ts @@ -176,8 +176,7 @@ const command: CommandModule< insertBranchCommits: insertBranchCommitsFactory({ db: projectDb }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createObject = createObjectFactory({ diff --git a/packages/server/modules/cli/commands/download/project.ts b/packages/server/modules/cli/commands/download/project.ts index b379fbfdb..e65e4191c 100644 --- a/packages/server/modules/cli/commands/download/project.ts +++ b/packages/server/modules/cli/commands/download/project.ts @@ -200,8 +200,7 @@ const command: CommandModule< insertBranchCommits: insertBranchCommitsFactory({ db: projectDb }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const getUser = getUserFactory({ db }) diff --git a/packages/server/modules/comments/tests/comments.graph.spec.js b/packages/server/modules/comments/tests/comments.graph.spec.js index b217f2cf0..1f09bcdd3 100644 --- a/packages/server/modules/comments/tests/comments.graph.spec.js +++ b/packages/server/modules/comments/tests/comments.graph.spec.js @@ -80,7 +80,6 @@ const { buildCoreInviteEmailContentsFactory } = require('@/modules/serverinvites/services/coreEmailContents') const { getEventBus } = require('@/modules/shared/services/eventBus') -const { publish } = require('@/modules/shared/utils/subscriptions') const { getUsersFactory, getUserFactory, @@ -140,8 +139,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ insertBranchCommits: insertBranchCommitsFactory({ db }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/comments/tests/comments.spec.ts b/packages/server/modules/comments/tests/comments.spec.ts index daeba767f..0a20f3d56 100644 --- a/packages/server/modules/comments/tests/comments.spec.ts +++ b/packages/server/modules/comments/tests/comments.spec.ts @@ -87,7 +87,6 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { publish } from '@/modules/shared/utils/subscriptions' import { getUsersFactory, getUserFactory, @@ -184,8 +183,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ insertBranchCommits: insertBranchCommitsFactory({ db }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/core/events/subscriptionListeners.ts b/packages/server/modules/core/events/subscriptionListeners.ts index 68c76c939..11d1ec164 100644 --- a/packages/server/modules/core/events/subscriptionListeners.ts +++ b/packages/server/modules/core/events/subscriptionListeners.ts @@ -1,6 +1,12 @@ import { ModelEvents } from '@/modules/core/domain/branches/events' -import { ProjectModelsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' -import { BranchPubsubEvents } from '@/modules/shared' +import { VersionEvents } from '@/modules/core/domain/commits/events' +import { + CommitUpdateInput, + ProjectModelsUpdatedMessageType, + ProjectVersionsUpdatedMessageType +} from '@/modules/core/graph/generated/graphql' +import { isOldVersionUpdateInput } from '@/modules/core/services/commit/management' +import { BranchPubsubEvents, CommitPubsubEvents } from '@/modules/shared' import { DependenciesOf } from '@/modules/shared/helpers/factory' import { EventBusListen, EventPayload } from '@/modules/shared/services/eventBus' import { @@ -72,6 +78,101 @@ const reportModelDeletedFactory = ]) } +const reportVersionMovedModelFactory = + (deps: { publish: PublishSubscription }) => + async (payload: EventPayload) => { + const { version, projectId, newModelId } = payload.payload + + await deps.publish(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId, + projectVersionsUpdated: { + id: version.id, + version: { ...version, streamId: projectId }, + type: ProjectVersionsUpdatedMessageType.Updated, + modelId: newModelId + } + }) + } + +const reportVersionDeletedFactory = + (deps: { publish: PublishSubscription }) => + async (payload: EventPayload) => { + const { version, projectId, modelId } = payload.payload + + await Promise.all([ + deps.publish(CommitPubsubEvents.CommitDeleted, { + commitDeleted: { + ...version, + streamId: projectId, + branchId: modelId + }, + streamId: projectId + }), + deps.publish(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId, + projectVersionsUpdated: { + id: version.id, + type: ProjectVersionsUpdatedMessageType.Deleted, + version: null, + modelId + } + }) + ]) + } + +const reportVersionCreatedFactory = + (deps: { publish: PublishSubscription }) => + async (payload: EventPayload) => { + const { version, projectId, modelId, input, userId } = payload.payload + + await Promise.all([ + deps.publish(CommitPubsubEvents.CommitCreated, { + commitCreated: { ...input, id: version.id, authorId: userId }, + streamId: projectId + }), + deps.publish(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId, + projectVersionsUpdated: { + id: version.id, + version: { ...version, streamId: projectId }, + type: ProjectVersionsUpdatedMessageType.Created, + modelId + } + }) + ]) + } + +const reportVersionUpdatedFactory = + (deps: { publish: PublishSubscription }) => + async (payload: EventPayload) => { + const { projectId, newVersion, update, modelId } = payload.payload + + const legacyUpdateStruct: CommitUpdateInput = isOldVersionUpdateInput(update) + ? update + : { + id: update.versionId, + message: update.message, + streamId: projectId + } + + await Promise.all([ + deps.publish(CommitPubsubEvents.CommitUpdated, { + commitUpdated: { ...legacyUpdateStruct }, + streamId: projectId, + commitId: newVersion.id + }), + deps.publish(ProjectSubscriptions.ProjectVersionsUpdated, { + projectId, + projectVersionsUpdated: { + id: newVersion.id, + version: { ...newVersion, streamId: projectId }, + type: ProjectVersionsUpdatedMessageType.Updated, + modelId + } + }) + ]) + } + export const reportSubscriptionEventsFactory = ( deps: { @@ -79,17 +180,32 @@ export const reportSubscriptionEventsFactory = publish: PublishSubscription } & DependenciesOf & DependenciesOf & - DependenciesOf + DependenciesOf & + DependenciesOf & + DependenciesOf & + DependenciesOf & + DependenciesOf ) => () => { const reportModelCreated = reportModelCreatedFactory(deps) const reportModelUpdated = reportModelUpdatedFactory(deps) const reportModelDeleted = reportModelDeletedFactory(deps) + const reportVersionMovedModel = reportVersionMovedModelFactory(deps) + const reportVersionDeleted = reportVersionDeletedFactory(deps) + const reportVersionCreated = reportVersionCreatedFactory(deps) + const reportVersionUpdated = reportVersionUpdatedFactory(deps) + const quitCbs = [ + // Models deps.eventListen(ModelEvents.Created, reportModelCreated), deps.eventListen(ModelEvents.Updated, reportModelUpdated), - deps.eventListen(ModelEvents.Deleted, reportModelDeleted) + deps.eventListen(ModelEvents.Deleted, reportModelDeleted), + // Versions + deps.eventListen(VersionEvents.MovedModel, reportVersionMovedModel), + deps.eventListen(VersionEvents.Deleted, reportVersionDeleted), + deps.eventListen(VersionEvents.Created, reportVersionCreated), + deps.eventListen(VersionEvents.Updated, reportVersionUpdated) ] return () => quitCbs.forEach((quit) => quit()) diff --git a/packages/server/modules/core/graph/resolvers/commits.ts b/packages/server/modules/core/graph/resolvers/commits.ts index 1ed71d400..38ac440c6 100644 --- a/packages/server/modules/core/graph/resolvers/commits.ts +++ b/packages/server/modules/core/graph/resolvers/commits.ts @@ -1,8 +1,7 @@ import { CommitNotFoundError } from '@/modules/core/errors/commit' import { CommitSubscriptions, - filteredSubscribe, - publish + filteredSubscribe } from '@/modules/shared/utils/subscriptions' import { authorizeResolver } from '@/modules/shared' import { Knex } from 'knex' @@ -342,8 +341,7 @@ export = { insertBranchCommits: insertBranchCommitsFactory({ db: projectDb }), markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ @@ -378,7 +376,6 @@ export = { getCommitBranch: getCommitBranchFactory({ db: projectDb }), switchCommitBranch: switchCommitBranchFactory({ db: projectDb }), updateCommit: updateCommitFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit, markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }) @@ -421,7 +418,6 @@ export = { markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), deleteCommit: deleteCommitFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit }) const deleted = await deleteCommitAndNotify( @@ -441,7 +437,6 @@ export = { getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDb }), createBranch: createBranchFactory({ db: projectDb }), moveCommitsToBranch: moveCommitsToBranchFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit }) await batchMoveCommits(args.input, ctx.userId!) @@ -455,7 +450,6 @@ export = { getCommits: getCommitsFactory({ db: projectDb }), getStreams, deleteCommits: deleteCommitsFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit }) await batchDeleteCommits(args.input, ctx.userId!) diff --git a/packages/server/modules/core/graph/resolvers/versions.ts b/packages/server/modules/core/graph/resolvers/versions.ts index 3f7203b64..5777bbe18 100644 --- a/packages/server/modules/core/graph/resolvers/versions.ts +++ b/packages/server/modules/core/graph/resolvers/versions.ts @@ -3,8 +3,7 @@ import { Resolvers } from '@/modules/core/graph/generated/graphql' import { authorizeResolver } from '@/modules/shared' import { filteredSubscribe, - ProjectSubscriptions, - publish + ProjectSubscriptions } from '@/modules/shared/utils/subscriptions' import { getServerOrigin } from '@/modules/shared/helpers/envHelper' import { @@ -105,7 +104,6 @@ export = { getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDb }), createBranch: createBranchFactory({ db: projectDb }), moveCommitsToBranch: moveCommitsToBranchFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit }) return await batchMoveCommits(args.input, ctx.userId!) @@ -118,7 +116,6 @@ export = { getCommits: getCommitsFactory({ db: projectDb }), getStreams: getStreamsFactory({ db: projectDb }), deleteCommits: deleteCommitsFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit }) await batchDeleteCommits(args.input, ctx.userId!) @@ -149,7 +146,6 @@ export = { getCommitBranch: getCommitBranchFactory({ db: projectDb }), switchCommitBranch: switchCommitBranchFactory({ db: projectDb }), updateCommit: updateCommitFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit, markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }) @@ -183,8 +179,7 @@ export = { insertBranchCommits: insertBranchCommitsFactory({ db: projectDb }), markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const commit = await createCommitByBranchId({ diff --git a/packages/server/modules/core/services/commit/batchCommitActions.ts b/packages/server/modules/core/services/commit/batchCommitActions.ts index e1e2147dd..66f41fa86 100644 --- a/packages/server/modules/core/services/commit/batchCommitActions.ts +++ b/packages/server/modules/core/services/commit/batchCommitActions.ts @@ -19,17 +19,11 @@ import { CommitsDeleteInput, CommitsMoveInput, DeleteVersionsInput, - MoveVersionsInput, - ProjectVersionsUpdatedMessageType + MoveVersionsInput } from '@/modules/core/graph/generated/graphql' import { Roles } from '@/modules/core/helpers/mainConstants' -import { CommitPubsubEvents } from '@/modules/shared' import { ensureError } from '@/modules/shared/helpers/errorHelper' import { EventBusEmit } from '@/modules/shared/services/eventBus' -import { - ProjectSubscriptions, - PublishSubscription -} from '@/modules/shared/utils/subscriptions' import { difference, groupBy, has, keyBy } from 'lodash' type OldBatchInput = CommitsMoveInput | CommitsDeleteInput @@ -158,7 +152,6 @@ export const batchMoveCommitsFactory = createBranch: StoreBranch moveCommitsToBranch: MoveCommitsToBranch emitEvent: EventBusEmit - publishSub: PublishSubscription } ): ValidateAndBatchMoveCommits => async (params: CommitsMoveInput | MoveVersionsInput, userId: string) => { @@ -183,29 +176,17 @@ export const batchMoveCommitsFactory = await deps.moveCommitsToBranch(commitIds, finalBranch.id) await Promise.all( commitsWithStreams.map(async ({ commit, stream }) => { - await Promise.all([ - deps.emitEvent({ - eventName: VersionEvents.MovedModel, - payload: { - versionId: commit.id, - projectId: stream.id, - userId, - originalModelId: commit.branchId, - newModelId: finalBranch.id, - version: commit - } - }), - // TODO: Move to event bus listeners - deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { + await deps.emitEvent({ + eventName: VersionEvents.MovedModel, + payload: { + versionId: commit.id, projectId: stream.id, - projectVersionsUpdated: { - id: commit.id, - version: { ...commit, streamId: stream.id }, - type: ProjectVersionsUpdatedMessageType.Updated, - modelId: finalBranch.id - } - }) - ]) + userId, + originalModelId: commit.branchId, + newModelId: finalBranch.id, + version: commit + } + }) }) ) return finalBranch @@ -223,7 +204,6 @@ export const batchDeleteCommitsFactory = deps: ValidateBatchBaseRulesDeps & { deleteCommits: DeleteCommits emitEvent: EventBusEmit - publishSub: PublishSubscription } ): ValidateAndBatchDeleteCommits => async (params: CommitsDeleteInput | DeleteVersionsInput, userId: string) => { @@ -248,24 +228,6 @@ export const batchDeleteCommitsFactory = userId, version: commit } - }), - // TODO: Move to event bus listeners - deps.publishSub(CommitPubsubEvents.CommitDeleted, { - commitDeleted: { - ...commit, - streamId: stream.id, - branchId: commit.branchId - }, - streamId: stream.id - }), - deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { - projectId: stream.id, - projectVersionsUpdated: { - id: commit.id, - type: ProjectVersionsUpdatedMessageType.Deleted, - version: null, - modelId: commit.branchId - } }) ]) }) diff --git a/packages/server/modules/core/services/commit/management.ts b/packages/server/modules/core/services/commit/management.ts index 5f08e0490..0c4db365f 100644 --- a/packages/server/modules/core/services/commit/management.ts +++ b/packages/server/modules/core/services/commit/management.ts @@ -34,16 +34,10 @@ import { CommitReceivedInput, CommitUpdateInput, MarkReceivedVersionInput, - ProjectVersionsUpdatedMessageType, UpdateVersionInput } from '@/modules/core/graph/generated/graphql' import { BranchRecord, CommitRecord } from '@/modules/core/helpers/types' -import { CommitPubsubEvents } from '@/modules/shared' import { EventBusEmit } from '@/modules/shared/services/eventBus' -import { - ProjectSubscriptions, - PublishSubscription -} from '@/modules/shared/utils/subscriptions' import { ensureError, Roles } from '@speckle/shared' import { has } from 'lodash' @@ -96,7 +90,6 @@ export const createCommitByBranchIdFactory = markCommitStreamUpdated: MarkCommitStreamUpdated markCommitBranchUpdated: MarkCommitBranchUpdated emitEvent: EventBusEmit - publishSub: PublishSubscription }): CreateCommitByBranchId => async (params) => { const { @@ -161,20 +154,6 @@ export const createCommitByBranchIdFactory = modelName: branch.name, userId: authorId } - }), - // TODO: Move to event bus listeners - deps.publishSub(CommitPubsubEvents.CommitCreated, { - commitCreated: { ...input, id, authorId }, - streamId - }), - deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { - projectId: streamId, - projectVersionsUpdated: { - id: commit.id, - version: { ...commit, streamId }, - type: ProjectVersionsUpdatedMessageType.Created, - modelId: branchId - } }) ]) @@ -225,7 +204,7 @@ export const createCommitByBranchNameFactory = return commit } -const isOldVersionUpdateInput = ( +export const isOldVersionUpdateInput = ( i: CommitUpdateInput | UpdateVersionInput ): i is CommitUpdateInput => has(i, 'streamId') @@ -241,7 +220,6 @@ export const updateCommitAndNotifyFactory = markCommitStreamUpdated: MarkCommitStreamUpdated markCommitBranchUpdated: MarkCommitBranchUpdated emitEvent: EventBusEmit - publishSub: PublishSubscription }): UpdateCommitAndNotify => async (params: CommitUpdateInput | UpdateVersionInput, userId: string) => { const { @@ -317,13 +295,6 @@ export const updateCommitAndNotifyFactory = } if (commit) { - const legacyUpdateStruct: CommitUpdateInput = isOldVersionUpdateInput(params) - ? params - : { - id: params.versionId, - message: params.message, - streamId: stream.id - } const [updatedBranch] = await Promise.all([ deps.markCommitBranchUpdated(commit.id), deps.markCommitStreamUpdated(commit.id), @@ -338,21 +309,6 @@ export const updateCommitAndNotifyFactory = userId, update: params } - }), - // TODO: Move to event bus listeners - deps.publishSub(CommitPubsubEvents.CommitUpdated, { - commitUpdated: { ...legacyUpdateStruct }, - streamId: stream.id, - commitId - }), - deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { - projectId: stream.id, - projectVersionsUpdated: { - id: commitId, - version: { ...newCommit, streamId: stream.id }, - type: ProjectVersionsUpdatedMessageType.Updated, - modelId: branch!.id - } }) ]) branch = updatedBranch @@ -368,7 +324,6 @@ export const deleteCommitAndNotifyFactory = markCommitBranchUpdated: MarkCommitBranchUpdated deleteCommit: DeleteCommit emitEvent: EventBusEmit - publishSub: PublishSubscription }): DeleteCommitAndNotify => async (commitId: string, streamId: string, userId: string) => { const commit = await deps.getCommit(commitId) @@ -391,32 +346,16 @@ export const deleteCommitAndNotifyFactory = const isDeleted = await deps.deleteCommit(commitId) if (isDeleted) { - await Promise.all([ - deps.emitEvent({ - eventName: VersionEvents.Deleted, - payload: { - projectId: streamId, - modelId: updatedBranch.id, - versionId: commitId, - userId, - version: commit - } - }), - // TODO: Move to event bus listeners - deps.publishSub(CommitPubsubEvents.CommitDeleted, { - commitDeleted: { ...commit, streamId, branchId: updatedBranch.id }, - streamId - }), - deps.publishSub(ProjectSubscriptions.ProjectVersionsUpdated, { + await deps.emitEvent({ + eventName: VersionEvents.Deleted, + payload: { projectId: streamId, - projectVersionsUpdated: { - id: commitId, - type: ProjectVersionsUpdatedMessageType.Deleted, - version: null, - modelId: updatedBranch.id - } - }) - ]) + modelId: updatedBranch.id, + versionId: commitId, + userId, + version: commit + } + }) } return isDeleted diff --git a/packages/server/modules/core/tests/branches.spec.ts b/packages/server/modules/core/tests/branches.spec.ts index 370f2fdec..08ae099a5 100644 --- a/packages/server/modules/core/tests/branches.spec.ts +++ b/packages/server/modules/core/tests/branches.spec.ts @@ -58,7 +58,6 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { publish } from '@/modules/shared/utils/subscriptions' import { getUsersFactory, getUserFactory, @@ -118,8 +117,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ insertBranchCommits: insertBranchCommitsFactory({ db }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/core/tests/commits.spec.ts b/packages/server/modules/core/tests/commits.spec.ts index 5896a720b..434651d10 100644 --- a/packages/server/modules/core/tests/commits.spec.ts +++ b/packages/server/modules/core/tests/commits.spec.ts @@ -59,7 +59,6 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { publish } from '@/modules/shared/utils/subscriptions' import { getUsersFactory, getUserFactory, @@ -106,7 +105,6 @@ const deleteCommitAndNotify = deleteCommitAndNotifyFactory({ markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), deleteCommit: deleteCommitFactory({ db }), - publishSub: publish, emitEvent: getEventBus().emit }) @@ -119,8 +117,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ insertBranchCommits: insertBranchCommitsFactory({ db }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ @@ -137,7 +134,6 @@ const updateCommitAndNotify = updateCommitAndNotifyFactory({ getCommitBranch: getCommitBranchFactory({ db }), switchCommitBranch: switchCommitBranchFactory({ db }), updateCommit: updateCommitFactory({ db }), - publishSub: publish, emitEvent: getEventBus().emit, markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }) diff --git a/packages/server/modules/core/tests/integration/subs.graph.spec.ts b/packages/server/modules/core/tests/integration/subs.graph.spec.ts index a14dc399d..83d638d4e 100644 --- a/packages/server/modules/core/tests/integration/subs.graph.spec.ts +++ b/packages/server/modules/core/tests/integration/subs.graph.spec.ts @@ -181,7 +181,6 @@ const buildDeleteVersion = async (params: { projectId: string }) => { getCommits: getCommitsFactory({ db: projectDb }), getStreams: getStreamsFactory({ db: projectDb }), deleteCommits: deleteCommitsFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit }) return batchDeleteCommits @@ -198,7 +197,6 @@ const buildUpdateVersion = async (params: { projectId: string }) => { getCommitBranch: getCommitBranchFactory({ db: projectDb }), switchCommitBranch: switchCommitBranchFactory({ db: projectDb }), updateCommit: updateCommitFactory({ db: projectDb }), - publishSub: publish, emitEvent: getEventBus().emit, markCommitStreamUpdated: markCommitStreamUpdatedFactory({ db: projectDb }), markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }) diff --git a/packages/server/modules/core/tests/streams.spec.ts b/packages/server/modules/core/tests/streams.spec.ts index 19ad556bc..433f69e2f 100644 --- a/packages/server/modules/core/tests/streams.spec.ts +++ b/packages/server/modules/core/tests/streams.spec.ts @@ -125,8 +125,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ insertBranchCommits: insertBranchCommitsFactory({ db }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/core/tests/users.spec.ts b/packages/server/modules/core/tests/users.spec.ts index 97edea8ba..c91e79171 100644 --- a/packages/server/modules/core/tests/users.spec.ts +++ b/packages/server/modules/core/tests/users.spec.ts @@ -60,7 +60,6 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { publish } from '@/modules/shared/utils/subscriptions' import { getUsersFactory, getUserFactory, @@ -134,8 +133,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ insertBranchCommits: insertBranchCommitsFactory({ db }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/modules/cross-server-sync/index.ts b/packages/server/modules/cross-server-sync/index.ts index 38973f6bd..3a6a30a84 100644 --- a/packages/server/modules/cross-server-sync/index.ts +++ b/packages/server/modules/cross-server-sync/index.ts @@ -152,8 +152,7 @@ const crossServerSyncModule: SpeckleModule = { insertBranchCommits: insertBranchCommitsFactory({ db }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createObject = createObjectFactory({ diff --git a/packages/server/modules/stats/tests/stats.spec.ts b/packages/server/modules/stats/tests/stats.spec.ts index b95aa737d..f9ba68fdc 100644 --- a/packages/server/modules/stats/tests/stats.spec.ts +++ b/packages/server/modules/stats/tests/stats.spec.ts @@ -50,7 +50,6 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { publish } from '@/modules/shared/utils/subscriptions' import { countAdminUsersFactory, getUserFactory, @@ -92,8 +91,7 @@ const createCommitByBranchId = createCommitByBranchIdFactory({ insertBranchCommits: insertBranchCommitsFactory({ db }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ diff --git a/packages/server/test/speckle-helpers/commitHelper.ts b/packages/server/test/speckle-helpers/commitHelper.ts index 64feac0d6..a484bf6b5 100644 --- a/packages/server/test/speckle-helpers/commitHelper.ts +++ b/packages/server/test/speckle-helpers/commitHelper.ts @@ -21,7 +21,6 @@ import { import { createObjectFactory } from '@/modules/core/services/objects/management' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import { getEventBus } from '@/modules/shared/services/eventBus' -import { publish } from '@/modules/shared/utils/subscriptions' import { BasicTestUser } from '@/test/authHelper' import { BasicTestStream } from '@/test/speckle-helpers/streamHelper' @@ -123,8 +122,7 @@ export async function createTestCommits( insertBranchCommits: insertBranchCommitsFactory({ db: projectDb }), markCommitStreamUpdated, markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db: projectDb }), - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommitByBranchName = createCommitByBranchNameFactory({ From b6871a61cc80e09c61b053acbd297adb2df5edb5 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Fri, 24 Jan 2025 10:28:39 +0200 Subject: [PATCH 08/78] chore(server): refactor activityStream invocations - batch #5 - comments --- .../activitystream/domain/operations.ts | 8 +- .../activitystream/events/commentListeners.ts | 120 +++++++++++ .../server/modules/activitystream/index.ts | 6 + .../services/commentActivity.ts | 200 ------------------ .../modules/cli/commands/download/commit.ts | 27 +-- .../modules/cli/commands/download/project.ts | 27 +-- .../server/modules/comments/domain/events.ts | 19 +- .../comments/graph/resolvers/comments.ts | 110 +++------- .../server/modules/comments/services/index.ts | 131 ++++++++++-- .../modules/comments/services/management.ts | 115 +++++++--- .../comments/tests/comments.graph.spec.js | 29 ++- .../modules/comments/tests/comments.spec.ts | 43 +++- .../server/modules/cross-server-sync/index.ts | 25 +-- .../modules/shared/utils/subscriptions.ts | 10 +- 14 files changed, 463 insertions(+), 407 deletions(-) create mode 100644 packages/server/modules/activitystream/events/commentListeners.ts delete mode 100644 packages/server/modules/activitystream/services/commentActivity.ts diff --git a/packages/server/modules/activitystream/domain/operations.ts b/packages/server/modules/activitystream/domain/operations.ts index 8bdcce5ec..480d650a4 100644 --- a/packages/server/modules/activitystream/domain/operations.ts +++ b/packages/server/modules/activitystream/domain/operations.ts @@ -253,26 +253,20 @@ export type AddCommitDeletedActivity = (params: { branchId: string }) => Promise -export type AddCommentCreatedActivity = (params: { - streamId: string - userId: string +export type AddThreadCreatedActivity = (params: { input: CommentCreatedActivityInput comment: CommentRecord }) => Promise export type AddCommentArchivedActivity = (params: { - streamId: string - commentId: string userId: string input: MutationCommentArchiveArgs comment: CommentRecord }) => Promise export type AddReplyAddedActivity = (params: { - streamId: string input: ReplyCreatedActivityInput reply: CommentRecord - userId: string }) => Promise export type AddBranchCreatedActivity = (params: { diff --git a/packages/server/modules/activitystream/events/commentListeners.ts b/packages/server/modules/activitystream/events/commentListeners.ts new file mode 100644 index 000000000..dc03d12f3 --- /dev/null +++ b/packages/server/modules/activitystream/events/commentListeners.ts @@ -0,0 +1,120 @@ +import { + AddThreadCreatedActivity, + AddReplyAddedActivity, + SaveActivity, + AddCommentArchivedActivity +} from '@/modules/activitystream/domain/operations' +import { + CommentCreatedActivityInput, + ReplyCreatedActivityInput +} from '@/modules/activitystream/domain/types' +import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' +import { CommentEvents, CommentEventsPayloads } from '@/modules/comments/domain/events' +import { ReplyCreateInput } from '@/modules/core/graph/generated/graphql' +import { EventBusListen } from '@/modules/shared/services/eventBus' +import { has } from 'lodash' +import { OverrideProperties } from 'type-fest' + +const addThreadCreatedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddThreadCreatedActivity => + async (params) => { + const { input, comment } = params + + await saveActivity({ + resourceId: comment.id, + streamId: comment.streamId, + resourceType: ResourceTypes.Comment, + actionType: ActionTypes.Comment.Create, + userId: comment.authorId, + info: { input }, + message: `Comment added: ${comment.id} (${input})` + }) + } + +const isLegacyReplyCreateInput = ( + i: ReplyCreatedActivityInput +): i is ReplyCreateInput => has(i, 'streamId') + +const addReplyAddedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddReplyAddedActivity => + async (params) => { + const { input, reply } = params + + const parentCommentId = isLegacyReplyCreateInput(input) + ? input.parentComment + : input.threadId + await saveActivity({ + streamId: reply.streamId, + resourceType: ResourceTypes.Comment, + resourceId: parentCommentId, + actionType: ActionTypes.Comment.Reply, + userId: reply.authorId, + info: { input }, + message: `Comment reply #${reply.id} created` + }) + } + +const addCommentArchivedActivityFactory = + ({ saveActivity }: { saveActivity: SaveActivity }): AddCommentArchivedActivity => + async (params) => { + const { userId, input, comment } = params + + await saveActivity({ + streamId: comment.streamId, + resourceType: ResourceTypes.Comment, + resourceId: comment.id, + actionType: ActionTypes.Comment.Archive, + userId, + info: { input }, + message: `Comment #${comment.id} archived` + }) + } + +const isReplyCreatedPayload = ( + payload: CommentEventsPayloads[typeof CommentEvents.Created] +): payload is OverrideProperties< + CommentEventsPayloads[typeof CommentEvents.Created], + { + input: ReplyCreatedActivityInput + } +> => { + return payload.isThread === false +} + +const isThreadCreatedPayload = ( + payload: CommentEventsPayloads[typeof CommentEvents.Created] +): payload is OverrideProperties< + CommentEventsPayloads[typeof CommentEvents.Created], + { + input: CommentCreatedActivityInput + } +> => { + return payload.isThread +} + +export const reportCommentActivityFactory = + (deps: { eventListen: EventBusListen; saveActivity: SaveActivity }) => () => { + const addThreadCreatedActivity = addThreadCreatedActivityFactory(deps) + const addReplyAddedActivity = addReplyAddedActivityFactory(deps) + const addCommentArchivedActivity = addCommentArchivedActivityFactory(deps) + + const quitters = [ + deps.eventListen(CommentEvents.Created, async ({ payload }) => { + if (isReplyCreatedPayload(payload)) { + await addReplyAddedActivity({ + reply: payload.comment, + input: payload.input + }) + } else if (isThreadCreatedPayload(payload)) { + await addThreadCreatedActivity(payload) + } + }), + deps.eventListen(CommentEvents.Archived, async ({ payload }) => { + await addCommentArchivedActivity(payload) + }) + ] + + return () => { + quitters.forEach((quit) => quit()) + } + } diff --git a/packages/server/modules/activitystream/index.ts b/packages/server/modules/activitystream/index.ts index 62596a91b..883e9b3a4 100644 --- a/packages/server/modules/activitystream/index.ts +++ b/packages/server/modules/activitystream/index.ts @@ -30,6 +30,7 @@ import { reportUserActivityFactory } from '@/modules/activitystream/events/userL import { reportAccessRequestActivityFactory } from '@/modules/activitystream/events/accessRequestListeners' import { reportBranchActivityFactory } from '@/modules/activitystream/events/branchListeners' import { reportCommitActivityFactory } from '@/modules/activitystream/events/commitListeners' +import { reportCommentActivityFactory } from '@/modules/activitystream/events/commentListeners' let scheduledTask: ReturnType | null = null let quitEventListeners: Optional<() => void> = undefined @@ -62,12 +63,17 @@ const initializeEventListeners = ({ eventListen: eventBus.listen, saveActivity }) + const reportCommentActivity = reportCommentActivityFactory({ + eventListen: eventBus.listen, + saveActivity + }) const quitCbs = [ reportUserActivity(), reportAccessRequestActivity(), reportBranchActivity(), reportCommitActivity(), + reportCommentActivity(), eventBus.listen(ServerInvitesEvents.Created, async ({ payload }) => { if (!isProjectResourceTarget(payload.invite.resource)) return await onServerInviteCreatedFactory({ diff --git a/packages/server/modules/activitystream/services/commentActivity.ts b/packages/server/modules/activitystream/services/commentActivity.ts deleted file mode 100644 index 7b9807318..000000000 --- a/packages/server/modules/activitystream/services/commentActivity.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { - AddCommentArchivedActivity, - AddCommentCreatedActivity, - AddReplyAddedActivity, - SaveActivity -} from '@/modules/activitystream/domain/operations' -import { - CommentCreatedActivityInput, - ReplyCreatedActivityInput -} from '@/modules/activitystream/domain/types' -import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' -import { - GetViewerResourceItemsUngrouped, - GetViewerResourcesForComment, - GetViewerResourcesFromLegacyIdentifiers -} from '@/modules/comments/domain/operations' -import { ViewerResourceItem } from '@/modules/comments/domain/types' -import { - CommentCreateInput, - ProjectCommentsUpdatedMessageType, - ReplyCreateInput -} from '@/modules/core/graph/generated/graphql' -import { PublishSubscription, pubsub } from '@/modules/shared/utils/subscriptions' -import { - CommentSubscriptions, - ProjectSubscriptions -} from '@/modules/shared/utils/subscriptions' -import { has } from 'lodash' - -const isLegacyCommentCreateInput = ( - i: CommentCreatedActivityInput -): i is CommentCreateInput => has(i, 'streamId') - -export const addCommentCreatedActivityFactory = - ({ - getViewerResourceItemsUngrouped, - getViewerResourcesFromLegacyIdentifiers, - saveActivity, - publish - }: { - getViewerResourceItemsUngrouped: GetViewerResourceItemsUngrouped - getViewerResourcesFromLegacyIdentifiers: GetViewerResourcesFromLegacyIdentifiers - saveActivity: SaveActivity - publish: PublishSubscription - }): AddCommentCreatedActivity => - async (params) => { - const { streamId, userId, input, comment } = params - - let resourceIds: string - let resourceItems: ViewerResourceItem[] - if (isLegacyCommentCreateInput(input)) { - resourceIds = input.resources.map((res) => res?.resourceId).join(',') - - const validResources = input.resources.filter( - (r): r is NonNullable => !!r - ) - resourceItems = await getViewerResourcesFromLegacyIdentifiers( - streamId, - validResources - ) - } else { - resourceItems = - input.resolvedResourceItems || - (await getViewerResourceItemsUngrouped({ - projectId: streamId, - resourceIdString: input.resourceIdString - })) - resourceIds = resourceItems.map((i) => i.versionId || i.objectId).join(',') - } - - await Promise.all([ - saveActivity({ - resourceId: comment.id, - streamId, - resourceType: ResourceTypes.Comment, - actionType: ActionTypes.Comment.Create, - userId, - info: { input }, - message: `Comment added: ${comment.id} (${input})` - }), - // @deprecated unused in FE2 - pubsub.publish(CommentSubscriptions.CommentActivity, { - commentActivity: { - type: 'comment-added', - comment - }, - streamId, - resourceIds - }), - publish(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: comment.id, - type: ProjectCommentsUpdatedMessageType.Created, - comment - }, - projectId: streamId, - resourceItems - }) - ]) - } - -/** - * Add comment archived/unarchived activity - */ -export const addCommentArchivedActivityFactory = - ({ - getViewerResourcesForComment, - saveActivity, - publish - }: { - getViewerResourcesForComment: GetViewerResourcesForComment - publish: PublishSubscription - saveActivity: SaveActivity - }): AddCommentArchivedActivity => - async (params) => { - const { streamId, commentId, userId, input, comment } = params - const isArchiving = !!input.archived - - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Comment, - resourceId: commentId, - actionType: ActionTypes.Comment.Archive, - userId, - info: { input }, - message: `Comment #${commentId} archived` - }), - // @deprecated not used in FE2 - pubsub.publish(CommentSubscriptions.CommentThreadActivity, { - commentThreadActivity: { - type: isArchiving ? 'comment-archived' : 'comment-added' - }, - streamId: input.streamId, - commentId: input.commentId - }), - publish(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: commentId, - type: isArchiving - ? ProjectCommentsUpdatedMessageType.Archived - : ProjectCommentsUpdatedMessageType.Created, - comment: isArchiving ? null : comment - }, - projectId: streamId, - resourceItems: await getViewerResourcesForComment(streamId, comment.id) - }) - ]) - } - -const isLegacyReplyCreateInput = ( - i: ReplyCreatedActivityInput -): i is ReplyCreateInput => has(i, 'streamId') - -export const addReplyAddedActivityFactory = - ({ - getViewerResourcesForComment, - saveActivity, - publish - }: { - getViewerResourcesForComment: GetViewerResourcesForComment - publish: PublishSubscription - saveActivity: SaveActivity - }): AddReplyAddedActivity => - async (params) => { - const { streamId, input, reply, userId } = params - - const parentCommentId = isLegacyReplyCreateInput(input) - ? input.parentComment - : input.threadId - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Comment, - resourceId: parentCommentId, - actionType: ActionTypes.Comment.Reply, - userId, - info: { input }, - message: `Comment reply #${reply.id} created` - }), - // @deprecated - pubsub.publish(CommentSubscriptions.CommentThreadActivity, { - commentThreadActivity: { - type: 'reply-added', - reply - }, - streamId, - commentId: parentCommentId - }), - publish(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: reply.id, - type: ProjectCommentsUpdatedMessageType.Created, - comment: reply - }, - projectId: streamId, - resourceItems: await getViewerResourcesForComment(streamId, reply.id) - }) - ]) - } diff --git a/packages/server/modules/cli/commands/download/commit.ts b/packages/server/modules/cli/commands/download/commit.ts index 5640670d0..c977d772f 100644 --- a/packages/server/modules/cli/commands/download/commit.ts +++ b/packages/server/modules/cli/commands/download/commit.ts @@ -46,19 +46,14 @@ import { markCommentUpdatedFactory, markCommentViewedFactory } from '@/modules/comments/repositories/comments' -import { - addCommentCreatedActivityFactory, - addReplyAddedActivityFactory -} from '@/modules/activitystream/services/commentActivity' import { validateInputAttachmentsFactory } from '@/modules/comments/services/commentTextService' import { getBlobsFactory } from '@/modules/blobstorage/repositories' import { createCommitByBranchIdFactory } from '@/modules/core/services/commit/management' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' import { getUserFactory } from '@/modules/core/repositories/users' import { createObjectFactory } from '@/modules/core/services/objects/management' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' -import { db, mainDb } from '@/db/knex' +import { db } from '@/db/knex' import { getEventBus } from '@/modules/shared/services/eventBus' const command: CommandModule< @@ -143,12 +138,7 @@ const command: CommandModule< insertCommentLinks, markCommentViewed, emitEvent: getEventBus().emit, - addCommentCreatedActivity: addCommentCreatedActivityFactory({ - getViewerResourcesFromLegacyIdentifiers, - getViewerResourceItemsUngrouped, - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + publishSub: publish }) const createCommentReplyAndNotify = createCommentReplyAndNotifyFactory({ @@ -158,14 +148,11 @@ const command: CommandModule< insertCommentLinks, markCommentUpdated: markCommentUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - addReplyAddedActivity: addReplyAddedActivityFactory({ - getViewerResourcesForComment: getViewerResourcesForCommentFactory({ - getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), - getViewerResourcesFromLegacyIdentifiers - }), - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + getViewerResourcesForComment: getViewerResourcesForCommentFactory({ + getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), + getViewerResourcesFromLegacyIdentifiers + }), + publishSub: publish }) const createCommitByBranchId = createCommitByBranchIdFactory({ diff --git a/packages/server/modules/cli/commands/download/project.ts b/packages/server/modules/cli/commands/download/project.ts index e65e4191c..53c63653a 100644 --- a/packages/server/modules/cli/commands/download/project.ts +++ b/packages/server/modules/cli/commands/download/project.ts @@ -27,10 +27,6 @@ import { createCommentThreadAndNotifyFactory } from '@/modules/comments/services/management' import { createBranchAndNotifyFactory } from '@/modules/core/services/branch/management' -import { - addCommentCreatedActivityFactory, - addReplyAddedActivityFactory -} from '@/modules/activitystream/services/commentActivity' import { createCommitFactory, getAllBranchCommitsFactory, @@ -46,7 +42,7 @@ import { getViewerResourcesForCommentsFactory, getViewerResourcesFromLegacyIdentifiersFactory } from '@/modules/core/services/commit/viewerResources' -import { db, mainDb } from '@/db/knex' +import { db } from '@/db/knex' import { getCommentFactory, getCommentsResourcesFactory, @@ -57,7 +53,6 @@ import { } from '@/modules/comments/repositories/comments' import { getBlobsFactory } from '@/modules/blobstorage/repositories' import { validateInputAttachmentsFactory } from '@/modules/comments/services/commentTextService' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' import { getUserFactory } from '@/modules/core/repositories/users' import { createObjectFactory } from '@/modules/core/services/objects/management' @@ -168,12 +163,7 @@ const command: CommandModule< insertCommentLinks, markCommentViewed, emitEvent: getEventBus().emit, - addCommentCreatedActivity: addCommentCreatedActivityFactory({ - getViewerResourcesFromLegacyIdentifiers, - getViewerResourceItemsUngrouped, - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + publishSub: publish }) const createCommentReplyAndNotify = createCommentReplyAndNotifyFactory({ getComment: getCommentFactory({ db: projectDb }), @@ -182,14 +172,11 @@ const command: CommandModule< insertCommentLinks, markCommentUpdated: markCommentUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - addReplyAddedActivity: addReplyAddedActivityFactory({ - getViewerResourcesForComment: getViewerResourcesForCommentFactory({ - getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), - getViewerResourcesFromLegacyIdentifiers - }), - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + getViewerResourcesForComment: getViewerResourcesForCommentFactory({ + getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), + getViewerResourcesFromLegacyIdentifiers + }), + publishSub: publish }) const createCommitByBranchId = createCommitByBranchIdFactory({ diff --git a/packages/server/modules/comments/domain/events.ts b/packages/server/modules/comments/domain/events.ts index 30f73395e..428912318 100644 --- a/packages/server/modules/comments/domain/events.ts +++ b/packages/server/modules/comments/domain/events.ts @@ -1,16 +1,31 @@ +import { + CommentCreatedActivityInput, + ReplyCreatedActivityInput +} from '@/modules/activitystream/domain/types' import { CommentRecord } from '@/modules/comments/helpers/types' +import { MutationCommentArchiveArgs } from '@/modules/core/graph/generated/graphql' export const commentEventsNamespace = 'comments' as const export const CommentEvents = { Created: `${commentEventsNamespace}.created`, - Updated: `${commentEventsNamespace}.updated` + Updated: `${commentEventsNamespace}.updated`, + Archived: `${commentEventsNamespace}.archived` } as const export type CommentEventsPayloads = { - [CommentEvents.Created]: { comment: CommentRecord } + [CommentEvents.Created]: { + comment: CommentRecord + isThread: boolean + input: CommentCreatedActivityInput | ReplyCreatedActivityInput + } [CommentEvents.Updated]: { previousComment: CommentRecord newComment: CommentRecord } + [CommentEvents.Archived]: { + userId: string + input: MutationCommentArchiveArgs + comment: CommentRecord + } } diff --git a/packages/server/modules/comments/graph/resolvers/comments.ts b/packages/server/modules/comments/graph/resolvers/comments.ts index f6f97da06..7164e1626 100644 --- a/packages/server/modules/comments/graph/resolvers/comments.ts +++ b/packages/server/modules/comments/graph/resolvers/comments.ts @@ -49,11 +49,6 @@ import { filteredSubscribe, ProjectSubscriptions } from '@/modules/shared/utils/subscriptions' -import { - addCommentArchivedActivityFactory, - addCommentCreatedActivityFactory, - addReplyAddedActivityFactory -} from '@/modules/activitystream/services/commentActivity' import { doViewerResourcesFit, getViewerResourcesForCommentFactory, @@ -95,7 +90,6 @@ import { } from '@/modules/core/repositories/branches' import { getStreamObjectsFactory } from '@/modules/core/repositories/objects' import { getStreamFactory } from '@/modules/core/repositories/streams' -import { saveActivityFactory } from '@/modules/activitystream/repositories' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import { Knex } from 'knex' import { getEventBus } from '@/modules/shared/services/eventBus' @@ -533,8 +527,6 @@ export = { const getViewerResourceItemsUngrouped = buildGetViewerResourceItemsUngrouped({ db: projectDb }) - const getViewerResourcesFromLegacyIdentifiers = - buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) const validateInputAttachments = validateInputAttachmentsFactory({ getBlobs: getBlobsFactory({ db: projectDb }) @@ -550,12 +542,7 @@ export = { insertCommentLinks, markCommentViewed, emitEvent: getEventBus().emit, - addCommentCreatedActivity: addCommentCreatedActivityFactory({ - getViewerResourcesFromLegacyIdentifiers, - getViewerResourceItemsUngrouped, - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + publishSub: publish }) return await createCommentThreadAndNotify(args.input, ctx.userId!) @@ -588,14 +575,11 @@ export = { insertCommentLinks, markCommentUpdated: markCommentUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - addReplyAddedActivity: addReplyAddedActivityFactory({ - getViewerResourcesForComment: getViewerResourcesForCommentFactory({ - getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), - getViewerResourcesFromLegacyIdentifiers - }), - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + getViewerResourcesForComment: getViewerResourcesForCommentFactory({ + getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), + getViewerResourcesFromLegacyIdentifiers + }), + publishSub: publish }) return await createCommentReplyAndNotify(args.input, ctx.userId!) @@ -656,11 +640,9 @@ export = { getComment, getStream, updateComment, - addCommentArchivedActivity: addCommentArchivedActivityFactory({ - getViewerResourcesForComment, - saveActivity: saveActivityFactory({ db: mainDb }), - publish - }) + getViewerResourcesForComment, + publishSub: publish, + emitEvent: getEventBus().emit }) await archiveCommentAndNotify( @@ -740,6 +722,8 @@ export = { throw new ForbiddenError('You are not authorized.') const projectDb = await getProjectDbClient({ projectId: args.input.streamId }) + const getViewerResourcesFromLegacyIdentifiers = + buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) const createComment = createCommentFactory({ checkStreamResourcesAccess: streamResourceCheckFactory({ @@ -752,31 +736,15 @@ export = { insertCommentLinks: insertCommentLinksFactory({ db: projectDb }), deleteComment: deleteCommentFactory({ db: projectDb }), markCommentViewed: markCommentViewedFactory({ db: projectDb }), - emitEvent: getEventBus().emit + emitEvent: getEventBus().emit, + publishSub: publish, + getViewerResourcesFromLegacyIdentifiers }) const comment = await createComment({ userId: context.userId, input: args.input }) - const getViewerResourceItemsUngrouped = buildGetViewerResourceItemsUngrouped({ - db: projectDb - }) - const getViewerResourcesFromLegacyIdentifiers = - buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) - - await addCommentCreatedActivityFactory({ - getViewerResourceItemsUngrouped, - getViewerResourcesFromLegacyIdentifiers, - saveActivity: saveActivityFactory({ db: mainDb }), - publish - })({ - streamId: args.input.streamId, - userId: context.userId, - input: args.input, - comment - }) - return comment.id }, @@ -825,30 +793,22 @@ export = { }) const projectDb = await getProjectDbClient({ projectId: args.streamId }) - const archiveComment = archiveCommentFactory({ - getComment: getCommentFactory({ db: projectDb }), - getStream, - updateComment: updateCommentFactory({ db: projectDb }) - }) - const updatedComment = await archiveComment({ ...args, userId: context.userId! }) // NOTE: permissions check inside service - const getViewerResourcesFromLegacyIdentifiers = buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) const getViewerResourcesForComment = getViewerResourcesForCommentFactory({ getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), getViewerResourcesFromLegacyIdentifiers }) - await addCommentArchivedActivityFactory({ - getViewerResourcesForComment, - saveActivity: saveActivityFactory({ db: mainDb }), - publish - })({ - streamId: args.streamId, - commentId: args.commentId, - userId: context.userId!, - input: args, - comment: updatedComment + + const archiveComment = archiveCommentFactory({ + getComment: getCommentFactory({ db: projectDb }), + getStream, + updateComment: updateCommentFactory({ db: projectDb }), + emitEvent: getEventBus().emit, + publishSub: publish, + getViewerResourcesForComment }) + await archiveComment({ ...args, userId: context.userId! }) // NOTE: permissions check inside service return true }, @@ -878,7 +838,13 @@ export = { }), deleteComment: deleteCommentFactory({ db: projectDb }), markCommentUpdated: markCommentUpdatedFactory({ db: projectDb }), - emitEvent: getEventBus().emit + emitEvent: getEventBus().emit, + publishSub: publish, + getViewerResourcesForComment: getViewerResourcesForCommentFactory({ + getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), + getViewerResourcesFromLegacyIdentifiers: + buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) + }) }) const reply = await createCommentReply({ authorId: context.userId, @@ -889,22 +855,6 @@ export = { blobIds: args.input.blobIds }) - const getViewerResourcesFromLegacyIdentifiers = - buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) - await addReplyAddedActivityFactory({ - getViewerResourcesForComment: getViewerResourcesForCommentFactory({ - getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), - getViewerResourcesFromLegacyIdentifiers - }), - saveActivity: saveActivityFactory({ db: mainDb }), - publish - })({ - streamId: args.input.streamId, - input: args.input, - reply, - userId: context.userId - }) - return reply.id } }, diff --git a/packages/server/modules/comments/services/index.ts b/packages/server/modules/comments/services/index.ts index 5c0fc9bd9..d6a7ecc6d 100644 --- a/packages/server/modules/comments/services/index.ts +++ b/packages/server/modules/comments/services/index.ts @@ -5,7 +5,8 @@ import { isNonNullable, Roles } from '@speckle/shared' import { ResourceIdentifier, CommentCreateInput, - CommentEditInput + CommentEditInput, + ProjectCommentsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' import { CommentLinkRecord, CommentRecord } from '@/modules/comments/helpers/types' import { SmartTextEditorValueSchema } from '@/modules/core/services/richTextEditorService' @@ -14,6 +15,8 @@ import { CheckStreamResourcesAccess, DeleteComment, GetComment, + GetViewerResourcesForComment, + GetViewerResourcesFromLegacyIdentifiers, InsertCommentLinks, InsertComments, MarkCommentUpdated, @@ -26,6 +29,11 @@ import { GetStream } from '@/modules/core/domain/streams/operations' import { EventBusEmit } from '@/modules/shared/services/eventBus' import { CommentEvents } from '@/modules/comments/domain/events' import { JSONContent } from '@tiptap/core' +import { + CommentSubscriptions, + ProjectSubscriptions, + PublishSubscription +} from '@/modules/shared/utils/subscriptions' export const streamResourceCheckFactory = (deps: { @@ -56,6 +64,8 @@ export const createCommentFactory = deleteComment: DeleteComment markCommentViewed: MarkCommentViewed emitEvent: EventBusEmit + publishSub: PublishSubscription + getViewerResourcesFromLegacyIdentifiers: GetViewerResourcesFromLegacyIdentifiers }) => async ({ userId, input }: { userId: string; input: CommentCreateInput }) => { if (input.resources.length < 1) @@ -116,12 +126,37 @@ export const createCommentFactory = await deps.markCommentViewed(id, userId) // so we don't self mark a comment as unread the moment it's created - await deps.emitEvent({ - eventName: CommentEvents.Created, - payload: { - comment: newComment - } - }) + await Promise.all([ + deps.emitEvent({ + eventName: CommentEvents.Created, + payload: { + comment: newComment, + input, + isThread: true + } + }), + // @deprecated unused in FE2 + deps.publishSub(CommentSubscriptions.CommentActivity, { + commentActivity: { + type: 'comment-added', + comment: newComment + }, + streamId: input.streamId, + resourceIds: input.resources.map((res) => res?.resourceId).join(',') + }), + deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { + projectCommentsUpdated: { + id: newComment.id, + type: ProjectCommentsUpdatedMessageType.Created, + comment: newComment + }, + projectId: input.streamId, + resourceItems: await deps.getViewerResourcesFromLegacyIdentifiers( + input.streamId, + input.resources.filter(isNonNullable) + ) + }) + ]) return newComment } @@ -138,6 +173,8 @@ export const createCommentReplyFactory = deleteComment: DeleteComment markCommentUpdated: MarkCommentUpdated emitEvent: EventBusEmit + publishSub: PublishSubscription + getViewerResourcesForComment: GetViewerResourcesForComment }) => async ({ authorId, @@ -188,12 +225,45 @@ export const createCommentReplyFactory = await deps.markCommentUpdated(parentCommentId) - await deps.emitEvent({ - eventName: CommentEvents.Created, - payload: { - comment: newComment - } - }) + await Promise.all([ + deps.emitEvent({ + eventName: CommentEvents.Created, + payload: { + comment: newComment, + isThread: false, + input: { + threadId: parentCommentId, + projectId: streamId, + content: { + blobIds, + doc: text + } + } + } + }), + // TODO: Move to event bus listeners + // @deprecated + deps.publishSub(CommentSubscriptions.CommentThreadActivity, { + commentThreadActivity: { + type: 'reply-added', + reply: newComment + }, + streamId: newComment.streamId, + commentId: parentCommentId + }), + deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { + projectCommentsUpdated: { + id: newComment.id, + type: ProjectCommentsUpdatedMessageType.Created, + comment: newComment + }, + projectId: newComment.streamId, + resourceItems: await deps.getViewerResourcesForComment( + newComment.streamId, + newComment.id + ) + }) + ]) return newComment } @@ -249,6 +319,9 @@ export const archiveCommentFactory = getComment: GetComment getStream: GetStream updateComment: UpdateComment + emitEvent: EventBusEmit + publishSub: PublishSubscription + getViewerResourcesForComment: GetViewerResourcesForComment }) => async ({ commentId, @@ -275,5 +348,37 @@ export const archiveCommentFactory = } const updatedComment = await deps.updateComment(commentId, { archived }) + + await Promise.all([ + deps.emitEvent({ + eventName: CommentEvents.Archived, + payload: { + userId, + input: { archived, commentId, streamId }, + comment: updatedComment! + } + }), + // TODO: Move to event bus listeners + // @deprecated not used in FE2 + deps.publishSub(CommentSubscriptions.CommentThreadActivity, { + commentThreadActivity: { + type: archived ? 'comment-archived' : 'comment-added' + }, + streamId, + commentId + }), + deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { + projectCommentsUpdated: { + id: commentId, + type: archived + ? ProjectCommentsUpdatedMessageType.Archived + : ProjectCommentsUpdatedMessageType.Created, + comment: archived ? null : comment + }, + projectId: streamId, + resourceItems: await deps.getViewerResourcesForComment(streamId, comment.id) + }) + ]) + return updatedComment! } diff --git a/packages/server/modules/comments/services/management.ts b/packages/server/modules/comments/services/management.ts index 0836579b4..fabc211ab 100644 --- a/packages/server/modules/comments/services/management.ts +++ b/packages/server/modules/comments/services/management.ts @@ -5,7 +5,8 @@ import { StreamInvalidAccessError } from '@/modules/core/errors/stream' import { CreateCommentInput, CreateCommentReplyInput, - EditCommentInput + EditCommentInput, + ProjectCommentsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' import { CommentCreateError, CommentUpdateError } from '@/modules/comments/errors' import { buildCommentTextFromInput } from '@/modules/comments/services/commentTextService' @@ -26,6 +27,7 @@ import { EditCommentAndNotify, GetComment, GetViewerResourceItemsUngrouped, + GetViewerResourcesForComment, InsertCommentLinks, InsertCommentPayload, InsertComments, @@ -35,13 +37,13 @@ import { ValidateInputAttachments } from '@/modules/comments/domain/operations' import { GetStream } from '@/modules/core/domain/streams/operations' -import { - AddCommentArchivedActivity, - AddCommentCreatedActivity, - AddReplyAddedActivity -} from '@/modules/activitystream/domain/operations' import { EventBusEmit } from '@/modules/shared/services/eventBus' import { CommentEvents } from '@/modules/comments/domain/events' +import { + CommentSubscriptions, + ProjectSubscriptions, + PublishSubscription +} from '@/modules/shared/utils/subscriptions' type AuthorizeProjectCommentsAccessDeps = { getStream: GetStream @@ -118,7 +120,7 @@ export const createCommentThreadAndNotifyFactory = insertCommentLinks: InsertCommentLinks markCommentViewed: MarkCommentViewed emitEvent: EventBusEmit - addCommentCreatedActivity: AddCommentCreatedActivity + publishSub: PublishSubscription }): CreateCommentThreadAndNotify => async (input: CreateCommentInput, userId: string) => { const [resources] = await Promise.all([ @@ -180,17 +182,29 @@ export const createCommentThreadAndNotifyFactory = deps.emitEvent({ eventName: CommentEvents.Created, payload: { - comment + comment, + input, + isThread: true } }), - deps.addCommentCreatedActivity({ - streamId: input.projectId, - userId, - input: { - ...input, - resolvedResourceItems: resources + // TODO: Move to event bus listeners + // @deprecated unused in FE2 + deps.publishSub(CommentSubscriptions.CommentActivity, { + commentActivity: { + type: 'comment-added', + comment }, - comment + streamId: input.projectId, + resourceIds: resources.map((i) => i.versionId || i.objectId).join(',') + }), + deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { + projectCommentsUpdated: { + id: comment.id, + type: ProjectCommentsUpdatedMessageType.Created, + comment + }, + projectId: input.projectId, + resourceItems: resources }) ]) @@ -205,7 +219,8 @@ export const createCommentReplyAndNotifyFactory = insertCommentLinks: InsertCommentLinks markCommentUpdated: MarkCommentUpdated emitEvent: EventBusEmit - addReplyAddedActivity: AddReplyAddedActivity + publishSub: PublishSubscription + getViewerResourcesForComment: GetViewerResourcesForComment }): CreateCommentReplyAndNotify => async (input: CreateCommentReplyInput, userId: string) => { const thread = await deps.getComment({ id: input.threadId, userId }) @@ -244,14 +259,29 @@ export const createCommentReplyAndNotifyFactory = deps.emitEvent({ eventName: CommentEvents.Created, payload: { - comment: reply + comment: reply, + input, + isThread: false } }), - deps.addReplyAddedActivity({ - streamId: thread.streamId, - input, - reply, - userId + // TODO: Move to event bus listeners + // @deprecated + deps.publishSub(CommentSubscriptions.CommentThreadActivity, { + commentThreadActivity: { + type: 'reply-added', + reply + }, + streamId: reply.streamId, + commentId: thread.id + }), + deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { + projectCommentsUpdated: { + id: reply.id, + type: ProjectCommentsUpdatedMessageType.Created, + comment: reply + }, + projectId: reply.streamId, + resourceItems: await deps.getViewerResourcesForComment(reply.streamId, reply.id) }) ]) @@ -298,7 +328,9 @@ export const archiveCommentAndNotifyFactory = getComment: GetComment getStream: GetStream updateComment: UpdateComment - addCommentArchivedActivity: AddCommentArchivedActivity + emitEvent: EventBusEmit + publishSub: PublishSubscription + getViewerResourcesForComment: GetViewerResourcesForComment }): ArchiveCommentAndNotify => async (commentId: string, userId: string, archived = true) => { const comment = await deps.getComment({ id: commentId, userId }) @@ -321,17 +353,36 @@ export const archiveCommentAndNotifyFactory = archived }) - await deps.addCommentArchivedActivity({ - streamId: stream.id, - commentId, - userId, - input: { - archived, + await Promise.all([ + deps.emitEvent({ + eventName: CommentEvents.Archived, + payload: { + userId, + input: { archived, commentId, streamId: stream.id }, + comment: updatedComment! + } + }), + // TODO: Move to event bus listeners + // @deprecated not used in FE2 + deps.publishSub(CommentSubscriptions.CommentThreadActivity, { + commentThreadActivity: { + type: archived ? 'comment-archived' : 'comment-added' + }, streamId: stream.id, commentId - }, - comment: updatedComment! - }) + }), + deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { + projectCommentsUpdated: { + id: commentId, + type: archived + ? ProjectCommentsUpdatedMessageType.Archived + : ProjectCommentsUpdatedMessageType.Created, + comment: archived ? null : comment + }, + projectId: stream.id, + resourceItems: await deps.getViewerResourcesForComment(stream.id, comment.id) + }) + ]) return updatedComment } diff --git a/packages/server/modules/comments/tests/comments.graph.spec.js b/packages/server/modules/comments/tests/comments.graph.spec.js index 1f09bcdd3..cf35a3ffd 100644 --- a/packages/server/modules/comments/tests/comments.graph.spec.js +++ b/packages/server/modules/comments/tests/comments.graph.spec.js @@ -22,7 +22,8 @@ const { markCommentViewedFactory, insertCommentsFactory, insertCommentLinksFactory, - deleteCommentFactory + deleteCommentFactory, + getCommentsResourcesFactory } = require('@/modules/comments/repositories/comments') const { db } = require('@/db/knex') const { @@ -36,7 +37,8 @@ const { const { createCommitFactory, insertStreamCommitsFactory, - insertBranchCommitsFactory + insertBranchCommitsFactory, + getCommitsAndTheirBranchIdsFactory } = require('@/modules/core/repositories/commits') const { getBranchByIdFactory, @@ -54,7 +56,8 @@ const { const { getObjectFactory, storeSingleObjectIfNotFoundFactory, - storeClosuresIfNotFoundFactory + storeClosuresIfNotFoundFactory, + getStreamObjectsFactory } = require('@/modules/core/repositories/objects') const { legacyCreateStreamFactory, @@ -109,6 +112,10 @@ const { } = require('@/modules/serverinvites/services/processing') const { getServerInfoFactory } = require('@/modules/core/repositories/server') const { createObjectFactory } = require('@/modules/core/services/objects/management') +const { + getViewerResourcesFromLegacyIdentifiersFactory, + getViewerResourcesForCommentsFactory +} = require('@/modules/core/services/commit/viewerResources') const getServerInfo = getServerInfoFactory({ db }) const getUser = getUserFactory({ db }) @@ -118,6 +125,18 @@ const streamResourceCheck = streamResourceCheckFactory({ checkStreamResourceAccess: checkStreamResourceAccessFactory({ db }) }) const markCommentViewed = markCommentViewedFactory({ db }) + +const getViewerResourcesFromLegacyIdentifiers = + getViewerResourcesFromLegacyIdentifiersFactory({ + getViewerResourcesForComments: getViewerResourcesForCommentsFactory({ + getCommentsResources: getCommentsResourcesFactory({ db }), + getViewerResourcesFromLegacyIdentifiers: (...args) => + getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep + }), + getCommitsAndTheirBranchIds: getCommitsAndTheirBranchIdsFactory({ db }), + getStreamObjects: getStreamObjectsFactory({ db }) + }) + const createComment = createCommentFactory({ checkStreamResourcesAccess: streamResourceCheck, validateInputAttachments: validateInputAttachmentsFactory({ @@ -127,7 +146,9 @@ const createComment = createCommentFactory({ insertCommentLinks: insertCommentLinksFactory({ db }), deleteComment: deleteCommentFactory({ db }), markCommentViewed, - emitEvent: getEventBus().emit + emitEvent: getEventBus().emit, + publishSub: publish, + getViewerResourcesFromLegacyIdentifiers }) const getObject = getObjectFactory({ db }) diff --git a/packages/server/modules/comments/tests/comments.spec.ts b/packages/server/modules/comments/tests/comments.spec.ts index 0a20f3d56..cf235e181 100644 --- a/packages/server/modules/comments/tests/comments.spec.ts +++ b/packages/server/modules/comments/tests/comments.spec.ts @@ -43,7 +43,8 @@ import { updateCommentFactory, getCommentsLegacyFactory, getResourceCommentCountFactory, - getStreamCommentCountFactory + getStreamCommentCountFactory, + getCommentsResourcesFactory } from '@/modules/comments/repositories/comments' import { db } from '@/db/knex' import { getBlobsFactory } from '@/modules/blobstorage/repositories' @@ -59,7 +60,8 @@ import { import { createCommitFactory, insertStreamCommitsFactory, - insertBranchCommitsFactory + insertBranchCommitsFactory, + getCommitsAndTheirBranchIdsFactory } from '@/modules/core/repositories/commits' import { getBranchByIdFactory, @@ -70,7 +72,8 @@ import { import { getObjectFactory, storeSingleObjectIfNotFoundFactory, - storeClosuresIfNotFoundFactory + storeClosuresIfNotFoundFactory, + getStreamObjectsFactory } from '@/modules/core/repositories/objects' import { legacyCreateStreamFactory, @@ -118,6 +121,11 @@ import { import { CommentRecord } from '@/modules/comments/helpers/types' import { MaybeNullOrUndefined } from '@speckle/shared' import { CommentEvents } from '@/modules/comments/domain/events' +import { + getViewerResourcesForCommentFactory, + getViewerResourcesForCommentsFactory, + getViewerResourcesFromLegacyIdentifiersFactory +} from '@/modules/core/services/commit/viewerResources' type LegacyCommentRecord = CommentRecord & { total_count: string @@ -138,6 +146,17 @@ const validateInputAttachments = validateInputAttachmentsFactory({ const insertComments = insertCommentsFactory({ db }) const insertCommentLinks = insertCommentLinksFactory({ db }) const deleteComment = deleteCommentFactory({ db }) + +const getViewerResourcesFromLegacyIdentifiers = + getViewerResourcesFromLegacyIdentifiersFactory({ + getViewerResourcesForComments: getViewerResourcesForCommentsFactory({ + getCommentsResources: getCommentsResourcesFactory({ db }), + getViewerResourcesFromLegacyIdentifiers: (...args) => + getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep + }), + getCommitsAndTheirBranchIds: getCommitsAndTheirBranchIdsFactory({ db }), + getStreamObjects: getStreamObjectsFactory({ db }) + }) const createComment = createCommentFactory({ checkStreamResourcesAccess: streamResourceCheck, validateInputAttachments, @@ -145,7 +164,14 @@ const createComment = createCommentFactory({ insertCommentLinks, deleteComment, markCommentViewed, - emitEvent: getEventBus().emit + emitEvent: getEventBus().emit, + publishSub: publish, + getViewerResourcesFromLegacyIdentifiers +}) +const getViewerResourcesForComment = getViewerResourcesForCommentFactory({ + getCommentsResources: getCommentsResourcesFactory({ db }), + getViewerResourcesFromLegacyIdentifiers: (...args) => + getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep }) const createCommentReply = createCommentReplyFactory({ validateInputAttachments, @@ -154,7 +180,9 @@ const createCommentReply = createCommentReplyFactory({ checkStreamResourcesAccess: streamResourceCheck, deleteComment, markCommentUpdated: markCommentUpdatedFactory({ db }), - emitEvent: getEventBus().emit + emitEvent: getEventBus().emit, + publishSub: publish, + getViewerResourcesForComment }) const getComment = getCommentFactory({ db }) const updateComment = updateCommentFactory({ db }) @@ -167,7 +195,10 @@ const editComment = editCommentFactory({ const archiveComment = archiveCommentFactory({ getComment, getStream, - updateComment + updateComment, + emitEvent: getEventBus().emit, + publishSub: publish, + getViewerResourcesForComment }) const getComments = getCommentsLegacyFactory({ db }) const getResourceCommentCount = getResourceCommentCountFactory({ db }) diff --git a/packages/server/modules/cross-server-sync/index.ts b/packages/server/modules/cross-server-sync/index.ts index 3a6a30a84..d2c28ea2a 100644 --- a/packages/server/modules/cross-server-sync/index.ts +++ b/packages/server/modules/cross-server-sync/index.ts @@ -1,10 +1,5 @@ import { db } from '@/db/knex' import { moduleLogger, crossServerSyncLogger } from '@/logging/logging' -import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { - addCommentCreatedActivityFactory, - addReplyAddedActivityFactory -} from '@/modules/activitystream/services/commentActivity' import { getBlobsFactory } from '@/modules/blobstorage/repositories' import { getCommentFactory, @@ -120,12 +115,7 @@ const crossServerSyncModule: SpeckleModule = { insertCommentLinks, markCommentViewed, emitEvent: getEventBus().emit, - addCommentCreatedActivity: addCommentCreatedActivityFactory({ - getViewerResourcesFromLegacyIdentifiers, - getViewerResourceItemsUngrouped, - saveActivity: saveActivityFactory({ db }), - publish - }) + publishSub: publish }) const createCommentReplyAndNotify = createCommentReplyAndNotifyFactory({ getComment: getCommentFactory({ db }), @@ -134,14 +124,11 @@ const crossServerSyncModule: SpeckleModule = { insertCommentLinks, markCommentUpdated: markCommentUpdatedFactory({ db }), emitEvent: getEventBus().emit, - addReplyAddedActivity: addReplyAddedActivityFactory({ - getViewerResourcesForComment: getViewerResourcesForCommentFactory({ - getCommentsResources: getCommentsResourcesFactory({ db }), - getViewerResourcesFromLegacyIdentifiers - }), - saveActivity: saveActivityFactory({ db }), - publish - }) + getViewerResourcesForComment: getViewerResourcesForCommentFactory({ + getCommentsResources: getCommentsResourcesFactory({ db }), + getViewerResourcesFromLegacyIdentifiers + }), + publishSub: publish }) const getStreamBranchByName = getStreamBranchByNameFactory({ db }) const createCommitByBranchId = createCommitByBranchIdFactory({ diff --git a/packages/server/modules/shared/utils/subscriptions.ts b/packages/server/modules/shared/utils/subscriptions.ts index 2bbb825c6..5f2feea68 100644 --- a/packages/server/modules/shared/utils/subscriptions.ts +++ b/packages/server/modules/shared/utils/subscriptions.ts @@ -56,7 +56,7 @@ import { SubscriptionWorkspaceUpdatedArgs, WorkspaceUpdatedMessage } from '@/modules/core/graph/generated/graphql' -import { Merge } from 'type-fest' +import { Merge, OverrideProperties } from 'type-fest' import { ModelGraphQLReturn, ProjectGraphQLReturn, @@ -276,8 +276,10 @@ type SubscriptionTypeMap = { } [CommentSubscriptions.CommentThreadActivity]: { payload: { - commentThreadActivity: Partial & - Pick + commentThreadActivity: OverrideProperties< + CommentThreadActivityMessage, + { reply?: CommentRecord } + > streamId: string commentId: string } @@ -299,7 +301,7 @@ type SubscriptionTypeMap = { comment: CommentRecord } streamId: string - resourceIds: string[] + resourceIds: string } variables: SubscriptionCommentActivityArgs } From 0b90dcf0df579c62cea5b094746efb055ac4bc72 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Fri, 24 Jan 2025 18:29:50 +0200 Subject: [PATCH 09/78] chore(server): moving out comments sub reporting to separate listeners --- .../modules/cli/commands/download/commit.ts | 7 +- .../modules/cli/commands/download/project.ts | 7 +- .../server/modules/comments/domain/events.ts | 2 + .../comments/events/subscriptionListeners.ts | 90 +++++++++++ .../comments/graph/resolvers/comments.ts | 20 +-- packages/server/modules/comments/index.ts | 32 ++++ .../server/modules/comments/services/index.ts | 149 +++++------------- .../modules/comments/services/management.ts | 97 ++---------- .../comments/tests/comments.graph.spec.js | 1 - .../modules/comments/tests/comments.spec.ts | 6 +- .../server/modules/cross-server-sync/index.ts | 7 +- 11 files changed, 191 insertions(+), 227 deletions(-) create mode 100644 packages/server/modules/comments/events/subscriptionListeners.ts diff --git a/packages/server/modules/cli/commands/download/commit.ts b/packages/server/modules/cli/commands/download/commit.ts index c977d772f..b984333f9 100644 --- a/packages/server/modules/cli/commands/download/commit.ts +++ b/packages/server/modules/cli/commands/download/commit.ts @@ -49,7 +49,6 @@ import { import { validateInputAttachmentsFactory } from '@/modules/comments/services/commentTextService' import { getBlobsFactory } from '@/modules/blobstorage/repositories' import { createCommitByBranchIdFactory } from '@/modules/core/services/commit/management' -import { publish } from '@/modules/shared/utils/subscriptions' import { getUserFactory } from '@/modules/core/repositories/users' import { createObjectFactory } from '@/modules/core/services/objects/management' import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' @@ -137,8 +136,7 @@ const command: CommandModule< insertComments, insertCommentLinks, markCommentViewed, - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommentReplyAndNotify = createCommentReplyAndNotifyFactory({ @@ -151,8 +149,7 @@ const command: CommandModule< getViewerResourcesForComment: getViewerResourcesForCommentFactory({ getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), getViewerResourcesFromLegacyIdentifiers - }), - publishSub: publish + }) }) const createCommitByBranchId = createCommitByBranchIdFactory({ diff --git a/packages/server/modules/cli/commands/download/project.ts b/packages/server/modules/cli/commands/download/project.ts index 53c63653a..a1f34f677 100644 --- a/packages/server/modules/cli/commands/download/project.ts +++ b/packages/server/modules/cli/commands/download/project.ts @@ -53,7 +53,6 @@ import { } from '@/modules/comments/repositories/comments' import { getBlobsFactory } from '@/modules/blobstorage/repositories' import { validateInputAttachmentsFactory } from '@/modules/comments/services/commentTextService' -import { publish } from '@/modules/shared/utils/subscriptions' import { getUserFactory } from '@/modules/core/repositories/users' import { createObjectFactory } from '@/modules/core/services/objects/management' import { authorizeResolver } from '@/modules/shared' @@ -162,8 +161,7 @@ const command: CommandModule< insertComments, insertCommentLinks, markCommentViewed, - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommentReplyAndNotify = createCommentReplyAndNotifyFactory({ getComment: getCommentFactory({ db: projectDb }), @@ -175,8 +173,7 @@ const command: CommandModule< getViewerResourcesForComment: getViewerResourcesForCommentFactory({ getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), getViewerResourcesFromLegacyIdentifiers - }), - publishSub: publish + }) }) const createCommitByBranchId = createCommitByBranchIdFactory({ diff --git a/packages/server/modules/comments/domain/events.ts b/packages/server/modules/comments/domain/events.ts index 428912318..6e19679d8 100644 --- a/packages/server/modules/comments/domain/events.ts +++ b/packages/server/modules/comments/domain/events.ts @@ -2,6 +2,7 @@ import { CommentCreatedActivityInput, ReplyCreatedActivityInput } from '@/modules/activitystream/domain/types' +import { ViewerResourceItem } from '@/modules/comments/domain/types' import { CommentRecord } from '@/modules/comments/helpers/types' import { MutationCommentArchiveArgs } from '@/modules/core/graph/generated/graphql' @@ -18,6 +19,7 @@ export type CommentEventsPayloads = { comment: CommentRecord isThread: boolean input: CommentCreatedActivityInput | ReplyCreatedActivityInput + resourceItems: ViewerResourceItem[] } [CommentEvents.Updated]: { previousComment: CommentRecord diff --git a/packages/server/modules/comments/events/subscriptionListeners.ts b/packages/server/modules/comments/events/subscriptionListeners.ts new file mode 100644 index 000000000..f6368aa20 --- /dev/null +++ b/packages/server/modules/comments/events/subscriptionListeners.ts @@ -0,0 +1,90 @@ +import { CommentEvents } from '@/modules/comments/domain/events' +import { GetViewerResourcesForComment } from '@/modules/comments/domain/operations' +import { ProjectCommentsUpdatedMessageType } from '@/modules/core/graph/generated/graphql' +import { DependenciesOf } from '@/modules/shared/helpers/factory' +import { EventBusListen, EventPayload } from '@/modules/shared/services/eventBus' +import { + CommentSubscriptions, + ProjectSubscriptions, + PublishSubscription +} from '@/modules/shared/utils/subscriptions' + +const reportCommentCreatedFactory = + (deps: { publish: PublishSubscription }) => + async (payload: EventPayload) => { + const { comment, resourceItems } = payload.payload + + await Promise.all([ + // @deprecated unused in FE2 + deps.publish(CommentSubscriptions.CommentActivity, { + commentActivity: { + type: 'comment-added', + comment + }, + streamId: comment.streamId, + resourceIds: resourceItems.map((i) => i.versionId || i.objectId).join(',') + }), + deps.publish(ProjectSubscriptions.ProjectCommentsUpdated, { + projectCommentsUpdated: { + id: comment.id, + type: ProjectCommentsUpdatedMessageType.Created, + comment + }, + projectId: comment.streamId, + resourceItems + }) + ]) + } + +const reportCommentArchivedFactory = + (deps: { + publish: PublishSubscription + getViewerResourcesForComment: GetViewerResourcesForComment + }) => + async (payload: EventPayload) => { + const { + comment, + input: { archived, streamId } + } = payload.payload + + await Promise.all([ + deps.publish(CommentSubscriptions.CommentThreadActivity, { + commentThreadActivity: { + type: archived ? 'comment-archived' : 'comment-added' + }, + streamId, + commentId: comment.id + }), + deps.publish(ProjectSubscriptions.ProjectCommentsUpdated, { + projectCommentsUpdated: { + id: comment.id, + type: archived + ? ProjectCommentsUpdatedMessageType.Archived + : ProjectCommentsUpdatedMessageType.Created, + comment: archived ? null : comment + }, + projectId: streamId, + resourceItems: await deps.getViewerResourcesForComment(streamId, comment.id) + }) + ]) + } + +export const reportSubscriptionEventsFactory = + ( + deps: { + eventListen: EventBusListen + publish: PublishSubscription + } & DependenciesOf & + DependenciesOf + ) => + () => { + const reportCommentCreated = reportCommentCreatedFactory(deps) + const reportCommentArchived = reportCommentArchivedFactory(deps) + + const quitters = [ + deps.eventListen(CommentEvents.Created, reportCommentCreated), + deps.eventListen(CommentEvents.Archived, reportCommentArchived) + ] + + return () => quitters.forEach((q) => q()) + } diff --git a/packages/server/modules/comments/graph/resolvers/comments.ts b/packages/server/modules/comments/graph/resolvers/comments.ts index 7164e1626..fa59df8a2 100644 --- a/packages/server/modules/comments/graph/resolvers/comments.ts +++ b/packages/server/modules/comments/graph/resolvers/comments.ts @@ -541,8 +541,7 @@ export = { insertComments, insertCommentLinks, markCommentViewed, - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) return await createCommentThreadAndNotify(args.input, ctx.userId!) @@ -578,8 +577,7 @@ export = { getViewerResourcesForComment: getViewerResourcesForCommentFactory({ getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), getViewerResourcesFromLegacyIdentifiers - }), - publishSub: publish + }) }) return await createCommentReplyAndNotify(args.input, ctx.userId!) @@ -641,7 +639,6 @@ export = { getStream, updateComment, getViewerResourcesForComment, - publishSub: publish, emitEvent: getEventBus().emit }) @@ -737,7 +734,6 @@ export = { deleteComment: deleteCommentFactory({ db: projectDb }), markCommentViewed: markCommentViewedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - publishSub: publish, getViewerResourcesFromLegacyIdentifiers }) const comment = await createComment({ @@ -793,20 +789,11 @@ export = { }) const projectDb = await getProjectDbClient({ projectId: args.streamId }) - const getViewerResourcesFromLegacyIdentifiers = - buildGetViewerResourcesFromLegacyIdentifiers({ db: projectDb }) - const getViewerResourcesForComment = getViewerResourcesForCommentFactory({ - getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), - getViewerResourcesFromLegacyIdentifiers - }) - const archiveComment = archiveCommentFactory({ getComment: getCommentFactory({ db: projectDb }), getStream, updateComment: updateCommentFactory({ db: projectDb }), - emitEvent: getEventBus().emit, - publishSub: publish, - getViewerResourcesForComment + emitEvent: getEventBus().emit }) await archiveComment({ ...args, userId: context.userId! }) // NOTE: permissions check inside service @@ -839,7 +826,6 @@ export = { deleteComment: deleteCommentFactory({ db: projectDb }), markCommentUpdated: markCommentUpdatedFactory({ db: projectDb }), emitEvent: getEventBus().emit, - publishSub: publish, getViewerResourcesForComment: getViewerResourcesForCommentFactory({ getCommentsResources: getCommentsResourcesFactory({ db: projectDb }), getViewerResourcesFromLegacyIdentifiers: diff --git a/packages/server/modules/comments/index.ts b/packages/server/modules/comments/index.ts index 0f495fa4c..39cba0970 100644 --- a/packages/server/modules/comments/index.ts +++ b/packages/server/modules/comments/index.ts @@ -2,10 +2,20 @@ import { db } from '@/db/knex' import { moduleLogger } from '@/logging/logging' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { addStreamCommentMentionActivityFactory } from '@/modules/activitystream/services/streamActivity' +import { reportSubscriptionEventsFactory } from '@/modules/comments/events/subscriptionListeners' +import { getCommentsResourcesFactory } from '@/modules/comments/repositories/comments' import { notifyUsersOnCommentEventsFactory } from '@/modules/comments/services/notifications' +import { getCommitsAndTheirBranchIdsFactory } from '@/modules/core/repositories/commits' +import { getStreamObjectsFactory } from '@/modules/core/repositories/objects' +import { + getViewerResourcesForCommentFactory, + getViewerResourcesForCommentsFactory, + getViewerResourcesFromLegacyIdentifiersFactory +} from '@/modules/core/services/commit/viewerResources' import { publishNotification } from '@/modules/notifications/services/publication' import { Optional, SpeckleModule } from '@/modules/shared/helpers/typeHelper' import { getEventBus } from '@/modules/shared/services/eventBus' +import { publish } from '@/modules/shared/utils/subscriptions' let unsubFromEvents: Optional<() => void> = undefined @@ -22,6 +32,28 @@ const commentsModule: SpeckleModule = { }) }) unsubFromEvents = await notifyUsersOnCommentEvents() + + const getViewerResourcesFromLegacyIdentifiers = + getViewerResourcesFromLegacyIdentifiersFactory({ + getViewerResourcesForComments: getViewerResourcesForCommentsFactory({ + getCommentsResources: getCommentsResourcesFactory({ db }), + getViewerResourcesFromLegacyIdentifiers: (...args) => + getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep + }), + getCommitsAndTheirBranchIds: getCommitsAndTheirBranchIdsFactory({ db }), + getStreamObjects: getStreamObjectsFactory({ db }) + }) + const getViewerResourcesForComment = getViewerResourcesForCommentFactory({ + getCommentsResources: getCommentsResourcesFactory({ db }), + getViewerResourcesFromLegacyIdentifiers: (...args) => + getViewerResourcesFromLegacyIdentifiers(...args) // recursive dep + }) + + reportSubscriptionEventsFactory({ + eventListen: getEventBus().listen, + publish, + getViewerResourcesForComment + })() } }, async finalize() {}, diff --git a/packages/server/modules/comments/services/index.ts b/packages/server/modules/comments/services/index.ts index d6a7ecc6d..ff75f9a81 100644 --- a/packages/server/modules/comments/services/index.ts +++ b/packages/server/modules/comments/services/index.ts @@ -5,8 +5,7 @@ import { isNonNullable, Roles } from '@speckle/shared' import { ResourceIdentifier, CommentCreateInput, - CommentEditInput, - ProjectCommentsUpdatedMessageType + CommentEditInput } from '@/modules/core/graph/generated/graphql' import { CommentLinkRecord, CommentRecord } from '@/modules/comments/helpers/types' import { SmartTextEditorValueSchema } from '@/modules/core/services/richTextEditorService' @@ -29,11 +28,6 @@ import { GetStream } from '@/modules/core/domain/streams/operations' import { EventBusEmit } from '@/modules/shared/services/eventBus' import { CommentEvents } from '@/modules/comments/domain/events' import { JSONContent } from '@tiptap/core' -import { - CommentSubscriptions, - ProjectSubscriptions, - PublishSubscription -} from '@/modules/shared/utils/subscriptions' export const streamResourceCheckFactory = (deps: { @@ -64,7 +58,6 @@ export const createCommentFactory = deleteComment: DeleteComment markCommentViewed: MarkCommentViewed emitEvent: EventBusEmit - publishSub: PublishSubscription getViewerResourcesFromLegacyIdentifiers: GetViewerResourcesFromLegacyIdentifiers }) => async ({ userId, input }: { userId: string; input: CommentCreateInput }) => { @@ -126,37 +119,19 @@ export const createCommentFactory = await deps.markCommentViewed(id, userId) // so we don't self mark a comment as unread the moment it's created - await Promise.all([ - deps.emitEvent({ - eventName: CommentEvents.Created, - payload: { - comment: newComment, - input, - isThread: true - } - }), - // @deprecated unused in FE2 - deps.publishSub(CommentSubscriptions.CommentActivity, { - commentActivity: { - type: 'comment-added', - comment: newComment - }, - streamId: input.streamId, - resourceIds: input.resources.map((res) => res?.resourceId).join(',') - }), - deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: newComment.id, - type: ProjectCommentsUpdatedMessageType.Created, - comment: newComment - }, - projectId: input.streamId, - resourceItems: await deps.getViewerResourcesFromLegacyIdentifiers( - input.streamId, - input.resources.filter(isNonNullable) - ) - }) - ]) + const resourceItems = await deps.getViewerResourcesFromLegacyIdentifiers( + input.streamId, + input.resources.filter(isNonNullable) + ) + await deps.emitEvent({ + eventName: CommentEvents.Created, + payload: { + comment: newComment, + input, + isThread: true, + resourceItems + } + }) return newComment } @@ -173,7 +148,6 @@ export const createCommentReplyFactory = deleteComment: DeleteComment markCommentUpdated: MarkCommentUpdated emitEvent: EventBusEmit - publishSub: PublishSubscription getViewerResourcesForComment: GetViewerResourcesForComment }) => async ({ @@ -225,45 +199,26 @@ export const createCommentReplyFactory = await deps.markCommentUpdated(parentCommentId) - await Promise.all([ - deps.emitEvent({ - eventName: CommentEvents.Created, - payload: { - comment: newComment, - isThread: false, - input: { - threadId: parentCommentId, - projectId: streamId, - content: { - blobIds, - doc: text - } + const resourceItems = await deps.getViewerResourcesForComment( + newComment.streamId, + newComment.id + ) + await deps.emitEvent({ + eventName: CommentEvents.Created, + payload: { + comment: newComment, + isThread: false, + input: { + threadId: parentCommentId, + projectId: streamId, + content: { + blobIds, + doc: text } - } - }), - // TODO: Move to event bus listeners - // @deprecated - deps.publishSub(CommentSubscriptions.CommentThreadActivity, { - commentThreadActivity: { - type: 'reply-added', - reply: newComment }, - streamId: newComment.streamId, - commentId: parentCommentId - }), - deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: newComment.id, - type: ProjectCommentsUpdatedMessageType.Created, - comment: newComment - }, - projectId: newComment.streamId, - resourceItems: await deps.getViewerResourcesForComment( - newComment.streamId, - newComment.id - ) - }) - ]) + resourceItems + } + }) return newComment } @@ -320,8 +275,6 @@ export const archiveCommentFactory = getStream: GetStream updateComment: UpdateComment emitEvent: EventBusEmit - publishSub: PublishSubscription - getViewerResourcesForComment: GetViewerResourcesForComment }) => async ({ commentId, @@ -349,36 +302,14 @@ export const archiveCommentFactory = const updatedComment = await deps.updateComment(commentId, { archived }) - await Promise.all([ - deps.emitEvent({ - eventName: CommentEvents.Archived, - payload: { - userId, - input: { archived, commentId, streamId }, - comment: updatedComment! - } - }), - // TODO: Move to event bus listeners - // @deprecated not used in FE2 - deps.publishSub(CommentSubscriptions.CommentThreadActivity, { - commentThreadActivity: { - type: archived ? 'comment-archived' : 'comment-added' - }, - streamId, - commentId - }), - deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: commentId, - type: archived - ? ProjectCommentsUpdatedMessageType.Archived - : ProjectCommentsUpdatedMessageType.Created, - comment: archived ? null : comment - }, - projectId: streamId, - resourceItems: await deps.getViewerResourcesForComment(streamId, comment.id) - }) - ]) + await deps.emitEvent({ + eventName: CommentEvents.Archived, + payload: { + userId, + input: { archived, commentId, streamId }, + comment: updatedComment! + } + }) return updatedComment! } diff --git a/packages/server/modules/comments/services/management.ts b/packages/server/modules/comments/services/management.ts index fabc211ab..a28c19f31 100644 --- a/packages/server/modules/comments/services/management.ts +++ b/packages/server/modules/comments/services/management.ts @@ -5,8 +5,7 @@ import { StreamInvalidAccessError } from '@/modules/core/errors/stream' import { CreateCommentInput, CreateCommentReplyInput, - EditCommentInput, - ProjectCommentsUpdatedMessageType + EditCommentInput } from '@/modules/core/graph/generated/graphql' import { CommentCreateError, CommentUpdateError } from '@/modules/comments/errors' import { buildCommentTextFromInput } from '@/modules/comments/services/commentTextService' @@ -39,11 +38,6 @@ import { import { GetStream } from '@/modules/core/domain/streams/operations' import { EventBusEmit } from '@/modules/shared/services/eventBus' import { CommentEvents } from '@/modules/comments/domain/events' -import { - CommentSubscriptions, - ProjectSubscriptions, - PublishSubscription -} from '@/modules/shared/utils/subscriptions' type AuthorizeProjectCommentsAccessDeps = { getStream: GetStream @@ -120,7 +114,6 @@ export const createCommentThreadAndNotifyFactory = insertCommentLinks: InsertCommentLinks markCommentViewed: MarkCommentViewed emitEvent: EventBusEmit - publishSub: PublishSubscription }): CreateCommentThreadAndNotify => async (input: CreateCommentInput, userId: string) => { const [resources] = await Promise.all([ @@ -184,27 +177,9 @@ export const createCommentThreadAndNotifyFactory = payload: { comment, input, - isThread: true + isThread: true, + resourceItems: resources } - }), - // TODO: Move to event bus listeners - // @deprecated unused in FE2 - deps.publishSub(CommentSubscriptions.CommentActivity, { - commentActivity: { - type: 'comment-added', - comment - }, - streamId: input.projectId, - resourceIds: resources.map((i) => i.versionId || i.objectId).join(',') - }), - deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: comment.id, - type: ProjectCommentsUpdatedMessageType.Created, - comment - }, - projectId: input.projectId, - resourceItems: resources }) ]) @@ -219,7 +194,6 @@ export const createCommentReplyAndNotifyFactory = insertCommentLinks: InsertCommentLinks markCommentUpdated: MarkCommentUpdated emitEvent: EventBusEmit - publishSub: PublishSubscription getViewerResourcesForComment: GetViewerResourcesForComment }): CreateCommentReplyAndNotify => async (input: CreateCommentReplyInput, userId: string) => { @@ -254,6 +228,10 @@ export const createCommentReplyAndNotifyFactory = } // Mark parent comment updated and emit events + const resourceItems = await deps.getViewerResourcesForComment( + reply.streamId, + reply.id + ) await Promise.all([ deps.markCommentUpdated(thread.id), deps.emitEvent({ @@ -261,27 +239,9 @@ export const createCommentReplyAndNotifyFactory = payload: { comment: reply, input, - isThread: false + isThread: false, + resourceItems } - }), - // TODO: Move to event bus listeners - // @deprecated - deps.publishSub(CommentSubscriptions.CommentThreadActivity, { - commentThreadActivity: { - type: 'reply-added', - reply - }, - streamId: reply.streamId, - commentId: thread.id - }), - deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: reply.id, - type: ProjectCommentsUpdatedMessageType.Created, - comment: reply - }, - projectId: reply.streamId, - resourceItems: await deps.getViewerResourcesForComment(reply.streamId, reply.id) }) ]) @@ -329,7 +289,6 @@ export const archiveCommentAndNotifyFactory = getStream: GetStream updateComment: UpdateComment emitEvent: EventBusEmit - publishSub: PublishSubscription getViewerResourcesForComment: GetViewerResourcesForComment }): ArchiveCommentAndNotify => async (commentId: string, userId: string, archived = true) => { @@ -353,36 +312,14 @@ export const archiveCommentAndNotifyFactory = archived }) - await Promise.all([ - deps.emitEvent({ - eventName: CommentEvents.Archived, - payload: { - userId, - input: { archived, commentId, streamId: stream.id }, - comment: updatedComment! - } - }), - // TODO: Move to event bus listeners - // @deprecated not used in FE2 - deps.publishSub(CommentSubscriptions.CommentThreadActivity, { - commentThreadActivity: { - type: archived ? 'comment-archived' : 'comment-added' - }, - streamId: stream.id, - commentId - }), - deps.publishSub(ProjectSubscriptions.ProjectCommentsUpdated, { - projectCommentsUpdated: { - id: commentId, - type: archived - ? ProjectCommentsUpdatedMessageType.Archived - : ProjectCommentsUpdatedMessageType.Created, - comment: archived ? null : comment - }, - projectId: stream.id, - resourceItems: await deps.getViewerResourcesForComment(stream.id, comment.id) - }) - ]) + await deps.emitEvent({ + eventName: CommentEvents.Archived, + payload: { + userId, + input: { archived, commentId, streamId: stream.id }, + comment: updatedComment! + } + }) return updatedComment } diff --git a/packages/server/modules/comments/tests/comments.graph.spec.js b/packages/server/modules/comments/tests/comments.graph.spec.js index cf35a3ffd..1a2664ef9 100644 --- a/packages/server/modules/comments/tests/comments.graph.spec.js +++ b/packages/server/modules/comments/tests/comments.graph.spec.js @@ -147,7 +147,6 @@ const createComment = createCommentFactory({ deleteComment: deleteCommentFactory({ db }), markCommentViewed, emitEvent: getEventBus().emit, - publishSub: publish, getViewerResourcesFromLegacyIdentifiers }) diff --git a/packages/server/modules/comments/tests/comments.spec.ts b/packages/server/modules/comments/tests/comments.spec.ts index cf235e181..d45821603 100644 --- a/packages/server/modules/comments/tests/comments.spec.ts +++ b/packages/server/modules/comments/tests/comments.spec.ts @@ -165,7 +165,6 @@ const createComment = createCommentFactory({ deleteComment, markCommentViewed, emitEvent: getEventBus().emit, - publishSub: publish, getViewerResourcesFromLegacyIdentifiers }) const getViewerResourcesForComment = getViewerResourcesForCommentFactory({ @@ -181,7 +180,6 @@ const createCommentReply = createCommentReplyFactory({ deleteComment, markCommentUpdated: markCommentUpdatedFactory({ db }), emitEvent: getEventBus().emit, - publishSub: publish, getViewerResourcesForComment }) const getComment = getCommentFactory({ db }) @@ -196,9 +194,7 @@ const archiveComment = archiveCommentFactory({ getComment, getStream, updateComment, - emitEvent: getEventBus().emit, - publishSub: publish, - getViewerResourcesForComment + emitEvent: getEventBus().emit }) const getComments = getCommentsLegacyFactory({ db }) const getResourceCommentCount = getResourceCommentCountFactory({ db }) diff --git a/packages/server/modules/cross-server-sync/index.ts b/packages/server/modules/cross-server-sync/index.ts index d2c28ea2a..310a16b90 100644 --- a/packages/server/modules/cross-server-sync/index.ts +++ b/packages/server/modules/cross-server-sync/index.ts @@ -67,7 +67,6 @@ import { ensureOnboardingProjectFactory } from '@/modules/cross-server-sync/serv import { downloadProjectFactory } from '@/modules/cross-server-sync/services/project' import { SpeckleModule } from '@/modules/shared/helpers/typeHelper' import { getEventBus } from '@/modules/shared/services/eventBus' -import { publish } from '@/modules/shared/utils/subscriptions' const crossServerSyncModule: SpeckleModule = { init() { @@ -114,8 +113,7 @@ const crossServerSyncModule: SpeckleModule = { insertComments, insertCommentLinks, markCommentViewed, - emitEvent: getEventBus().emit, - publishSub: publish + emitEvent: getEventBus().emit }) const createCommentReplyAndNotify = createCommentReplyAndNotifyFactory({ getComment: getCommentFactory({ db }), @@ -127,8 +125,7 @@ const crossServerSyncModule: SpeckleModule = { getViewerResourcesForComment: getViewerResourcesForCommentFactory({ getCommentsResources: getCommentsResourcesFactory({ db }), getViewerResourcesFromLegacyIdentifiers - }), - publishSub: publish + }) }) const getStreamBranchByName = getStreamBranchByNameFactory({ db }) const createCommitByBranchId = createCommitByBranchIdFactory({ From 9b467478152635512dd8c7e844ab6ff7dfe5ef25 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Fri, 24 Jan 2025 16:24:59 +0200 Subject: [PATCH 10/78] chore(server): refactor activityStream invocations - batch #6 - project invites --- .../accessrequests/graph/resolvers/index.ts | 10 +- .../tests/projectAccessRequests.spec.ts | 6 +- .../tests/streamAccessRequests.spec.ts | 6 +- .../activitystream/domain/operations.ts | 15 --- .../events/streamInviteListeners.ts | 122 +++++++++++++++++ .../server/modules/activitystream/index.ts | 31 ++--- .../activitystream/services/eventListener.ts | 43 ------ .../activitystream/services/streamActivity.ts | 125 ------------------ .../activitystream/tests/activity.spec.js | 6 +- .../automate/tests/automations.spec.ts | 10 +- .../modules/core/domain/streams/operations.ts | 3 +- .../modules/core/graph/resolvers/projects.ts | 6 +- .../modules/core/graph/resolvers/streams.ts | 6 +- .../modules/core/services/streams/access.ts | 20 +-- .../modules/core/tests/batchCommits.spec.ts | 11 +- .../modules/core/tests/commitsGraphql.spec.ts | 11 +- .../server/modules/core/tests/graph.spec.js | 6 +- .../core/tests/integration/subs.graph.spec.ts | 6 +- .../server/modules/core/tests/streams.spec.ts | 10 +- .../events/subscriptionListeners.ts | 101 ++++++++++++++ .../graph/resolvers/serverInvites.ts | 19 +-- .../server/modules/serverinvites/index.ts | 18 ++- .../services/coreFinalization.ts | 14 +- .../serverinvites/services/operations.ts | 5 + .../services/projectInviteManagement.ts | 15 ++- .../workspaces/graph/resolvers/workspaces.ts | 6 +- .../tests/integration/invites.graph.spec.ts | 11 +- .../test/speckle-helpers/streamHelper.ts | 6 +- 28 files changed, 311 insertions(+), 337 deletions(-) create mode 100644 packages/server/modules/activitystream/events/streamInviteListeners.ts delete mode 100644 packages/server/modules/activitystream/services/eventListener.ts create mode 100644 packages/server/modules/serverinvites/events/subscriptionListeners.ts diff --git a/packages/server/modules/accessrequests/graph/resolvers/index.ts b/packages/server/modules/accessrequests/graph/resolvers/index.ts index 33b0e509e..e65f35144 100644 --- a/packages/server/modules/accessrequests/graph/resolvers/index.ts +++ b/packages/server/modules/accessrequests/graph/resolvers/index.ts @@ -17,10 +17,7 @@ import { requestStreamAccessFactory } from '@/modules/accessrequests/services/stream' import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { - addStreamInviteAcceptedActivityFactory, - addStreamPermissionsAddedActivityFactory -} from '@/modules/activitystream/services/streamActivity' +import { addStreamPermissionsAddedActivityFactory } from '@/modules/activitystream/services/streamActivity' import { Resolvers } from '@/modules/core/graph/generated/graphql' import { mapStreamRoleToValue } from '@/modules/core/helpers/graphTypes' import { Roles } from '@/modules/core/helpers/mainConstants' @@ -75,10 +72,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/accessrequests/tests/projectAccessRequests.spec.ts b/packages/server/modules/accessrequests/tests/projectAccessRequests.spec.ts index 299ddc074..d5051c20c 100644 --- a/packages/server/modules/accessrequests/tests/projectAccessRequests.spec.ts +++ b/packages/server/modules/accessrequests/tests/projectAccessRequests.spec.ts @@ -14,7 +14,6 @@ import { import { ActionTypes } from '@/modules/activitystream/helpers/types' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory, addStreamPermissionsRevokedActivityFactory } from '@/modules/activitystream/services/streamActivity' @@ -97,10 +96,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/accessrequests/tests/streamAccessRequests.spec.ts b/packages/server/modules/accessrequests/tests/streamAccessRequests.spec.ts index e9f9a2266..93f236b56 100644 --- a/packages/server/modules/accessrequests/tests/streamAccessRequests.spec.ts +++ b/packages/server/modules/accessrequests/tests/streamAccessRequests.spec.ts @@ -15,7 +15,6 @@ import { import { ActionTypes } from '@/modules/activitystream/helpers/types' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory, addStreamPermissionsRevokedActivityFactory } from '@/modules/activitystream/services/streamActivity' @@ -100,10 +99,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/activitystream/domain/operations.ts b/packages/server/modules/activitystream/domain/operations.ts index 480d650a4..506725804 100644 --- a/packages/server/modules/activitystream/domain/operations.ts +++ b/packages/server/modules/activitystream/domain/operations.ts @@ -176,21 +176,6 @@ export type AddStreamCommentMentionActivity = (params: { threadId: string }) => Promise -export type AddStreamInviteDeclinedActivity = (params: { - streamId: string - inviteTargetId: string - inviterId: string - stream: StreamRecord -}) => Promise - -export type AddStreamInviteSentOutActivity = (params: { - streamId: string - inviteTargetId: string | null - inviterId: string - inviteTargetEmail: string | null - stream: StreamRecord -}) => Promise - export type AddStreamDeletedActivity = (params: { streamId: string deleterId: string diff --git a/packages/server/modules/activitystream/events/streamInviteListeners.ts b/packages/server/modules/activitystream/events/streamInviteListeners.ts new file mode 100644 index 000000000..8b0d4e681 --- /dev/null +++ b/packages/server/modules/activitystream/events/streamInviteListeners.ts @@ -0,0 +1,122 @@ +import { SaveActivity } from '@/modules/activitystream/domain/operations' +import { ActionTypes, ResourceTypes } from '@/modules/activitystream/helpers/types' +import { ProjectInviteResourceType } from '@/modules/serverinvites/domain/constants' +import { ServerInvitesEvents } from '@/modules/serverinvites/domain/events' +import { + getResourceTypeRole, + resolveTarget +} from '@/modules/serverinvites/helpers/core' +import { GetProjectInviteProject } from '@/modules/serverinvites/services/operations' +import { EventBusListen, EventPayload } from '@/modules/shared/services/eventBus' +import { Roles } from '@speckle/shared' + +/** + * Save "user invited another user to stream" activity item + */ +const addStreamInviteSentOutActivityFactory = + (deps: { + saveActivity: SaveActivity + getProjectInviteProject: GetProjectInviteProject + }) => + async (payload: EventPayload) => { + const { invite } = payload.payload + const project = await deps.getProjectInviteProject({ invite }) + if (!project) return + + const userTarget = resolveTarget(invite.target) + const targetDisplay = userTarget.userId || userTarget.userEmail + + await deps.saveActivity({ + streamId: project.id, + resourceType: ResourceTypes.Stream, + resourceId: project.id, + actionType: ActionTypes.Stream.InviteSent, + userId: invite.inviterId, + message: `User ${invite.inviterId} has invited ${targetDisplay} to stream ${project.id}`, + info: { + targetId: userTarget.userId || null, + targetEmail: userTarget.userEmail || null + } + }) + } + +/** + * Save "user accepted stream invite" activity item + */ +const addStreamInviteAcceptedActivityFactory = + (deps: { + saveActivity: SaveActivity + getProjectInviteProject: GetProjectInviteProject + }) => + async (payload: EventPayload) => { + const { invite } = payload.payload + const project = await deps.getProjectInviteProject({ invite }) + if (!project) return + + const userTarget = resolveTarget(invite.target) + const role = + getResourceTypeRole(invite.resource, ProjectInviteResourceType) || + Roles.Stream.Contributor + + await deps.saveActivity({ + streamId: project.id, + resourceType: ResourceTypes.Stream, + resourceId: project.id, + actionType: ActionTypes.Stream.InviteAccepted, + userId: userTarget.userId!, + info: { inviterUser: invite.inviterId, role }, + message: `User ${userTarget.userId!} has accepted an invitation to become a ${role}` + }) + } + +/** + * Save "user declined an invite" activity item + */ +const addStreamInviteDeclinedActivityFactory = + (deps: { + saveActivity: SaveActivity + getProjectInviteProject: GetProjectInviteProject + }) => + async (payload: EventPayload) => { + const { invite } = payload.payload + const project = await deps.getProjectInviteProject({ invite }) + if (!project) return + + const userTarget = resolveTarget(invite.target) + await deps.saveActivity({ + streamId: project.id, + resourceType: ResourceTypes.Stream, + resourceId: project.id, + actionType: ActionTypes.Stream.InviteDeclined, + userId: userTarget.userId!, + message: `User ${userTarget!} declined to join the stream ${project.id}`, + info: { targetId: userTarget!, inviterId: invite.inviterId } + }) + } + +export const reportStreamInviteActivityFactory = + (deps: { + eventListen: EventBusListen + saveActivity: SaveActivity + getProjectInviteProject: GetProjectInviteProject + }) => + () => { + const addStreamInviteSentOutActivity = addStreamInviteSentOutActivityFactory(deps) + const addStreamInviteAcceptedActivity = addStreamInviteAcceptedActivityFactory(deps) + const addStreamInviteDeclinedActivity = addStreamInviteDeclinedActivityFactory(deps) + + const quitters = [ + deps.eventListen(ServerInvitesEvents.Created, addStreamInviteSentOutActivity), + deps.eventListen(ServerInvitesEvents.Finalized, async (payload) => { + if (payload.payload.accept) { + await addStreamInviteAcceptedActivity(payload) + } else { + await addStreamInviteDeclinedActivity(payload) + } + }) + ] + + return () => { + quitters.forEach((quit) => quit()) + } + } diff --git a/packages/server/modules/activitystream/index.ts b/packages/server/modules/activitystream/index.ts index 883e9b3a4..4cfdba7ec 100644 --- a/packages/server/modules/activitystream/index.ts +++ b/packages/server/modules/activitystream/index.ts @@ -1,6 +1,6 @@ import { Optional, SpeckleModule } from '@/modules/shared/helpers/typeHelper' import { publishNotification } from '@/modules/notifications/services/publication' -import { activitiesLogger, logger, moduleLogger } from '@/logging/logging' +import { activitiesLogger, moduleLogger } from '@/logging/logging' import { weeklyEmailDigestEnabled } from '@/modules/shared/helpers/envHelper' import { EventBus, getEventBus } from '@/modules/shared/services/eventBus' import { sendActivityNotificationsFactory } from '@/modules/activitystream/services/summary' @@ -9,10 +9,7 @@ import { saveActivityFactory } from '@/modules/activitystream/repositories' import { db } from '@/db/knex' -import { - addStreamCreatedActivityFactory, - addStreamInviteSentOutActivityFactory -} from '@/modules/activitystream/services/streamActivity' +import { addStreamCreatedActivityFactory } from '@/modules/activitystream/services/streamActivity' import { getStreamFactory } from '@/modules/core/repositories/streams' import { ScheduleExecution } from '@/modules/core/domain/scheduledTasks/operations' import { scheduleExecutionFactory } from '@/modules/core/services/taskScheduler' @@ -21,16 +18,15 @@ import { releaseTaskLockFactory } from '@/modules/core/repositories/scheduledTasks' import { Knex } from 'knex' -import { onServerInviteCreatedFactory } from '@/modules/activitystream/services/eventListener' -import { isProjectResourceTarget } from '@/modules/serverinvites/helpers/core' import { publish } from '@/modules/shared/utils/subscriptions' -import { ServerInvitesEvents } from '@/modules/serverinvites/domain/events' import { ProjectEvents } from '@/modules/core/domain/projects/events' import { reportUserActivityFactory } from '@/modules/activitystream/events/userListeners' import { reportAccessRequestActivityFactory } from '@/modules/activitystream/events/accessRequestListeners' import { reportBranchActivityFactory } from '@/modules/activitystream/events/branchListeners' import { reportCommitActivityFactory } from '@/modules/activitystream/events/commitListeners' import { reportCommentActivityFactory } from '@/modules/activitystream/events/commentListeners' +import { reportStreamInviteActivityFactory } from '@/modules/activitystream/events/streamInviteListeners' +import { getProjectInviteProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement' let scheduledTask: ReturnType | null = null let quitEventListeners: Optional<() => void> = undefined @@ -67,6 +63,13 @@ const initializeEventListeners = ({ eventListen: eventBus.listen, saveActivity }) + const reportStreamInviteActivity = reportStreamInviteActivityFactory({ + eventListen: eventBus.listen, + saveActivity, + getProjectInviteProject: getProjectInviteProjectFactory({ + getStream: getStreamFactory({ db }) + }) + }) const quitCbs = [ reportUserActivity(), @@ -74,17 +77,7 @@ const initializeEventListeners = ({ reportBranchActivity(), reportCommitActivity(), reportCommentActivity(), - eventBus.listen(ServerInvitesEvents.Created, async ({ payload }) => { - if (!isProjectResourceTarget(payload.invite.resource)) return - await onServerInviteCreatedFactory({ - addStreamInviteSentOutActivity: addStreamInviteSentOutActivityFactory({ - publish, - saveActivity: saveActivityFactory({ db }) - }), - logger, - getStream: getStreamFactory({ db }) - })(payload) - }), + reportStreamInviteActivity(), eventBus.listen( ProjectEvents.Created, async ({ payload: { ownerId, project } }) => { diff --git a/packages/server/modules/activitystream/services/eventListener.ts b/packages/server/modules/activitystream/services/eventListener.ts deleted file mode 100644 index fa7595b1b..000000000 --- a/packages/server/modules/activitystream/services/eventListener.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Logger } from '@/logging/logging' -import { AddStreamInviteSentOutActivity } from '@/modules/activitystream/domain/operations' -import { GetStream } from '@/modules/core/domain/streams/operations' -import { - ServerInvitesEvents, - ServerInvitesEventsPayloads -} from '@/modules/serverinvites/domain/events' -import { - isProjectResourceTarget, - resolveTarget -} from '@/modules/serverinvites/helpers/core' - -export const onServerInviteCreatedFactory = - ({ - getStream, - logger, - addStreamInviteSentOutActivity - }: { - getStream: GetStream - logger: Logger - addStreamInviteSentOutActivity: AddStreamInviteSentOutActivity - }) => - async (payload: ServerInvitesEventsPayloads[typeof ServerInvitesEvents.Created]) => { - const { invite } = payload - const primaryResourceTarget = invite.resource - - if (!isProjectResourceTarget(primaryResourceTarget)) return - - const userTarget = resolveTarget(invite.target) - const project = await getStream({ streamId: primaryResourceTarget.resourceId }) - if (!project) { - logger.warn('No project found for project invite', { invite }) - return - } - - await addStreamInviteSentOutActivity({ - streamId: project.id, - inviterId: invite.inviterId, - inviteTargetEmail: userTarget.userEmail, - inviteTargetId: userTarget.userId, - stream: project - }) - } diff --git a/packages/server/modules/activitystream/services/streamActivity.ts b/packages/server/modules/activitystream/services/streamActivity.ts index a216ab8a1..238c23d53 100644 --- a/packages/server/modules/activitystream/services/streamActivity.ts +++ b/packages/server/modules/activitystream/services/streamActivity.ts @@ -24,8 +24,6 @@ import { import { AddStreamCommentMentionActivity, AddStreamDeletedActivity, - AddStreamInviteDeclinedActivity, - AddStreamInviteSentOutActivity, AddStreamUpdatedActivity, SaveActivity } from '@/modules/activitystream/domain/operations' @@ -334,60 +332,6 @@ export const addStreamPermissionsAddedActivityFactory = ]) } -/** - * Save "user accepted stream invite" activity item - */ -export const addStreamInviteAcceptedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }) => - async (params: { - streamId: string - inviteTargetId: string - inviterId: string - role: StreamRoles - stream: StreamRecord - }) => { - const { streamId, inviteTargetId, inviterId, role, stream } = params - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Stream, - resourceId: streamId, - actionType: ActionTypes.Stream.InviteAccepted, - userId: inviteTargetId, - info: { inviterUser: inviterId, role }, - message: `User ${inviteTargetId} has accepted an invitation to become a ${role}` - }), - publish(StreamPubsubEvents.UserStreamAdded, { - userStreamAdded: { - id: streamId, - sharedBy: inviterId - }, - ownerId: inviteTargetId - }), - publish(UserSubscriptions.UserProjectsUpdated, { - userProjectsUpdated: { - id: streamId, - type: UserProjectsUpdatedMessageType.Added, - project: stream - }, - ownerId: inviteTargetId - }), - publish(ProjectSubscriptions.ProjectUpdated, { - projectUpdated: { - id: streamId, - type: ProjectUpdatedMessageType.Updated, - project: stream - } - }) - ]) - } - /** * Save "stream permissions revoked for user" activity item */ @@ -445,75 +389,6 @@ export const addStreamPermissionsRevokedActivityFactory = ]) } -/** - * Save "user invited another user to stream" activity item - */ -export const addStreamInviteSentOutActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }): AddStreamInviteSentOutActivity => - async ({ streamId, inviteTargetId, inviterId, inviteTargetEmail, stream }) => { - const targetDisplay = inviteTargetId || inviteTargetEmail - - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Stream, - resourceId: streamId, - actionType: ActionTypes.Stream.InviteSent, - userId: inviterId, - message: `User ${inviterId} has invited ${targetDisplay} to stream ${streamId}`, - info: { - targetId: inviteTargetId || null, - targetEmail: inviteTargetEmail || null - } - }), - publish(ProjectSubscriptions.ProjectUpdated, { - projectUpdated: { - id: streamId, - type: ProjectUpdatedMessageType.Updated, - project: stream - } - }) - ]) - } - -/** - * Save "user declined an invite" activity item - */ -export const addStreamInviteDeclinedActivityFactory = - ({ - saveActivity, - publish - }: { - saveActivity: SaveActivity - publish: PublishSubscription - }): AddStreamInviteDeclinedActivity => - async ({ streamId, inviteTargetId, inviterId, stream }) => { - await Promise.all([ - saveActivity({ - streamId, - resourceType: ResourceTypes.Stream, - resourceId: streamId, - actionType: ActionTypes.Stream.InviteDeclined, - userId: inviteTargetId, - message: `User ${inviteTargetId} declined to join the stream ${streamId}`, - info: { targetId: inviteTargetId, inviterId } - }), - publish(ProjectSubscriptions.ProjectUpdated, { - projectUpdated: { - id: streamId, - type: ProjectUpdatedMessageType.Updated, - project: stream - } - }) - ]) - } - /** * Save "user mentioned in stream comment" activity item */ diff --git a/packages/server/modules/activitystream/tests/activity.spec.js b/packages/server/modules/activitystream/tests/activity.spec.js index 4d79869cc..4ae419a58 100644 --- a/packages/server/modules/activitystream/tests/activity.spec.js +++ b/packages/server/modules/activitystream/tests/activity.spec.js @@ -17,7 +17,6 @@ const { const { authorizeResolver } = require('@/modules/shared') const { grantStreamPermissionsFactory } = require('@/modules/core/repositories/streams') const { - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory } = require('@/modules/activitystream/services/streamActivity') const { publish } = require('@/modules/shared/utils/subscriptions') @@ -74,10 +73,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/automate/tests/automations.spec.ts b/packages/server/modules/automate/tests/automations.spec.ts index cb916dcd0..6d02b202d 100644 --- a/packages/server/modules/automate/tests/automations.spec.ts +++ b/packages/server/modules/automate/tests/automations.spec.ts @@ -48,10 +48,7 @@ import { } from '@/modules/core/services/streams/access' import { authorizeResolver } from '@/modules/shared' import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams' -import { - addStreamInviteAcceptedActivityFactory, - addStreamPermissionsAddedActivityFactory -} from '@/modules/activitystream/services/streamActivity' +import { addStreamPermissionsAddedActivityFactory } from '@/modules/activitystream/services/streamActivity' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' import { getUserFactory } from '@/modules/core/repositories/users' @@ -73,10 +70,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/core/domain/streams/operations.ts b/packages/server/modules/core/domain/streams/operations.ts index e4fd2af70..eac89d2d0 100644 --- a/packages/server/modules/core/domain/streams/operations.ts +++ b/packages/server/modules/core/domain/streams/operations.ts @@ -21,6 +21,7 @@ import { MaybeNullOrUndefined, Nullable, Optional, StreamRoles } from '@speckle/ import { Knex } from 'knex' import type express from 'express' import { ProjectCreateArgs } from '@/modules/core/domain/projects/operations' +import { ServerInviteRecord } from '@/modules/serverinvites/domain/types' export type LegacyGetStreams = (params: { cursor?: string | Date | null | undefined @@ -308,7 +309,7 @@ export type AddOrUpdateStreamCollaborator = ( addedById: string, adderResourceAccessRules?: MaybeNullOrUndefined, options?: Partial<{ - fromInvite: boolean + fromInvite: ServerInviteRecord }> ) => Promise diff --git a/packages/server/modules/core/graph/resolvers/projects.ts b/packages/server/modules/core/graph/resolvers/projects.ts index 3e8e8fe71..b7d9d8a85 100644 --- a/packages/server/modules/core/graph/resolvers/projects.ts +++ b/packages/server/modules/core/graph/resolvers/projects.ts @@ -3,7 +3,6 @@ import { saveActivityFactory } from '@/modules/activitystream/repositories' import { addStreamClonedActivityFactory, addStreamDeletedActivityFactory, - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory, addStreamPermissionsRevokedActivityFactory, addStreamUpdatedActivityFactory @@ -150,10 +149,7 @@ const updateStreamRoleAndNotify = updateStreamRoleAndNotifyFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/core/graph/resolvers/streams.ts b/packages/server/modules/core/graph/resolvers/streams.ts index a54c93bb9..32402571e 100644 --- a/packages/server/modules/core/graph/resolvers/streams.ts +++ b/packages/server/modules/core/graph/resolvers/streams.ts @@ -64,7 +64,6 @@ import { getEventBus } from '@/modules/shared/services/eventBus' import { createBranchFactory } from '@/modules/core/repositories/branches' import { addStreamDeletedActivityFactory, - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory, addStreamPermissionsRevokedActivityFactory, addStreamUpdatedActivityFactory @@ -155,10 +154,7 @@ const updateStreamRoleAndNotify = updateStreamRoleAndNotifyFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/core/services/streams/access.ts b/packages/server/modules/core/services/streams/access.ts index 32ca4bd78..59158de6a 100644 --- a/packages/server/modules/core/services/streams/access.ts +++ b/packages/server/modules/core/services/streams/access.ts @@ -1,5 +1,4 @@ import { - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory, addStreamPermissionsRevokedActivityFactory } from '@/modules/activitystream/services/streamActivity' @@ -18,8 +17,10 @@ import { StreamInvalidAccessError } from '@/modules/core/errors/stream' import { StreamRecord } from '@/modules/core/helpers/types' +import { ServerInvitesEvents } from '@/modules/serverinvites/domain/events' import { AuthorizeResolver } from '@/modules/shared/domain/operations' import { BadRequestError, ForbiddenError, LogicError } from '@/modules/shared/errors' +import { EventBusEmit } from '@/modules/shared/services/eventBus' import { ensureError, Roles, StreamRoles } from '@speckle/shared' /** @@ -144,9 +145,7 @@ export const addOrUpdateStreamCollaboratorFactory = validateStreamAccess: ValidateStreamAccess getUser: GetUser grantStreamPermissions: GrantStreamPermissions - addStreamInviteAcceptedActivity: ReturnType< - typeof addStreamInviteAcceptedActivityFactory - > + emitEvent: EventBusEmit addStreamPermissionsAddedActivity: ReturnType< typeof addStreamPermissionsAddedActivityFactory > @@ -191,12 +190,13 @@ export const addOrUpdateStreamCollaboratorFactory = })) as StreamRecord // validateStreamAccess already checked that it exists if (fromInvite) { - await deps.addStreamInviteAcceptedActivity({ - streamId, - inviterId: addedById, - inviteTargetId: userId, - role: role as StreamRoles, - stream + await deps.emitEvent({ + eventName: ServerInvitesEvents.Finalized, + payload: { + invite: fromInvite, + finalizerUserId: addedById, + accept: true + } }) } else { await deps.addStreamPermissionsAddedActivity({ diff --git a/packages/server/modules/core/tests/batchCommits.spec.ts b/packages/server/modules/core/tests/batchCommits.spec.ts index 6c3059b88..24062ecdb 100644 --- a/packages/server/modules/core/tests/batchCommits.spec.ts +++ b/packages/server/modules/core/tests/batchCommits.spec.ts @@ -1,10 +1,7 @@ import { buildApolloServer } from '@/app' import { db } from '@/db/knex' import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { - addStreamInviteAcceptedActivityFactory, - addStreamPermissionsAddedActivityFactory -} from '@/modules/activitystream/services/streamActivity' +import { addStreamPermissionsAddedActivityFactory } from '@/modules/activitystream/services/streamActivity' import { Commits, Streams, Users } from '@/modules/core/dbSchema' import { Roles } from '@/modules/core/helpers/mainConstants' import { createBranchFactory } from '@/modules/core/repositories/branches' @@ -16,6 +13,7 @@ import { validateStreamAccessFactory } from '@/modules/core/services/streams/access' import { authorizeResolver } from '@/modules/shared' +import { getEventBus } from '@/modules/shared/services/eventBus' import { publish } from '@/modules/shared/utils/subscriptions' import { BasicTestUser, createTestUsers } from '@/test/authHelper' import { deleteCommits, moveCommits } from '@/test/graphql/commits' @@ -45,10 +43,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/core/tests/commitsGraphql.spec.ts b/packages/server/modules/core/tests/commitsGraphql.spec.ts index 2c0eacfbc..24de8e7ce 100644 --- a/packages/server/modules/core/tests/commitsGraphql.spec.ts +++ b/packages/server/modules/core/tests/commitsGraphql.spec.ts @@ -1,10 +1,7 @@ import { buildApolloServer } from '@/app' import { db } from '@/db/knex' import { saveActivityFactory } from '@/modules/activitystream/repositories' -import { - addStreamInviteAcceptedActivityFactory, - addStreamPermissionsAddedActivityFactory -} from '@/modules/activitystream/services/streamActivity' +import { addStreamPermissionsAddedActivityFactory } from '@/modules/activitystream/services/streamActivity' import { Commits, Streams, Users } from '@/modules/core/dbSchema' import { Roles } from '@/modules/core/helpers/mainConstants' import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams' @@ -15,6 +12,7 @@ import { } from '@/modules/core/services/streams/access' import { authorizeResolver } from '@/modules/shared' import { Nullable } from '@/modules/shared/helpers/typeHelper' +import { getEventBus } from '@/modules/shared/services/eventBus' import { publish } from '@/modules/shared/utils/subscriptions' import { BasicTestUser, createTestUsers } from '@/test/authHelper' import { readOtherUsersCommits, readOwnCommits } from '@/test/graphql/commits' @@ -31,10 +29,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/core/tests/graph.spec.js b/packages/server/modules/core/tests/graph.spec.js index 528056052..42b26801f 100644 --- a/packages/server/modules/core/tests/graph.spec.js +++ b/packages/server/modules/core/tests/graph.spec.js @@ -23,7 +23,6 @@ const { } = require('@/modules/core/repositories/streams') const { addStreamPermissionsRevokedActivityFactory, - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory } = require('@/modules/activitystream/services/streamActivity') const { publish } = require('@/modules/shared/utils/subscriptions') @@ -94,10 +93,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/core/tests/integration/subs.graph.spec.ts b/packages/server/modules/core/tests/integration/subs.graph.spec.ts index 83d638d4e..8da8d527c 100644 --- a/packages/server/modules/core/tests/integration/subs.graph.spec.ts +++ b/packages/server/modules/core/tests/integration/subs.graph.spec.ts @@ -3,7 +3,6 @@ import { db } from '@/db/knex' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { addStreamDeletedActivityFactory, - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory, addStreamPermissionsRevokedActivityFactory, addStreamUpdatedActivityFactory @@ -208,10 +207,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser: getUserFactory({ db }), grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/core/tests/streams.spec.ts b/packages/server/modules/core/tests/streams.spec.ts index 433f69e2f..e6cacb67e 100644 --- a/packages/server/modules/core/tests/streams.spec.ts +++ b/packages/server/modules/core/tests/streams.spec.ts @@ -78,10 +78,7 @@ import { import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection' import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents' import { getEventBus } from '@/modules/shared/services/eventBus' -import { - addStreamInviteAcceptedActivityFactory, - addStreamPermissionsAddedActivityFactory -} from '@/modules/activitystream/services/streamActivity' +import { addStreamPermissionsAddedActivityFactory } from '@/modules/activitystream/services/streamActivity' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { publish } from '@/modules/shared/utils/subscriptions' import { @@ -175,10 +172,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/serverinvites/events/subscriptionListeners.ts b/packages/server/modules/serverinvites/events/subscriptionListeners.ts new file mode 100644 index 000000000..8ff20f73d --- /dev/null +++ b/packages/server/modules/serverinvites/events/subscriptionListeners.ts @@ -0,0 +1,101 @@ +import { + ProjectUpdatedMessageType, + UserProjectsUpdatedMessageType +} from '@/modules/core/graph/generated/graphql' +import { ServerInvitesEvents } from '@/modules/serverinvites/domain/events' +import { resolveTarget } from '@/modules/serverinvites/helpers/core' +import { GetProjectInviteProject } from '@/modules/serverinvites/services/operations' +import { StreamPubsubEvents } from '@/modules/shared' +import { DependenciesOf } from '@/modules/shared/helpers/factory' +import { EventBusListen, EventPayload } from '@/modules/shared/services/eventBus' +import { + ProjectSubscriptions, + PublishSubscription, + UserSubscriptions +} from '@/modules/shared/utils/subscriptions' + +const reportProjectInviteCreatedFactory = + (deps: { + publish: PublishSubscription + getProjectInviteProject: GetProjectInviteProject + }) => + async (payload: EventPayload) => { + const { invite } = payload.payload + const project = await deps.getProjectInviteProject({ invite }) + if (!project) return + + await deps.publish(ProjectSubscriptions.ProjectUpdated, { + projectUpdated: { + id: project.id, + type: ProjectUpdatedMessageType.Updated, + project + } + }) + } + +const reportProjectInviteFinalizedFactory = + (deps: { + publish: PublishSubscription + getProjectInviteProject: GetProjectInviteProject + }) => + async (payload: EventPayload) => { + const { invite, accept } = payload.payload + const project = await deps.getProjectInviteProject({ invite }) + if (!project) return + + const userTarget = resolveTarget(invite.target) + if (accept) { + await Promise.all([ + deps.publish(StreamPubsubEvents.UserStreamAdded, { + userStreamAdded: { + id: project.id, + sharedBy: invite.inviterId + }, + ownerId: userTarget.userId! + }), + deps.publish(UserSubscriptions.UserProjectsUpdated, { + userProjectsUpdated: { + id: project.id, + type: UserProjectsUpdatedMessageType.Added, + project + }, + ownerId: userTarget.userId! + }), + deps.publish(ProjectSubscriptions.ProjectUpdated, { + projectUpdated: { + id: project.id, + type: ProjectUpdatedMessageType.Updated, + project + } + }) + ]) + } else { + await deps.publish(ProjectSubscriptions.ProjectUpdated, { + projectUpdated: { + id: project.id, + type: ProjectUpdatedMessageType.Updated, + project + } + }) + } + } + +export const reportSubscriptionEventsFactory = + ( + deps: { + eventListen: EventBusListen + publish: PublishSubscription + } & DependenciesOf & + DependenciesOf + ) => + () => { + const reportProjectInviteCreated = reportProjectInviteCreatedFactory(deps) + const reportProjectInviteFinalized = reportProjectInviteFinalizedFactory(deps) + + const quitCbs = [ + deps.eventListen(ServerInvitesEvents.Created, reportProjectInviteCreated), + deps.eventListen(ServerInvitesEvents.Finalized, reportProjectInviteFinalized) + ] + + return () => quitCbs.forEach((quit) => quit()) + } diff --git a/packages/server/modules/serverinvites/graph/resolvers/serverInvites.ts b/packages/server/modules/serverinvites/graph/resolvers/serverInvites.ts index b34f2847f..6277110a1 100644 --- a/packages/server/modules/serverinvites/graph/resolvers/serverInvites.ts +++ b/packages/server/modules/serverinvites/graph/resolvers/serverInvites.ts @@ -56,11 +56,7 @@ import { processFinalizedProjectInviteFactory, validateProjectInviteBeforeFinalizationFactory } from '@/modules/serverinvites/services/coreFinalization' -import { - addStreamInviteAcceptedActivityFactory, - addStreamInviteDeclinedActivityFactory, - addStreamPermissionsAddedActivityFactory -} from '@/modules/activitystream/services/streamActivity' +import { addStreamPermissionsAddedActivityFactory } from '@/modules/activitystream/services/streamActivity' import { createUserEmailFactory, ensureNoPrimaryEmailForUserFactory, @@ -93,10 +89,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish @@ -321,10 +314,6 @@ export = { }), processInvite: processFinalizedProjectInviteFactory({ getProject: getStream, - addInviteDeclinedActivity: addStreamInviteDeclinedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), addProjectRole: addOrUpdateStreamCollaborator }), deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }), @@ -470,10 +459,6 @@ export = { }), processInvite: processFinalizedProjectInviteFactory({ getProject: getStream, - addInviteDeclinedActivity: addStreamInviteDeclinedActivityFactory({ - saveActivity: saveActivityFactory({ db }), - publish - }), addProjectRole: addOrUpdateStreamCollaborator }), deleteInvitesByTarget: deleteInvitesByTargetFactory({ db }), diff --git a/packages/server/modules/serverinvites/index.ts b/packages/server/modules/serverinvites/index.ts index f8e7d5d5a..73c70e001 100644 --- a/packages/server/modules/serverinvites/index.ts +++ b/packages/server/modules/serverinvites/index.ts @@ -3,6 +3,11 @@ import { moduleLogger } from '@/logging/logging' import db from '@/db/knex' import { Scopes } from '@speckle/shared' import { SpeckleModule } from '@/modules/shared/helpers/typeHelper' +import { getEventBus } from '@/modules/shared/services/eventBus' +import { publish } from '@/modules/shared/utils/subscriptions' +import { getStreamFactory } from '@/modules/core/repositories/streams' +import { getProjectInviteProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement' +import { reportSubscriptionEventsFactory } from '@/modules/serverinvites/events/subscriptionListeners' const scopes = [ { @@ -12,11 +17,22 @@ const scopes = [ } ] -export const init: SpeckleModule['init'] = async () => { +export const init: SpeckleModule['init'] = async (_app, isInitial) => { moduleLogger.info('💌 Init invites module') const registerFunc = registerOrUpdateScopeFactory({ db }) for (const scope of scopes) { await registerFunc({ scope }) } + + if (isInitial) { + // Setup GQL sub emits + reportSubscriptionEventsFactory({ + eventListen: getEventBus().listen, + publish, + getProjectInviteProject: getProjectInviteProjectFactory({ + getStream: getStreamFactory({ db }) + }) + })() + } } diff --git a/packages/server/modules/serverinvites/services/coreFinalization.ts b/packages/server/modules/serverinvites/services/coreFinalization.ts index 80fde78f3..56a287241 100644 --- a/packages/server/modules/serverinvites/services/coreFinalization.ts +++ b/packages/server/modules/serverinvites/services/coreFinalization.ts @@ -1,4 +1,3 @@ -import { AddStreamInviteDeclinedActivity } from '@/modules/activitystream/domain/operations' import { AddOrUpdateStreamCollaborator, GetStream @@ -77,28 +76,19 @@ export const validateProjectInviteBeforeFinalizationFactory = type ProcessFinalizedProjectInviteFactoryDeps = { getProject: GetStream - addInviteDeclinedActivity: AddStreamInviteDeclinedActivity addProjectRole: AddOrUpdateStreamCollaborator } export const processFinalizedProjectInviteFactory = (deps: ProcessFinalizedProjectInviteFactoryDeps): ProcessFinalizedResourceInvite => async (params) => { - const { getProject, addInviteDeclinedActivity, addProjectRole } = deps + const { getProject, addProjectRole } = deps const { invite, finalizerUserId, action } = params const project = await getProject({ streamId: invite.resource.resourceId }) if (action === InviteFinalizationAction.DECLINE) { // Skip validation so user can get rid of the invite regardless - if (project) { - await addInviteDeclinedActivity({ - streamId: invite.resource.resourceId, - inviteTargetId: finalizerUserId, - inviterId: invite.inviterId, - stream: project - }) - } return } @@ -116,7 +106,7 @@ export const processFinalizedProjectInviteFactory = invite.resource.role || Roles.Stream.Contributor, invite.inviterId, null, - { fromInvite: true } + { fromInvite: invite } ) } catch (e) { if (!(e instanceof StreamInvalidAccessError)) { diff --git a/packages/server/modules/serverinvites/services/operations.ts b/packages/server/modules/serverinvites/services/operations.ts index 214028a21..d042d18a4 100644 --- a/packages/server/modules/serverinvites/services/operations.ts +++ b/packages/server/modules/serverinvites/services/operations.ts @@ -1,3 +1,4 @@ +import { Project } from '@/modules/core/domain/streams/types' import { TokenResourceIdentifier } from '@/modules/core/domain/tokens/types' import { ServerInfo } from '@/modules/core/helpers/types' import { UserWithOptionalRole } from '@/modules/core/repositories/users' @@ -109,3 +110,7 @@ export type FinalizeInvitedServerRegistration = ( ) => Promise export type ResolveAuthRedirectPath = (invite?: ServerInviteRecord) => string + +export type GetProjectInviteProject = (params: { + invite: ServerInviteRecord +}) => Promise diff --git a/packages/server/modules/serverinvites/services/projectInviteManagement.ts b/packages/server/modules/serverinvites/services/projectInviteManagement.ts index 77206c1dd..32df8633f 100644 --- a/packages/server/modules/serverinvites/services/projectInviteManagement.ts +++ b/packages/server/modules/serverinvites/services/projectInviteManagement.ts @@ -31,6 +31,7 @@ import { } from '@/modules/serverinvites/errors' import { buildUserTarget, + isProjectResourceTarget, resolveInviteTargetTitle, resolveTarget, ResourceTargetTypeRoleTypeMap @@ -39,7 +40,8 @@ import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/ import { CreateAndSendInvite, FinalizeInvite, - GetInvitationTargetUsers + GetInvitationTargetUsers, + GetProjectInviteProject } from '@/modules/serverinvites/services/operations' import { MaybeNullOrUndefined, @@ -292,3 +294,14 @@ export const getPendingProjectCollaboratorsFactory = return results } + +export const getProjectInviteProjectFactory = + (deps: { getStream: GetStream }): GetProjectInviteProject => + async (params) => { + const { invite } = params + const primaryResourceTarget = invite.resource + + if (!isProjectResourceTarget(primaryResourceTarget)) return undefined + + return await deps.getStream({ streamId: primaryResourceTarget.resourceId }) + } diff --git a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts index 9b7eabf08..b9249eba2 100644 --- a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts +++ b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts @@ -148,7 +148,6 @@ import { validateStreamAccessFactory } from '@/modules/core/services/streams/access' import { - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory, addStreamPermissionsRevokedActivityFactory } from '@/modules/activitystream/services/streamActivity' @@ -289,10 +288,7 @@ const updateStreamRoleAndNotify = updateStreamRoleAndNotifyFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts b/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts index 8b7a767bd..5c85b465e 100644 --- a/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts +++ b/packages/server/modules/workspaces/tests/integration/invites.graph.spec.ts @@ -58,16 +58,14 @@ import { validateStreamAccessFactory } from '@/modules/core/services/streams/access' import { authorizeResolver } from '@/modules/shared' -import { - addStreamInviteAcceptedActivityFactory, - addStreamPermissionsAddedActivityFactory -} from '@/modules/activitystream/services/streamActivity' +import { addStreamPermissionsAddedActivityFactory } from '@/modules/activitystream/services/streamActivity' import { publish } from '@/modules/shared/utils/subscriptions' import { getUserFactory } from '@/modules/core/repositories/users' import { TestInvitesGraphQLOperations, buildInvitesGraphqlOperations } from '@/modules/workspaces/tests/helpers/invites' +import { getEventBus } from '@/modules/shared/services/eventBus' enum InviteByTarget { Email = 'email', @@ -82,10 +80,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish diff --git a/packages/server/test/speckle-helpers/streamHelper.ts b/packages/server/test/speckle-helpers/streamHelper.ts index 6414e35ca..78a44ee98 100644 --- a/packages/server/test/speckle-helpers/streamHelper.ts +++ b/packages/server/test/speckle-helpers/streamHelper.ts @@ -1,7 +1,6 @@ import { db } from '@/db/knex' import { saveActivityFactory } from '@/modules/activitystream/repositories' import { - addStreamInviteAcceptedActivityFactory, addStreamPermissionsAddedActivityFactory, addStreamPermissionsRevokedActivityFactory } from '@/modules/activitystream/services/streamActivity' @@ -98,10 +97,7 @@ const addOrUpdateStreamCollaborator = addOrUpdateStreamCollaboratorFactory({ validateStreamAccess, getUser, grantStreamPermissions: grantStreamPermissionsFactory({ db }), - addStreamInviteAcceptedActivity: addStreamInviteAcceptedActivityFactory({ - saveActivity, - publish - }), + emitEvent: getEventBus().emit, addStreamPermissionsAddedActivity: addStreamPermissionsAddedActivityFactory({ saveActivity, publish From 5560582758e83ecfe6d15759591991daba3b33ec Mon Sep 17 00:00:00 2001 From: Mike Tasset Date: Tue, 28 Jan 2025 13:11:15 +0100 Subject: [PATCH 11/78] Fix: Change loadObjectAsync to new loader --- .../lib/viewer/composables/setup/postSetup.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts index 2c042355a..536c862d5 100644 --- a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts +++ b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts @@ -14,7 +14,8 @@ import { SectionToolEvent, SectionTool, ViewModes, - ViewModeEvent + ViewModeEvent, + SpeckleLoader } from '@speckle/viewer' import { useAuthCookie } from '~~/lib/auth/composables/auth' import type { @@ -107,12 +108,15 @@ function useViewerObjectAutoLoading() { if (unload) { viewer.unloadObject(objectUrl) } else { - viewer.loadObjectAsync( + const loader = new SpeckleLoader( + viewer.getWorldTree(), objectUrl, authToken.value || undefined, disableViewerCache ? false : undefined, - options?.zoomToObject + undefined ) + + viewer.loadObject(loader, options?.zoomToObject) } } From 1f9f3f98973e38a27b67fd12f63a7f1392e97894 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Wed, 29 Jan 2025 15:26:16 +0000 Subject: [PATCH 12/78] feat(viewer): adds loading progress basics --- .../components/viewer/LoadingBar.vue | 10 ++++--- .../components/viewer/PreSetupWrapper.vue | 5 +++- .../lib/common/generated/gql/graphql.ts | 24 +++++++-------- .../lib/viewer/composables/setup.ts | 4 +++ .../lib/viewer/composables/setup/postSetup.ts | 30 ++++++++++++++++++- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/packages/frontend-2/components/viewer/LoadingBar.vue b/packages/frontend-2/components/viewer/LoadingBar.vue index 1edfc7b9c..41ee90074 100644 --- a/packages/frontend-2/components/viewer/LoadingBar.vue +++ b/packages/frontend-2/components/viewer/LoadingBar.vue @@ -2,11 +2,13 @@
-
+ + {{ Math.floor(loadProgress * 100) }}% +
@@ -16,7 +18,7 @@ import { useViewerTour } from '~/lib/viewer/composables/tour' import { useInjectedViewerInterfaceState } from '~~/lib/viewer/composables/setup' const { isEnabled: isEmbedEnabled } = useEmbed() -const { viewerBusy } = useInjectedViewerInterfaceState() +const { viewerBusy, loadProgress } = useInjectedViewerInterfaceState() const { showNavbar } = useViewerTour() From 209ae9582a745d641c48c2a93cb3cad620c7f074 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:34:59 +0000 Subject: [PATCH 14/78] Fix broken logic and add 3xl variant. (#3955) --- packages/ui-components/src/components/user/AvatarEditor.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ui-components/src/components/user/AvatarEditor.vue b/packages/ui-components/src/components/user/AvatarEditor.vue index a66b8d6b8..bae1d7795 100644 --- a/packages/ui-components/src/components/user/AvatarEditor.vue +++ b/packages/ui-components/src/components/user/AvatarEditor.vue @@ -129,9 +129,13 @@ const activeImageUrl = ref(null as Nullable) const canvasSize = computed(() => { switch (props.size) { - case 'xs' || 'sm' || 'lg' || 'xl': + case 'xs': + case 'sm': + case 'lg': + case 'xl': return { width: 64, height: 64 } case 'xxl': + case '3xl': return { width: 140, height: 140 } case 'editable': return { width: 240, height: 240 } From 6b2e1c375042db0a50f7281730b701a73c955614 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Mon, 10 Feb 2025 14:40:00 +0000 Subject: [PATCH 15/78] feat: beautfication --- .../components/viewer/LoadingBar.vue | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/packages/frontend-2/components/viewer/LoadingBar.vue b/packages/frontend-2/components/viewer/LoadingBar.vue index 41ee90074..6ceb94a3a 100644 --- a/packages/frontend-2/components/viewer/LoadingBar.vue +++ b/packages/frontend-2/components/viewer/LoadingBar.vue @@ -1,14 +1,23 @@ @@ -21,25 +30,3 @@ const { isEnabled: isEmbedEnabled } = useEmbed() const { viewerBusy, loadProgress } = useInjectedViewerInterfaceState() const { showNavbar } = useViewerTour() - From 0a39db023627415a09766d3b0110742889aaf36a Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Mon, 10 Feb 2025 15:53:29 +0000 Subject: [PATCH 16/78] feat: disposes of loaders on complete/cancel --- .../lib/viewer/composables/setup/postSetup.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts index 4cd7c5034..830067e74 100644 --- a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts +++ b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts @@ -102,9 +102,13 @@ function useViewerObjectAutoLoading() { } = useInjectedViewerState() const loadingProgressMap: { [id: string]: number } = {} + const loadersMap: { [id: string]: SpeckleLoader } = {} viewer.on(ViewerEvent.LoadComplete, (id) => { delete loadingProgressMap[id] + // disposes of loader on load complete + loadersMap[id].dispose() + delete loadersMap[id] consolidateProgressInternal({ id, progress: 1 }) }) @@ -141,9 +145,15 @@ function useViewerObjectAutoLoading() { loader.on(LoaderEvent.LoadProgress, (args) => consolidateProgressThorttled(args)) loader.on(LoaderEvent.LoadCancelled, (id) => { delete loadingProgressMap[id] + + // disposes of loader on load complete + loadersMap[id].dispose() + delete loadersMap[id] consolidateProgressInternal({ id, progress: 1 }) }) + loadersMap[objectUrl] = loader + viewer.loadObject(loader, options?.zoomToObject) } } From 1b529fb5aca7114d8a8a956340acaf2d3b2673b8 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Mon, 10 Feb 2025 16:43:56 +0000 Subject: [PATCH 17/78] feat: progress starts at 0, vs 1 --- packages/frontend-2/lib/viewer/composables/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-2/lib/viewer/composables/setup.ts b/packages/frontend-2/lib/viewer/composables/setup.ts index cd27d56c7..1c1f06d51 100644 --- a/packages/frontend-2/lib/viewer/composables/setup.ts +++ b/packages/frontend-2/lib/viewer/composables/setup.ts @@ -920,7 +920,7 @@ function setupInterfaceState( set: (newVal) => (isViewerBusy.value = !!newVal) }) - const loadProgress = ref(1) + const loadProgress = ref(0) const isolatedObjectIds = ref([] as string[]) const hiddenObjectIds = ref([] as string[]) From 19b9cc68e77ae72792ad202ee3c109a0fc0d35cc Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:55:05 +0000 Subject: [PATCH 18/78] fix(fileimport): remove references to object_children_closures table (#3956) --- packages/fileimport-service/src/api.js | 40 +------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/packages/fileimport-service/src/api.js b/packages/fileimport-service/src/api.js index f75c13faa..08e5b566c 100644 --- a/packages/fileimport-service/src/api.js +++ b/packages/fileimport-service/src/api.js @@ -9,7 +9,6 @@ const Observability = require('@speckle/shared/dist/commonjs/observability/index const tables = (db) => ({ objects: db('objects'), - closures: db('object_children_closure'), branches: db('branches'), streams: db('streams'), apiTokens: db('api_tokens'), @@ -47,17 +46,9 @@ module.exports = class ServerAPI { async createObject({ streamId, object }) { const insertionObject = this.prepInsertionObject(streamId, object) - const closures = [] const totalChildrenCountByDepth = {} if (object.__closure !== null) { for (const prop in object.__closure) { - closures.push({ - streamId, - parent: insertionObject.id, - child: prop, - minDepth: object.__closure[prop] - }) - if (totalChildrenCountByDepth[object.__closure[prop].toString()]) totalChildrenCountByDepth[object.__closure[prop].toString()]++ else totalChildrenCountByDepth[object.__closure[prop].toString()] = 1 @@ -67,22 +58,17 @@ module.exports = class ServerAPI { delete insertionObject.__tree delete insertionObject.__closure - insertionObject.totalChildrenCount = closures.length + insertionObject.totalChildrenCount = object.__closures.length insertionObject.totalChildrenCountByDepth = JSON.stringify( totalChildrenCountByDepth ) await this.tables.objects.insert(insertionObject).onConflict().ignore() - if (closures.length > 0) { - await this.tables.closures.insert(closures).onConflict().ignore() - } - return insertionObject.id } async createObjectsBatched(streamId, objects) { - const closures = [] const objsToInsert = [] const ids = [] @@ -94,12 +80,6 @@ module.exports = class ServerAPI { if (obj.__closure !== null) { for (const prop in obj.__closure) { - closures.push({ - streamId, - parent: insertionObject.id, - child: prop, - minDepth: obj.__closure[prop] - }) totalChildrenCountGlobal++ if (totalChildrenCountByDepth[obj.__closure[prop].toString()]) totalChildrenCountByDepth[obj.__closure[prop].toString()]++ @@ -119,7 +99,6 @@ module.exports = class ServerAPI { ids.push(insertionObject.id) }) - const closureBatchSize = 1000 const objectsBatchSize = 500 // step 1: insert objects @@ -139,23 +118,6 @@ module.exports = class ServerAPI { } } - // step 2: insert closures - if (closures.length > 0) { - const batches = chunk(closures, closureBatchSize) - - for (const [index, batch] of batches.entries()) { - this.prepInsertionClosureBatch(batch) - await this.tables.closures.insert(batch).onConflict().ignore() - this.logger.info( - { - currentBatchCount: batch.length, - currentBatchId: index + 1, - totalNumberOfBatches: batches.length - }, - 'Inserted {currentBatchCount} closures from batch {currentBatchId} of {totalNumberOfBatches}' - ) - } - } return ids } From cacc7aaabe4e93a0ee09ac16fe72904e8943feb0 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Tue, 11 Feb 2025 09:56:07 +0000 Subject: [PATCH 19/78] fix: uncorrects the correct disposal --- .../lib/viewer/composables/setup/postSetup.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts index 830067e74..27cc14a5a 100644 --- a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts +++ b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts @@ -102,13 +102,9 @@ function useViewerObjectAutoLoading() { } = useInjectedViewerState() const loadingProgressMap: { [id: string]: number } = {} - const loadersMap: { [id: string]: SpeckleLoader } = {} viewer.on(ViewerEvent.LoadComplete, (id) => { delete loadingProgressMap[id] - // disposes of loader on load complete - loadersMap[id].dispose() - delete loadersMap[id] consolidateProgressInternal({ id, progress: 1 }) }) @@ -131,6 +127,7 @@ function useViewerObjectAutoLoading() { options?: Partial<{ zoomToObject: boolean }> ) => { const objectUrl = getObjectUrl(projectId.value, objectId) + if (unload) { viewer.unloadObject(objectUrl) } else { @@ -145,15 +142,9 @@ function useViewerObjectAutoLoading() { loader.on(LoaderEvent.LoadProgress, (args) => consolidateProgressThorttled(args)) loader.on(LoaderEvent.LoadCancelled, (id) => { delete loadingProgressMap[id] - - // disposes of loader on load complete - loadersMap[id].dispose() - delete loadersMap[id] consolidateProgressInternal({ id, progress: 1 }) }) - loadersMap[objectUrl] = loader - viewer.loadObject(loader, options?.zoomToObject) } } From e0577c5f92822c0d0ded7a2a7145e93ed2a3fbd9 Mon Sep 17 00:00:00 2001 From: Chuck Driesler Date: Tue, 11 Feb 2025 10:03:00 +0000 Subject: [PATCH 20/78] fix(automate): redirect functions page (#3952) --- .../lib/common/generated/gql/gql.ts | 5 - .../lib/common/generated/gql/graphql.ts | 9 -- packages/frontend-2/nuxt.config.ts | 6 + packages/frontend-2/pages/functions/index.vue | 112 ------------------ 4 files changed, 6 insertions(+), 126 deletions(-) delete mode 100644 packages/frontend-2/pages/functions/index.vue diff --git a/packages/frontend-2/lib/common/generated/gql/gql.ts b/packages/frontend-2/lib/common/generated/gql/gql.ts index 5f84036ca..cc3174cb6 100644 --- a/packages/frontend-2/lib/common/generated/gql/gql.ts +++ b/packages/frontend-2/lib/common/generated/gql/gql.ts @@ -382,7 +382,6 @@ const documents = { "\n fragment AutomateFunctionPage_AutomateFunction on AutomateFunction {\n id\n name\n description\n logo\n supportedSourceApps\n tags\n ...AutomateFunctionPageHeader_Function\n ...AutomateFunctionPageInfo_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n creator {\n id\n }\n }\n": types.AutomateFunctionPage_AutomateFunctionFragmentDoc, "\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n activeUser {\n workspaces {\n items {\n ...AutomateFunctionCreateDialog_Workspace\n ...AutomateFunctionEditDialog_Workspace\n }\n }\n }\n }\n": types.AutomateFunctionPageDocument, "\n query AutomateFunctionPageWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n id\n ...AutomateFunctionPageHeader_Workspace\n }\n }\n": types.AutomateFunctionPageWorkspaceDocument, - "\n query AutomateFunctionsPage($search: String, $cursor: String = null) {\n ...AutomateFunctionsPageItems_Query\n ...AutomateFunctionsPageHeader_Query\n }\n": types.AutomateFunctionsPageDocument, "\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n workspace {\n id\n }\n ...ProjectPageTeamInternals_Project\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n ...ProjectsMoveToWorkspaceDialog_Project\n }\n": types.ProjectPageProjectFragmentDoc, "\n fragment ProjectPageAutomationPage_Automation on Automation {\n id\n ...ProjectPageAutomationHeader_Automation\n ...ProjectPageAutomationFunctions_Automation\n ...ProjectPageAutomationRuns_Automation\n }\n": types.ProjectPageAutomationPage_AutomationFragmentDoc, "\n fragment ProjectPageAutomationPage_Project on Project {\n id\n workspaceId\n ...ProjectPageAutomationHeader_Project\n }\n": types.ProjectPageAutomationPage_ProjectFragmentDoc, @@ -1885,10 +1884,6 @@ export function graphql(source: "\n query AutomateFunctionPage($functionId: ID! * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n query AutomateFunctionPageWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n id\n ...AutomateFunctionPageHeader_Workspace\n }\n }\n"): (typeof documents)["\n query AutomateFunctionPageWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n id\n ...AutomateFunctionPageHeader_Workspace\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query AutomateFunctionsPage($search: String, $cursor: String = null) {\n ...AutomateFunctionsPageItems_Query\n ...AutomateFunctionsPageHeader_Query\n }\n"): (typeof documents)["\n query AutomateFunctionsPage($search: String, $cursor: String = null) {\n ...AutomateFunctionsPageItems_Query\n ...AutomateFunctionsPageHeader_Query\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index 1e6f73f33..36ef136fa 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -6504,14 +6504,6 @@ export type AutomateFunctionPageWorkspaceQueryVariables = Exact<{ export type AutomateFunctionPageWorkspaceQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', id: string, name: string, slug: string } }; -export type AutomateFunctionsPageQueryVariables = Exact<{ - search?: InputMaybe; - cursor?: InputMaybe; -}>; - - -export type AutomateFunctionsPageQuery = { __typename?: 'Query', automateFunctions: { __typename?: 'AutomateFunctionCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'AutomateFunction', id: string, name: string, isFeatured: boolean, description: string, logo?: string | null, repo: { __typename?: 'BasicGitRepositoryMetadata', id: string, url: string, owner: string, name: string }, releases: { __typename?: 'AutomateFunctionReleaseCollection', items: Array<{ __typename?: 'AutomateFunctionRelease', id: string, inputSchema?: {} | null }> } }> }, activeUser?: { __typename?: 'User', id: string, role?: string | null, automateInfo: { __typename?: 'UserAutomateInfo', hasAutomateGithubApp: boolean, availableGithubOrgs: Array } } | null, serverInfo: { __typename?: 'ServerInfo', automate: { __typename?: 'ServerAutomateInfo', availableFunctionTemplates: Array<{ __typename?: 'AutomateFunctionTemplate', id: AutomateFunctionTemplateLanguage, title: string, logo: string, url: string }> } } }; - export type ProjectPageProjectFragment = { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number }, workspace?: { __typename?: 'Workspace', id: string, slug: string, name: string, logo?: string | null } | null, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, role: string, inviteId: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, team: Array<{ __typename?: 'ProjectCollaborator', role: string, id: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, versions: { __typename?: 'VersionCollection', totalCount: number } }; export type ProjectPageAutomationPage_AutomationFragment = { __typename?: 'Automation', id: string, name: string, enabled: boolean, isTestAutomation: boolean, currentRevision?: { __typename?: 'AutomationRevision', id: string, triggerDefinitions: Array<{ __typename?: 'VersionCreatedTriggerDefinition', type: AutomateRunTriggerType, model?: { __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null } | null }>, functions: Array<{ __typename?: 'AutomationRevisionFunction', parameters?: {} | null, release: { __typename?: 'AutomateFunctionRelease', id: string, inputSchema?: {} | null, versionTag: string, createdAt: string, function: { __typename?: 'AutomateFunction', id: string, name: string, isFeatured: boolean, description: string, logo?: string | null, releases: { __typename?: 'AutomateFunctionReleaseCollection', items: Array<{ __typename?: 'AutomateFunctionRelease', id: string }> }, repo: { __typename?: 'BasicGitRepositoryMetadata', id: string, url: string, owner: string, name: string } } } }> } | null, runs: { __typename?: 'AutomateRunCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'AutomateRun', id: string, status: AutomateRunStatus, createdAt: string, updatedAt: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', statusMessage?: string | null, id: string, status: AutomateRunStatus }>, trigger: { __typename?: 'VersionCreatedTrigger', version?: { __typename?: 'Version', id: string } | null, model?: { __typename?: 'Model', id: string } | null } }> } }; @@ -6928,7 +6920,6 @@ export const AutoAcceptableWorkspaceInviteDocument = {"kind":"Document","definit export const ResolveCommentLinkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ResolveCommentLink"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"commentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"comment"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"commentId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LinkableComment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LinkableComment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"viewerResources"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"versionId"}},{"kind":"Field","name":{"kind":"Name","value":"objectId"}}]}}]}}]} as unknown as DocumentNode; export const AutomateFunctionPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AutomateFunctionPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"functionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"automateFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"functionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionPage_AutomateFunction"}}]}},{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionCreateDialog_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionEditDialog_Workspace"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionPageHeader_Function"},"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":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaceIds"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionPageParametersDialog_AutomateFunctionRelease"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRelease"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inputSchema"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionPageInfo_AutomateFunction"},"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":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"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":"inputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"commitId"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionPageParametersDialog_AutomateFunctionRelease"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"},"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":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction"},"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":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"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":"inputSchema"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateAutomationCreateDialog_AutomateFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionPage_AutomateFunction"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"supportedSourceApps"}},{"kind":"Field","name":{"kind":"Name","value":"tags"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionPageHeader_Function"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionPageInfo_AutomateFunction"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateAutomationCreateDialog_AutomateFunction"}},{"kind":"Field","name":{"kind":"Name","value":"creator"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionCreateDialog_Workspace"},"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":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionEditDialog_Workspace"},"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"}}]}}]} as unknown as DocumentNode; export const AutomateFunctionPageWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AutomateFunctionPageWorkspace"},"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":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionPageHeader_Workspace"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionPageHeader_Workspace"},"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"}}]}}]} as unknown as DocumentNode; -export const AutomateFunctionsPageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AutomateFunctionsPage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"search"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionsPageItems_Query"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionsPageHeader_Query"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"},"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":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction"},"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":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"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":"inputSchema"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateAutomationCreateDialog_AutomateFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionTemplate"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionsPageItems_Query"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Query"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"automateFunctions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"6"}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"search"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateAutomationCreateDialog_AutomateFunction"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionsPageHeader_Query"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Query"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"automateInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasAutomateGithubApp"}},{"kind":"Field","name":{"kind":"Name","value":"availableGithubOrgs"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"automate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"availableFunctionTemplates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const SettingsServerRegionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SettingsServerRegions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"multiRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SettingsServerRegionsTable_ServerRegionItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"availableKeys"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SettingsServerRegionsTable_ServerRegionItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerRegionItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]} as unknown as DocumentNode; export type AllObjectTypes = { ActiveUserMutations: ActiveUserMutations, diff --git a/packages/frontend-2/nuxt.config.ts b/packages/frontend-2/nuxt.config.ts index 64d279849..7ab8204fe 100644 --- a/packages/frontend-2/nuxt.config.ts +++ b/packages/frontend-2/nuxt.config.ts @@ -158,6 +158,12 @@ export default defineNuxtConfig({ 'Access-Control-Expose-Headers': '*' } }, + '/functions': { + redirect: { + to: '/', + statusCode: 307 + } + }, // Redirect old settings pages '/server-management/projects': { redirect: { diff --git a/packages/frontend-2/pages/functions/index.vue b/packages/frontend-2/pages/functions/index.vue deleted file mode 100644 index bbee0f901..000000000 --- a/packages/frontend-2/pages/functions/index.vue +++ /dev/null @@ -1,112 +0,0 @@ - - From b8f76568e9c194ffd847c1aa40932a0c4e6b11fb Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Tue, 11 Feb 2025 11:09:01 +0000 Subject: [PATCH 21/78] Add keydown.stop (#3959) --- packages/ui-components/src/components/form/TextArea.vue | 1 + packages/ui-components/src/components/form/TextInput.vue | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/ui-components/src/components/form/TextArea.vue b/packages/ui-components/src/components/form/TextArea.vue index 66fe0b7d1..088d99798 100644 --- a/packages/ui-components/src/components/form/TextArea.vue +++ b/packages/ui-components/src/components/form/TextArea.vue @@ -37,6 +37,7 @@ v-bind="$attrs" @change="$emit('change', { event: $event, value })" @input="$emit('input', { event: $event, value })" + @keydown.stop /> From fc5f680c1248269ce0864ba9bf9f91f162a6d2e2 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle Date: Tue, 11 Feb 2025 13:21:08 +0000 Subject: [PATCH 22/78] Remove debug code --- packages/frontend-2/components/viewer/PreSetupWrapper.vue | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/frontend-2/components/viewer/PreSetupWrapper.vue b/packages/frontend-2/components/viewer/PreSetupWrapper.vue index 495cd75e4..1a3261e45 100644 --- a/packages/frontend-2/components/viewer/PreSetupWrapper.vue +++ b/packages/frontend-2/components/viewer/PreSetupWrapper.vue @@ -56,9 +56,6 @@ -
- test -
Date: Tue, 11 Feb 2025 16:06:14 +0000 Subject: [PATCH 23/78] chore(automate): mixpanel metrics by function id and type (#3960) * chore(automate): mixpanel metrics for function id and type * chore(automate): catch test env --- packages/server/modules/automate/index.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/server/modules/automate/index.ts b/packages/server/modules/automate/index.ts index 5fa95234f..00e95e55a 100644 --- a/packages/server/modules/automate/index.ts +++ b/packages/server/modules/automate/index.ts @@ -16,7 +16,10 @@ import { } from '@/modules/automate/repositories/automations' import { isNonNullable, Scopes, throwUncoveredError } from '@speckle/shared' import { registerOrUpdateScopeFactory } from '@/modules/shared/repositories/scopes' -import { triggerAutomationRun } from '@/modules/automate/clients/executionEngine' +import { + getFunction, + triggerAutomationRun +} from '@/modules/automate/clients/executionEngine' import logStreamRest from '@/modules/automate/rest/logStream' import { getEncryptionKeyPairFor, @@ -25,7 +28,7 @@ import { import { buildDecryptor } from '@/modules/shared/utils/libsodium' import { getUserEmailFromAutomationRunFactory } from '@/modules/automate/services/tracking' import authGithubAppRest from '@/modules/automate/rest/authGithubApp' -import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' +import { getFeatureFlags, isTestEnv } from '@/modules/shared/helpers/envHelper' import { TokenScopeData } from '@/modules/shared/domain/rolesAndScopes/types' import { db } from '@/db/knex' import { ProjectSubscriptions, publish } from '@/modules/shared/utils/subscriptions' @@ -285,6 +288,10 @@ const initializeEventListeners = () => { return } + const fn = isTestEnv() + ? null + : await getFunction({ functionId: functionRun.functionId }) + const userEmail = await getUserEmailFromAutomationRunFactory({ getFullAutomationRevisionMetadata: getFullAutomationRevisionMetadataFactory({ db: projectDb @@ -300,6 +307,9 @@ const initializeEventListeners = () => { automationRevisionId: automationWithRevision.id, automationName: automationWithRevision.name, runId: run.id, + functionId: fn?.functionId, + functionName: fn?.functionName, + functionType: fn?.isFeatured ? 'public' : 'private', functionRunId: functionRun.id, status: functionRun.status, durationInSeconds: functionRun.elapsed / 1000, From 8d0678b3bdaeaaddc5c72e25d520177380d16b99 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:07:25 +0000 Subject: [PATCH 24/78] fix(auth/error): improve error handling of auth issues (#3950) --- packages/server/modules/auth/strategies/github.ts | 5 +++-- packages/server/modules/auth/strategies/google.ts | 5 +++-- packages/server/modules/auth/strategies/local.ts | 12 +++++++++++- packages/server/modules/auth/strategies/oidc.ts | 5 +++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/server/modules/auth/strategies/github.ts b/packages/server/modules/auth/strategies/github.ts index b70905edf..962c643f4 100644 --- a/packages/server/modules/auth/strategies/github.ts +++ b/packages/server/modules/auth/strategies/github.ts @@ -164,11 +164,12 @@ const githubStrategyBuilderFactory = case InviteNotFoundError: case UnverifiedEmailSSOLoginError: logger.info(err) - break + return done(null, false, { message: e.message }) default: logger.error(err) + // Only when the server is operating abnormally should err be set, to indicate an internal error. + return done(err, false, { message: e.message }) } - return done(err, false, { message: e.message }) } } ) diff --git a/packages/server/modules/auth/strategies/google.ts b/packages/server/modules/auth/strategies/google.ts index 84d2fd1b0..aac6f43f2 100644 --- a/packages/server/modules/auth/strategies/google.ts +++ b/packages/server/modules/auth/strategies/google.ts @@ -147,11 +147,12 @@ const googleStrategyBuilderFactory = case UserInputError: case InviteNotFoundError: logger.info({ err: e }) - break + return done(null, false, { message: e.message }) default: logger.error({ err: e }) + // Only when the server is operating abnormally should err be set, to indicate an internal error. + return done(e, false, { message: e.message }) } - return done(e, false, { message: e.message }) } } ) diff --git a/packages/server/modules/auth/strategies/local.ts b/packages/server/modules/auth/strategies/local.ts index 5fb92c777..1bc363ea7 100644 --- a/packages/server/modules/auth/strategies/local.ts +++ b/packages/server/modules/auth/strategies/local.ts @@ -23,6 +23,7 @@ import { ValidateUserPassword } from '@/modules/core/domain/users/operations' import { GetServerInfo } from '@/modules/core/domain/server/operations' +import { UserValidationError } from '@/modules/core/errors/user' const localStrategyBuilderFactory = (deps: { @@ -69,7 +70,16 @@ const localStrategyBuilderFactory = return next() } catch (err) { - req.log.info({ err }, 'Error while logging in.') + const e = ensureError(err, 'Unexpected issue occured while logging in') + switch (e.constructor) { + case UserInputError: + case UserValidationError: + req.log.info({ err }, 'Error while logging in.') + break + default: + req.log.error({ err }, 'Error while logging in.') + break + } return res.status(401).send({ err: true, message: 'Invalid credentials.' }) } }, diff --git a/packages/server/modules/auth/strategies/oidc.ts b/packages/server/modules/auth/strategies/oidc.ts index f456b5563..6d075e576 100644 --- a/packages/server/modules/auth/strategies/oidc.ts +++ b/packages/server/modules/auth/strategies/oidc.ts @@ -162,11 +162,12 @@ const oidcStrategyBuilderFactory = case UserInputError: case InviteNotFoundError: logger.info({ err: e }) - break + return done(null, undefined) default: logger.error({ err: e }) + // Only when the server is operating abnormally should err be set, to indicate an internal error. + return done(e, undefined) } - return done(e, undefined) } } ) From a8969c9b33c0cded81c11b93da5c0d5e0ff69391 Mon Sep 17 00:00:00 2001 From: Chuck Driesler Date: Tue, 11 Feb 2025 16:08:09 +0000 Subject: [PATCH 25/78] fix(billing): can't be read only if no billing (#3949) --- packages/server/modules/gatekeeper/graph/resolvers/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/modules/gatekeeper/graph/resolvers/index.ts b/packages/server/modules/gatekeeper/graph/resolvers/index.ts index 10ed138a2..5df129137 100644 --- a/packages/server/modules/gatekeeper/graph/resolvers/index.ts +++ b/packages/server/modules/gatekeeper/graph/resolvers/index.ts @@ -36,7 +36,8 @@ import { calculateSubscriptionSeats } from '@/modules/gatekeeper/domain/billing' import { WorkspacePaymentMethod } from '@/test/graphql/generated/graphql' import { LogicError } from '@/modules/shared/errors' -const { FF_GATEKEEPER_MODULE_ENABLED } = getFeatureFlags() +const { FF_GATEKEEPER_MODULE_ENABLED, FF_BILLING_INTEGRATION_ENABLED } = + getFeatureFlags() const getWorkspacePlan = getWorkspacePlanFactory({ db }) @@ -116,6 +117,7 @@ export = FF_GATEKEEPER_MODULE_ENABLED return hasAccess }, readOnly: async (parent) => { + if (!FF_BILLING_INTEGRATION_ENABLED) return false return await isWorkspaceReadOnlyFactory({ getWorkspacePlan })({ workspaceId: parent.id }) From e75c3a523c669c52f99b6a138d18bad0bd95a554 Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:34:14 +0000 Subject: [PATCH 26/78] chore(logging): adds a log line for notifications of file upload status change (#3964) --- packages/server/logging/logging.ts | 1 + .../server/modules/fileuploads/helpers/errors.ts | 8 ++++++++ .../fileuploads/services/resultListener.ts | 16 ++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 packages/server/modules/fileuploads/helpers/errors.ts diff --git a/packages/server/logging/logging.ts b/packages/server/logging/logging.ts index 3878375d2..1ab0b98fa 100644 --- a/packages/server/logging/logging.ts +++ b/packages/server/logging/logging.ts @@ -32,6 +32,7 @@ export const automateLogger = extendLoggerComponent(logger, 'automate') export const subscriptionLogger = extendLoggerComponent(logger, 'subscription') export const healthCheckLogger = extendLoggerComponent(logger, 'healthcheck') export const testLogger = extendLoggerComponent(logger, 'test') +export const fileUploadsLogger = extendLoggerComponent(logger, 'file-uploads') export type Logger = typeof logger export { extendLoggerComponent, Observability } diff --git a/packages/server/modules/fileuploads/helpers/errors.ts b/packages/server/modules/fileuploads/helpers/errors.ts new file mode 100644 index 000000000..29895a767 --- /dev/null +++ b/packages/server/modules/fileuploads/helpers/errors.ts @@ -0,0 +1,8 @@ +import { BaseError } from '@/modules/shared/errors' + +//TODO this represents an internal server error and not a client/user error (e.g. error in a file content) +export class FileUploadInternalError extends BaseError { + static defaultMessage = 'A file upload error occurred.' + static code = 'FILE_UPLOAD_ERROR' + static statusCode = 500 +} diff --git a/packages/server/modules/fileuploads/services/resultListener.ts b/packages/server/modules/fileuploads/services/resultListener.ts index 8dfbad4a8..59756bf78 100644 --- a/packages/server/modules/fileuploads/services/resultListener.ts +++ b/packages/server/modules/fileuploads/services/resultListener.ts @@ -11,6 +11,9 @@ import { import { GetFileInfo } from '@/modules/fileuploads/domain/operations' import { GetStreamBranchByName } from '@/modules/core/domain/branches/operations' import { AddBranchCreatedActivity } from '@/modules/activitystream/domain/operations' +import { fileUploadsLogger as logger } from '@/logging/logging' +import { FileUploadConvertedStatus } from '@/modules/fileuploads/helpers/types' +import { FileUploadInternalError } from '@/modules/fileuploads/helpers/errors' type OnFileImportProcessedDeps = { getFileInfo: GetFileInfo @@ -45,6 +48,19 @@ export const onFileImportProcessedFactory = ]) if (!upload) return + if (upload.convertedStatus === FileUploadConvertedStatus.Error) { + //TODO in future differentiate between internal server errors and user errors + const err = new FileUploadInternalError( + upload.convertedMessage || 'Unknown error while uploading file.' + ) + logger.error( + { err, fileImportDetails: upload }, + 'Error while processing file upload.' + ) + } else { + logger.info({ fileImportDetails: upload }, 'File upload processed.') + } + if (isNewBranch) { await publish(FileImportSubscriptions.ProjectPendingModelsUpdated, { projectPendingModelsUpdated: { From 8855bd4506b9b0f074d8077ff611f1c820d01ba5 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:16:50 +0000 Subject: [PATCH 27/78] remove negative margin when embed (#3966) --- packages/frontend-2/components/viewer/PreSetupWrapper.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/frontend-2/components/viewer/PreSetupWrapper.vue b/packages/frontend-2/components/viewer/PreSetupWrapper.vue index 1a3261e45..c9a772de5 100644 --- a/packages/frontend-2/components/viewer/PreSetupWrapper.vue +++ b/packages/frontend-2/components/viewer/PreSetupWrapper.vue @@ -55,7 +55,10 @@ - + Date: Wed, 12 Feb 2025 12:20:21 +0000 Subject: [PATCH 28/78] Use modifyObjectField for cache modification (#3967) --- .../security/DomainRemoveDialog.vue | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/frontend-2/components/settings/workspaces/security/DomainRemoveDialog.vue b/packages/frontend-2/components/settings/workspaces/security/DomainRemoveDialog.vue index 7cce2e6ca..0bfdd0ce8 100644 --- a/packages/frontend-2/components/settings/workspaces/security/DomainRemoveDialog.vue +++ b/packages/frontend-2/components/settings/workspaces/security/DomainRemoveDialog.vue @@ -17,11 +17,12 @@ import type { LayoutDialogButton } from '@speckle/ui-components' import { useApolloClient } from '@vue/apollo-composable' import { graphql } from '~/lib/common/generated/gql' +import { type SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragment } from '~/lib/common/generated/gql/graphql' import { - type Workspace, - type SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragment -} from '~/lib/common/generated/gql/graphql' -import { getCacheId, getFirstErrorMessage } from '~/lib/common/helpers/graphql' + getCacheId, + getFirstErrorMessage, + modifyObjectField +} from '~/lib/common/helpers/graphql' import { settingsDeleteWorkspaceDomainMutation } from '~/lib/settings/graphql/mutations' import { useMixpanel } from '~/lib/core/composables/mp' import type { MaybeNullOrUndefined } from '@speckle/shared' @@ -69,16 +70,16 @@ const handleRemove = async () => { const { data } = res if (!data?.workspaceMutations || !props.workspaceId) return - cache.modify({ - id: getCacheId('Workspace', props.workspaceId), - fields: { - domains(currentDomains, { isReference }) { - return [...(currentDomains ?? [])].filter((domain) => - isReference(domain) ? false : domain.id !== props.domain.id - ) - } + modifyObjectField( + cache, + getCacheId('Workspace', props.workspaceId), + 'domains', + ({ value, helpers }) => { + return value?.filter( + (domain) => helpers.readField(domain, 'id') !== props.domain.id + ) } - }) + ) } }) .catch(convertThrowIntoFetchResult) From e6263b1fb053b3127976440e98ebd2b9029f28e5 Mon Sep 17 00:00:00 2001 From: oguzhankoral Date: Wed, 12 Feb 2025 16:50:38 +0300 Subject: [PATCH 29/78] Create from objects instead parsing JSON --- packages/objectloader/src/index.js | 17 +++++++++++------ packages/objectloader/types/index.d.ts | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/objectloader/src/index.js b/packages/objectloader/src/index.js index 878ed40e9..395096fd8 100644 --- a/packages/objectloader/src/index.js +++ b/packages/objectloader/src/index.js @@ -107,12 +107,9 @@ class ObjectLoader { } } - static createFromJSON(json) { - const start = performance.now() - const jsonObj = JSON.parse(json) - console.warn('JSON Parse Time -> ', performance.now() - start) + static createFromObjects(objects) { + const rootObject = objects[0] - const rootObject = jsonObj[0] const loader = new (class extends ObjectLoader { constructor() { super({ @@ -136,7 +133,7 @@ class ObjectLoader { async *getObjectIterator() { const t0 = Date.now() let count = 0 - for await (const { id, obj } of this.getRawObjectIterator(jsonObj)) { + for await (const { id, obj } of this.getRawObjectIterator(objects)) { this.buffer[id] = obj count += 1 yield obj @@ -170,6 +167,14 @@ class ObjectLoader { return loader } + static createFromJSON(json) { + const start = performance.now() + const jsonObj = JSON.parse(json) + console.warn('JSON Parse Time -> ', performance.now() - start) + + return this.createFromObjects(jsonObj) + } + async asyncPause() { // Don't freeze the UI // while ( this.existingAsyncPause ) { diff --git a/packages/objectloader/types/index.d.ts b/packages/objectloader/types/index.d.ts index 5a1bee220..2a97b9586 100644 --- a/packages/objectloader/types/index.d.ts +++ b/packages/objectloader/types/index.d.ts @@ -41,6 +41,7 @@ class ObjectLoader { }> }) + static createFromObjects(objects: object[]): ObjectLoader static createFromJSON(input: string): ObjectLoader async getRootObject(): Promise async getTotalObjectCount(): Promise From 967eec9db48b7d32720da09ea44c28d7a25c4e45 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:50:02 +0000 Subject: [PATCH 30/78] Sanitize model names in server (#3970) --- packages/frontend-2/lib/projects/helpers/models.ts | 1 + .../server/modules/core/graph/resolvers/models.ts | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/frontend-2/lib/projects/helpers/models.ts b/packages/frontend-2/lib/projects/helpers/models.ts index 2f71f6f69..775b50834 100644 --- a/packages/frontend-2/lib/projects/helpers/models.ts +++ b/packages/frontend-2/lib/projects/helpers/models.ts @@ -22,5 +22,6 @@ export function sanitizeModelName(name: string): string { return name .split('/') .map((part) => part.trim()) + .filter((part) => part.length > 0) .join('/') } diff --git a/packages/server/modules/core/graph/resolvers/models.ts b/packages/server/modules/core/graph/resolvers/models.ts index 1d74bad0a..1c274552d 100644 --- a/packages/server/modules/core/graph/resolvers/models.ts +++ b/packages/server/modules/core/graph/resolvers/models.ts @@ -310,6 +310,17 @@ export = { ctx.resourceAccessRules ) const projectDB = await getProjectDbClient({ projectId: args.input.projectId }) + + // Sanitize model name by trimming spaces around slashes + const sanitizedInput = { + ...args.input, + name: args.input.name + .split('/') + .map((part) => part.trim()) + .filter((part) => part.length > 0) + .join('/') + } + const createBranchAndNotify = createBranchAndNotifyFactory({ getStreamBranchByName: getStreamBranchByNameFactory({ db: projectDB }), createBranch: createBranchFactory({ db: projectDB }), @@ -318,7 +329,7 @@ export = { publish }) }) - return await createBranchAndNotify(args.input, ctx.userId!) + return await createBranchAndNotify(sanitizedInput, ctx.userId!) }, async update(_parent, args, ctx) { await authorizeResolver( From 647141cca93d6be46f5a35bacc19f1c571297ed3 Mon Sep 17 00:00:00 2001 From: Alexandru Popovici Date: Wed, 12 Feb 2025 23:25:45 +0200 Subject: [PATCH 31/78] feat(viewer-lib): Added support for custom vertex normals. Moved nnormal computation from batch level to object level, handling WEB-596 in the process (#3944) --- packages/viewer-sandbox/src/main.ts | 3 ++ .../modules/batching/InstancedMeshBatch.ts | 11 +++- .../viewer/src/modules/batching/MeshBatch.ts | 25 ++++++++- .../viewer/src/modules/converter/Geometry.ts | 51 +++++++++++++++++++ .../loaders/Speckle/SpeckleConverter.ts | 3 ++ .../Speckle/SpeckleGeometryConverter.ts | 13 ++++- 6 files changed, 103 insertions(+), 3 deletions(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 8c850a468..05fbc7922 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -482,6 +482,9 @@ const getStream = () => { // v2 colored lines // 'https://app.speckle.systems/projects/052b576a45/models/c756235fcc' + + // Custom normals + // 'https://latest.speckle.systems/projects/51c449c440/models/08e97226cf' ) } diff --git a/packages/viewer/src/modules/batching/InstancedMeshBatch.ts b/packages/viewer/src/modules/batching/InstancedMeshBatch.ts index d85cced3d..37192d118 100644 --- a/packages/viewer/src/modules/batching/InstancedMeshBatch.ts +++ b/packages/viewer/src/modules/batching/InstancedMeshBatch.ts @@ -562,6 +562,9 @@ export class InstancedMeshBatch implements Batch { const colors: number[] | undefined = this.renderViews[0].renderData.geometry.attributes?.COLOR + const normals: number[] | undefined = + this.renderViews[0].renderData.geometry.attributes?.NORMAL + /** Catering to typescript * There is no unniverse where indices or positions are undefined at this point */ @@ -573,6 +576,7 @@ export class InstancedMeshBatch implements Batch { ? new Uint32Array(indices) : new Uint16Array(indices), new Float64Array(positions), + normals ? new Float32Array(normals) : undefined, colors ? new Float32Array(colors) : undefined ) this.mesh = new SpeckleInstancedMesh(this.geometry) @@ -631,6 +635,7 @@ export class InstancedMeshBatch implements Batch { private makeInstancedMeshGeometry( indices: Uint32Array | Uint16Array, position: Float64Array, + normal?: Float32Array, color?: Float32Array ): BufferGeometry { this.geometry = new BufferGeometry() @@ -655,7 +660,11 @@ export class InstancedMeshBatch implements Batch { this.instanceGradientBuffer = new Float32Array(this.renderViews.length) - Geometry.computeVertexNormals(this.geometry, position) + if (normal) { + this.geometry + .setAttribute('normal', new Float32BufferAttribute(normal, 3)) + .normalizeNormals() + } else Geometry.computeVertexNormals(this.geometry, position) Geometry.updateRTEGeometry(this.geometry, position) diff --git a/packages/viewer/src/modules/batching/MeshBatch.ts b/packages/viewer/src/modules/batching/MeshBatch.ts index c62b3efdf..bc4c48c4f 100644 --- a/packages/viewer/src/modules/batching/MeshBatch.ts +++ b/packages/viewer/src/modules/batching/MeshBatch.ts @@ -215,6 +215,7 @@ export class MeshBatch extends PrimitiveBatch { const color = new Float32Array(hasVertexColors ? attributeCount : 0) color.fill(1) const batchIndices = new Float32Array(attributeCount / 3) + const normals = new Float32Array(attributeCount) let offset = 0 let arrayOffset = 0 @@ -234,6 +235,21 @@ export class MeshBatch extends PrimitiveBatch { ) position.set(geometry.attributes.POSITION, offset) if (geometry.attributes.COLOR) color.set(geometry.attributes.COLOR, offset) + + /** We either copy over the provided vertex normals */ + if (geometry.attributes.NORMAL) { + normals.set(geometry.attributes.NORMAL, offset) + } else { + /** Either we compute them ourselves */ + Geometry.computeVertexNormalsBuffer( + normals.subarray( + offset, + offset + geometry.attributes.POSITION.length + ) as unknown as number[], + geometry.attributes.POSITION, + geometry.attributes.INDEX + ) + } batchIndices.fill( k, offset / 3, @@ -258,6 +274,7 @@ export class MeshBatch extends PrimitiveBatch { const geometry = this.makeMeshGeometry( indices, position, + normals, batchIndices, hasVertexColors ? color : undefined ) @@ -285,6 +302,7 @@ export class MeshBatch extends PrimitiveBatch { protected makeMeshGeometry( indices: Uint32Array | Uint16Array, position: Float64Array, + normals: Float32Array, batchIndices: Float32Array, color?: Float32Array ): BufferGeometry { @@ -306,6 +324,12 @@ export class MeshBatch extends PrimitiveBatch { geometry.setAttribute('position', new Float32BufferAttribute(position, 3)) } + if (normals) { + geometry + .setAttribute('normal', new Float32BufferAttribute(normals, 3)) + .normalizeNormals() + } + if (batchIndices) { geometry.setAttribute('objIndex', new Float32BufferAttribute(batchIndices, 1)) } @@ -319,7 +343,6 @@ export class MeshBatch extends PrimitiveBatch { this.gradientIndexBuffer.setUsage(DynamicDrawUsage) geometry.setAttribute('gradientIndex', this.gradientIndexBuffer) - Geometry.computeVertexNormals(geometry, position) Geometry.updateRTEGeometry(geometry, position) return geometry diff --git a/packages/viewer/src/modules/converter/Geometry.ts b/packages/viewer/src/modules/converter/Geometry.ts index 8f3234cd4..ecb324cd0 100644 --- a/packages/viewer/src/modules/converter/Geometry.ts +++ b/packages/viewer/src/modules/converter/Geometry.ts @@ -285,6 +285,57 @@ export class Geometry { } } + /** Only supports indexed geometry */ + public static computeVertexNormalsBuffer( + buffer: number[], + position: number[], + index: number[] + ) { + const pA = new Vector3(), + pB = new Vector3(), + pC = new Vector3() + const nA = new Vector3(), + nB = new Vector3(), + nC = new Vector3() + const cb = new Vector3(), + ab = new Vector3() + + // indexed elements + for (let i = 0, il = index.length; i < il; i += 3) { + const vA = index[i + 0] + const vB = index[i + 1] + const vC = index[i + 2] + + pA.fromArray(position, vA * 3) + pB.fromArray(position, vB * 3) + pC.fromArray(position, vC * 3) + + cb.subVectors(pC, pB) + ab.subVectors(pA, pB) + cb.cross(ab) + + nA.fromArray(buffer, vA * 3) + nB.fromArray(buffer, vB * 3) + nC.fromArray(buffer, vC * 3) + + nA.add(cb) + nB.add(cb) + nC.add(cb) + + buffer[vA * 3] = nA.x + buffer[vA * 3 + 1] = nA.y + buffer[vA * 3 + 2] = nA.z + + buffer[vB * 3] = nB.x + buffer[vB * 3 + 1] = nB.y + buffer[vB * 3 + 2] = nB.z + + buffer[vC * 3] = nC.x + buffer[vC * 3 + 1] = nC.y + buffer[vC * 3 + 2] = nC.z + } + } + public static computeVertexNormals( buffer: BufferGeometry, doublePositions: Float64Array diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts index bf685e421..9be143dca 100644 --- a/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts +++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts @@ -897,6 +897,9 @@ export default class SpeckleConverter { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore node.model.raw.colors = await this.dechunk(obj.colors) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + node.model.raw.vertexNormals = await this.dechunk(obj.vertexNormals) } private async TextToNode(_obj: SpeckleObject, _node: TreeNode) { diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts index 866f32ffd..417a23333 100644 --- a/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts +++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts @@ -250,6 +250,7 @@ export class SpeckleGeometryConverter extends GeometryConverter { const vertices = node.raw.vertices const faces = node.raw.faces const colorsRaw = node.raw.colors + let normals = node.raw.vertexNormals let colors = undefined let k = 0 while (k < faces.length) { @@ -291,11 +292,21 @@ export class SpeckleGeometryConverter extends GeometryConverter { colors = this.unpackColors(colorsRaw, true) } + if (normals && normals.length !== 0) { + if (normals.length !== vertices.length) { + Logger.warn( + `Mesh (id ${node.raw.id}) normals are mismatched with vertice counts. The number of normals must equal the number of vertices.` + ) + normals = undefined + } + } + return { attributes: { POSITION: vertices, INDEX: indices, - ...(colors && { COLOR: colors }) + ...(colors && { COLOR: colors }), + ...(normals && { NORMAL: normals }) }, bakeTransform: new Matrix4().makeScale( conversionFactor, From 0dd545bf381fd3739302221adc7425dae6d0e962 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 13 Feb 2025 12:57:35 +0100 Subject: [PATCH 32/78] Feat: Improve Workspace Delete webhook (#3975) --- .../settings/workspaces/General/DeleteDialog.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue index e8a0f8729..8a7bec50c 100644 --- a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue +++ b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue @@ -130,9 +130,11 @@ const onDelete = async () => { await sendWebhook(defaultZapierWebhookUrl, { userId: activeUser.value?.id ?? '', - feedback: feedback.value - ? `Action: Workspace Deleted(${props.workspace.name}) Feedback: ${feedback.value}` - : `Action: Workspace Deleted(${props.workspace.name}) - No feedback provided` + feedback: `Action: Workspace Deleted (${props.workspace.name}) - User ID: ${ + activeUser.value?.id + } - Workspace ID: ${props.workspace.id} - ${ + feedback.value ? `Feedback: ${feedback.value}` : 'No feedback provided' + }` }) triggerNotification({ From adb901f0c494b19550a17d476979d9973f08f681 Mon Sep 17 00:00:00 2001 From: Chuck Driesler Date: Thu, 13 Feb 2025 13:11:47 +0000 Subject: [PATCH 33/78] chore(sso): more logs for missing profile data (#3978) * chore(sso): more logs for missing profile data * fix(sso): use logger in req * fix(sso): log provided claims without values --- packages/server/modules/workspaces/rest/sso.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/server/modules/workspaces/rest/sso.ts b/packages/server/modules/workspaces/rest/sso.ts index 4116d4081..bb744745d 100644 --- a/packages/server/modules/workspaces/rest/sso.ts +++ b/packages/server/modules/workspaces/rest/sso.ts @@ -659,6 +659,10 @@ const getOidcProviderUserDataFactory = throw new SsoProviderProfileMissingError() } if (!oidcProviderUserData.email) { + req.log.error( + { providedClaims: Object.keys(oidcProviderUserData) }, + 'Missing required properties on OIDC provider.' + ) throw new SsoProviderProfileMissingPropertiesError(['email']) } From c1d6036830c0f6b1cac26501be3294b4076d8ae3 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 13 Feb 2025 15:13:11 +0100 Subject: [PATCH 34/78] Fix: WS delete redirect and settings middleware (#3979) --- .../settings/workspaces/General/DeleteDialog.vue | 12 +++++++----- packages/frontend-2/nuxt.config.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue index 8a7bec50c..e45c7e0dd 100644 --- a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue +++ b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue @@ -89,6 +89,8 @@ const onDelete = async () => { if (!props.workspace) return if (workspaceNameInput.value !== props.workspace.name) return + // Create a copy of the workspace name and ID before deletion to avoid errors after deletion/cache update + const { name: workspaceName, id: workspaceId } = props.workspace const cache = apollo.cache const result = await deleteWorkspace({ workspaceId: props.workspace.id @@ -121,18 +123,18 @@ const onDelete = async () => { mixpanel.track('Workspace Deleted', { // eslint-disable-next-line camelcase - workspace_id: props.workspace.id, + workspace_id: workspaceId, feedback: feedback.value }) - mixpanel.get_group('workspace_id', props.workspace.id).set_once({ + mixpanel.get_group('workspace_id', workspaceId).set_once({ isDeleted: true }) await sendWebhook(defaultZapierWebhookUrl, { userId: activeUser.value?.id ?? '', - feedback: `Action: Workspace Deleted (${props.workspace.name}) - User ID: ${ + feedback: `Action: Workspace Deleted (${workspaceName}) - User ID: ${ activeUser.value?.id - } - Workspace ID: ${props.workspace.id} - ${ + } - Workspace ID: ${workspaceId} - ${ feedback.value ? `Feedback: ${feedback.value}` : 'No feedback provided' }` }) @@ -140,7 +142,7 @@ const onDelete = async () => { triggerNotification({ type: ToastNotificationType.Success, title: 'Workspace deleted', - description: `The ${props.workspace.name} workspace has been deleted` + description: `The ${workspaceName} workspace has been deleted` }) router.push(homeRoute) diff --git a/packages/frontend-2/nuxt.config.ts b/packages/frontend-2/nuxt.config.ts index 7ab8204fe..5af02c17e 100644 --- a/packages/frontend-2/nuxt.config.ts +++ b/packages/frontend-2/nuxt.config.ts @@ -213,7 +213,7 @@ export default defineNuxtConfig({ '/settings/server/*': { appMiddleware: ['auth', 'settings', 'admin'] }, - '/settings/workspaces/*': { + '/settings/workspaces/:slug/*': { appMiddleware: [ 'auth', 'settings', From c382064585e67f962f240a4e6e76e24219d1be19 Mon Sep 17 00:00:00 2001 From: Chuck Driesler Date: Thu, 13 Feb 2025 14:39:23 +0000 Subject: [PATCH 35/78] feat(regions): move project branches and commits (#3843) * feat(regions): repo functions for copying project branches and commits * chore(regions): wire up move to resolver * chore(regions): successful basic test of project region change * fix(regions): sabrina carpenter please please please * fix(regions): repair multiregion test setup * chore(regions): appease ts * chore(multiregion): update test multiregion config * chore(multiregion): fix test docker config and test * chore(multiregion): use transaction * chore(multiregion): maybe this will work * fix(multiregion): drop subs synchronously * chore(multiregion): desperate test logs * chore(multiregion): somehow that worked? * chore(multiregion): add load-bearing log statement * chore(multiregion): move services * fix(multiregion): test drop waits * chore(regions): fix import * chore(regions): make test a bit more thorough for good measure * fix(regions): speed up inserts * fix(regions): ignore workspace conflict on move --- .circleci/config.yml | 10 +- .circleci/multiregion.test-ci.json | 13 ++ docker-compose-deps.yml | 28 +++ .../lib/common/generated/gql/graphql.ts | 12 + .../workspacesCore/typedefs/regions.graphql | 8 + .../modules/auth/tests/apps.graphql.spec.js | 6 - .../server/modules/auth/tests/auth.spec.js | 6 - .../modules/core/graph/generated/graphql.ts | 12 + .../graph/generated/graphql.ts | 11 + .../modules/multiregion/utils/dbSelector.ts | 13 +- .../server/modules/shared/helpers/dbHelper.ts | 4 +- .../server/modules/stats/tests/stats.spec.ts | 9 +- .../modules/webhooks/tests/webhooks.spec.js | 20 +- .../modules/workspaces/domain/operations.ts | 23 +- .../modules/workspaces/errors/regions.ts | 6 + .../workspaces/graph/resolvers/regions.ts | 56 ++++- .../workspaces/repositories/projectRegions.ts | 218 ++++++++++++++++++ .../workspaces/repositories/regions.ts | 4 +- .../workspaces/services/projectRegions.ts | 89 +++++++ .../modules/workspaces/services/regions.ts | 6 +- .../workspaces/tests/helpers/creation.ts | 4 +- .../tests/integration/projects.graph.spec.ts | 174 +++++++++++++- packages/server/multiregion.test.example.json | 14 ++ .../server/test/graphql/generated/graphql.ts | 20 ++ packages/server/test/graphql/multiRegion.ts | 12 + packages/server/test/hooks.ts | 10 +- packages/shared/src/environment/index.ts | 12 + 27 files changed, 739 insertions(+), 61 deletions(-) create mode 100644 packages/server/modules/workspaces/repositories/projectRegions.ts create mode 100644 packages/server/modules/workspaces/services/projectRegions.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 47030bcee..1ea6bcbd1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -598,16 +598,24 @@ jobs: POSTGRES_PASSWORD: speckle POSTGRES_USER: speckle command: -c 'max_connections=1000' -c 'port=5433' -c 'wal_level=logical' + - image: 'speckle/speckle-postgres' + environment: + POSTGRES_DB: speckle2_test + POSTGRES_PASSWORD: speckle + POSTGRES_USER: speckle + command: -c 'max_connections=1000' -c 'port=5434' -c 'wal_level=logical' - image: 'minio/minio' command: server /data --console-address ":9001" --address "0.0.0.0:9000" - image: 'minio/minio' command: server /data --console-address ":9021" --address "0.0.0.0:9020" + - image: 'minio/minio' + command: server /data --console-address ":9041" --address "0.0.0.0:9040" environment: # Same as test-server: NODE_ENV: test DATABASE_URL: 'postgres://speckle:speckle@127.0.0.1:5432/speckle2_test' PGDATABASE: speckle2_test - POSTGRES_MAX_CONNECTIONS_SERVER: 20 + POSTGRES_MAX_CONNECTIONS_SERVER: 50 PGUSER: speckle SESSION_SECRET: 'keyboard cat' STRATEGY_LOCAL: 'true' diff --git a/.circleci/multiregion.test-ci.json b/.circleci/multiregion.test-ci.json index 3d5a9ec1c..78619c2af 100644 --- a/.circleci/multiregion.test-ci.json +++ b/.circleci/multiregion.test-ci.json @@ -25,6 +25,19 @@ "endpoint": "http://127.0.0.1:9020", "s3Region": "us-east-1" } + }, + "region2": { + "postgres": { + "connectionUri": "postgresql://speckle:speckle@127.0.0.1:5434/speckle2_test" + }, + "blobStorage": { + "accessKey": "minioadmin", + "secretKey": "minioadmin", + "bucket": "speckle-server", + "createBucketIfNotExists": true, + "endpoint": "http://127.0.0.1:9040", + "s3Region": "us-east-1" + } } } } diff --git a/docker-compose-deps.yml b/docker-compose-deps.yml index d5068bf4e..e6e59ea02 100644 --- a/docker-compose-deps.yml +++ b/docker-compose-deps.yml @@ -34,6 +34,22 @@ services: ports: - '127.0.0.1:5401:5432' + postgres-region2: + build: + context: . + dockerfile: utils/postgres/Dockerfile + restart: always + environment: + POSTGRES_DB: speckle + POSTGRES_USER: speckle + POSTGRES_PASSWORD: speckle + volumes: + - postgres-region2-data:/var/lib/postgresql/data/ + - ./setup/db/10-docker_postgres_init.sql:/docker-entrypoint-initdb.d/10-docker_postgres_init.sql + - ./setup/db/11-docker_postgres_keycloack_init.sql:/docker-entrypoint-initdb.d/11-docker_postgres_keycloack_init.sql + ports: + - '127.0.0.1:5402:5432' + redis: image: 'redis:7-alpine' restart: always @@ -62,6 +78,16 @@ services: - '127.0.0.1:9020:9000' - '127.0.0.1:9021:9001' + minio-region2: + image: 'minio/minio' + command: server /data --console-address ":9001" + restart: always + volumes: + - minio-region2-data:/data + ports: + - '127.0.0.1:9040:9000' + - '127.0.0.1:9041:9001' + # Local OIDC provider for testing keycloak: image: quay.io/keycloak/keycloak:25.0 @@ -133,8 +159,10 @@ services: volumes: postgres-data: postgres-region1-data: + postgres-region2-data: redis-data: pgadmin-data: redis_insight-data: minio-data: minio-region1-data: + minio-region2-data: diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index 36ef136fa..5e0a4ab74 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -4587,6 +4587,11 @@ export type WorkspaceProjectInviteCreateInput = { export type WorkspaceProjectMutations = { __typename?: 'WorkspaceProjectMutations'; create: Project; + /** + * Update project region and move all regional data to new db. + * TODO: Currently performs all operations synchronously in request, should probably be scheduled. + */ + moveToRegion: Project; moveToWorkspace: Project; updateRole: Project; }; @@ -4597,6 +4602,12 @@ export type WorkspaceProjectMutationsCreateArgs = { }; +export type WorkspaceProjectMutationsMoveToRegionArgs = { + projectId: Scalars['String']['input']; + regionKey: Scalars['String']['input']; +}; + + export type WorkspaceProjectMutationsMoveToWorkspaceArgs = { projectId: Scalars['String']['input']; workspaceId: Scalars['String']['input']; @@ -8290,6 +8301,7 @@ export type WorkspacePlanFieldArgs = { } export type WorkspaceProjectMutationsFieldArgs = { create: WorkspaceProjectMutationsCreateArgs, + moveToRegion: WorkspaceProjectMutationsMoveToRegionArgs, moveToWorkspace: WorkspaceProjectMutationsMoveToWorkspaceArgs, updateRole: WorkspaceProjectMutationsUpdateRoleArgs, } diff --git a/packages/server/assets/workspacesCore/typedefs/regions.graphql b/packages/server/assets/workspacesCore/typedefs/regions.graphql index b394c8655..e61b93102 100644 --- a/packages/server/assets/workspacesCore/typedefs/regions.graphql +++ b/packages/server/assets/workspacesCore/typedefs/regions.graphql @@ -12,3 +12,11 @@ extend type WorkspaceMutations { """ setDefaultRegion(workspaceId: String!, regionKey: String!): Workspace! } + +extend type WorkspaceProjectMutations { + """ + Update project region and move all regional data to new db. + TODO: Currently performs all operations synchronously in request, should probably be scheduled. + """ + moveToRegion(projectId: String!, regionKey: String!): Project! +} diff --git a/packages/server/modules/auth/tests/apps.graphql.spec.js b/packages/server/modules/auth/tests/apps.graphql.spec.js index 6f651fde0..4e90f06f8 100644 --- a/packages/server/modules/auth/tests/apps.graphql.spec.js +++ b/packages/server/modules/auth/tests/apps.graphql.spec.js @@ -63,7 +63,6 @@ const { getServerInfoFactory } = require('@/modules/core/repositories/server') const { getEventBus } = require('@/modules/shared/services/eventBus') let sendRequest -let server const createAppToken = createAppTokenFactory({ storeApiToken: storeApiTokenFactory({ db }), @@ -128,7 +127,6 @@ describe('GraphQL @apps-api', () => { before(async () => { const ctx = await beforeEachContext() - server = ctx.server ;({ sendRequest } = await initializeTestServer(ctx)) testUser = { name: 'Dimitrie Stefanescu', @@ -157,10 +155,6 @@ describe('GraphQL @apps-api', () => { ])}` }) - after(async () => { - await server.close() - }) - let testAppId let testApp diff --git a/packages/server/modules/auth/tests/auth.spec.js b/packages/server/modules/auth/tests/auth.spec.js index 45f75cd95..67888198b 100644 --- a/packages/server/modules/auth/tests/auth.spec.js +++ b/packages/server/modules/auth/tests/auth.spec.js @@ -138,7 +138,6 @@ const expect = chai.expect let app let sendRequest -let server describe('Auth @auth', () => { describe('Local authN & authZ (token endpoints)', () => { @@ -160,7 +159,6 @@ describe('Auth @auth', () => { before(async () => { const ctx = await beforeEachContext() - server = ctx.server app = ctx.app ;({ sendRequest } = await initializeTestServer(ctx)) @@ -173,10 +171,6 @@ describe('Auth @auth', () => { ) }) - after(async () => { - await server.close() - }) - it('Should register a new user (speckle frontend)', async () => { await request(app) .post('/auth/local/register?challenge=test') diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index a98d8dd20..cfa220c24 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -4609,6 +4609,11 @@ export type WorkspaceProjectInviteCreateInput = { export type WorkspaceProjectMutations = { __typename?: 'WorkspaceProjectMutations'; create: Project; + /** + * Update project region and move all regional data to new db. + * TODO: Currently performs all operations synchronously in request, should probably be scheduled. + */ + moveToRegion: Project; moveToWorkspace: Project; updateRole: Project; }; @@ -4619,6 +4624,12 @@ export type WorkspaceProjectMutationsCreateArgs = { }; +export type WorkspaceProjectMutationsMoveToRegionArgs = { + projectId: Scalars['String']['input']; + regionKey: Scalars['String']['input']; +}; + + export type WorkspaceProjectMutationsMoveToWorkspaceArgs = { projectId: Scalars['String']['input']; workspaceId: Scalars['String']['input']; @@ -6943,6 +6954,7 @@ export type WorkspacePlanResolvers = { create?: Resolver>; + moveToRegion?: Resolver>; moveToWorkspace?: Resolver>; updateRole?: Resolver>; __isTypeOf?: IsTypeOfResolverFn; 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 217626242..3888e479c 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -4590,6 +4590,11 @@ export type WorkspaceProjectInviteCreateInput = { export type WorkspaceProjectMutations = { __typename?: 'WorkspaceProjectMutations'; create: Project; + /** + * Update project region and move all regional data to new db. + * TODO: Currently performs all operations synchronously in request, should probably be scheduled. + */ + moveToRegion: Project; moveToWorkspace: Project; updateRole: Project; }; @@ -4600,6 +4605,12 @@ export type WorkspaceProjectMutationsCreateArgs = { }; +export type WorkspaceProjectMutationsMoveToRegionArgs = { + projectId: Scalars['String']['input']; + regionKey: Scalars['String']['input']; +}; + + export type WorkspaceProjectMutationsMoveToWorkspaceArgs = { projectId: Scalars['String']['input']; workspaceId: Scalars['String']['input']; diff --git a/packages/server/modules/multiregion/utils/dbSelector.ts b/packages/server/modules/multiregion/utils/dbSelector.ts index 69d1f4e1f..3887ba044 100644 --- a/packages/server/modules/multiregion/utils/dbSelector.ts +++ b/packages/server/modules/multiregion/utils/dbSelector.ts @@ -214,7 +214,7 @@ const setUpUserReplication = async ({ try { await from.public.raw(`CREATE PUBLICATION ${pubName} FOR TABLE users;`) } catch (err) { - if (!(err instanceof Error)) + if (!(err instanceof Error)) { throw new DatabaseError( 'Could not create publication {pubName} when setting up user replication for region {regionName}', from.public, @@ -223,7 +223,16 @@ const setUpUserReplication = async ({ info: { pubName, regionName } } ) - if (!err.message.includes('already exists')) throw err + } + + const errorMessage = err.message + + if ( + !['already exists', 'violates unique constraint'].some((message) => + errorMessage.includes(message) + ) + ) + throw err } const fromUrl = new URL( diff --git a/packages/server/modules/shared/helpers/dbHelper.ts b/packages/server/modules/shared/helpers/dbHelper.ts index f5c83f882..7252ee5bd 100644 --- a/packages/server/modules/shared/helpers/dbHelper.ts +++ b/packages/server/modules/shared/helpers/dbHelper.ts @@ -23,7 +23,7 @@ export async function* executeBatchedSelect< >( selectQuery: Knex.QueryBuilder, options?: Partial -): AsyncGenerator { +): AsyncGenerator, void, unknown> { const { batchSize = 100, trx } = options || {} if (trx) selectQuery.transacting(trx) @@ -34,7 +34,7 @@ export async function* executeBatchedSelect< let currentOffset = 0 while (hasMorePages) { const q = selectQuery.clone().offset(currentOffset) - const results = (await q) as TResult + const results = (await q) as Awaited if (!results.length) { hasMorePages = false diff --git a/packages/server/modules/stats/tests/stats.spec.ts b/packages/server/modules/stats/tests/stats.spec.ts index d06cf842a..42d2000f7 100644 --- a/packages/server/modules/stats/tests/stats.spec.ts +++ b/packages/server/modules/stats/tests/stats.spec.ts @@ -8,7 +8,6 @@ import { getTotalUserCountFactory } from '@/modules/stats/repositories/index' import { Scopes } from '@speckle/shared' -import { Server } from 'node:http' import { db } from '@/db/knex' import { createCommitByBranchIdFactory, @@ -194,8 +193,7 @@ describe('Server stats services @stats-services', function () { }) describe('Server stats api @stats-api', function () { - let server: Server, - sendRequest: Awaited>['sendRequest'] + let sendRequest: Awaited>['sendRequest'] const adminUser = { name: 'Dimitrie', @@ -233,7 +231,6 @@ describe('Server stats api @stats-api', function () { before(async function () { this.timeout(15000) const ctx = await beforeEachContext() - server = ctx.server ;({ sendRequest } = await initializeTestServer(ctx)) adminUser.id = await createUser(adminUser) @@ -263,10 +260,6 @@ describe('Server stats api @stats-api', function () { await seedDb(params) }) - after(async function () { - await server.close() - }) - it('Should not get stats if user is not admin', async () => { const res = await sendRequest(adminUser.badToken, { query: fullQuery }) expect(res.body.errors).to.exist diff --git a/packages/server/modules/webhooks/tests/webhooks.spec.js b/packages/server/modules/webhooks/tests/webhooks.spec.js index 0c8001a16..0befc5da0 100644 --- a/packages/server/modules/webhooks/tests/webhooks.spec.js +++ b/packages/server/modules/webhooks/tests/webhooks.spec.js @@ -2,11 +2,7 @@ const expect = require('chai').expect const assert = require('assert') -const { - beforeEachContext, - initializeTestServer, - truncateTables -} = require('@/test/hooks') +const { beforeEachContext, initializeTestServer } = require('@/test/hooks') const { noErrors } = require('@/test/helpers') const { Scopes, Roles } = require('@speckle/shared') const { @@ -26,7 +22,6 @@ const { deleteWebhookFactory, dispatchStreamEventFactory } = require('@/modules/webhooks/services/webhooks') -const { Users, Streams } = require('@/modules/core/dbSchema') const { getStreamFactory, createStreamFactory, @@ -166,7 +161,7 @@ const createPersonalAccessToken = createPersonalAccessTokenFactory({ describe('Webhooks @webhooks', () => { const getWebhook = getWebhookByIdFactory({ db }) - let server, sendRequest + let sendRequest const userOne = { name: 'User', @@ -191,7 +186,6 @@ describe('Webhooks @webhooks', () => { before(async () => { const ctx = await beforeEachContext() - server = ctx.server ;({ sendRequest } = await initializeTestServer(ctx)) userOne.id = await createUser(userOne) @@ -201,16 +195,6 @@ describe('Webhooks @webhooks', () => { webhookOne.streamId = streamOne.id }) - after(async () => { - await truncateTables([ - Users.name, - Streams.name, - 'webhooks_config', - 'webhooks_events' - ]) - await server.close() - }) - describe('Create, Read, Update, Delete Webhooks', () => { it('Should create a webhook', async () => { webhookOne.id = await createWebhookFactory({ diff --git a/packages/server/modules/workspaces/domain/operations.ts b/packages/server/modules/workspaces/domain/operations.ts index 614ffa36d..66f0983c3 100644 --- a/packages/server/modules/workspaces/domain/operations.ts +++ b/packages/server/modules/workspaces/domain/operations.ts @@ -283,7 +283,7 @@ export type GetAvailableRegions = (params: { workspaceId: string }) => Promise -export type AssignRegion = (params: { +export type AssignWorkspaceRegion = (params: { workspaceId: string regionKey: string }) => Promise @@ -342,3 +342,24 @@ export type ApproveWorkspaceJoinRequest = ( export type DenyWorkspaceJoinRequest = ( params: Pick ) => Promise + +/** + * Project regions + */ + +/** + * Updates project region and moves all regional data to target regional db + */ +export type UpdateProjectRegion = (params: { + projectId: string + regionKey: string +}) => Promise + +export type CopyWorkspace = (params: { workspaceId: string }) => Promise +export type CopyProjects = (params: { projectIds: string[] }) => Promise +export type CopyProjectModels = (params: { + projectIds: string[] +}) => Promise> +export type CopyProjectVersions = (params: { + projectIds: string[] +}) => Promise> diff --git a/packages/server/modules/workspaces/errors/regions.ts b/packages/server/modules/workspaces/errors/regions.ts index 60ce6774e..5a73b52bc 100644 --- a/packages/server/modules/workspaces/errors/regions.ts +++ b/packages/server/modules/workspaces/errors/regions.ts @@ -5,3 +5,9 @@ export class WorkspaceRegionAssignmentError extends BaseError { static code = 'WORKSPACE_REGION_ASSIGNMENT_ERROR' static statusCode = 400 } + +export class ProjectRegionAssignmentError extends BaseError { + static defaultMessage = 'Failed to assign region to project' + static code = 'PROJECT_REGION_ASSIGNMENT_ERROR' + static statusCode = 400 +} diff --git a/packages/server/modules/workspaces/graph/resolvers/regions.ts b/packages/server/modules/workspaces/graph/resolvers/regions.ts index a80df9e94..bb01c8195 100644 --- a/packages/server/modules/workspaces/graph/resolvers/regions.ts +++ b/packages/server/modules/workspaces/graph/resolvers/regions.ts @@ -2,22 +2,37 @@ import { db } from '@/db/knex' import { Resolvers } from '@/modules/core/graph/generated/graphql' import { getWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing' import { canWorkspaceUseRegionsFactory } from '@/modules/gatekeeper/services/featureAuthorization' -import { getDb } from '@/modules/multiregion/utils/dbSelector' +import { getDb, getProjectDbClient } from '@/modules/multiregion/utils/dbSelector' import { getRegionsFactory } from '@/modules/multiregion/repositories' import { authorizeResolver } from '@/modules/shared' import { getDefaultRegionFactory, upsertRegionAssignmentFactory } from '@/modules/workspaces/repositories/regions' +import { + copyProjectModelsFactory, + copyProjectsFactory, + copyProjectVersionsFactory, + copyWorkspaceFactory +} from '@/modules/workspaces/repositories/projectRegions' import { getWorkspaceFactory, upsertWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces' import { - assignRegionFactory, + assignWorkspaceRegionFactory, getAvailableRegionsFactory } from '@/modules/workspaces/services/regions' +import { updateProjectRegionFactory } from '@/modules/workspaces/services/projectRegions' import { Roles } from '@speckle/shared' +import { getProjectFactory } from '@/modules/core/repositories/projects' +import { getStreamBranchCountFactory } from '@/modules/core/repositories/branches' +import { getStreamCommitCountFactory } from '@/modules/core/repositories/commits' +import { withTransaction } from '@/modules/shared/helpers/dbHelper' +import { getFeatureFlags, isTestEnv } from '@/modules/shared/helpers/envHelper' +import { WorkspacesNotYetImplementedError } from '@/modules/workspaces/errors/workspace' + +const { FF_MOVE_PROJECT_REGION_ENABLED } = getFeatureFlags() export default { Workspace: { @@ -37,7 +52,7 @@ export default { const regionDb = await getDb({ regionKey: args.regionKey }) - const assignRegion = assignRegionFactory({ + const assignRegion = assignWorkspaceRegionFactory({ getAvailableRegions: getAvailableRegionsFactory({ getRegions: getRegionsFactory({ db }), canWorkspaceUseRegions: canWorkspaceUseRegionsFactory({ @@ -53,5 +68,40 @@ export default { return await ctx.loaders.workspaces!.getWorkspace.load(args.workspaceId) } + }, + WorkspaceProjectMutations: { + moveToRegion: async (_parent, args, context) => { + if (!FF_MOVE_PROJECT_REGION_ENABLED && !isTestEnv()) { + throw new WorkspacesNotYetImplementedError() + } + + await authorizeResolver( + context.userId, + args.projectId, + Roles.Stream.Owner, + context.resourceAccessRules + ) + + const sourceDb = await getProjectDbClient({ projectId: args.projectId }) + const targetDb = await (await getDb({ regionKey: args.regionKey })).transaction() + + const updateProjectRegion = updateProjectRegionFactory({ + getProject: getProjectFactory({ db: sourceDb }), + countProjectModels: getStreamBranchCountFactory({ db: sourceDb }), + countProjectVersions: getStreamCommitCountFactory({ db: sourceDb }), + getAvailableRegions: getAvailableRegionsFactory({ + getRegions: getRegionsFactory({ db }), + canWorkspaceUseRegions: canWorkspaceUseRegionsFactory({ + getWorkspacePlan: getWorkspacePlanFactory({ db }) + }) + }), + copyWorkspace: copyWorkspaceFactory({ sourceDb, targetDb }), + copyProjects: copyProjectsFactory({ sourceDb, targetDb }), + copyProjectModels: copyProjectModelsFactory({ sourceDb, targetDb }), + copyProjectVersions: copyProjectVersionsFactory({ sourceDb, targetDb }) + }) + + return await withTransaction(updateProjectRegion(args), targetDb) + } } } as Resolvers diff --git a/packages/server/modules/workspaces/repositories/projectRegions.ts b/packages/server/modules/workspaces/repositories/projectRegions.ts new file mode 100644 index 000000000..6e1e021ef --- /dev/null +++ b/packages/server/modules/workspaces/repositories/projectRegions.ts @@ -0,0 +1,218 @@ +import { + BranchCommits, + Branches, + Commits, + StreamCommits, + StreamFavorites, + Streams, + StreamsMeta +} from '@/modules/core/dbSchema' +import { Branch } from '@/modules/core/domain/branches/types' +import { Commit } from '@/modules/core/domain/commits/types' +import { Stream } from '@/modules/core/domain/streams/types' +import { + BranchCommitRecord, + CommitRecord, + StreamCommitRecord, + StreamFavoriteRecord, + StreamRecord +} from '@/modules/core/helpers/types' +import { executeBatchedSelect } from '@/modules/shared/helpers/dbHelper' +import { + CopyProjectModels, + CopyProjects, + CopyProjectVersions, + CopyWorkspace +} from '@/modules/workspaces/domain/operations' +import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace' +import { Knex } from 'knex' +import { Workspace } from '@/modules/workspacesCore/domain/types' +import { Workspaces } from '@/modules/workspacesCore/helpers/db' + +const tables = { + workspaces: (db: Knex) => db(Workspaces.name), + projects: (db: Knex) => db(Streams.name), + models: (db: Knex) => db(Branches.name), + versions: (db: Knex) => db(Commits.name), + branchCommits: (db: Knex) => db(BranchCommits.name), + streamCommits: (db: Knex) => db(StreamCommits.name), + streamFavorites: (db: Knex) => db(StreamFavorites.name), + streamsMeta: (db: Knex) => db(StreamsMeta.name) +} + +export const copyWorkspaceFactory = + (deps: { sourceDb: Knex; targetDb: Knex }): CopyWorkspace => + async ({ workspaceId }) => { + const workspace = await tables + .workspaces(deps.sourceDb) + .select('*') + .where({ id: workspaceId }) + + if (!workspace) { + throw new WorkspaceNotFoundError() + } + + await tables + .workspaces(deps.targetDb) + .insert(workspace) + .onConflict(Workspaces.withoutTablePrefix.col.id) + .ignore() + + return workspaceId + } + +export const copyProjectsFactory = + (deps: { sourceDb: Knex; targetDb: Knex }): CopyProjects => + async ({ projectIds }) => { + const selectProjects = tables + .projects(deps.sourceDb) + .select('*') + .whereIn(Streams.col.id, projectIds) + const copiedProjectIds: string[] = [] + + // Copy project record + for await (const projects of executeBatchedSelect(selectProjects)) { + const projectIds = projects.map((project) => project.id) + copiedProjectIds.push(...projectIds) + + // Copy `streams` rows to target db + await tables + .projects(deps.targetDb) + .insert(projects) + .onConflict(Streams.withoutTablePrefix.col.id) + .merge(Streams.withoutTablePrefix.cols as (keyof StreamRecord)[]) + + // Fetch `stream_favorites` rows for projects in batch + const selectStreamFavorites = tables + .streamFavorites(deps.sourceDb) + .select('*') + .whereIn(StreamFavorites.col.streamId, projectIds) + + for await (const streamFavorites of executeBatchedSelect(selectStreamFavorites)) { + // Copy `stream_favorites` rows to target db + await tables + .streamFavorites(deps.targetDb) + .insert(streamFavorites) + .onConflict() + .ignore() + } + + // Fetch `streams_meta` rows for projects in batch + const selectStreamsMetadata = tables + .streamsMeta(deps.sourceDb) + .select('*') + .whereIn(StreamsMeta.col.streamId, projectIds) + + for await (const streamsMetadataBatch of executeBatchedSelect( + selectStreamsMetadata + )) { + // Copy `streams_meta` rows to target db + await tables + .streamsMeta(deps.targetDb) + .insert(streamsMetadataBatch) + .onConflict() + .ignore() + } + } + + return copiedProjectIds + } + +export const copyProjectModelsFactory = + (deps: { sourceDb: Knex; targetDb: Knex }): CopyProjectModels => + async ({ projectIds }) => { + const copiedModelCountByProjectId: Record = {} + + // Fetch `branches` rows for projects in batch + const selectModels = tables + .models(deps.sourceDb) + .select('*') + .whereIn(Branches.col.streamId, projectIds) + + for await (const models of executeBatchedSelect(selectModels)) { + // Copy `branches` rows to target db + await tables.models(deps.targetDb).insert(models).onConflict().ignore() + + for (const model of models) { + copiedModelCountByProjectId[model.streamId] ??= 0 + copiedModelCountByProjectId[model.streamId]++ + } + } + + return copiedModelCountByProjectId + } + +export const copyProjectVersionsFactory = + (deps: { sourceDb: Knex; targetDb: Knex }): CopyProjectVersions => + async ({ projectIds }) => { + const copiedVersionCountByProjectId: Record = {} + + const selectVersions = tables + .streamCommits(deps.sourceDb) + .select('*') + .join( + Commits.name, + Commits.col.id, + StreamCommits.col.commitId + ) + .whereIn(StreamCommits.col.streamId, projectIds) + + for await (const versions of executeBatchedSelect(selectVersions)) { + const { commitIds, commits } = versions.reduce( + (all, version) => { + const { commitId, streamId, ...commit } = version + + all.commitIds.push(commitId) + all.streamIds.push(streamId) + all.commits.push(commit) + + return all + }, + { commitIds: [], streamIds: [], commits: [] } as { + commitIds: string[] + streamIds: string[] + commits: CommitRecord[] + } + ) + + // Copy `commits` rows to target db + await tables.versions(deps.targetDb).insert(commits).onConflict().ignore() + + for (const version of versions) { + copiedVersionCountByProjectId[version.streamId] ??= 0 + copiedVersionCountByProjectId[version.streamId]++ + } + + // Fetch `branch_commits` rows for versions in batch + const selectBranchCommits = tables + .branchCommits(deps.sourceDb) + .select('*') + .whereIn(BranchCommits.col.commitId, commitIds) + + for await (const branchCommits of executeBatchedSelect(selectBranchCommits)) { + // Copy `branch_commits` row to target db + await tables + .branchCommits(deps.targetDb) + .insert(branchCommits) + .onConflict() + .ignore() + } + + // Fetch `stream_commits` rows for versions in batch + const selectStreamCommits = tables + .streamCommits(deps.sourceDb) + .select('*') + .whereIn(StreamCommits.col.commitId, commitIds) + + for await (const streamCommits of executeBatchedSelect(selectStreamCommits)) { + // Copy `stream_commits` row to target db + await tables + .streamCommits(deps.targetDb) + .insert(streamCommits) + .onConflict() + .ignore() + } + } + + return copiedVersionCountByProjectId + } diff --git a/packages/server/modules/workspaces/repositories/regions.ts b/packages/server/modules/workspaces/repositories/regions.ts index 20c267c62..dc9563829 100644 --- a/packages/server/modules/workspaces/repositories/regions.ts +++ b/packages/server/modules/workspaces/repositories/regions.ts @@ -14,8 +14,8 @@ export const WorkspaceRegions = buildTableHelper('workspace_regions', [ ]) const tables = { - workspaceRegions: (db: Knex) => db(WorkspaceRegions.name), - regions: (db: Knex) => db(Regions.name) + regions: (db: Knex) => db(Regions.name), + workspaceRegions: (db: Knex) => db(WorkspaceRegions.name) } export const upsertRegionAssignmentFactory = diff --git a/packages/server/modules/workspaces/services/projectRegions.ts b/packages/server/modules/workspaces/services/projectRegions.ts new file mode 100644 index 000000000..2bc60cd66 --- /dev/null +++ b/packages/server/modules/workspaces/services/projectRegions.ts @@ -0,0 +1,89 @@ +import { GetStreamBranchCount } from '@/modules/core/domain/branches/operations' +import { GetStreamCommitCount } from '@/modules/core/domain/commits/operations' +import { GetProject } from '@/modules/core/domain/projects/operations' +import { + CopyProjectModels, + CopyProjects, + CopyProjectVersions, + CopyWorkspace, + GetAvailableRegions, + UpdateProjectRegion +} from '@/modules/workspaces/domain/operations' +import { ProjectRegionAssignmentError } from '@/modules/workspaces/errors/regions' + +export const updateProjectRegionFactory = + (deps: { + getProject: GetProject + countProjectModels: GetStreamBranchCount + countProjectVersions: GetStreamCommitCount + getAvailableRegions: GetAvailableRegions + copyWorkspace: CopyWorkspace + copyProjects: CopyProjects + copyProjectModels: CopyProjectModels + copyProjectVersions: CopyProjectVersions + }): UpdateProjectRegion => + async (params) => { + const { projectId, regionKey } = params + + const project = await deps.getProject({ projectId }) + if (!project) { + throw new ProjectRegionAssignmentError('Project not found', { + info: { params } + }) + } + if (!project.workspaceId) { + throw new ProjectRegionAssignmentError('Project not a part of a workspace', { + info: { params } + }) + } + + const availableRegions = await deps.getAvailableRegions({ + workspaceId: project.workspaceId + }) + if (!availableRegions.find((region) => region.key === regionKey)) { + throw new ProjectRegionAssignmentError( + 'Specified region not available for workspace', + { + info: { + params, + workspaceId: project.workspaceId + } + } + ) + } + + // Move workspace + await deps.copyWorkspace({ workspaceId: project.workspaceId }) + + // Move commits + const projectIds = await deps.copyProjects({ projectIds: [projectId] }) + const modelIds = await deps.copyProjectModels({ projectIds }) + const versionIds = await deps.copyProjectVersions({ projectIds }) + + // TODO: Move objects + // TODO: Move automations + // TODO: Move comments + // TODO: Move file blobs + // TODO: Move webhooks + + // TODO: Validate state after move captures latest state of project + const sourceProjectModelCount = await deps.countProjectModels(projectId) + const sourceProjectVersionCount = await deps.countProjectVersions(projectId) + + const tests = [ + modelIds[projectId] === sourceProjectModelCount, + versionIds[projectId] === sourceProjectVersionCount + ] + + const isReconciled = tests.every((test) => !!test) + + if (!isReconciled) { + // TODO: Move failed or source project added data while changing regions. Retry move. + throw new ProjectRegionAssignmentError( + 'Missing data from source project in target region copy after move.' + ) + } + + // TODO: Update project region in db + return { ...project, regionKey } + } diff --git a/packages/server/modules/workspaces/services/regions.ts b/packages/server/modules/workspaces/services/regions.ts index 6d5c9cb22..88623c067 100644 --- a/packages/server/modules/workspaces/services/regions.ts +++ b/packages/server/modules/workspaces/services/regions.ts @@ -1,7 +1,7 @@ import { WorkspaceFeatureAccessFunction } from '@/modules/gatekeeper/domain/operations' import { GetRegions } from '@/modules/multiregion/domain/operations' import { - AssignRegion, + AssignWorkspaceRegion, GetAvailableRegions, GetDefaultRegion, GetWorkspace, @@ -25,14 +25,14 @@ export const getAvailableRegionsFactory = return await deps.getRegions() } -export const assignRegionFactory = +export const assignWorkspaceRegionFactory = (deps: { getAvailableRegions: GetAvailableRegions upsertRegionAssignment: UpsertRegionAssignment getDefaultRegion: GetDefaultRegion getWorkspace: GetWorkspace insertRegionWorkspace: UpsertWorkspace - }): AssignRegion => + }): AssignWorkspaceRegion => async (params) => { const { workspaceId, regionKey } = params diff --git a/packages/server/modules/workspaces/tests/helpers/creation.ts b/packages/server/modules/workspaces/tests/helpers/creation.ts index c93c750d9..90062488d 100644 --- a/packages/server/modules/workspaces/tests/helpers/creation.ts +++ b/packages/server/modules/workspaces/tests/helpers/creation.ts @@ -64,7 +64,7 @@ import { import { SetOptional } from 'type-fest' import { isMultiRegionTestMode } from '@/test/speckle-helpers/regions' import { - assignRegionFactory, + assignWorkspaceRegionFactory, getAvailableRegionsFactory } from '@/modules/workspaces/services/regions' import { getRegionsFactory } from '@/modules/multiregion/repositories' @@ -184,7 +184,7 @@ export const createTestWorkspace = async ( if (useRegion) { const regionDb = await getDb({ regionKey }) - const assignRegion = assignRegionFactory({ + const assignRegion = assignWorkspaceRegionFactory({ getAvailableRegions: getAvailableRegionsFactory({ getRegions: getRegionsFactory({ db }), canWorkspaceUseRegions: canWorkspaceUseRegionsFactory({ diff --git a/packages/server/modules/workspaces/tests/integration/projects.graph.spec.ts b/packages/server/modules/workspaces/tests/integration/projects.graph.spec.ts index d9c66f859..985e1c6ee 100644 --- a/packages/server/modules/workspaces/tests/integration/projects.graph.spec.ts +++ b/packages/server/modules/workspaces/tests/integration/projects.graph.spec.ts @@ -1,6 +1,15 @@ import { db } from '@/db/knex' import { AllScopes } from '@/modules/core/helpers/mainConstants' +import { createRandomEmail } from '@/modules/core/helpers/testHelpers' +import { + BranchCommitRecord, + BranchRecord, + CommitRecord, + StreamCommitRecord, + StreamRecord +} from '@/modules/core/helpers/types' import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams' +import { getDb } from '@/modules/multiregion/utils/dbSelector' import { BasicTestWorkspace, createTestWorkspace @@ -8,6 +17,7 @@ import { import { BasicTestUser, createAuthTokenForUser, + createTestUser, createTestUsers } from '@/test/authHelper' import { @@ -15,7 +25,8 @@ import { CreateWorkspaceProjectDocument, GetWorkspaceProjectsDocument, GetWorkspaceTeamDocument, - MoveProjectToWorkspaceDocument + MoveProjectToWorkspaceDocument, + UpdateProjectRegionDocument } from '@/test/graphql/generated/graphql' import { createTestContext, @@ -23,10 +34,22 @@ import { TestApolloServer } from '@/test/graphqlHelper' import { beforeEachContext } from '@/test/hooks' +import { BasicTestBranch, createTestBranch } from '@/test/speckle-helpers/branchHelper' +import { + BasicTestCommit, + createTestCommit, + createTestObject +} from '@/test/speckle-helpers/commitHelper' +import { + isMultiRegionTestMode, + waitForRegionUser +} from '@/test/speckle-helpers/regions' import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper' import { Roles } from '@speckle/shared' import { expect } from 'chai' import cryptoRandomString from 'crypto-random-string' +import { Knex } from 'knex' +import { SetOptional } from 'type-fest' const grantStreamPermissions = grantStreamPermissionsFactory({ db }) @@ -272,3 +295,152 @@ describe('Workspace project GQL CRUD', () => { }) }) }) + +isMultiRegionTestMode() + ? describe('Workspace project region changes', () => { + const regionKey1 = 'region1' + const regionKey2 = 'region2' + + const adminUser: BasicTestUser = { + id: '', + name: 'John Speckle', + email: createRandomEmail() + } + + const testWorkspace: SetOptional = { + id: '', + ownerId: '', + name: 'Unlimited Workspace' + } + + const testProject: BasicTestStream = { + id: '', + ownerId: '', + name: 'Regional Project', + isPublic: true + } + + const testModel: BasicTestBranch = { + id: '', + name: cryptoRandomString({ length: 8 }), + streamId: '', + authorId: '' + } + + const testVersion: BasicTestCommit = { + id: '', + objectId: '', + streamId: '', + authorId: '' + } + + let apollo: TestApolloServer + let targetRegionDb: Knex + + before(async () => { + await createTestUser(adminUser) + await waitForRegionUser(adminUser) + + apollo = await testApolloServer({ authUserId: adminUser.id }) + targetRegionDb = await getDb({ regionKey: regionKey2 }) + }) + + beforeEach(async () => { + delete testWorkspace.slug + + await createTestWorkspace(testWorkspace, adminUser, { + regionKey: regionKey1, + addPlan: { + name: 'unlimited', + status: 'valid' + } + }) + + testProject.workspaceId = testWorkspace.id + + await createTestStream(testProject, adminUser) + await createTestBranch({ + stream: testProject, + branch: testModel, + owner: adminUser + }) + + testVersion.branchName = testModel.name + testVersion.objectId = await createTestObject({ projectId: testProject.id }) + + await createTestCommit(testVersion, { + owner: adminUser, + stream: testProject + }) + }) + + it('moves project record to target regional db', async () => { + const res = await apollo.execute(UpdateProjectRegionDocument, { + projectId: testProject.id, + regionKey: regionKey2 + }) + + expect(res).to.not.haveGraphQLErrors() + + // TODO: Replace with gql query when possible + const project = await targetRegionDb + .table('streams') + .select('*') + .where({ id: testProject.id }) + .first() + + expect(project).to.not.be.undefined + }) + + it('moves project models to target regional db', async () => { + const res = await apollo.execute(UpdateProjectRegionDocument, { + projectId: testProject.id, + regionKey: regionKey2 + }) + + expect(res).to.not.haveGraphQLErrors() + + // TODO: Replace with gql query when possible + const branch = await targetRegionDb + .table('branches') + .select('*') + .where({ id: testModel.id }) + .first() + + expect(branch).to.not.be.undefined + }) + + it('moves project model versions to target regional db', async () => { + const res = await apollo.execute(UpdateProjectRegionDocument, { + projectId: testProject.id, + regionKey: regionKey2 + }) + + expect(res).to.not.haveGraphQLErrors() + + // TODO: Replace with gql query when possible + const version = await targetRegionDb + .table('commits') + .select('*') + .where({ id: testVersion.id }) + .first() + expect(version).to.not.be.undefined + + // TODO: Replace with gql query when possible + const streamCommitsRecord = await targetRegionDb + .table('stream_commits') + .select('*') + .where({ commitId: testVersion.id }) + .first() + expect(streamCommitsRecord).to.not.be.undefined + + // TODO: Replace with gql query when possible + const branchCommitsRecord = await targetRegionDb + .table('branch_commits') + .select('*') + .where({ commitId: testVersion.id }) + .first() + expect(branchCommitsRecord).to.not.be.undefined + }) + }) + : void 0 diff --git a/packages/server/multiregion.test.example.json b/packages/server/multiregion.test.example.json index 0eff18956..6fc82a924 100644 --- a/packages/server/multiregion.test.example.json +++ b/packages/server/multiregion.test.example.json @@ -27,6 +27,20 @@ "endpoint": "http://127.0.0.1:9020", "s3Region": "us-east-1" } + }, + "region2": { + "postgres": { + "connectionUri": "postgresql://speckle:speckle@127.0.0.1:5402/speckle2_test", + "privateConnectionUri": "postgresql://speckle:speckle@postgres-region2:5432/speckle2_test" + }, + "blobStorage": { + "accessKey": "minioadmin", + "secretKey": "minioadmin", + "bucket": "test-speckle-server", + "createBucketIfNotExists": true, + "endpoint": "http://127.0.0.1:9040", + "s3Region": "us-east-1" + } } } } diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index b2d31ffcb..be9dada4a 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -4591,6 +4591,11 @@ export type WorkspaceProjectInviteCreateInput = { export type WorkspaceProjectMutations = { __typename?: 'WorkspaceProjectMutations'; create: Project; + /** + * Update project region and move all regional data to new db. + * TODO: Currently performs all operations synchronously in request, should probably be scheduled. + */ + moveToRegion: Project; moveToWorkspace: Project; updateRole: Project; }; @@ -4601,6 +4606,12 @@ export type WorkspaceProjectMutationsCreateArgs = { }; +export type WorkspaceProjectMutationsMoveToRegionArgs = { + projectId: Scalars['String']['input']; + regionKey: Scalars['String']['input']; +}; + + export type WorkspaceProjectMutationsMoveToWorkspaceArgs = { projectId: Scalars['String']['input']; workspaceId: Scalars['String']['input']; @@ -5209,6 +5220,14 @@ export type UpdateRegionMutationVariables = Exact<{ export type UpdateRegionMutation = { __typename?: 'Mutation', serverInfoMutations: { __typename?: 'ServerInfoMutations', multiRegion: { __typename?: 'ServerRegionMutations', update: { __typename?: 'ServerRegionItem', id: string, key: string, name: string, description?: string | null } } } }; +export type UpdateProjectRegionMutationVariables = Exact<{ + projectId: Scalars['String']['input']; + regionKey: Scalars['String']['input']; +}>; + + +export type UpdateProjectRegionMutation = { __typename?: 'Mutation', workspaceMutations: { __typename?: 'WorkspaceMutations', projects: { __typename?: 'WorkspaceProjectMutations', moveToRegion: { __typename?: 'Project', id: string } } } }; + export type BasicProjectAccessRequestFieldsFragment = { __typename?: 'ProjectAccessRequest', id: string, requesterId: string, projectId: string, createdAt: string, requester: { __typename?: 'LimitedUser', id: string, name: string } }; export type CreateProjectAccessRequestMutationVariables = Exact<{ @@ -5751,6 +5770,7 @@ export const GetAvailableRegionKeysDocument = {"kind":"Document","definitions":[ export const CreateNewRegionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateNewRegion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateServerRegionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfoMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"multiRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"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":"MainRegionMetadata"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MainRegionMetadata"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerRegionItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]} as unknown as DocumentNode; export const GetRegionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetRegions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"multiRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MainRegionMetadata"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MainRegionMetadata"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerRegionItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]} as unknown as DocumentNode; export const UpdateRegionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateRegion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateServerRegionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfoMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"multiRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update"},"arguments":[{"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":"MainRegionMetadata"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MainRegionMetadata"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerRegionItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]} as unknown as DocumentNode; +export const UpdateProjectRegionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateProjectRegion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"regionKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"moveToRegion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}},{"kind":"Argument","name":{"kind":"Name","value":"regionKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"regionKey"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const CreateProjectAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProjectAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessRequestMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAccessRequest"}},"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":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; export const GetActiveUserProjectAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetActiveUserProjectAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAccessRequest"}},"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":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; export const GetActiveUserFullProjectAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetActiveUserFullProjectAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAccessRequest"}},"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":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; diff --git a/packages/server/test/graphql/multiRegion.ts b/packages/server/test/graphql/multiRegion.ts index ea44f957f..71a73e57b 100644 --- a/packages/server/test/graphql/multiRegion.ts +++ b/packages/server/test/graphql/multiRegion.ts @@ -60,3 +60,15 @@ export const updateRegionMutation = gql` ${mainRegionMetadataFragment} ` + +export const updateProjectRegionMutation = gql` + mutation UpdateProjectRegion($projectId: String!, $regionKey: String!) { + workspaceMutations { + projects { + moveToRegion(projectId: $projectId, regionKey: $regionKey) { + id + } + } + } + } +` diff --git a/packages/server/test/hooks.ts b/packages/server/test/hooks.ts index 8bc17fac2..99a3cf402 100644 --- a/packages/server/test/hooks.ts +++ b/packages/server/test/hooks.ts @@ -22,8 +22,7 @@ import { MaybeAsync, MaybeNullOrUndefined, Nullable, - Optional, - wait + Optional } from '@speckle/shared' import * as mocha from 'mocha' import { @@ -199,19 +198,18 @@ export const resetPubSubFactory = (deps: { db: Knex }) => async () => { await deps.db.raw( `SELECT * FROM aiven_extras.pg_alter_subscription_disable('${info.subname}');` ) - await wait(500) await deps.db.raw( `SELECT * FROM aiven_extras.pg_drop_subscription('${info.subname}');` ) - await wait(1000) await deps.db.raw( `SELECT * FROM aiven_extras.dblink_slot_create_or_drop('${info.subconninfo}', '${info.subslotname}', 'drop');` ) } // Drop all subs - // (concurrently, cause it seems possible and we have those delays there) - await Promise.all(subscriptions.rows.map(dropSubs)) + for (const sub of subscriptions.rows) { + await dropSubs(sub) + } // Drop all pubs for (const pub of publications.rows) { diff --git a/packages/shared/src/environment/index.ts b/packages/shared/src/environment/index.ts index dca6a7524..cbe7619f2 100644 --- a/packages/shared/src/environment/index.ts +++ b/packages/shared/src/environment/index.ts @@ -60,6 +60,16 @@ const parseFeatureFlags = () => { FF_FORCE_ONBOARDING: { schema: z.boolean(), defaults: { production: false, _: false } + }, + // Fixes the streaming of objects by ensuring that the database stream is closed properly + FF_OBJECTS_STREAMING_FIX: { + schema: z.boolean(), + defaults: { production: false, _: false } + }, + // Enables endpoint(s) for updating a project's region + FF_MOVE_PROJECT_REGION_ENABLED: { + schema: z.boolean(), + defaults: { production: false, _: true } } }) @@ -86,6 +96,8 @@ export function getFeatureFlags(): { FF_FILEIMPORT_IFC_DOTNET_ENABLED: boolean FF_FORCE_EMAIL_VERIFICATION: boolean FF_FORCE_ONBOARDING: boolean + FF_OBJECTS_STREAMING_FIX: boolean + FF_MOVE_PROJECT_REGION_ENABLED: boolean } { if (!parsedFlags) parsedFlags = parseFeatureFlags() return parsedFlags From e9997f1c629cbcded5de8ec09f61548d23733d09 Mon Sep 17 00:00:00 2001 From: Alexandru Popovici Date: Thu, 13 Feb 2025 17:32:25 +0200 Subject: [PATCH 36/78] feat(viewer-lib): Loader now allows unknown type for resourceData, so each loader can treat it the way it wants (#3981) --- packages/viewer-sandbox/src/main.ts | 1 - packages/viewer/src/modules/loaders/Loader.ts | 7 ++----- packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts | 2 +- .../viewer/src/modules/loaders/Speckle/SpeckleLoader.ts | 5 ++--- .../src/modules/loaders/Speckle/SpeckleOfflineLoader.ts | 4 ++-- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 05fbc7922..c27fcdd54 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -20,7 +20,6 @@ import { import { SectionTool } from '@speckle/viewer' import { SectionOutlines } from '@speckle/viewer' import { ViewModesKeys } from './Extensions/ViewModesKeys' -// import { JSONSpeckleStream } from './JSONSpeckleStream' import { BoxSelection } from './Extensions/BoxSelection' import { PassReader } from './Extensions/PassReader' diff --git a/packages/viewer/src/modules/loaders/Loader.ts b/packages/viewer/src/modules/loaders/Loader.ts index fda341230..ac338e580 100644 --- a/packages/viewer/src/modules/loaders/Loader.ts +++ b/packages/viewer/src/modules/loaders/Loader.ts @@ -14,15 +14,12 @@ export interface LoaderEventPayload { export abstract class Loader extends EventEmitter { protected _resource: string - protected _resourceData: string | ArrayBuffer | undefined + protected _resourceData: unknown public abstract get resource(): string public abstract get finished(): boolean - protected constructor( - resource: string, - resourceData?: string | ArrayBuffer | undefined - ) { + protected constructor(resource: string, resourceData?: unknown) { super() this._resource = resource this._resourceData = resourceData diff --git a/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts b/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts index 67bc4d577..bfb5479f5 100644 --- a/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts +++ b/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts @@ -20,7 +20,7 @@ export class ObjLoader extends Loader { return this.isFinished } - public constructor(targetTree: WorldTree, resource: string, resourceData?: string) { + public constructor(targetTree: WorldTree, resource: string, resourceData?: unknown) { super(resource, resourceData) this.tree = targetTree this.baseLoader = new OBJLoader() diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts index 5e3a2d81d..df54596ef 100644 --- a/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts +++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts @@ -26,7 +26,7 @@ export class SpeckleLoader extends Loader { resource: string, authToken?: string, enableCaching?: boolean, - resourceData?: string | ArrayBuffer + resourceData?: unknown ) { super(resource, resourceData) this.tree = targetTree @@ -49,10 +49,9 @@ export class SpeckleLoader extends Loader { resource: string, authToken?: string, enableCaching?: boolean, - resourceData?: string | ArrayBuffer + resourceData?: unknown ): ObjectLoader { resourceData - let token = undefined try { token = authToken || (localStorage.getItem('AuthToken') as string | undefined) diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleOfflineLoader.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleOfflineLoader.ts index d2acedf59..aa46a7bda 100644 --- a/packages/viewer/src/modules/loaders/Speckle/SpeckleOfflineLoader.ts +++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleOfflineLoader.ts @@ -4,7 +4,7 @@ import { WorldTree } from '../../tree/WorldTree.js' import Logger from '../../utils/Logger.js' export class SpeckleOfflineLoader extends SpeckleLoader { - constructor(targetTree: WorldTree, resourceData: string, resourceId?: string) { + constructor(targetTree: WorldTree, resourceData: unknown, resourceId?: string) { super(targetTree, resourceId || '', undefined, undefined, resourceData) } @@ -12,7 +12,7 @@ export class SpeckleOfflineLoader extends SpeckleLoader { _resource: string, _authToken?: string, _enableCaching?: boolean, - resourceData?: string | ArrayBuffer + resourceData?: unknown ): ObjectLoader { return ObjectLoader.createFromJSON(resourceData as string) } From 7b303ddbd6998a6a5f4a9d10fc025238fc4865c7 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 13 Feb 2025 16:40:31 +0100 Subject: [PATCH 37/78] Feat: Show embed URL (#3980) --- .../components/project/model-page/dialog/embed/Embed.vue | 2 ++ packages/ui-components/src/components/form/ClipboardInput.vue | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/frontend-2/components/project/model-page/dialog/embed/Embed.vue b/packages/frontend-2/components/project/model-page/dialog/embed/Embed.vue index eed39446c..cfb518c34 100644 --- a/packages/frontend-2/components/project/model-page/dialog/embed/Embed.vue +++ b/packages/frontend-2/components/project/model-page/dialog/embed/Embed.vue @@ -42,6 +42,8 @@

Copy this code to embed your model in a webpage or document.

+

Embed URL

+
diff --git a/packages/ui-components/src/components/form/ClipboardInput.vue b/packages/ui-components/src/components/form/ClipboardInput.vue index bab5d0e56..168b37552 100644 --- a/packages/ui-components/src/components/form/ClipboardInput.vue +++ b/packages/ui-components/src/components/form/ClipboardInput.vue @@ -16,7 +16,7 @@ name="contentInput" readonly :model-value="value" - class="relative z-10 text-sm text-foreground font-mono select-all" + class="relative z-10 text-body-2xs text-foreground font-mono select-all" />
Date: Thu, 13 Feb 2025 17:11:54 +0100 Subject: [PATCH 38/78] Feat: Update project invites (#3937) --- .../components/form/select/ProjectRoles.vue | 5 +- .../components/form/select/Projects.vue | 4 + .../invite/dialog/project/Project.vue | 284 +++++++++++++++ .../dialog/project/workspaceMembers/Row.vue | 62 ++++ .../workspaceMembers/WorkspaceMembers.vue | 53 +++ .../components/project/page/InviteDialog.vue | 327 ------------------ .../settings/collaborators/Collaborators.vue | 19 +- .../workspace/invite/dialog/EmailsRow.vue | 76 ---- .../workspace/invite/dialog/UserRow.vue | 65 ---- .../components/workspaces/Promo/Banner.vue | 46 --- .../components/workspaces/Promo/Card.vue | 17 - .../lib/common/generated/gql/gql.ts | 24 +- .../lib/common/generated/gql/graphql.ts | 18 +- .../lib/invites/helpers/constants.ts | 13 +- .../frontend-2/lib/invites/helpers/types.ts | 14 + .../lib/invites/helpers/validation.ts | 51 ++- .../projects/composables/projectManagement.ts | 9 +- 17 files changed, 519 insertions(+), 568 deletions(-) create mode 100644 packages/frontend-2/components/invite/dialog/project/Project.vue create mode 100644 packages/frontend-2/components/invite/dialog/project/workspaceMembers/Row.vue create mode 100644 packages/frontend-2/components/invite/dialog/project/workspaceMembers/WorkspaceMembers.vue delete mode 100644 packages/frontend-2/components/project/page/InviteDialog.vue delete mode 100644 packages/frontend-2/components/workspace/invite/dialog/EmailsRow.vue delete mode 100644 packages/frontend-2/components/workspace/invite/dialog/UserRow.vue delete mode 100644 packages/frontend-2/components/workspaces/Promo/Banner.vue delete mode 100644 packages/frontend-2/components/workspaces/Promo/Card.vue diff --git a/packages/frontend-2/components/form/select/ProjectRoles.vue b/packages/frontend-2/components/form/select/ProjectRoles.vue index 4017f908d..8d30ba763 100644 --- a/packages/frontend-2/components/form/select/ProjectRoles.vue +++ b/packages/frontend-2/components/form/select/ProjectRoles.vue @@ -62,6 +62,7 @@ const emit = defineEmits<{ const props = defineProps<{ modelValue?: ValueType clearable?: boolean + hiddenItems?: StreamRoles[] disabledItems?: StreamRoles[] disabledItemsTooltip?: string allowUnset?: boolean @@ -80,7 +81,9 @@ const { selectedValue, firstItem, isMultiItemArrayValue, hiddenSelectedItemCount dynamicVisibility: { elementToWatchForChanges, itemContainer } }) -const roles = computed(() => Object.values(Roles.Stream)) +const roles = computed(() => + Object.values(Roles.Stream).filter((role) => !props.hiddenItems?.includes(role)) +) const disabledItemPredicate = (item: StreamRoles) => props.disabledItems && props.disabledItems.length > 0 diff --git a/packages/frontend-2/components/form/select/Projects.vue b/packages/frontend-2/components/form/select/Projects.vue index b1c343dbb..f65f37fbe 100644 --- a/packages/frontend-2/components/form/select/Projects.vue +++ b/packages/frontend-2/components/form/select/Projects.vue @@ -11,6 +11,7 @@ :name="name || 'projects'" :label-id="labelId" :button-id="buttonId" + :tooltip-text="tooltipText" by="id" >
@@ -21,19 +21,17 @@ />
- diff --git a/packages/frontend-2/components/workspace/invite/dialog/UserRow.vue b/packages/frontend-2/components/workspace/invite/dialog/UserRow.vue deleted file mode 100644 index 6758f5d4d..000000000 --- a/packages/frontend-2/components/workspace/invite/dialog/UserRow.vue +++ /dev/null @@ -1,65 +0,0 @@ - - diff --git a/packages/frontend-2/components/workspaces/Promo/Banner.vue b/packages/frontend-2/components/workspaces/Promo/Banner.vue deleted file mode 100644 index dbfd5600b..000000000 --- a/packages/frontend-2/components/workspaces/Promo/Banner.vue +++ /dev/null @@ -1,46 +0,0 @@ - - diff --git a/packages/frontend-2/components/workspaces/Promo/Card.vue b/packages/frontend-2/components/workspaces/Promo/Card.vue deleted file mode 100644 index 0f2d9af78..000000000 --- a/packages/frontend-2/components/workspaces/Promo/Card.vue +++ /dev/null @@ -1,17 +0,0 @@ - - diff --git a/packages/frontend-2/lib/common/generated/gql/gql.ts b/packages/frontend-2/lib/common/generated/gql/gql.ts index cc3174cb6..ea24827c6 100644 --- a/packages/frontend-2/lib/common/generated/gql/gql.ts +++ b/packages/frontend-2/lib/common/generated/gql/gql.ts @@ -51,6 +51,9 @@ const documents = { "\n fragment FormUsersSelectItem on LimitedUser {\n id\n name\n avatar\n }\n": types.FormUsersSelectItemFragmentDoc, "\n fragment HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\n }\n": types.HeaderNavShare_ProjectFragmentDoc, "\n fragment InviteDialogWorkspace_Workspace on Workspace {\n id\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n plan {\n status\n name\n }\n subscription {\n seats {\n guest\n plan\n }\n }\n }\n": types.InviteDialogWorkspace_WorkspaceFragmentDoc, + "\n fragment InviteDialogProject_Project on Project {\n id\n name\n ...InviteDialogProjectWorkspaceMembers_Project\n workspace {\n id\n name\n defaultProjectRole\n role\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n plan {\n status\n name\n }\n subscription {\n seats {\n guest\n plan\n }\n }\n }\n }\n": types.InviteDialogProject_ProjectFragmentDoc, + "\n fragment InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator on WorkspaceCollaborator {\n role\n id\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n": types.InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaboratorFragmentDoc, + "\n fragment InviteDialogProjectWorkspaceMembers_Project on Project {\n id\n ...ProjectPageTeamInternals_Project\n workspace {\n team {\n items {\n ...InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator\n }\n }\n }\n }\n": types.InviteDialogProjectWorkspaceMembers_ProjectFragmentDoc, "\n fragment ProjectModelPageHeaderProject on Project {\n id\n name\n model(id: $modelId) {\n id\n name\n description\n }\n workspace {\n id\n slug\n name\n }\n }\n": types.ProjectModelPageHeaderProjectFragmentDoc, "\n fragment ProjectModelPageVersionsPagination on Project {\n id\n visibility\n model(id: $modelId) {\n id\n versions(limit: 16, cursor: $versionsCursor) {\n cursor\n totalCount\n items {\n ...ProjectModelPageVersionsCardVersion\n }\n }\n }\n ...ProjectsModelPageEmbed_Project\n }\n": types.ProjectModelPageVersionsPaginationFragmentDoc, "\n fragment ProjectModelPageVersionsProject on Project {\n ...ProjectPageProjectHeader\n model(id: $modelId) {\n id\n name\n pendingImportedVersions {\n ...PendingFileUpload\n }\n }\n ...ProjectModelPageVersionsPagination\n ...ProjectsModelPageEmbed_Project\n workspace {\n id\n readOnly\n }\n }\n": types.ProjectModelPageVersionsProjectFragmentDoc, @@ -60,7 +63,6 @@ const documents = { "\n fragment ProjectsModelPageEmbed_Project on Project {\n id\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": types.ProjectsModelPageEmbed_ProjectFragmentDoc, "\n fragment ProjectModelPageVersionsCardVersion on Version {\n id\n message\n authorUser {\n ...LimitedUserAvatar\n }\n createdAt\n previewUrl\n sourceApplication\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectModelPageDialogDeleteVersion\n ...ProjectModelPageDialogMoveToVersion\n automationsStatus {\n ...AutomateRunsTriggerStatus_TriggeredAutomationsStatus\n }\n }\n": types.ProjectModelPageVersionsCardVersionFragmentDoc, "\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n": types.ProjectPageProjectHeaderFragmentDoc, - "\n fragment ProjectPageInviteDialog_Project on Project {\n id\n workspaceId\n workspace {\n id\n defaultProjectRole\n team {\n items {\n role\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n }\n }\n ...ProjectPageTeamInternals_Project\n workspace {\n id\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n }\n }\n": types.ProjectPageInviteDialog_ProjectFragmentDoc, "\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n versionTag\n createdAt\n inputSchema\n function {\n id\n }\n }\n }\n": types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragmentDoc, "\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevision on AutomationRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n type\n model {\n id\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFragmentDoc, "\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n inputSchema\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n": types.ProjectPageAutomationFunctions_AutomationFragmentDoc, @@ -80,7 +82,7 @@ const documents = { "\n fragment SingleLevelModelTreeItem on ModelsTreeItem {\n id\n name\n fullName\n model {\n ...ProjectPageLatestItemsModelItem\n }\n hasChildren\n updatedAt\n }\n": types.SingleLevelModelTreeItemFragmentDoc, "\n fragment ProjectPageModelsCardDeleteDialog on Model {\n id\n name\n }\n": types.ProjectPageModelsCardDeleteDialogFragmentDoc, "\n fragment ProjectPageModelsCardRenameDialog on Model {\n id\n name\n description\n }\n": types.ProjectPageModelsCardRenameDialogFragmentDoc, - "\n query ProjectPageSettingsCollaborators($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageTeamInternals_Project\n ...ProjectPageInviteDialog_Project\n }\n }\n": types.ProjectPageSettingsCollaboratorsDocument, + "\n query ProjectPageSettingsCollaborators($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageTeamInternals_Project\n ...InviteDialogProject_Project\n workspaceId\n }\n }\n": types.ProjectPageSettingsCollaboratorsDocument, "\n query ProjectPageSettingsCollaboratorsWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n ...ProjectPageTeamInternals_Workspace\n }\n }\n": types.ProjectPageSettingsCollaboratorsWorkspaceDocument, "\n query ProjectPageSettingsGeneral($projectId: String!) {\n project(id: $projectId) {\n id\n role\n ...ProjectPageSettingsGeneralBlockProjectInfo_Project\n ...ProjectPageSettingsGeneralBlockAccess_Project\n ...ProjectPageSettingsGeneralBlockDiscussions_Project\n ...ProjectPageSettingsGeneralBlockLeave_Project\n ...ProjectPageSettingsGeneralBlockDelete_Project\n ...ProjectPageTeamInternals_Project\n }\n }\n": types.ProjectPageSettingsGeneralDocument, "\n fragment ProjectPageSettingsGeneralBlockAccess_Project on Project {\n id\n visibility\n }\n": types.ProjectPageSettingsGeneralBlockAccess_ProjectFragmentDoc, @@ -560,6 +562,18 @@ export function graphql(source: "\n fragment HeaderNavShare_Project on Project * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n fragment InviteDialogWorkspace_Workspace on Workspace {\n id\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n plan {\n status\n name\n }\n subscription {\n seats {\n guest\n plan\n }\n }\n }\n"): (typeof documents)["\n fragment InviteDialogWorkspace_Workspace on Workspace {\n id\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n plan {\n status\n name\n }\n subscription {\n seats {\n guest\n plan\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment InviteDialogProject_Project on Project {\n id\n name\n ...InviteDialogProjectWorkspaceMembers_Project\n workspace {\n id\n name\n defaultProjectRole\n role\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n plan {\n status\n name\n }\n subscription {\n seats {\n guest\n plan\n }\n }\n }\n }\n"): (typeof documents)["\n fragment InviteDialogProject_Project on Project {\n id\n name\n ...InviteDialogProjectWorkspaceMembers_Project\n workspace {\n id\n name\n defaultProjectRole\n role\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n plan {\n status\n name\n }\n subscription {\n seats {\n guest\n plan\n }\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator on WorkspaceCollaborator {\n role\n id\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n"): (typeof documents)["\n fragment InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator on WorkspaceCollaborator {\n role\n id\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment InviteDialogProjectWorkspaceMembers_Project on Project {\n id\n ...ProjectPageTeamInternals_Project\n workspace {\n team {\n items {\n ...InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator\n }\n }\n }\n }\n"): (typeof documents)["\n fragment InviteDialogProjectWorkspaceMembers_Project on Project {\n id\n ...ProjectPageTeamInternals_Project\n workspace {\n team {\n items {\n ...InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -596,10 +610,6 @@ export function graphql(source: "\n fragment ProjectModelPageVersionsCardVersio * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n"): (typeof documents)["\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment ProjectPageInviteDialog_Project on Project {\n id\n workspaceId\n workspace {\n id\n defaultProjectRole\n team {\n items {\n role\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n }\n }\n ...ProjectPageTeamInternals_Project\n workspace {\n id\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectPageInviteDialog_Project on Project {\n id\n workspaceId\n workspace {\n id\n defaultProjectRole\n team {\n items {\n role\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n }\n }\n ...ProjectPageTeamInternals_Project\n workspace {\n id\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -679,7 +689,7 @@ export function graphql(source: "\n fragment ProjectPageModelsCardRenameDialog /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query ProjectPageSettingsCollaborators($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageTeamInternals_Project\n ...ProjectPageInviteDialog_Project\n }\n }\n"): (typeof documents)["\n query ProjectPageSettingsCollaborators($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageTeamInternals_Project\n ...ProjectPageInviteDialog_Project\n }\n }\n"]; +export function graphql(source: "\n query ProjectPageSettingsCollaborators($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageTeamInternals_Project\n ...InviteDialogProject_Project\n workspaceId\n }\n }\n"): (typeof documents)["\n query ProjectPageSettingsCollaborators($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageTeamInternals_Project\n ...InviteDialogProject_Project\n workspaceId\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index 5e0a4ab74..39b30cf30 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -4819,6 +4819,12 @@ export type HeaderNavShare_ProjectFragment = { __typename?: 'Project', id: strin export type InviteDialogWorkspace_WorkspaceFragment = { __typename?: 'Workspace', id: string, domainBasedMembershipProtectionEnabled: boolean, domains?: Array<{ __typename?: 'WorkspaceDomain', domain: string, id: string }> | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, name: WorkspacePlans } | null, subscription?: { __typename?: 'WorkspaceSubscription', seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null }; +export type InviteDialogProject_ProjectFragment = { __typename?: 'Project', id: string, name: string, role?: string | null, workspace?: { __typename?: 'Workspace', id: string, name: string, defaultProjectRole: string, role?: string | null, domainBasedMembershipProtectionEnabled: boolean, domains?: Array<{ __typename?: 'WorkspaceDomain', domain: string, id: string }> | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, name: WorkspacePlans } | null, subscription?: { __typename?: 'WorkspaceSubscription', seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', role: string, id: string, user: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, role?: string | null } }> } } | null, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, role: string, inviteId: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, role?: string | null, name: string, avatar?: string | null } }> }; + +export type InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaboratorFragment = { __typename?: 'WorkspaceCollaborator', role: string, id: string, user: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, role?: string | null } }; + +export type InviteDialogProjectWorkspaceMembers_ProjectFragment = { __typename?: 'Project', id: string, role?: string | null, workspace?: { __typename?: 'Workspace', team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', role: string, id: string, user: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, role?: string | null } }> } } | null, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, role: string, inviteId: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, role?: string | null, name: string, avatar?: string | null } }> }; + export type ProjectModelPageHeaderProjectFragment = { __typename?: 'Project', id: string, name: string, model: { __typename?: 'Model', id: string, name: string, description?: string | null }, workspace?: { __typename?: 'Workspace', id: string, slug: string, name: string } | null }; export type ProjectModelPageVersionsPaginationFragment = { __typename?: 'Project', id: string, visibility: ProjectVisibility, role?: string | null, model: { __typename?: 'Model', id: string, versions: { __typename?: 'VersionCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Version', id: string, message?: string | null, createdAt: string, previewUrl: string, sourceApplication?: string | null, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null }> } } }; @@ -4837,8 +4843,6 @@ export type ProjectModelPageVersionsCardVersionFragment = { __typename?: 'Versio export type ProjectPageProjectHeaderFragment = { __typename?: 'Project', id: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, workspace?: { __typename?: 'Workspace', id: string, slug: string, name: string, logo?: string | null } | null }; -export type ProjectPageInviteDialog_ProjectFragment = { __typename?: 'Project', id: string, workspaceId?: string | null, role?: string | null, workspace?: { __typename?: 'Workspace', id: string, defaultProjectRole: string, domainBasedMembershipProtectionEnabled: boolean, team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, role?: string | null } }> }, domains?: Array<{ __typename?: 'WorkspaceDomain', domain: string, id: string }> | null } | null, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, role: string, inviteId: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, role?: string | null, name: string, avatar?: string | null } }> }; - export type ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragment = { __typename?: 'AutomationRevisionFunction', parameters?: {} | null, release: { __typename?: 'AutomateFunctionRelease', id: string, versionTag: string, createdAt: string, inputSchema?: {} | null, function: { __typename?: 'AutomateFunction', id: string } } }; export type ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFragment = { __typename?: 'AutomationRevision', id: string, triggerDefinitions: Array<{ __typename?: 'VersionCreatedTriggerDefinition', type: AutomateRunTriggerType, model?: { __typename?: 'Model', id: string, name: string } | null }> }; @@ -4882,7 +4886,7 @@ export type ProjectPageSettingsCollaboratorsQueryVariables = Exact<{ }>; -export type ProjectPageSettingsCollaboratorsQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, role?: string | null, workspaceId?: string | null, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, role: string, inviteId: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, workspace?: { __typename?: 'Workspace', id: string, defaultProjectRole: string, domainBasedMembershipProtectionEnabled: boolean, team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', role: string, user: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, role?: string | null } }> }, domains?: Array<{ __typename?: 'WorkspaceDomain', domain: string, id: string }> | null } | null } }; +export type ProjectPageSettingsCollaboratorsQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, workspaceId?: string | null, role?: string | null, name: string, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, role: string, inviteId: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, workspace?: { __typename?: 'Workspace', id: string, name: string, defaultProjectRole: string, role?: string | null, domainBasedMembershipProtectionEnabled: boolean, domains?: Array<{ __typename?: 'WorkspaceDomain', domain: string, id: string }> | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, name: WorkspacePlans } | null, subscription?: { __typename?: 'WorkspaceSubscription', seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', items: Array<{ __typename?: 'WorkspaceCollaborator', role: string, id: string, user: { __typename?: 'LimitedUser', id: string, name: string, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, role?: string | null } }> } } | null } }; export type ProjectPageSettingsCollaboratorsWorkspaceQueryVariables = Exact<{ workspaceId: Scalars['String']['input']; @@ -6575,6 +6579,10 @@ export const FormSelectProjects_ProjectFragmentDoc = {"kind":"Document","definit export const ProjectsPageTeamDialogManagePermissions_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]} as unknown as DocumentNode; export const ProjectsModelPageEmbed_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]} as unknown as DocumentNode; export const HeaderNavShare_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"HeaderNavShare_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}}]} as unknown as DocumentNode; +export const ProjectPageTeamInternals_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]} as unknown as DocumentNode; +export const InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaboratorFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const InviteDialogProjectWorkspaceMembers_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembers_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const InviteDialogProject_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProject_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembers_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"defaultProjectRole"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembers_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectModelPageHeaderProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageHeaderProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const ProjectPageProjectHeaderFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageProjectHeader"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}}]}}]}}]} as unknown as DocumentNode; export const PendingFileUploadFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}}]} as unknown as DocumentNode; @@ -6590,8 +6598,6 @@ export const ProjectModelPageVersionsCardVersionFragmentDoc = {"kind":"Document" export const ProjectModelPageVersionsPaginationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsPagination"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"16"}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionsCursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageVersionsCardVersion"}}]}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageDialogDeleteVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageDialogMoveToVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsCardVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"authorUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageDialogDeleteVersion"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageDialogMoveToVersion"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}}]} as unknown as DocumentNode; export const ProjectModelPageVersionsProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageProjectHeader"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageVersionsPagination"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageDialogDeleteVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageDialogMoveToVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsCardVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"authorUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageDialogDeleteVersion"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageDialogMoveToVersion"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageProjectHeader"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsPagination"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"16"}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionsCursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageVersionsCardVersion"}}]}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}}]} as unknown as DocumentNode; export const ProjectModelPageDialogEditMessageVersionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageDialogEditMessageVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]} as unknown as DocumentNode; -export const ProjectPageTeamInternals_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]} as unknown as DocumentNode; -export const ProjectPageInviteDialog_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageInviteDialog_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"defaultProjectRole"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"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":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectPageModelsActions_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}}]} as unknown as DocumentNode; export const ProjectPageModelsCardProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}}]} as unknown as DocumentNode; export const ProjectPageAutomationModels_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageAutomationModels_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}}]} as unknown as DocumentNode; @@ -6733,7 +6739,7 @@ export const AuthRegisterPanelWorkspaceInviteDocument = {"kind":"Document","defi export const EmailVerificationBannerStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EmailVerificationBannerState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"hasPendingVerification"}}]}}]}}]} as unknown as DocumentNode; export const RequestVerificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RequestVerification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"requestVerification"}}]}}]} as unknown as DocumentNode; export const AutomationCreateDialogFunctionsSearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AutomationCreateDialogFunctionsSearch"},"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":"search"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"automateFunctions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"20"}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"search"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"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":"FragmentSpread","name":{"kind":"Name","value":"AutomateAutomationCreateDialog_AutomateFunction"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"},"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":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction"},"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":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"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":"inputSchema"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateAutomationCreateDialog_AutomateFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction"}}]}}]} as unknown as DocumentNode; -export const ProjectPageSettingsCollaboratorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectPageSettingsCollaborators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageInviteDialog_Project"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageInviteDialog_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"defaultProjectRole"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"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":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; +export const ProjectPageSettingsCollaboratorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectPageSettingsCollaborators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogProject_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"bio"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembers_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogProject_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogProjectWorkspaceMembers_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"defaultProjectRole"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectPageSettingsCollaboratorsWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectPageSettingsCollaboratorsWorkspace"},"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":"ProjectPageTeamInternals_Workspace"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Workspace"},"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":"team"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const ProjectPageSettingsGeneralDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectPageSettingsGeneral"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockProjectInfo_Project"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockAccess_Project"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockDiscussions_Project"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockLeave_Project"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockDelete_Project"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsDeleteDialog_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockProjectInfo_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockAccess_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockDiscussions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockLeave_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageSettingsGeneralBlockDelete_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsDeleteDialog_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageTeamInternals_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}}]} as unknown as DocumentNode; export const OnUserProjectsUpdateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"OnUserProjectsUpdate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userProjectsUpdated"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/frontend-2/lib/invites/helpers/constants.ts b/packages/frontend-2/lib/invites/helpers/constants.ts index 94fe2fa89..b39699e56 100644 --- a/packages/frontend-2/lib/invites/helpers/constants.ts +++ b/packages/frontend-2/lib/invites/helpers/constants.ts @@ -1,4 +1,8 @@ -import type { InviteServerItem, InviteGenericItem } from '~~/lib/invites/helpers/types' +import type { + InviteServerItem, + InviteGenericItem, + InviteProjectItem +} from '~~/lib/invites/helpers/types' import { Roles } from '@speckle/shared' export const emptyInviteServerItem: InviteServerItem = { @@ -7,6 +11,13 @@ export const emptyInviteServerItem: InviteServerItem = { project: undefined } +export const emptyInviteProjectItem: InviteProjectItem = { + email: '', + serverRole: Roles.Server.User, + projectRole: Roles.Stream.Contributor, + project: undefined +} + export const emptyInviteGenericItem: InviteGenericItem = { email: '', workspaceRole: undefined, diff --git a/packages/frontend-2/lib/invites/helpers/types.ts b/packages/frontend-2/lib/invites/helpers/types.ts index 783684472..77fb3e4b1 100644 --- a/packages/frontend-2/lib/invites/helpers/types.ts +++ b/packages/frontend-2/lib/invites/helpers/types.ts @@ -1,6 +1,7 @@ import type { ServerRoles, WorkspaceRoles, StreamRoles } from '@speckle/shared' import type { FormSelectProjects_ProjectFragment } from '~~/lib/common/generated/gql/graphql' +// Server export type InviteServerItem = { email: string serverRole: ServerRoles @@ -11,6 +12,19 @@ export interface InviteServerForm { fields: InviteServerItem[] } +// Project +export type InviteProjectItem = { + email: string + serverRole: ServerRoles + projectRole?: StreamRoles + project?: FormSelectProjects_ProjectFragment +} + +export interface InviteProjectForm { + fields: InviteProjectItem[] +} + +// Workspace export type InviteGenericItem = { email: string workspaceRole?: WorkspaceRoles diff --git a/packages/frontend-2/lib/invites/helpers/validation.ts b/packages/frontend-2/lib/invites/helpers/validation.ts index 44300383f..764d3fa13 100644 --- a/packages/frontend-2/lib/invites/helpers/validation.ts +++ b/packages/frontend-2/lib/invites/helpers/validation.ts @@ -1,6 +1,12 @@ import { isEmail } from '~/lib/common/helpers/validation' import type { GenericValidateFunction } from 'vee-validate' -import { Roles, type WorkspaceRoles, type MaybeNullOrUndefined } from '@speckle/shared' +import { + Roles, + type StreamRoles, + type WorkspaceRoles, + type ServerRoles, + type MaybeNullOrUndefined +} from '@speckle/shared' export const isValidEmail = (val: string) => isEmail(val || '', { @@ -23,16 +29,47 @@ export const canHaveRole = (params: { allowedDomains: MaybeNullOrUndefined workspaceRole?: WorkspaceRoles + projectRole?: StreamRoles }): GenericValidateFunction => (val) => { - const { allowedDomains, workspaceRole } = params + const { allowedDomains, workspaceRole, projectRole } = params if (!allowedDomains || !val) return true - if ( - !matchesDomainPolicy(val, allowedDomains) && - workspaceRole !== Roles.Workspace.Guest - ) { - return 'This email does not match the set domain policy, and can only be invited as a guest' + if (!matchesDomainPolicy(val, allowedDomains)) { + if (workspaceRole && workspaceRole !== Roles.Workspace.Guest) { + return 'This email does not match the set domain policy, and can only be invited as a guest' + } + if (projectRole && projectRole !== Roles.Stream.Reviewer) { + return 'This email does not match the set domain policy, and can only be invited as a reviewer' + } + } + + return true + } + +export const isRequiredIfDependencyExists = + (dependency: () => string) => (val?: string) => + !dependency() || !!val || 'This field is required' + +export const canBeServerGuest = + ({ + workspaceRole, + projectRole + }: { + workspaceRole?: WorkspaceRoles + projectRole?: StreamRoles + }) => + (val?: ServerRoles) => { + if (val === Roles.Server.Guest) { + if (projectRole === Roles.Stream.Owner) { + return 'A guest user cannot be a stream owner' + } + if (workspaceRole === Roles.Workspace.Admin) { + return 'A guest user cannot be a workspace admin' + } + if (workspaceRole === Roles.Workspace.Member) { + return 'A guest user cannot be a workspace member' + } } return true diff --git a/packages/frontend-2/lib/projects/composables/projectManagement.ts b/packages/frontend-2/lib/projects/composables/projectManagement.ts index 04c2d320a..65610ec92 100644 --- a/packages/frontend-2/lib/projects/composables/projectManagement.ts +++ b/packages/frontend-2/lib/projects/composables/projectManagement.ts @@ -304,10 +304,7 @@ export function useInviteUserToProject() { if (err && !hideToasts) { triggerNotification({ type: ToastNotificationType.Danger, - title: - input.length > 1 - ? "Couldn't send invites" - : `Coudldn't send invite to ${input[0].email}`, + title: input.length > 1 ? "Couldn't send invites" : "Couldn't send invite", description: err }) } else { @@ -315,9 +312,7 @@ export function useInviteUserToProject() { triggerNotification({ type: ToastNotificationType.Success, title: - input.length > 1 - ? 'Invites successfully send' - : `Invite successfully sent to ${input[0].email}` + input.length > 1 ? 'Invites successfully send' : 'Invite successfully sent' }) } } From 4f35d994b4780ed84d1f7e038a25ee84804f92c0 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 13 Feb 2025 21:39:00 +0100 Subject: [PATCH 39/78] Fix: Improve feedback logging formatting (#3984) --- .../frontend-2/components/feedback/Dialog.vue | 8 ++++++-- .../workspaces/General/DeleteDialog.vue | 15 ++++++++------ .../lib/billing/composables/actions.ts | 20 +------------------ 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/packages/frontend-2/components/feedback/Dialog.vue b/packages/frontend-2/components/feedback/Dialog.vue index 208c4be2a..a551e4f18 100644 --- a/packages/frontend-2/components/feedback/Dialog.vue +++ b/packages/frontend-2/components/feedback/Dialog.vue @@ -98,8 +98,12 @@ const onSubmit = handleSubmit(async () => { }) await sendWebhook(defaultZapierWebhookUrl, { - userId: user.value?.id ?? '', - feedback: feedback.value + feedback: [ + `**Action:** User Feedback`, + `**Type:** ${props.type}`, + `**User ID:** ${user.value?.id}`, + `**Feedback:** ${feedback.value}` + ].join('\n') }) }) diff --git a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue index e45c7e0dd..170d89b8f 100644 --- a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue +++ b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue @@ -131,12 +131,15 @@ const onDelete = async () => { }) await sendWebhook(defaultZapierWebhookUrl, { - userId: activeUser.value?.id ?? '', - feedback: `Action: Workspace Deleted (${workspaceName}) - User ID: ${ - activeUser.value?.id - } - Workspace ID: ${workspaceId} - ${ - feedback.value ? `Feedback: ${feedback.value}` : 'No feedback provided' - }` + feedback: [ + `**Action:** Workspace Deleted`, + `**Workspace:** ${workspaceName}`, + `**User ID:** ${activeUser.value?.id}`, + `**Workspace ID:** ${workspaceId}`, + feedback.value + ? `**Feedback:** ${feedback.value}` + : '**Feedback:** No feedback provided' + ].join('\n') }) triggerNotification({ diff --git a/packages/frontend-2/lib/billing/composables/actions.ts b/packages/frontend-2/lib/billing/composables/actions.ts index e9d43980b..d280e6671 100644 --- a/packages/frontend-2/lib/billing/composables/actions.ts +++ b/packages/frontend-2/lib/billing/composables/actions.ts @@ -14,8 +14,6 @@ import { settingsBillingCancelCheckoutSessionMutation } from '~/lib/settings/gra import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast' import { useMixpanel } from '~/lib/core/composables/mp' import { graphql } from '~~/lib/common/generated/gql' -import { useZapier } from '~/lib/core/composables/zapier' -import { defaultZapierWebhookUrl } from '~/lib/common/helpers/route' graphql(` fragment BillingActions_Workspace on Workspace { @@ -49,7 +47,6 @@ export const useBillingActions = () => { const { mutate: cancelCheckoutSessionMutation } = useMutation( settingsBillingCancelCheckoutSessionMutation ) - const { sendWebhook } = useZapier() const billingPortalRedirect = async (workspaceId?: string) => { if (!workspaceId) return @@ -188,9 +185,7 @@ export const useBillingActions = () => { }) } - const validateCheckoutSession = async ( - workspace: BillingActions_WorkspaceFragment - ) => { + const validateCheckoutSession = (workspace: BillingActions_WorkspaceFragment) => { const sessionIdQuery = route.query?.session_id const paymentStatusQuery = route.query?.payment_status @@ -218,19 +213,6 @@ export const useBillingActions = () => { // eslint-disable-next-line camelcase workspace_id: workspace.id }) - - if (import.meta.server) { - await sendWebhook(defaultZapierWebhookUrl, { - workspaceId: workspace.id, - workspaceName: workspace.name, - plan: workspace.plan?.name ?? '', - cycle: workspace.subscription?.billingInterval ?? '', - status: WorkspacePlanStatuses.Valid, - invitedTeamCount: workspace.invitedTeam?.length ?? 0, - teamCount: workspace.team?.totalCount ?? 0, - defaultRegion: workspace.defaultRegion?.name ?? '' - }) - } } const currentQueryParams = { ...route.query } From 91cb011ded2a4d3b0dfe634cb79b10eaaa525f42 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:20:14 +0000 Subject: [PATCH 40/78] feat(fe2): New user onboarding flow (#3932) * CodeInput. verify-email page * middleware * Loading toast * Countdown only for registration * Improve middleware * Fix middleware breaking auth flow * Remove old notifications * Remove old onboarding. New segmentation * Remove skip button * Block verify email when verified * useUserEmails composable. Cancel addition * Move user emails queries * Fix fragments etc * redirect updates * HeaderWithEmptyPage * Check env before enforcing * Join workspace * Updates * Fix console warnings on login * Fix register console warnings * Working cache updates * Verify secondary email * Force onboarding off * EMAIL WIP * useIsJustRegistered state * Improve isRequired * Uneeded change * Improved slots * Updates from CR * CR comments * Only show message if forced * Update onboarding middleware * Update loading bar * ref > computed to fix onboarding * Resend tooltip. Better errors * Add other to form. * Email changes * Updates to emails * Remove force email FF * Remove FF's * Hide header on embed * Update graphql.ts * Re-add FF * Update graphql.ts * GQL Fragments * Fix build --- .../components/auth/LoginWithEmailBlock.vue | 2 + .../components/auth/RegisterNewsletter.vue | 5 +- .../auth/RegisterWithEmailBlock.vue | 3 + .../auth/VerificationReminderMenuNotice.vue | 113 ----- .../components/auth/sso/Register.vue | 2 +- .../frontend-2/components/header/Empty.vue | 14 + .../components/header/LogoBlock.vue | 2 +- .../frontend-2/components/header/NavBar.vue | 8 - .../components/header/NavNotifications.vue | 51 -- .../components/header/WithEmptyPage.vue | 31 ++ .../components/onboarding/JoinTeammates.vue | 100 ++++ .../components/onboarding/checklist/v1.vue | 445 ------------------ .../components/onboarding/questions/Form.vue | 56 +++ .../onboarding/questions/PlanSelect.vue | 50 ++ .../onboarding/questions/RoleSelect.vue | 46 ++ .../onboarding/questions/SourceSelect.vue | 49 ++ .../settings/user/email/DeleteDialog.vue | 53 +-- .../components/settings/user/email/List.vue | 23 +- .../settings/user/email/ListItem.vue | 64 +-- .../frontend-2/components/tour/Comment.vue | 118 ----- .../frontend-2/components/tour/Onboarding.vue | 25 - .../components/tour/Segmentation.vue | 144 ------ .../frontend-2/components/tour/Slideshow.vue | 162 ------- .../tour/content/BasicViewerNavigation.vue | 78 --- .../components/tour/content/FirstTip.vue | 12 - .../components/tour/content/LastTip.vue | 13 - .../components/tour/content/OverlayModel.vue | 72 --- .../frontend-2/components/viewer/Controls.vue | 15 +- .../components/viewer/LoadingBar.vue | 5 +- .../components/viewer/PreSetupWrapper.vue | 17 +- .../components/workspace/CreatePage.vue | 43 +- packages/frontend-2/composables/globals.ts | 8 + packages/frontend-2/layouts/onboarding.vue | 7 - packages/frontend-2/layouts/viewer.vue | 39 +- .../lib/auth/composables/activeUser.ts | 4 + .../frontend-2/lib/auth/composables/auth.ts | 15 +- .../lib/auth/composables/onboarding.ts | 13 +- .../frontend-2/lib/auth/helpers/onboarding.ts | 75 ++- .../lib/common/generated/gql/gql.ts | 66 +-- .../lib/common/generated/gql/graphql.ts | 66 +-- .../frontend-2/lib/common/helpers/route.ts | 2 + .../lib/onboarding/graphql/queries.ts | 10 + .../lib/onboarding/helpers/types.ts | 4 + .../lib/settings/graphql/mutations.ts | 15 +- .../lib/settings/graphql/queries.ts | 8 - .../frontend-2/lib/user/composables/emails.ts | 191 ++++++++ .../frontend-2/lib/user/graphql/mutations.ts | 10 + .../frontend-2/lib/user/graphql/queries.ts | 23 + .../frontend-2/lib/viewer/composables/tour.ts | 41 -- .../frontend-2/lib/viewer/composables/ui.ts | 4 - .../004-emailVerification.global.ts | 36 ++ ...ing.global.ts => 005-onboarding.global.ts} | 8 + packages/frontend-2/pages/authn.vue | 4 +- .../verify/[appId]/[challenge]/index.vue | 2 +- .../frontend-2/pages/error-email-verify.vue | 2 +- packages/frontend-2/pages/onboarding.vue | 134 ++++-- .../frontend-2/pages/settings/user/emails.vue | 61 +-- packages/frontend-2/pages/verify-email.vue | 183 +++++++ .../emails/templates/components/footer.mjml | 2 +- .../templates/components/headerLogo.mjml | 9 +- .../assets/public/speckle-email-logo.png | Bin 0 -> 4771 bytes .../emails/services/verification/request.ts | 36 +- .../src/components/form/CodeInput.stories.ts | 90 ++++ .../src/components/form/CodeInput.vue | 151 ++++++ .../src/components/form/TextInput.vue | 6 - .../src/components/form/select/Multi.vue | 5 +- .../src/components/global/ToastRenderer.vue | 5 + .../src/composables/form/textInput.ts | 2 +- .../ui-components/src/helpers/global/toast.ts | 3 +- packages/ui-components/src/lib.ts | 2 + 70 files changed, 1419 insertions(+), 1744 deletions(-) delete mode 100644 packages/frontend-2/components/auth/VerificationReminderMenuNotice.vue create mode 100644 packages/frontend-2/components/header/Empty.vue delete mode 100644 packages/frontend-2/components/header/NavNotifications.vue create mode 100644 packages/frontend-2/components/header/WithEmptyPage.vue create mode 100644 packages/frontend-2/components/onboarding/JoinTeammates.vue delete mode 100644 packages/frontend-2/components/onboarding/checklist/v1.vue create mode 100644 packages/frontend-2/components/onboarding/questions/Form.vue create mode 100644 packages/frontend-2/components/onboarding/questions/PlanSelect.vue create mode 100644 packages/frontend-2/components/onboarding/questions/RoleSelect.vue create mode 100644 packages/frontend-2/components/onboarding/questions/SourceSelect.vue delete mode 100644 packages/frontend-2/components/tour/Comment.vue delete mode 100644 packages/frontend-2/components/tour/Onboarding.vue delete mode 100644 packages/frontend-2/components/tour/Segmentation.vue delete mode 100644 packages/frontend-2/components/tour/Slideshow.vue delete mode 100644 packages/frontend-2/components/tour/content/BasicViewerNavigation.vue delete mode 100644 packages/frontend-2/components/tour/content/FirstTip.vue delete mode 100644 packages/frontend-2/components/tour/content/LastTip.vue delete mode 100644 packages/frontend-2/components/tour/content/OverlayModel.vue delete mode 100644 packages/frontend-2/layouts/onboarding.vue create mode 100644 packages/frontend-2/lib/onboarding/graphql/queries.ts create mode 100644 packages/frontend-2/lib/onboarding/helpers/types.ts create mode 100644 packages/frontend-2/lib/user/composables/emails.ts create mode 100644 packages/frontend-2/lib/user/graphql/queries.ts delete mode 100644 packages/frontend-2/lib/viewer/composables/tour.ts create mode 100644 packages/frontend-2/middleware/004-emailVerification.global.ts rename packages/frontend-2/middleware/{004-onboarding.global.ts => 005-onboarding.global.ts} (82%) create mode 100644 packages/frontend-2/pages/verify-email.vue create mode 100644 packages/server/assets/public/speckle-email-logo.png create mode 100644 packages/ui-components/src/components/form/CodeInput.stories.ts create mode 100644 packages/ui-components/src/components/form/CodeInput.vue diff --git a/packages/frontend-2/components/auth/LoginWithEmailBlock.vue b/packages/frontend-2/components/auth/LoginWithEmailBlock.vue index 748743f83..9e2dbf8f8 100644 --- a/packages/frontend-2/components/auth/LoginWithEmailBlock.vue +++ b/packages/frontend-2/components/auth/LoginWithEmailBlock.vue @@ -12,6 +12,7 @@ show-label :disabled="!!(loading || shouldForceInviteEmail)" auto-focus + autocomplete="email" />
diff --git a/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue b/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue index 4949acc51..2b339b982 100644 --- a/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue +++ b/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue @@ -13,6 +13,7 @@ :rules="emailRules" show-label :disabled="isEmailDisabled" + autocomplete="email" />
diff --git a/packages/frontend-2/components/auth/VerificationReminderMenuNotice.vue b/packages/frontend-2/components/auth/VerificationReminderMenuNotice.vue deleted file mode 100644 index 3c0df1c75..000000000 --- a/packages/frontend-2/components/auth/VerificationReminderMenuNotice.vue +++ /dev/null @@ -1,113 +0,0 @@ - - diff --git a/packages/frontend-2/components/auth/sso/Register.vue b/packages/frontend-2/components/auth/sso/Register.vue index 14f3756b5..3c638ce2f 100644 --- a/packages/frontend-2/components/auth/sso/Register.vue +++ b/packages/frontend-2/components/auth/sso/Register.vue @@ -39,7 +39,7 @@ import { useQuery } from '@vue/apollo-composable' const route = useRoute() const loading = ref(false) -const newsletterConsent = ref(undefined) +const newsletterConsent = ref(false) const { challenge } = useLoginOrRegisterUtils() const { signInOrSignUpWithSso } = useAuthManager() diff --git a/packages/frontend-2/components/header/Empty.vue b/packages/frontend-2/components/header/Empty.vue new file mode 100644 index 000000000..8e34c5b93 --- /dev/null +++ b/packages/frontend-2/components/header/Empty.vue @@ -0,0 +1,14 @@ + diff --git a/packages/frontend-2/components/header/LogoBlock.vue b/packages/frontend-2/components/header/LogoBlock.vue index 8d19559cf..c9ddf3881 100644 --- a/packages/frontend-2/components/header/LogoBlock.vue +++ b/packages/frontend-2/components/header/LogoBlock.vue @@ -1,7 +1,7 @@ diff --git a/packages/frontend-2/pages/verify-email.vue b/packages/frontend-2/pages/verify-email.vue new file mode 100644 index 000000000..f8c6d63b3 --- /dev/null +++ b/packages/frontend-2/pages/verify-email.vue @@ -0,0 +1,183 @@ + + + diff --git a/packages/server/assets/emails/templates/components/footer.mjml b/packages/server/assets/emails/templates/components/footer.mjml index 40b35e764..efb333941 100644 --- a/packages/server/assets/emails/templates/components/footer.mjml +++ b/packages/server/assets/emails/templates/components/footer.mjml @@ -18,7 +18,7 @@ - + Brought to you by
Speckle , the Open Source Data Platform for 3D Data. Follow Us diff --git a/packages/server/assets/emails/templates/components/headerLogo.mjml b/packages/server/assets/emails/templates/components/headerLogo.mjml index 0bf3640f5..383d1750f 100644 --- a/packages/server/assets/emails/templates/components/headerLogo.mjml +++ b/packages/server/assets/emails/templates/components/headerLogo.mjml @@ -1,13 +1,8 @@ - - - - - diff --git a/packages/server/assets/public/speckle-email-logo.png b/packages/server/assets/public/speckle-email-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..79aef8d1581177d9519675f3b0dd719cf661968c GIT binary patch literal 4771 zcmaJ_cQhMp-%dS2tO{yZh}lv_OQ@JZ?Oh{g#i~`Sikht;HZhv2QZ=drMeS7;RAa}k zy|Rl0v7ITj=X`My^0B^s{)yQU{KM&G zR?4*wLTz9XRj;AsW13Lm_%!x~l?HQ)G86 z^k^Cq`n5>_4PJPA2fE|+iLXTu_**hZp%3(E;4CY#%PXBFz1aAQ_{+JIowo{Iyh?PJN4&E$=Pz6Biz3ahkT*l+XDXbo_qRSy3E!(u zrImw_LS{q$7=fTpS49+2oV?0y#6IQE8@}3FLWxcR&S76Tx9Ff~4y!7O#PB^txb#^; zs?OSvgs5f~2`~bbb`L=xe!BT`|1u>)qMWf4p(B#97-btT1r^NCe7X?W&*&)imzW>` zExOCJxZAu#-e%ry!T^De_`SG#ZP6WcG`<~n>7xdM(&-a2`IsCJQ?-Mef>`GsUCwXr z9k*VR<^$2-$}hLh1+y`GEf-yr&ZoZuFLl^BK{w=vRlm2*FUd*&6T>bMW$Jb{`9*hd zAzc2?UsK@FNNWmtUTP4H)HnPzW5ho7X>CM{;N@Z9C%?fGbrl+DA+RZ0ZeB$BT%K*Y zAvEdcTXd@In;r!HoCm!pNJ<|BL-v4kF6BBFKV%qOKmL`-9b~r;mBRW1qj5pLf6k6><1M^@&m^wJ|5bjiAk9DBp;m%VfY0aWXnpa49UIJ8GS!z&WcIw01543hYB;G&r;XllP zmDx7Jh9Uc2$Eh@@RFfEO(aD7A0m$!MC2?^?&hdi=(JjL=n|eP@5LD_K>JGb%MyLgE zjcYI1Isf?kx#%GY4MsqPT~!S&dqIqa&(7RGJ_H$6{x-sN-+ah28hhTdUhUdg-er{X z{BNUUZisjCN5viL6*vvjQVywUkPmaf(IKq4K&7;6UEa+{qkLXN+tY!w4A!OQ_QV-K zob2QZ+?x(cM;n4)0v_s_Nrxmm4U)s;OZ)JN+F+&7qY;a=;&rI_jN`dVsK`ZnbSh;u zLeIcQt}RN-t7{XC+ylB*uWnsiv?#YbLjSijlC$lpT}T-msV=7q?>l!duG>D@A^kfO zhVpwsF!OSATQ}{dF(0;VeCnmV4S#@X|4Pc%5NiHYwNp=$?S?wu29wAzG(dpzsUY0NWv7c(<6-yD3I>kOE_ z^3$k%Ud0`6mbKEb5VY0Ult(_ZBmrxBPeCEEki*g{TlhGwFv7cp@qs ziRnaG9lp}qd>g^ zD2(SrAt7^>pN08cTK}kQN%JNyK?=5rw3#G>v^vdn_nfjvRe_k+`{8M?TNFzj-oKp$A;XoEiig( z6M!ijNm`=bToOfDsPsfeqIS45qIL&Tsf$iZSaN}wA|YQ+vu4qYN10R``t+_IX;T{?2R?@hCiMml$0^RTNqc!tG6|KzR| zB|0yyL3guf9*VDdYkq@_k)}%pUU2<1If!!p8+H@JwA36-;)urOyp{b~etk7$H2_)J zZ83=B0Dtl7#xUI+lav>$0JyL^3Ve57rEK4}!jIc@=HGCLxmTAuKjs#kQJ%kNox3ryUtTJ-T{|j#rRNoPS%I4D{(OG zz_D$)yE>+{l6SRboH_eomDk7y6~}tLekk8HvBVSXq(`OQ&KEiG8Jt}-8hBLabyb1) zEIij+ZP65&-c(Y0R1ArTQoFQQYr!{FXaF8Y<2%7~Lz8P-Z4oGge8{ zBPCUf&yt<0Cuoo9_JHg`qQN=gP}!i1RoWn`m3Zgn8hGiDmYMoX=hmIyNlCc zZPB2|d4)mKkt?BX>U=Mt<4=AHNxYya*yM@_pQSM$s z@h4f?>2xt2)TFOqdqAl;TbyP&FKh!|*7;++VvJ{&^`#Km4BFH1xrJqy6k+A#`)OKCWATq1?Q}3UeQqRLFY7>e$ z{a+6Dq?R)0aKiodsB4`?}i|^5wE5ymEYft%KE@vRJqqbG*W9 z$yEcp55I=O(#Dv(a9nEXd_OVS;W|gUJ$zN=1Z2gh37TD8ubHcvN{14Qfps4iju56`u#Qb+RvWkR!Pc|)N%9;l9Nz4T^46Q zoLW}#)db}PCR-n)8T_+z92;wL#WG0X@$8haI!ED(ZaKlo??or!PW*-uCXT(>gx={6 zcjD`6vGn|q_mPa^q1g6!x(S*6g32i!w`t`9bu7)mYP!!JG(5GdRkB9bSe5!5%jinn z>n1qf6XBzZLdw)S=u)R!vgFGC=JK5;P?l{n5Y|aI@q%|vZ_Dou%+&D)NX4ZG#7XF} zT9nzG-b&GzQEi1|FVs<&TX$KBhk9PSz73bls)P+|e>=L4+?QH?jQMpIy$sp3(r8Oj zE4vS?^Y)M7_K`3ze4!*qC(6%IY(PF|@;w_UC|~xZnV%&{cA&j!lmHR)IpA8#a+&p-zc05(4&u&z5 z$GFgw>zx-Hak#H((9qThqwS2NqzLN|+w2Q)s!h;IU%;~CYAf;QK`d{Ra1X8mks^rE z>cC?>_H&Za=KFYO@P>`L5~T(r@K3Pi6L!@w1pj+r6ob zk-GP749xj5yVh9#N$$QeW>*U9=j(q`pS8RjG^O{#3sT8)divkgTwg%msXutnzqQ|< z>ul`j(ZwiEESk=I{AtnO4$iirb@tyYxoH%<1d1nSW=pmo|G)}bOgyA%$i{ZMY32&` zfv1rXz7~@#3)>UTX zyGuzOktL5spKycw>GK?pCi;T6J(E_#kx#7;5e^{%f*z|A-$h08toovZ;et3!ZbRAp zRxqtpoUd0pkg2!Pe)>V7IJa?{bE$B|OuLVTQJ~t#Zy$oj`D?2Www#{(4MF&U)Ntcn z_g8;r4po@$O9%AJ^%4e->rdXC*?E&xQjAiIZoiJ1y+=_B#|9kAPQ8pbt|ESW3Q;}t z-xA>PzAH3qGbXaqz|S>*TTkK|ig^)pk(nCYDt`!2AQ^M(_(IM+ecXoXL<0)n$?3uC zzkWsg8&3vrIi(fN`$?PoHaYb6Pi;DuO&i@>JLf&gNHQ$oJlXcb(QG}13O(k|WG519 zlO*L`CQ)RYX&97thyY;T^=<*_1*) z*XjTaj_#?x$?xqVQK@L*!>-usco3=*eFO?zAMpu>nUCVWLxNx3e*N@$sJa>ch2(Pm zx{j;Oq-a1lad+)k-|OwRW1BJ7)g)Pye(bH={e0nP^HoNjT`<@4`pKoOl+troU&2;bgQg_<^tEiyK?bWDyh?AH%g(r!z7>{khU?m#u4(ES2x>Whn5@4Hct2y>hTZ17<6fq{5YUPW^$L-M+>$$g;f7eRgU6R8tOn9eO5#54k~A zhBvi59sT>8&MPyW*KE~-75BYLB%3tVtc>lj`W9kcV@17PE4DzdaaelpV@iC;p3o&3 zesj(>&s7n9nIv_uGTkOHh_3Dst2~$2-&mO0-4IZ(AnfOUNHUH!fmHACtZoib8KOnE zJL+W35_P9V>E|YUvP7bBJss)>jmQP6cT#mU)!5Q=PtDxSrbfd8QuHhmul{0xD8{+B zoq-Umm7<@Kwgd>4f(?t`4BFO`6LqtBg+cV%)7H56tKn1#v0+alQgmh@ZnnH`J~8L2=jGsvg(og)90Ki zV&5(gMZVj}JbMZU>cZZT6u`XO0_Hsy!ksu%JI4K%yDFS=Cu{Y2t}=x1*Te(6GKV=1 zLa@E!Gu^nnIEn%4G9DqhFU$Vx{#mjZJqmf4+tru)T#Buid~@G95L&{OG*TyPZkowf zzeLsM?=hpuBz@^S))vzi+mA4-njX>JMN>xOD0D!`pnV!CBPny}j9u9In>Z~KGW!=~ bkGRsG%#`wHPkfWCDFL)K4AjdI4`2KTXn`4g literal 0 HcmV?d00001 diff --git a/packages/server/modules/emails/services/verification/request.ts b/packages/server/modules/emails/services/verification/request.ts index 87347ac28..5547b33cb 100644 --- a/packages/server/modules/emails/services/verification/request.ts +++ b/packages/server/modules/emails/services/verification/request.ts @@ -101,33 +101,17 @@ const createNewEmailVerificationFactory = } function buildMjmlBody(verificationCode: string) { - const bodyStart = `Hello,

You have just registered to the Speckle server, or initiated the email verification process manually. To finalize the verification process, use the code below.
` - const bodyEnd = `This code expires in 5 minutes:
-${verificationCode} -
- If the code does not work, please proceed by

- - Logging in with your e-mail address and password - Clicking on the Notification icon - Selecting "Send Verification" - Verifying your e-mail address by using the new code -
- - See you soon,
- Speckle -
- ` - - return { bodyStart, bodyEnd } + const bodyStart = `

You have just registered to the Speckle server, or initiated the email verification process manually. To finalize the verification process, use the code below.

+ ${verificationCode} +

This code will expire in 5 minutes. Please do not disclose this code to others.

+

If you did not make this request, please disregard this email.

+

See you soon,
Speckle

` + return { bodyStart } } -function buildTextBody(verificationCode: string) { - const bodyStart = `Hello,\n\nYou have just registered to the Speckle server, or initiated the email verification process manually. To finalize the verification process, use the code below:` - const bodyEnd = `This code expires in 5 minutes: -${verificationCode} -\r\n -If the code does not work, please proceed by logging in to your Speckle account with your e-mail address and password, clicking the Notification icon, selecting "Send Verification" and verifying your e-mail address by new code.\n\nSee you soon,\nSpeckle - ` +function buildTextBody() { + const bodyStart = `` + const bodyEnd = `` return { bodyStart, bodyEnd } } @@ -135,7 +119,7 @@ If the code does not work, please proceed by logging in to your Speckle account function buildEmailTemplateParams(verificationCode: string): EmailTemplateParams { return { mjml: buildMjmlBody(verificationCode), - text: buildTextBody(verificationCode) + text: buildTextBody() } } diff --git a/packages/ui-components/src/components/form/CodeInput.stories.ts b/packages/ui-components/src/components/form/CodeInput.stories.ts new file mode 100644 index 000000000..4ac6da267 --- /dev/null +++ b/packages/ui-components/src/components/form/CodeInput.stories.ts @@ -0,0 +1,90 @@ +import type { Meta, StoryObj } from '@storybook/vue3' +import FormCodeInput from '~~/src/components/form/CodeInput.vue' + +type StoryType = StoryObj< + Record & { + 'update:modelValue': (val: string) => void + complete: (val: string) => void + } +> + +export default { + component: FormCodeInput, + parameters: { + docs: { + description: { + component: + 'A verification code input component that handles digit-by-digit entry with auto-advance and paste support.' + } + } + }, + argTypes: { + 'update:modelValue': { + type: 'function', + action: 'v-model' + }, + complete: { + type: 'function', + action: 'complete' + }, + digitCount: { + control: { type: 'number' } + } + } +} as Meta + +export const Default: StoryType = { + render: (args, ctx) => ({ + components: { FormCodeInput }, + setup: () => ({ args }), + template: ` +
+ +
+ `, + methods: { + onModelUpdate(val: string) { + args['update:modelValue'](val) + ctx.updateArgs({ ...args, modelValue: val }) + } + } + }), + args: { + modelValue: '', + digitCount: 6, + disabled: false, + errorMessage: '', + error: false, + complete: (val: string) => console.log('Complete:', val) + } +} + +export const Disabled: StoryType = { + ...Default, + args: { + ...Default.args, + disabled: true, + modelValue: '123456' + } +} + +export const DifferentLength: StoryType = { + ...Default, + args: { + ...Default.args, + digitCount: 4 + } +} + +export const WithError: StoryType = { + ...Default, + args: { + ...Default.args, + error: true, + modelValue: '123456' + } +} diff --git a/packages/ui-components/src/components/form/CodeInput.vue b/packages/ui-components/src/components/form/CodeInput.vue new file mode 100644 index 000000000..517221745 --- /dev/null +++ b/packages/ui-components/src/components/form/CodeInput.vue @@ -0,0 +1,151 @@ + + + diff --git a/packages/ui-components/src/components/form/TextInput.vue b/packages/ui-components/src/components/form/TextInput.vue index 2eecce44e..99f215459 100644 --- a/packages/ui-components/src/components/form/TextInput.vue +++ b/packages/ui-components/src/components/form/TextInput.vue @@ -358,12 +358,6 @@ const leadingIconClasses = computed(() => { const iconClasses = computed((): string => { const classParts: string[] = [] - if (props.customIcon) { - classParts.push('pl-8') - } else { - classParts.push('pl-2') - } - if (!slots['input-right']) { if (props.rightIcon || errorMessage.value || shouldShowClear.value) { classParts.push('pr-8') diff --git a/packages/ui-components/src/components/form/select/Multi.vue b/packages/ui-components/src/components/form/select/Multi.vue index 0fd02da70..e1f3ba7fc 100644 --- a/packages/ui-components/src/components/form/select/Multi.vue +++ b/packages/ui-components/src/components/form/select/Multi.vue @@ -132,7 +132,9 @@
-
+
@@ -162,7 +164,6 @@

): void diff --git a/packages/ui-components/src/composables/form/textInput.ts b/packages/ui-components/src/composables/form/textInput.ts index e33be00e6..692e638e5 100644 --- a/packages/ui-components/src/composables/form/textInput.ts +++ b/packages/ui-components/src/composables/form/textInput.ts @@ -91,7 +91,7 @@ export function useTextInputCore(params: { const color = unref(props.color) if (color === 'foundation') { classParts.push( - 'bg-foundation !border border-outline-2 hover:border-outline-5 focus-visible:border-outline-4 !ring-0 focus-visible:!outline-0 !text-[13px]' + 'bg-foundation !border border-outline-2 hover:border-outline-5 focus-visible:border-outline-4 !ring-0 focus-visible:!outline-0' ) } else if (color === 'transparent') { classParts.push('bg-transparent') diff --git a/packages/ui-components/src/helpers/global/toast.ts b/packages/ui-components/src/helpers/global/toast.ts index 4c070cf69..ef7fead14 100644 --- a/packages/ui-components/src/helpers/global/toast.ts +++ b/packages/ui-components/src/helpers/global/toast.ts @@ -2,7 +2,8 @@ export enum ToastNotificationType { Success, Warning, Danger, - Info + Info, + Loading } export type ToastNotification = { diff --git a/packages/ui-components/src/lib.ts b/packages/ui-components/src/lib.ts index 5cf4c3fb2..2919200f4 100644 --- a/packages/ui-components/src/lib.ts +++ b/packages/ui-components/src/lib.ts @@ -35,6 +35,7 @@ import FormSelectBadges from '~~/src/components/form/select/Badges.vue' import FormSelectMulti from '~~/src/components/form/select/Multi.vue' import FormSwitch from '~~/src/components/form/Switch.vue' import FormClipboardInput from '~~/src/components/form/ClipboardInput.vue' +import FormCodeInput from '~~/src/components/form/CodeInput.vue' import CommonLoadingBar from '~~/src/components/common/loading/Bar.vue' import SourceAppBadge from '~~/src/components/SourceAppBadge.vue' import { onKeyboardShortcut, useFormCheckboxModel } from '~~/src/composables/form/input' @@ -130,6 +131,7 @@ export { FormTextInput, FormSwitch, FormClipboardInput, + FormCodeInput, ValidationHelpers, useWrappingContainerHiddenCount, useFormSelectChildInternals, From ece665a8ae37a6bcace771ac4b4c9c7fcce85d26 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 14 Feb 2025 11:23:09 +0100 Subject: [PATCH 41/78] Fix: Add banner back (#3986) --- .../components/workspaces/Promo/Banner.vue | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 packages/frontend-2/components/workspaces/Promo/Banner.vue diff --git a/packages/frontend-2/components/workspaces/Promo/Banner.vue b/packages/frontend-2/components/workspaces/Promo/Banner.vue new file mode 100644 index 000000000..dbfd5600b --- /dev/null +++ b/packages/frontend-2/components/workspaces/Promo/Banner.vue @@ -0,0 +1,46 @@ + + From c10df776e0c7fba717c90511c1da74973bc413e9 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Fri, 14 Feb 2025 12:41:14 +0000 Subject: [PATCH 42/78] Add padding when customIcon is defined (#3987) --- packages/ui-components/src/components/form/TextInput.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui-components/src/components/form/TextInput.vue b/packages/ui-components/src/components/form/TextInput.vue index 99f215459..48b0fb926 100644 --- a/packages/ui-components/src/components/form/TextInput.vue +++ b/packages/ui-components/src/components/form/TextInput.vue @@ -358,6 +358,10 @@ const leadingIconClasses = computed(() => { const iconClasses = computed((): string => { const classParts: string[] = [] + if (props.customIcon) { + classParts.push('pl-8') + } + if (!slots['input-right']) { if (props.rightIcon || errorMessage.value || shouldShowClear.value) { classParts.push('pr-8') From f376cfcc46dfd09e1ab53eab0a6891b8436a91d1 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 15 Feb 2025 08:30:57 +0100 Subject: [PATCH 43/78] Fix: Always force email verification (#3990) --- packages/server/modules/auth/index.ts | 34 +-- .../core/graph/resolvers/userEmails.ts | 35 +--- .../integration/userEmails.graph.spec.ts | 158 +++++++------- .../modules/emails/repositories/index.ts | 7 +- .../services/verification/request.old.ts | 198 ------------------ packages/shared/src/environment/index.ts | 6 - .../speckle-server/templates/_helpers.tpl | 3 - .../templates/frontend_2/deployment.yml | 2 - 8 files changed, 99 insertions(+), 344 deletions(-) delete mode 100644 packages/server/modules/emails/services/verification/request.old.ts diff --git a/packages/server/modules/auth/index.ts b/packages/server/modules/auth/index.ts index 3aaefe2d8..9ed05608d 100644 --- a/packages/server/modules/auth/index.ts +++ b/packages/server/modules/auth/index.ts @@ -54,38 +54,24 @@ import { } from '@/modules/core/repositories/userEmails' import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails' import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request' -import { requestNewEmailVerificationFactory as requestNewEmailVerificationFactoryOld } from '@/modules/emails/services/verification/request.old' import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories' import { renderEmail } from '@/modules/emails/services/emailRendering' import { sendEmail } from '@/modules/emails/services/sending' import { getServerInfoFactory } from '@/modules/core/repositories/server' import { initializeEventListenerFactory } from '@/modules/auth/services/postAuth' import { getEventBus } from '@/modules/shared/services/eventBus' -import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' -const { FF_FORCE_EMAIL_VERIFICATION } = getFeatureFlags() const findEmail = findEmailFactory({ db }) -const requestNewEmailVerification = FF_FORCE_EMAIL_VERIFICATION - ? requestNewEmailVerificationFactory({ - findEmail, - getUser: getUserFactory({ db }), - getServerInfo: getServerInfoFactory({ db }), - deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ - db - }), - renderEmail, - sendEmail - }) - : requestNewEmailVerificationFactoryOld({ - findEmail, - getUser: getUserFactory({ db }), - getServerInfo: getServerInfoFactory({ db }), - deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ - db - }), - renderEmail, - sendEmail - }) +const requestNewEmailVerification = requestNewEmailVerificationFactory({ + findEmail, + getUser: getUserFactory({ db }), + getServerInfo: getServerInfoFactory({ db }), + deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ + db + }), + renderEmail, + sendEmail +}) const createUser = createUserFactory({ getServerInfo: getServerInfoFactory({ db }), diff --git a/packages/server/modules/core/graph/resolvers/userEmails.ts b/packages/server/modules/core/graph/resolvers/userEmails.ts index 83e0ef441..74439855c 100644 --- a/packages/server/modules/core/graph/resolvers/userEmails.ts +++ b/packages/server/modules/core/graph/resolvers/userEmails.ts @@ -10,7 +10,6 @@ import { } from '@/modules/core/repositories/userEmails' import { db } from '@/db/knex' import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request' -import { requestNewEmailVerificationFactory as requestNewEmailVerificationFactoryOld } from '@/modules/emails/services/verification/request.old' import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing' import { deleteServerOnlyInvitesFactory, @@ -31,32 +30,18 @@ import { verifyUserEmailFactory } from '@/modules/core/services/users/emailVerification' import { commandFactory } from '@/modules/shared/command' -import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' - -const { FF_FORCE_EMAIL_VERIFICATION } = getFeatureFlags() const getUser = getUserFactory({ db }) -const requestNewEmailVerification = FF_FORCE_EMAIL_VERIFICATION - ? requestNewEmailVerificationFactory({ - findEmail: findEmailFactory({ db }), - getUser, - getServerInfo: getServerInfoFactory({ db }), - deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ - db - }), - renderEmail, - sendEmail - }) - : requestNewEmailVerificationFactoryOld({ - findEmail: findEmailFactory({ db }), - getUser, - getServerInfo: getServerInfoFactory({ db }), - deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ - db - }), - renderEmail, - sendEmail - }) +const requestNewEmailVerification = requestNewEmailVerificationFactory({ + findEmail: findEmailFactory({ db }), + getUser, + getServerInfo: getServerInfoFactory({ db }), + deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory({ + db + }), + renderEmail, + sendEmail +}) export = { ActiveUserMutations: { diff --git a/packages/server/modules/core/tests/integration/userEmails.graph.spec.ts b/packages/server/modules/core/tests/integration/userEmails.graph.spec.ts index ce2d5a574..110f2c375 100644 --- a/packages/server/modules/core/tests/integration/userEmails.graph.spec.ts +++ b/packages/server/modules/core/tests/integration/userEmails.graph.spec.ts @@ -41,9 +41,7 @@ import { getEventBus } from '@/modules/shared/services/eventBus' import { createTestUser, login } from '@/test/authHelper' import { EmailVerificationFinalizationError } from '@/modules/emails/errors' import { Roles } from '@speckle/shared' -import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' -const { FF_FORCE_EMAIL_VERIFICATION } = getFeatureFlags() const getServerInfo = getServerInfoFactory({ db }) const getUser = legacyGetUserFactory({ db }) const requestNewEmailVerification = requestNewEmailVerificationFactory({ @@ -178,91 +176,89 @@ describe('User emails graphql @core', () => { ).to.eq(email.toLowerCase()) }) }) - ;(FF_FORCE_EMAIL_VERIFICATION ? describe : describe.skip)( - 'verify user email mutation', - () => { - it('should throw an error if there is no pending verification for the email', async () => { - const email = createRandomEmail() - const user = await createTestUser({ - email, - role: Roles.Server.User - }) - const session = await login(user) - // Delete email verification - await db(EmailVerifications.name).where({ email }).delete() - - const res = await session.execute(VerifyUserEmailDocument, { - input: { email, code: '123456' } - }) - - expect(res).to.haveGraphQLErrors({ - code: EmailVerificationFinalizationError.code - }) + describe('verify user email mutation', () => { + it('should throw an error if there is no pending verification for the email', async () => { + const email = createRandomEmail() + const user = await createTestUser({ + email, + role: Roles.Server.User }) - it('should throw an error if verification is expired', async () => { - const email = createRandomEmail() - const user = await createTestUser({ - email, - role: Roles.Server.User - }) - const session = await login(user) + const session = await login(user) - // Manually reset email verification code - const verificationCode = await deleteOldAndInsertNewVerificationFactory({ db })( - email - ) - // Manually expire email verification - await db(EmailVerifications.name) - .where({ email }) - .update({ createdAt: new Date('2020-01-01') }) + // Delete email verification + await db(EmailVerifications.name).where({ email }).delete() - const res = await session.execute(VerifyUserEmailDocument, { - input: { email, code: verificationCode } - }) - - expect(res).to.haveGraphQLErrors({ - code: EmailVerificationFinalizationError.code - }) + const res = await session.execute(VerifyUserEmailDocument, { + input: { email, code: '123456' } }) - it('should throw an error if code is not correct', async () => { - const email = createRandomEmail() - const user = await createTestUser({ - email, - role: Roles.Server.User - }) - const session = await login(user) - const res = await session.execute(VerifyUserEmailDocument, { - input: { email, code: '123456' } - }) - - expect(res).to.haveGraphQLErrors({ - code: EmailVerificationFinalizationError.code - }) + expect(res).to.haveGraphQLErrors({ + code: EmailVerificationFinalizationError.code }) - it('should mark user email as verified', async () => { - const email = createRandomEmail() - const user = await createTestUser({ - email, - role: Roles.Server.User - }) - const session = await login(user) - - // Manually reset email verification code - const verificationCode = await deleteOldAndInsertNewVerificationFactory({ db })( - email - ) - - const res = await session.execute(VerifyUserEmailDocument, { - input: { email, code: verificationCode } - }) - - expect(res).to.not.haveGraphQLErrors() - - const userEmail = await findEmailFactory({ db })({ email, userId: user.id }) - expect(userEmail?.verified).to.be.true + }) + it('should throw an error if verification is expired', async () => { + const email = createRandomEmail() + const user = await createTestUser({ + email, + role: Roles.Server.User }) - } - ) + const session = await login(user) + + // Manually reset email verification code + const verificationCode = await deleteOldAndInsertNewVerificationFactory({ db })( + email + ) + // Manually expire email verification + await db(EmailVerifications.name) + .where({ email }) + .update({ createdAt: new Date('2020-01-01') }) + + const res = await session.execute(VerifyUserEmailDocument, { + input: { email, code: verificationCode } + }) + + expect(res).to.haveGraphQLErrors({ + code: EmailVerificationFinalizationError.code + }) + }) + it('should throw an error if code is not correct', async () => { + const email = createRandomEmail() + const user = await createTestUser({ + email, + role: Roles.Server.User + }) + const session = await login(user) + + const res = await session.execute(VerifyUserEmailDocument, { + input: { email, code: '123456' } + }) + + expect(res).to.haveGraphQLErrors({ + code: EmailVerificationFinalizationError.code + }) + }) + it('should mark user email as verified', async () => { + const email = createRandomEmail() + const user = await createTestUser({ + email, + role: Roles.Server.User + }) + const session = await login(user) + + // Manually reset email verification code + const verificationCode = await deleteOldAndInsertNewVerificationFactory({ db })( + email + ) + + const res = await session.execute(VerifyUserEmailDocument, { + input: { email, code: verificationCode } + }) + + expect(res).to.not.haveGraphQLErrors() + + const userEmail = await findEmailFactory({ db })({ email, userId: user.id }) + expect(userEmail?.verified).to.be.true + }) + }) }) diff --git a/packages/server/modules/emails/repositories/index.ts b/packages/server/modules/emails/repositories/index.ts index 67b98cfff..95a96abc0 100644 --- a/packages/server/modules/emails/repositories/index.ts +++ b/packages/server/modules/emails/repositories/index.ts @@ -11,7 +11,6 @@ import dayjs from 'dayjs' import { Knex } from 'knex' import { hash } from 'bcrypt' import { EmailVerification } from '@/modules/emails/domain/types' -import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' const tables = { emailVerifications: (db: Knex) => db(EmailVerifications.name) @@ -64,7 +63,6 @@ function generateEmailVerificationCode() { return cryptoRandomString({ length: 6, type: 'numeric' }) } -const { FF_FORCE_EMAIL_VERIFICATION } = getFeatureFlags() /** * Delete all previous verification entries and create a new one */ @@ -81,15 +79,14 @@ export const deleteOldAndInsertNewVerificationFactory = withoutTablePrefix: true }).col - const newId = cryptoRandomString({ length: 20 }) const code = generateEmailVerificationCode() await tables.emailVerifications(deps.db).insert({ - [EmailVerificationCols.id]: newId, + [EmailVerificationCols.id]: cryptoRandomString({ length: 20 }), [EmailVerificationCols.email]: email, [EmailVerificationCols.code]: await hashEmailVerificationCode(code) }) - return FF_FORCE_EMAIL_VERIFICATION ? code : newId + return code } export const getPendingVerificationByEmailFactory = diff --git a/packages/server/modules/emails/services/verification/request.old.ts b/packages/server/modules/emails/services/verification/request.old.ts deleted file mode 100644 index 3c6f3a9d8..000000000 --- a/packages/server/modules/emails/services/verification/request.old.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { - FindEmail, - FindPrimaryEmailForUser -} from '@/modules/core/domain/userEmails/operations' -import { UserEmail } from '@/modules/core/domain/userEmails/types' -import { getEmailVerificationFinalizationRoute } from '@/modules/core/helpers/routeHelper' -import { ServerInfo, UserRecord } from '@/modules/core/helpers/types' -import { EmailVerificationRequestError } from '@/modules/emails/errors' -import { getServerOrigin } from '@/modules/shared/helpers/envHelper' -import { - DeleteOldAndInsertNewVerification, - EmailTemplateParams, - RenderEmail, - RequestEmailVerification, - RequestNewEmailVerification, - SendEmail -} from '@/modules/emails/domain/operations' -import { GetUser } from '@/modules/core/domain/users/operations' -import { GetServerInfo } from '@/modules/core/domain/server/operations' - -const EMAIL_SUBJECT = 'Speckle Account E-mail Verification' - -type CreateNewVerificationDeps = { - getUser: GetUser - findPrimaryEmailForUser: FindPrimaryEmailForUser - getServerInfo: GetServerInfo - deleteOldAndInsertNewVerification: DeleteOldAndInsertNewVerification -} - -const createNewVerificationFactory = - (deps: CreateNewVerificationDeps) => - async (userId: string): Promise => { - if (!userId) - throw new EmailVerificationRequestError('User for verification not specified') - - const [user, email, serverInfo] = await Promise.all([ - deps.getUser(userId), - deps.findPrimaryEmailForUser({ userId }), - deps.getServerInfo() - ]) - - if (!user || !email) - throw new EmailVerificationRequestError( - 'Unable to resolve verification target user' - ) - - if (user.verified) - throw new EmailVerificationRequestError("User's email is already verified") - - const verificationId = await deps.deleteOldAndInsertNewVerification(user.email) - - return { - user, - email, - verificationId, - serverInfo - } - } - -type VerificationRequestContext = { - user: UserRecord - verificationId: string - serverInfo: ServerInfo - email: UserEmail -} - -type CreateNewEmailVerificationFactoryDeps = { - findEmail: FindEmail - getUser: GetUser - getServerInfo: GetServerInfo - deleteOldAndInsertNewVerification: DeleteOldAndInsertNewVerification -} - -const createNewEmailVerificationFactory = - (deps: CreateNewEmailVerificationFactoryDeps) => - async (emailId: string): Promise => { - const emailRecord = await deps.findEmail({ id: emailId }) - - if (!emailRecord) throw new EmailVerificationRequestError('Email not found') - - if (emailRecord.verified) - throw new EmailVerificationRequestError('Email is already verified') - - const [user, serverInfo] = await Promise.all([ - deps.getUser(emailRecord.userId), - deps.getServerInfo() - ]) - - if (!user) - throw new EmailVerificationRequestError( - 'Unable to resolve verification target user' - ) - - const verificationId = await deps.deleteOldAndInsertNewVerification( - emailRecord.email - ) - return { - user, - email: emailRecord, - verificationId, - serverInfo - } - } - -function buildMjmlBody() { - const bodyStart = `Hello,

You have just registered to the Speckle server, or initiated the email verification process manually. To finalize the verification process, click the button below:
` - const bodyEnd = `This link expires in 1 week.
- If the link does not work, please proceed by

- - Logging in with your e-mail address and password - Clicking on the Notification icon - Selecting "Send Verification" - Verifying your e-mail address by clicking on the link in the e-mail you will receive -
- - See you soon,
- Speckle -
- ` - - return { bodyStart, bodyEnd } -} - -function buildTextBody() { - const bodyStart = `Hello,\n\nYou have just registered to the Speckle server, or initiated the email verification process manually. To finalize the verification process, open the link below:` - const bodyEnd = `This link expires in 1 week. If the link does not work, please proceed by logging in to your Speckle account with your e-mail address and password, clicking the Notification icon, selecting "Send Verification" and verifying your e-mail address by clicking on the link in the e-mail you will receive.\n\nSee you soon,\nSpeckle - ` - - return { bodyStart, bodyEnd } -} - -function buildEmailLink(verificationId: string): string { - return new URL( - getEmailVerificationFinalizationRoute(verificationId), - getServerOrigin() - ).toString() -} - -function buildEmailTemplateParams(verificationId: string): EmailTemplateParams { - return { - mjml: buildMjmlBody(), - text: buildTextBody(), - cta: { - title: 'Verify your E-mail', - url: buildEmailLink(verificationId) - } - } -} - -type SendVerificationEmailDeps = { - sendEmail: SendEmail - renderEmail: RenderEmail -} - -const sendVerificationEmailFactory = - (deps: SendVerificationEmailDeps) => async (state: VerificationRequestContext) => { - const emailTemplateParams = buildEmailTemplateParams(state.verificationId) - const { html, text } = await deps.renderEmail( - emailTemplateParams, - state.serverInfo, - // im deliberately setting this to null, so that the email will not show the unsubscribe bit - null - ) - await deps.sendEmail({ - to: state.email.email, - subject: EMAIL_SUBJECT, - text, - html - }) - } - -/** - * Request email verification (send out verification message) for user with specified ID - */ -export const requestEmailVerificationFactory = - ( - deps: CreateNewVerificationDeps & SendVerificationEmailDeps - ): RequestEmailVerification => - async (userId) => { - const newVerificationState = await createNewVerificationFactory(deps)(userId) - await sendVerificationEmailFactory(deps)(newVerificationState) - } - -type RequestNewEmailVerificationDeps = CreateNewEmailVerificationFactoryDeps - -/** - * Request email verification for email with specified ID - */ -export const requestNewEmailVerificationFactory = - ( - deps: RequestNewEmailVerificationDeps & SendVerificationEmailDeps - ): RequestNewEmailVerification => - async (emailId) => { - const createNewEmailVerification = createNewEmailVerificationFactory(deps) - const newVerificationState = await createNewEmailVerification(emailId) - - await sendVerificationEmailFactory(deps)(newVerificationState) - } diff --git a/packages/shared/src/environment/index.ts b/packages/shared/src/environment/index.ts index cbe7619f2..75cd9cd04 100644 --- a/packages/shared/src/environment/index.ts +++ b/packages/shared/src/environment/index.ts @@ -51,11 +51,6 @@ const parseFeatureFlags = () => { schema: z.boolean(), defaults: { production: false, _: false } }, - // Forces email verification for all users - FF_FORCE_EMAIL_VERIFICATION: { - schema: z.boolean(), - defaults: { production: false, _: false } - }, // Forces onboarding for all users FF_FORCE_ONBOARDING: { schema: z.boolean(), @@ -94,7 +89,6 @@ export function getFeatureFlags(): { FF_BILLING_INTEGRATION_ENABLED: boolean FF_WORKSPACES_MULTI_REGION_ENABLED: boolean FF_FILEIMPORT_IFC_DOTNET_ENABLED: boolean - FF_FORCE_EMAIL_VERIFICATION: boolean FF_FORCE_ONBOARDING: boolean FF_OBJECTS_STREAMING_FIX: boolean FF_MOVE_PROJECT_REGION_ENABLED: boolean diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index 0e01eb278..e38a5cfba 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -589,9 +589,6 @@ Generate the environment variables for Speckle server and Speckle objects deploy - name: FF_WORKSPACES_MULTI_REGION_ENABLED value: {{ .Values.featureFlags.workspacesMultiRegionEnabled | quote }} -- name: FF_FORCE_EMAIL_VERIFICATION - value: {{ .Values.featureFlags.forceEmailVerification | quote }} - - name: FF_FORCE_ONBOARDING value: {{ .Values.featureFlags.forceOnboarding | quote }} diff --git a/utils/helm/speckle-server/templates/frontend_2/deployment.yml b/utils/helm/speckle-server/templates/frontend_2/deployment.yml index 0440c0dc2..dfbb7ca33 100644 --- a/utils/helm/speckle-server/templates/frontend_2/deployment.yml +++ b/utils/helm/speckle-server/templates/frontend_2/deployment.yml @@ -137,8 +137,6 @@ spec: value: {{ .Values.featureFlags.workspacesMultiRegionEnabled | quote }} - name: NUXT_PUBLIC_FF_GENDOAI_MODULE_ENABLED value: {{ .Values.featureFlags.gendoAIModuleEnabled | quote }} - - name: NUXT_PUBLIC_FF_FORCE_EMAIL_VERIFICATION - value: {{ .Values.featureFlags.forceEmailVerification | quote }} - name: NUXT_PUBLIC_FF_FORCE_ONBOARDING value: {{ .Values.featureFlags.forceOnboarding | quote }} {{- if .Values.analytics.survicate_workspace_key }} From bf80347abf7bbafe9d6675a16ce5351f50f867da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Jedlicska?= <57442769+gjedlicska@users.noreply.github.com> Date: Mon, 17 Feb 2025 09:50:16 +0100 Subject: [PATCH 44/78] gergo/web 2664 workspace backend powered metrics (#3985) * feat(workspaces): delete workspace emit event * feat(workspaces): move workspace group metrics to the backend * Removed FE mixpanel group update * Remove fragment * test(gatekeeper): add unittest to new gatekeeper service --------- Co-authored-by: Mike Tasset --- .../workspaces/General/DeleteDialog.vue | 3 - .../components/workspace/ProjectList.vue | 9 - .../lib/common/generated/gql/gql.ts | 14 +- .../lib/common/generated/gql/graphql.ts | 18 +- .../lib/workspaces/composables/mixpanel.ts | 135 ------------ .../cli/commands/workspaces/set-plan.ts | 6 +- .../modules/core/repositories/userEmails.ts | 8 +- .../modules/gatekeeper/clients/stripe.ts | 2 +- .../modules/gatekeeper/domain/billing.ts | 46 +--- .../modules/gatekeeper/domain/operations.ts | 7 +- .../gatekeeper/domain/workspacePricing.ts | 47 +--- .../gatekeeper/repositories/billing.ts | 4 +- .../server/modules/gatekeeper/rest/billing.ts | 4 +- .../modules/gatekeeper/services/checkout.ts | 36 +++- .../modules/gatekeeper/services/readOnly.ts | 3 +- .../gatekeeper/services/subscriptions.ts | 12 +- .../gatekeeper/services/workspacePlans.ts | 95 +++++++++ packages/server/modules/gatekeeper/stripe.ts | 2 +- .../gatekeeper/tests/unit/checkout.spec.ts | 28 ++- .../tests/unit/featureAuthorization.spec.ts | 2 +- .../tests/unit/subscriptions.spec.ts | 2 +- .../tests/unit/workspacePlans.spec.ts | 201 ++++++++++++++++++ .../modules/gatekeeperCore/domain/billing.ts | 86 ++++++++ .../modules/gatekeeperCore/domain/events.ts | 8 +- packages/server/modules/index.ts | 3 +- .../modules/shared/helpers/envHelper.ts | 2 +- .../server/modules/shared/utils/mixpanel.ts | 2 +- .../modules/workspaces/domain/constants.ts | 6 - .../modules/workspaces/domain/operations.ts | 2 +- .../server/modules/workspaces/domain/types.ts | 2 +- .../workspaces/events/eventListener.ts | 175 ++++++++++++++- .../workspaces/graph/resolvers/workspaces.ts | 89 ++------ packages/server/modules/workspaces/index.ts | 46 ++++ .../workspaces/repositories/workspaces.ts | 15 +- .../modules/workspaces/services/invites.ts | 2 +- .../modules/workspaces/services/management.ts | 10 +- .../workspaces/tests/helpers/creation.ts | 2 +- .../modules/workspacesCore/domain/events.ts | 4 + 38 files changed, 738 insertions(+), 400 deletions(-) delete mode 100644 packages/frontend-2/lib/workspaces/composables/mixpanel.ts create mode 100644 packages/server/modules/gatekeeper/services/workspacePlans.ts create mode 100644 packages/server/modules/gatekeeper/tests/unit/workspacePlans.spec.ts create mode 100644 packages/server/modules/gatekeeperCore/domain/billing.ts delete mode 100644 packages/server/modules/workspaces/domain/constants.ts diff --git a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue index 170d89b8f..be28839b8 100644 --- a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue +++ b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue @@ -126,9 +126,6 @@ const onDelete = async () => { workspace_id: workspaceId, feedback: feedback.value }) - mixpanel.get_group('workspace_id', workspaceId).set_once({ - isDeleted: true - }) await sendWebhook(defaultZapierWebhookUrl, { feedback: [ diff --git a/packages/frontend-2/components/workspace/ProjectList.vue b/packages/frontend-2/components/workspace/ProjectList.vue index dd2192138..3543eefb0 100644 --- a/packages/frontend-2/components/workspace/ProjectList.vue +++ b/packages/frontend-2/components/workspace/ProjectList.vue @@ -94,11 +94,9 @@ import { usePaginatedQuery } from '~/lib/common/composables/graphql' import { graphql } from '~~/lib/common/generated/gql' import type { WorkspaceProjectsQueryQueryVariables } from '~~/lib/common/generated/gql/graphql' import { workspaceRoute } from '~/lib/common/helpers/route' -import { useWorkspacesMixpanel } from '~/lib/workspaces/composables/mixpanel' import { useBillingActions } from '~/lib/billing/composables/actions' import { useWorkspacesWizard } from '~/lib/workspaces/composables/wizard' import type { WorkspaceWizardState } from '~/lib/workspaces/helpers/types' -import { useActiveUser } from '~~/lib/auth/composables/activeUser' graphql(` fragment WorkspaceProjectList_Workspace on Workspace { @@ -107,7 +105,6 @@ graphql(` ...WorkspaceTeam_Workspace ...WorkspaceSecurity_Workspace ...BillingAlert_Workspace - ...WorkspaceMixpanelUpdateGroup_Workspace ...MoveProjectsDialog_Workspace ...InviteDialogWorkspace_Workspace projects { @@ -131,9 +128,7 @@ graphql(` } `) -const { activeUser } = useActiveUser() const { validateCheckoutSession } = useBillingActions() -const { workspaceMixpanelUpdateGroup } = useWorkspacesMixpanel() const areQueriesLoading = useQueryLoading() const route = useRoute() const { @@ -236,10 +231,6 @@ onResult((queryResult) => { } if (queryResult.data?.workspaceBySlug) { - workspaceMixpanelUpdateGroup( - queryResult.data.workspaceBySlug, - activeUser.value?.email - ) useHeadSafe({ title: queryResult.data.workspaceBySlug.name }) diff --git a/packages/frontend-2/lib/common/generated/gql/gql.ts b/packages/frontend-2/lib/common/generated/gql/gql.ts index fc9dd5687..064ff0d2d 100644 --- a/packages/frontend-2/lib/common/generated/gql/gql.ts +++ b/packages/frontend-2/lib/common/generated/gql/gql.ts @@ -138,7 +138,7 @@ const documents = { "\n fragment ViewerModelVersionCardItem on Version {\n id\n message\n referencedObject\n sourceApplication\n createdAt\n previewUrl\n authorUser {\n ...LimitedUserAvatar\n }\n }\n": types.ViewerModelVersionCardItemFragmentDoc, "\n fragment MoveProjectsDialog_Workspace on Workspace {\n id\n ...ProjectsMoveToWorkspaceDialog_Workspace\n projects {\n items {\n id\n modelCount: models(limit: 0) {\n totalCount\n }\n versions(limit: 0) {\n totalCount\n }\n }\n }\n }\n": types.MoveProjectsDialog_WorkspaceFragmentDoc, "\n fragment MoveProjectsDialog_User on User {\n projects {\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n role\n workspace {\n id\n }\n }\n }\n }\n": types.MoveProjectsDialog_UserFragmentDoc, - "\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n ...MoveProjectsDialog_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n": types.WorkspaceProjectList_WorkspaceFragmentDoc, + "\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...MoveProjectsDialog_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n": types.WorkspaceProjectList_WorkspaceFragmentDoc, "\n fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...ProjectDashboardItem\n }\n cursor\n }\n": types.WorkspaceProjectList_ProjectCollectionFragmentDoc, "\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...BillingAlert_Workspace\n slug\n readOnly\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc, "\n fragment WorkspaceInviteBanner_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBanner_PendingWorkspaceCollaboratorFragmentDoc, @@ -341,8 +341,6 @@ const documents = { "\n subscription OnViewerCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n id\n type\n comment {\n id\n parent {\n id\n }\n ...ViewerCommentThread\n }\n }\n }\n": types.OnViewerCommentsUpdatedDocument, "\n fragment LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\n }\n }\n": types.LinkableCommentFragmentDoc, "\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n": types.UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragmentDoc, - "\n fragment WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n }\n": types.WorkspaceMixpanelUpdateGroup_WorkspaceCollaboratorFragmentDoc, - "\n fragment WorkspaceMixpanelUpdateGroup_Workspace on Workspace {\n id\n name\n description\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n plan {\n status\n name\n createdAt\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n team {\n totalCount\n items {\n ...WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator\n }\n }\n defaultRegion {\n key\n }\n }\n": types.WorkspaceMixpanelUpdateGroup_WorkspaceFragmentDoc, "\n subscription OnWorkspaceProjectsUpdate($slug: String!) {\n workspaceProjectsUpdated(workspaceId: null, workspaceSlug: $slug) {\n projectId\n workspaceId\n type\n project {\n ...ProjectDashboardItem\n }\n }\n }\n ": types.OnWorkspaceProjectsUpdateDocument, "\n fragment WorkspaceHasCustomDataResidency_Workspace on Workspace {\n id\n defaultRegion {\n id\n name\n }\n }\n": types.WorkspaceHasCustomDataResidency_WorkspaceFragmentDoc, "\n query CheckProjectWorkspaceDataResidency($projectId: String!) {\n project(id: $projectId) {\n id\n workspace {\n ...WorkspaceHasCustomDataResidency_Workspace\n }\n }\n }\n": types.CheckProjectWorkspaceDataResidencyDocument, @@ -913,7 +911,7 @@ export function graphql(source: "\n fragment MoveProjectsDialog_User on User {\ /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n ...MoveProjectsDialog_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n"): (typeof documents)["\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...WorkspaceMixpanelUpdateGroup_Workspace\n ...MoveProjectsDialog_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n"]; +export function graphql(source: "\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...MoveProjectsDialog_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n"): (typeof documents)["\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...MoveProjectsDialog_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -1722,14 +1720,6 @@ export function graphql(source: "\n fragment LinkableComment on Comment {\n * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n"): (typeof documents)["\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n }\n"): (typeof documents)["\n fragment WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n fragment WorkspaceMixpanelUpdateGroup_Workspace on Workspace {\n id\n name\n description\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n plan {\n status\n name\n createdAt\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n team {\n totalCount\n items {\n ...WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator\n }\n }\n defaultRegion {\n key\n }\n }\n"): (typeof documents)["\n fragment WorkspaceMixpanelUpdateGroup_Workspace on Workspace {\n id\n name\n description\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n plan {\n status\n name\n createdAt\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n team {\n totalCount\n items {\n ...WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator\n }\n }\n defaultRegion {\n key\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index 33348f159..81b23aaf3 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -5008,7 +5008,7 @@ export type MoveProjectsDialog_WorkspaceFragment = { __typename?: 'Workspace', i export type MoveProjectsDialog_UserFragment = { __typename?: 'User', projects: { __typename?: 'UserProjectCollection', items: Array<{ __typename?: 'Project', role?: string | null, id: string, name: string, workspace?: { __typename?: 'Workspace', id: string } | null, modelCount: { __typename?: 'ModelCollection', totalCount: number }, versions: { __typename?: 'VersionCollection', totalCount: number } }> } }; -export type WorkspaceProjectList_WorkspaceFragment = { __typename?: 'Workspace', id: string, readOnly: boolean, name: string, slug: string, role?: string | null, description?: string | null, logo?: string | null, domainBasedMembershipProtectionEnabled: boolean, discoverabilityEnabled: boolean, projects: { __typename?: 'ProjectCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, modelCount: { __typename?: 'ModelCollection', totalCount: number }, versions: { __typename?: 'VersionCollection', totalCount: number }, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null }> }, workspace?: { __typename?: 'Workspace', id: string, readOnly: boolean, slug: string, name: string, logo?: string | null } | null, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean, state: {} } | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, createdAt: string, name: WorkspacePlans } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', totalCount: number, items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, role: string, user: { __typename?: 'LimitedUser', id: string, avatar?: string | null, name: string } }> }, adminWorkspacesJoinRequests?: { __typename?: 'WorkspaceJoinRequestCollection', totalCount: number } | null, domains?: Array<{ __typename?: 'WorkspaceDomain', id: string, domain: string }> | null, subscription?: { __typename?: 'WorkspaceSubscription', billingInterval: BillingInterval, currentBillingCycleEnd: string, seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, defaultRegion?: { __typename?: 'ServerRegionItem', key: string, id: string, name: string } | null, invitedTeam?: Array<{ __typename?: 'PendingWorkspaceCollaborator', id: string, role: string, email?: string | null }> | null }; +export type WorkspaceProjectList_WorkspaceFragment = { __typename?: 'Workspace', id: string, readOnly: boolean, name: string, slug: string, role?: string | null, description?: string | null, logo?: string | null, domainBasedMembershipProtectionEnabled: boolean, projects: { __typename?: 'ProjectCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, modelCount: { __typename?: 'ModelCollection', totalCount: number }, versions: { __typename?: 'VersionCollection', totalCount: number }, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null }> }, workspace?: { __typename?: 'Workspace', id: string, readOnly: boolean, slug: string, name: string, logo?: string | null } | null, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean, state: {} } | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, createdAt: string, name: WorkspacePlans } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', totalCount: number, items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }, adminWorkspacesJoinRequests?: { __typename?: 'WorkspaceJoinRequestCollection', totalCount: number } | null, domains?: Array<{ __typename?: 'WorkspaceDomain', id: string, domain: string }> | null, subscription?: { __typename?: 'WorkspaceSubscription', billingInterval: BillingInterval, currentBillingCycleEnd: string, seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, invitedTeam?: Array<{ __typename?: 'PendingWorkspaceCollaborator', id: string, role: string, email?: string | null }> | null, defaultRegion?: { __typename?: 'ServerRegionItem', id: string, name: string } | null }; export type WorkspaceProjectList_ProjectCollectionFragment = { __typename?: 'ProjectCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null }> }, workspace?: { __typename?: 'Workspace', id: string, readOnly: boolean, slug: string, name: string, logo?: string | null } | null, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }; @@ -6264,10 +6264,6 @@ export type LinkableCommentFragment = { __typename?: 'Comment', id: string, view export type UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragment = { __typename?: 'PendingWorkspaceCollaborator', id: string, token?: string | null, workspaceId: string, workspaceSlug: string, user?: { __typename?: 'LimitedUser', id: string } | null }; -export type WorkspaceMixpanelUpdateGroup_WorkspaceCollaboratorFragment = { __typename?: 'WorkspaceCollaborator', id: string, role: string }; - -export type WorkspaceMixpanelUpdateGroup_WorkspaceFragment = { __typename?: 'Workspace', id: string, name: string, description?: string | null, domainBasedMembershipProtectionEnabled: boolean, discoverabilityEnabled: boolean, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, name: WorkspacePlans, createdAt: string } | null, subscription?: { __typename?: 'WorkspaceSubscription', billingInterval: BillingInterval, currentBillingCycleEnd: string, seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', totalCount: number, items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, role: string }> }, defaultRegion?: { __typename?: 'ServerRegionItem', key: string } | null }; - export type OnWorkspaceProjectsUpdateSubscriptionVariables = Exact<{ slug: Scalars['String']['input']; }>; @@ -6391,7 +6387,7 @@ export type WorkspacePageQueryQueryVariables = Exact<{ }>; -export type WorkspacePageQueryQuery = { __typename?: 'Query', workspaceBySlug: { __typename?: 'Workspace', id: string, readOnly: boolean, name: string, slug: string, role?: string | null, description?: string | null, logo?: string | null, domainBasedMembershipProtectionEnabled: boolean, discoverabilityEnabled: boolean, projects: { __typename?: 'ProjectCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, modelCount: { __typename?: 'ModelCollection', totalCount: number }, versions: { __typename?: 'VersionCollection', totalCount: number }, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null }> }, workspace?: { __typename?: 'Workspace', id: string, readOnly: boolean, slug: string, name: string, logo?: string | null } | null, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean, state: {} } | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, createdAt: string, name: WorkspacePlans } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', totalCount: number, items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, role: string, user: { __typename?: 'LimitedUser', id: string, avatar?: string | null, name: string } }> }, adminWorkspacesJoinRequests?: { __typename?: 'WorkspaceJoinRequestCollection', totalCount: number } | null, domains?: Array<{ __typename?: 'WorkspaceDomain', id: string, domain: string }> | null, subscription?: { __typename?: 'WorkspaceSubscription', billingInterval: BillingInterval, currentBillingCycleEnd: string, seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, defaultRegion?: { __typename?: 'ServerRegionItem', key: string, id: string, name: string } | null, invitedTeam?: Array<{ __typename?: 'PendingWorkspaceCollaborator', id: string, role: string, email?: string | null }> | null }, workspaceInvite?: { __typename?: 'PendingWorkspaceCollaborator', id: string, workspaceId: string, workspaceName: string, token?: string | null, title: string, email?: string | null, workspaceSlug: string, invitedBy: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null }, user?: { __typename?: 'LimitedUser', id: string, avatar?: string | null, name: string } | null } | null }; +export type WorkspacePageQueryQuery = { __typename?: 'Query', workspaceBySlug: { __typename?: 'Workspace', id: string, readOnly: boolean, name: string, slug: string, role?: string | null, description?: string | null, logo?: string | null, domainBasedMembershipProtectionEnabled: boolean, projects: { __typename?: 'ProjectCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, modelCount: { __typename?: 'ModelCollection', totalCount: number }, versions: { __typename?: 'VersionCollection', totalCount: number }, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null }> }, workspace?: { __typename?: 'Workspace', id: string, readOnly: boolean, slug: string, name: string, logo?: string | null } | null, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean, state: {} } | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, createdAt: string, name: WorkspacePlans } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', totalCount: number, items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }, adminWorkspacesJoinRequests?: { __typename?: 'WorkspaceJoinRequestCollection', totalCount: number } | null, domains?: Array<{ __typename?: 'WorkspaceDomain', id: string, domain: string }> | null, subscription?: { __typename?: 'WorkspaceSubscription', billingInterval: BillingInterval, currentBillingCycleEnd: string, seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, invitedTeam?: Array<{ __typename?: 'PendingWorkspaceCollaborator', id: string, role: string, email?: string | null }> | null, defaultRegion?: { __typename?: 'ServerRegionItem', id: string, name: string } | null }, workspaceInvite?: { __typename?: 'PendingWorkspaceCollaborator', id: string, workspaceId: string, workspaceName: string, token?: string | null, title: string, email?: string | null, workspaceSlug: string, invitedBy: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null }, user?: { __typename?: 'LimitedUser', id: string, avatar?: string | null, name: string } | null } | null }; export type WorkspaceProjectsQueryQueryVariables = Exact<{ workspaceSlug: Scalars['String']['input']; @@ -6463,7 +6459,7 @@ export type OnWorkspaceUpdatedSubscriptionVariables = Exact<{ }>; -export type OnWorkspaceUpdatedSubscription = { __typename?: 'Subscription', workspaceUpdated: { __typename?: 'WorkspaceUpdatedMessage', id: string, workspace: { __typename?: 'Workspace', id: string, readOnly: boolean, name: string, slug: string, role?: string | null, description?: string | null, logo?: string | null, domainBasedMembershipProtectionEnabled: boolean, discoverabilityEnabled: boolean, projects: { __typename?: 'ProjectCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, modelCount: { __typename?: 'ModelCollection', totalCount: number }, versions: { __typename?: 'VersionCollection', totalCount: number }, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null }> }, workspace?: { __typename?: 'Workspace', id: string, readOnly: boolean, slug: string, name: string, logo?: string | null } | null, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean, state: {} } | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, createdAt: string, name: WorkspacePlans } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', totalCount: number, items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, role: string, user: { __typename?: 'LimitedUser', id: string, avatar?: string | null, name: string } }> }, adminWorkspacesJoinRequests?: { __typename?: 'WorkspaceJoinRequestCollection', totalCount: number } | null, domains?: Array<{ __typename?: 'WorkspaceDomain', id: string, domain: string }> | null, subscription?: { __typename?: 'WorkspaceSubscription', billingInterval: BillingInterval, currentBillingCycleEnd: string, seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, defaultRegion?: { __typename?: 'ServerRegionItem', key: string, id: string, name: string } | null, invitedTeam?: Array<{ __typename?: 'PendingWorkspaceCollaborator', id: string, role: string, email?: string | null }> | null } } }; +export type OnWorkspaceUpdatedSubscription = { __typename?: 'Subscription', workspaceUpdated: { __typename?: 'WorkspaceUpdatedMessage', id: string, workspace: { __typename?: 'Workspace', id: string, readOnly: boolean, name: string, slug: string, role?: string | null, description?: string | null, logo?: string | null, domainBasedMembershipProtectionEnabled: boolean, projects: { __typename?: 'ProjectCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, modelCount: { __typename?: 'ModelCollection', totalCount: number }, versions: { __typename?: 'VersionCollection', totalCount: number }, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationsStatus?: { __typename?: 'TriggeredAutomationsStatus', id: string, automationRuns: Array<{ __typename?: 'AutomateRun', id: string, functionRuns: Array<{ __typename?: 'AutomateFunctionRun', id: string, updatedAt: string, status: AutomateRunStatus, results?: {} | null, statusMessage?: string | null, contextView?: string | null, createdAt: string, function?: { __typename?: 'AutomateFunction', id: string, logo?: string | null, name: string } | null }>, automation: { __typename?: 'Automation', id: string, name: string } }> } | null }> }, workspace?: { __typename?: 'Workspace', id: string, readOnly: boolean, slug: string, name: string, logo?: string | null } | null, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean, state: {} } | null, plan?: { __typename?: 'WorkspacePlan', status: WorkspacePlanStatuses, createdAt: string, name: WorkspacePlans } | null, team: { __typename?: 'WorkspaceCollaboratorCollection', totalCount: number, items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }, adminWorkspacesJoinRequests?: { __typename?: 'WorkspaceJoinRequestCollection', totalCount: number } | null, domains?: Array<{ __typename?: 'WorkspaceDomain', id: string, domain: string }> | null, subscription?: { __typename?: 'WorkspaceSubscription', billingInterval: BillingInterval, currentBillingCycleEnd: string, seats: { __typename?: 'WorkspaceSubscriptionSeats', guest: number, plan: number } } | null, invitedTeam?: Array<{ __typename?: 'PendingWorkspaceCollaborator', id: string, role: string, email?: string | null }> | null, defaultRegion?: { __typename?: 'ServerRegionItem', id: string, name: string } | null } } }; export type LegacyBranchRedirectMetadataQueryVariables = Exact<{ streamId: Scalars['String']['input']; @@ -6674,11 +6670,9 @@ export const WorkspaceInvitedTeam_WorkspaceFragmentDoc = {"kind":"Document","def export const WorkspaceTeam_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"},"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":"invitedTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]} as unknown as DocumentNode; export const WorkspaceSecurity_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}}]}}]}}]} as unknown as DocumentNode; export const BillingAlert_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingAlert_Workspace"},"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":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}}]}}]}}]} as unknown as DocumentNode; -export const WorkspaceMixpanelUpdateGroup_WorkspaceCollaboratorFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]} as unknown as DocumentNode; -export const WorkspaceMixpanelUpdateGroup_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_Workspace"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}},{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]} as unknown as DocumentNode; export const MoveProjectsDialog_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"},"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":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"}}]}}]} as unknown as DocumentNode; export const WorkspaceProjectList_ProjectCollectionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}}]} as unknown as DocumentNode; -export const WorkspaceProjectList_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBase_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingAlert_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"}}]}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"},"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":"invitedTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"},"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":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceBase_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingAlert_Workspace"},"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":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_Workspace"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}},{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"},"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":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}}]} as unknown as DocumentNode; +export const WorkspaceProjectList_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBase_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingAlert_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"}}]}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"},"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":"invitedTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"},"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":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceBase_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingAlert_Workspace"},"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":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"},"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":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}}]} as unknown as DocumentNode; export const WorkspaceHeader_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceHeader_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBase_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingAlert_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"},"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":"invitedTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceBase_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingAlert_Workspace"},"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":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}}]}}]}}]} as unknown as DocumentNode; export const WorkspaceInviteBlock_PendingWorkspaceCollaboratorFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"},"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":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"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":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"},"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":"token"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceSlug"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const WorkspaceDashboardAbout_WorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceDashboardAbout_Workspace"},"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":"description"}}]}}]} as unknown as DocumentNode; @@ -6921,7 +6915,7 @@ export const WorkspaceUpdateDiscoverabilityMutationDocument = {"kind":"Document" export const ApproveWorkspaceJoinRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ApproveWorkspaceJoinRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ApproveWorkspaceJoinRequestInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceJoinRequestMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"approve"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode; export const DenyWorkspaceJoinRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DenyWorkspaceJoinRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DenyWorkspaceJoinRequestInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceJoinRequestMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deny"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode; export const WorkspaceAccessCheckDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceAccessCheck"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; -export const WorkspacePageQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspacePageQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaboratorsFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaceInvite"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}},{"kind":"Argument","name":{"kind":"Name","value":"options"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"useSlug"},"value":{"kind":"BooleanValue","value":true}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInviteBanner_PendingWorkspaceCollaborator"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceBase_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"},"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":"invitedTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingAlert_Workspace"},"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":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_Workspace"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}},{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"},"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":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"},"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":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"},"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":"token"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceSlug"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBase_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingAlert_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"}}]}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBanner_PendingWorkspaceCollaborator"},"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":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"},"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":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"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":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}}]} as unknown as DocumentNode; +export const WorkspacePageQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspacePageQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaboratorsFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaceInvite"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}},{"kind":"Argument","name":{"kind":"Name","value":"options"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"useSlug"},"value":{"kind":"BooleanValue","value":true}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInviteBanner_PendingWorkspaceCollaborator"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceBase_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"},"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":"invitedTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingAlert_Workspace"},"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":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"},"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":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"},"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":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"},"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":"token"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceSlug"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBase_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingAlert_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"}}]}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBanner_PendingWorkspaceCollaborator"},"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":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"},"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":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"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":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}}]} as unknown as DocumentNode; export const WorkspaceProjectsQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceProjectsQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceProjectsFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}}]} as unknown as DocumentNode; export const WorkspaceFunctionsQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceFunctionsQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionsPageHeader_Query"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"automateFunctions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateAutomationCreateDialog_AutomateFunction"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionTemplate"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"},"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":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction"},"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":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"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":"inputSchema"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionsPageHeader_Query"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Query"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"automateInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasAutomateGithubApp"}},{"kind":"Field","name":{"kind":"Name","value":"availableGithubOrgs"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"automate"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"availableFunctionTemplates"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateAutomationCreateDialog_AutomateFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsFunctionsCard_AutomateFunction"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction"}}]}}]} as unknown as DocumentNode; export const WorkspaceInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"options"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceInviteLookupOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceInvite"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}},{"kind":"Argument","name":{"kind":"Name","value":"options"},"value":{"kind":"Variable","name":{"kind":"Name","value":"options"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInviteBanner_PendingWorkspaceCollaborator"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"},"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":"token"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceSlug"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBanner_PendingWorkspaceCollaborator"},"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":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"},"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":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"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":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}}]} as unknown as DocumentNode; @@ -6931,7 +6925,7 @@ export const WorkspaceSsoByEmailDocument = {"kind":"Document","definitions":[{"k export const WorkspaceSsoCheckDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceSsoCheck"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceSsoStatus_Workspace"}}]}},{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceSsoStatus_User"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSsoStatus_Workspace"},"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":"sso"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"provider"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"clientId"}},{"kind":"Field","name":{"kind":"Name","value":"issuerUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"session"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"validUntil"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSsoStatus_User"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"expiredSsoSessions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}}]} as unknown as DocumentNode; export const WorkspaceWizardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceWizard"},"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":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceWizard_Workspace"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceWizard_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]} as unknown as DocumentNode; export const WorkspaceWizardRegionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceWizardRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceWizardStepRegion_ServerInfo"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SettingsWorkspacesRegionsSelect_ServerRegionItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerRegionItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceWizardStepRegion_ServerInfo"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"multiRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"SettingsWorkspacesRegionsSelect_ServerRegionItem"}}]}}]}}]}}]} as unknown as DocumentNode; -export const OnWorkspaceUpdatedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"onWorkspaceUpdated"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaboratorsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceUpdated"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"workspaceSlug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceBase_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"},"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":"invitedTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingAlert_Workspace"},"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":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_Workspace"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"discoverabilityEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}},{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"},"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":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"},"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":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBase_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingAlert_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceMixpanelUpdateGroup_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"}}]}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode; +export const OnWorkspaceUpdatedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"onWorkspaceUpdated"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaboratorsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceUpdated"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"workspaceSlug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceBase_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"},"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":"invitedTeam"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"invitesFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"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":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"adminWorkspacesJoinRequests"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInvitedTeam_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"},"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":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"domain"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingAlert_Workspace"},"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":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"billingInterval"}},{"kind":"Field","name":{"kind":"Name","value":"currentBillingCycleEnd"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"},"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":"defaultRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceHasCustomDataResidency_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsWorkspaceSelect_Workspace"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"},"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":"domainBasedMembershipProtectionEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"domains"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"domain"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seats"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"guest"}},{"kind":"Field","name":{"kind":"Name","value":"plan"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"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":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceBase_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceTeam_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceSecurity_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BillingAlert_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MoveProjectsDialog_Workspace"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"InviteDialogWorkspace_Workspace"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"}}]}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}},{"kind":"Field","name":{"kind":"Name","value":"state"}}]}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode; export const LegacyBranchRedirectMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LegacyBranchRedirectMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"branchName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelByName"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"branchName"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const LegacyViewerCommitRedirectMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LegacyViewerCommitRedirectMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"commitId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"commitId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const LegacyViewerStreamRedirectMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LegacyViewerStreamRedirectMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"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":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/frontend-2/lib/workspaces/composables/mixpanel.ts b/packages/frontend-2/lib/workspaces/composables/mixpanel.ts deleted file mode 100644 index 05556a6ef..000000000 --- a/packages/frontend-2/lib/workspaces/composables/mixpanel.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { useMixpanel } from '~/lib/core/composables/mp' -import { graphql } from '~~/lib/common/generated/gql' -import { - type WorkspaceMixpanelUpdateGroup_WorkspaceFragment, - type WorkspaceMixpanelUpdateGroup_WorkspaceCollaboratorFragment, - type PaidWorkspacePlans, - BillingInterval -} from '~/lib/common/generated/gql/graphql' -import { type MaybeNullOrUndefined, Roles, type WorkspaceRoles } from '@speckle/shared' -import { resolveMixpanelServerId } from '@speckle/shared' -import { WorkspacePlanStatuses } from '~/lib/common/generated/gql/graphql' -import { isPaidPlan } from '@/lib/billing/helpers/types' -import { pricingPlansConfig } from '~/lib/billing/helpers/constants' - -graphql(` - fragment WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator on WorkspaceCollaborator { - id - role - } -`) - -graphql(` - fragment WorkspaceMixpanelUpdateGroup_Workspace on Workspace { - id - name - description - domainBasedMembershipProtectionEnabled - discoverabilityEnabled - plan { - status - name - createdAt - } - subscription { - billingInterval - currentBillingCycleEnd - seats { - guest - plan - } - } - team { - totalCount - items { - ...WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator - } - } - defaultRegion { - key - } - } -`) - -export const useWorkspacesMixpanel = () => { - const mixpanel = useMixpanel() - const isBillingIntegrationEnabled = useIsBillingIntegrationEnabled() - - const workspaceMixpanelUpdateGroup = ( - workspace: WorkspaceMixpanelUpdateGroup_WorkspaceFragment, - userEmail: MaybeNullOrUndefined - ) => { - if (!workspace.id || !import.meta.client) return - - const getEstimatedBill = () => { - if ( - !isBillingIntegrationEnabled.value || - !isPaidPlan(workspace.plan?.name) || - workspace.plan?.status !== WorkspacePlanStatuses.Valid || - !workspace.subscription?.billingInterval - ) - return null - - const planConfig = - pricingPlansConfig.plans[workspace.plan.name as unknown as PaidWorkspacePlans] - const cost = planConfig.cost[workspace.subscription.billingInterval] - const memberPrice = cost[Roles.Workspace.Member] - const guestPrice = cost[Roles.Workspace.Guest] - const memberCount = workspace.subscription?.seats?.plan || 0 - const guestCount = workspace.subscription?.seats?.guest || 0 - const totalPrice = memberPrice * memberCount + guestPrice * guestCount - return workspace.subscription.billingInterval === BillingInterval.Yearly - ? totalPrice * 12 - : totalPrice - } - - const roleCount = { - [Roles.Workspace.Admin]: 0, - [Roles.Workspace.Member]: 0, - [Roles.Workspace.Guest]: 0 - } - - workspace.team.items.forEach( - (item: WorkspaceMixpanelUpdateGroup_WorkspaceCollaboratorFragment) => { - roleCount[item.role as WorkspaceRoles] = - (roleCount[item.role as WorkspaceRoles] ?? 0) + 1 - } - ) - - const input = { - name: workspace.name, - description: workspace.description, - domainBasedMembershipProtectionEnabled: - workspace.domainBasedMembershipProtectionEnabled, - discoverabilityEnabled: workspace.discoverabilityEnabled, - teamTotalCount: workspace.team.totalCount, - teamAdminCount: roleCount[Roles.Workspace.Admin], - teamMemberCount: roleCount[Roles.Workspace.Member], - teamGuestCount: roleCount[Roles.Workspace.Guest], - defaultRegionKey: workspace.defaultRegion?.key, - planName: workspace.plan?.name || '', - planStatus: workspace.plan?.status || '', - planCreatedAt: workspace.plan?.createdAt, - subscriptionBillingInterval: workspace.subscription?.billingInterval, - subscriptionCurrentBillingCycleEnd: - workspace.subscription?.currentBillingCycleEnd, - seats: workspace.subscription?.seats?.plan || 0, - seatsGuest: workspace.subscription?.seats?.guest || 0, - estimatedBill: getEstimatedBill(), - // eslint-disable-next-line camelcase - server_id: resolveMixpanelServerId(window.location.hostname) - } - - mixpanel.get_group('workspace_id', workspace.id).set(input) - - if (userEmail?.includes('speckle.systems')) { - mixpanel.get_group('workspace_id', workspace.id).set_once({ - hasSpeckleMembers: true - }) - } - } - - return { - workspaceMixpanelUpdateGroup - } -} diff --git a/packages/server/modules/cli/commands/workspaces/set-plan.ts b/packages/server/modules/cli/commands/workspaces/set-plan.ts index 534205304..d63f4e6b4 100644 --- a/packages/server/modules/cli/commands/workspaces/set-plan.ts +++ b/packages/server/modules/cli/commands/workspaces/set-plan.ts @@ -2,9 +2,11 @@ import { CommandModule } from 'yargs' import { cliLogger } from '@/logging/logging' import { getWorkspaceBySlugOrIdFactory } from '@/modules/workspaces/repositories/workspaces' import { db } from '@/db/knex' -import { PaidWorkspacePlanStatuses } from '@/modules/gatekeeper/domain/billing' import { upsertPaidWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing' -import { PaidWorkspacePlans } from '@/modules/gatekeeper/domain/workspacePricing' +import { + PaidWorkspacePlans, + PaidWorkspacePlanStatuses +} from '@/modules/gatekeeperCore/domain/billing' import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace' const command: CommandModule< diff --git a/packages/server/modules/core/repositories/userEmails.ts b/packages/server/modules/core/repositories/userEmails.ts index a3e09f61c..30e5b5c1c 100644 --- a/packages/server/modules/core/repositories/userEmails.ts +++ b/packages/server/modules/core/repositories/userEmails.ts @@ -141,12 +141,8 @@ export const findEmailFactory = export const findEmailsByUserIdFactory = ({ db }: { db: Knex }): FindEmailsByUserId => - async ({ userId }) => { - if (!userId) return [] - return db(UserEmails.name).where({ - [UserEmails.col.userId]: userId - }) - } + async ({ userId }) => + userId ? db(UserEmails.name).where({ userId }) : [] export const setPrimaryUserEmailFactory = ({ db }: { db: Knex }): SetPrimaryUserEmail => diff --git a/packages/server/modules/gatekeeper/clients/stripe.ts b/packages/server/modules/gatekeeper/clients/stripe.ts index d4decae0f..cd1f602f1 100644 --- a/packages/server/modules/gatekeeper/clients/stripe.ts +++ b/packages/server/modules/gatekeeper/clients/stripe.ts @@ -8,7 +8,7 @@ import { import { WorkspacePlanBillingIntervals, WorkspacePricingPlans -} from '@/modules/gatekeeper/domain/workspacePricing' +} from '@/modules/gatekeeperCore/domain/billing' import { EnvironmentResourceError, LogicError } from '@/modules/shared/errors' import { Stripe } from 'stripe' diff --git a/packages/server/modules/gatekeeper/domain/billing.ts b/packages/server/modules/gatekeeper/domain/billing.ts index 2d88b428f..d76568051 100644 --- a/packages/server/modules/gatekeeper/domain/billing.ts +++ b/packages/server/modules/gatekeeper/domain/billing.ts @@ -1,51 +1,15 @@ import { - TrialWorkspacePlans, + PaidWorkspacePlan, PaidWorkspacePlans, - UnpaidWorkspacePlans, + TrialWorkspacePlan, + UnpaidWorkspacePlan, + WorkspacePlan, WorkspacePlanBillingIntervals, WorkspacePricingPlans -} from '@/modules/gatekeeper/domain/workspacePricing' +} from '@/modules/gatekeeperCore/domain/billing' import { OverrideProperties } from 'type-fest' import { z } from 'zod' -export type UnpaidWorkspacePlanStatuses = 'valid' - -export type PaidWorkspacePlanStatuses = - | UnpaidWorkspacePlanStatuses - // | 'paymentNeeded' // unsure if this is needed - | 'paymentFailed' - | 'cancelationScheduled' - | 'canceled' - -export type TrialWorkspacePlanStatuses = 'trial' | 'expired' - -export type PlanStatuses = - | PaidWorkspacePlanStatuses - | TrialWorkspacePlanStatuses - | UnpaidWorkspacePlanStatuses - -type BaseWorkspacePlan = { - workspaceId: string - createdAt: Date -} - -export type PaidWorkspacePlan = BaseWorkspacePlan & { - name: PaidWorkspacePlans - status: PaidWorkspacePlanStatuses -} - -export type TrialWorkspacePlan = BaseWorkspacePlan & { - name: TrialWorkspacePlans - status: TrialWorkspacePlanStatuses -} - -export type UnpaidWorkspacePlan = BaseWorkspacePlan & { - name: UnpaidWorkspacePlans - status: UnpaidWorkspacePlanStatuses -} - -export type WorkspacePlan = PaidWorkspacePlan | TrialWorkspacePlan | UnpaidWorkspacePlan - export type GetWorkspacePlan = (args: { workspaceId: string }) => Promise diff --git a/packages/server/modules/gatekeeper/domain/operations.ts b/packages/server/modules/gatekeeper/domain/operations.ts index 6d19a84a4..80139ead6 100644 --- a/packages/server/modules/gatekeeper/domain/operations.ts +++ b/packages/server/modules/gatekeeper/domain/operations.ts @@ -1,8 +1,9 @@ -import { PlanStatuses, WorkspacePlan } from '@/modules/gatekeeper/domain/billing' +import { WorkspaceFeatureName } from '@/modules/gatekeeper/domain/workspacePricing' import { - WorkspaceFeatureName, + PlanStatuses, + WorkspacePlan, WorkspacePlans -} from '@/modules/gatekeeper/domain/workspacePricing' +} from '@/modules/gatekeeperCore/domain/billing' import { Workspace } from '@/modules/workspacesCore/domain/types' export type CanWorkspaceAccessFeature = (args: { diff --git a/packages/server/modules/gatekeeper/domain/workspacePricing.ts b/packages/server/modules/gatekeeper/domain/workspacePricing.ts index b75c26b25..6b29861ab 100644 --- a/packages/server/modules/gatekeeper/domain/workspacePricing.ts +++ b/packages/server/modules/gatekeeper/domain/workspacePricing.ts @@ -1,4 +1,8 @@ -import { z } from 'zod' +import { + PaidWorkspacePlans, + UnpaidWorkspacePlans, + WorkspacePlans +} from '@/modules/gatekeeperCore/domain/billing' import type { MaybeNullOrUndefined } from '@speckle/shared' export type WorkspaceFeatureName = @@ -73,47 +77,6 @@ const baseFeatures = { workspace: true } -// team -export const trialWorkspacePlans = z.literal('starter') - -export type TrialWorkspacePlans = z.infer - -export const paidWorkspacePlans = z.union([ - trialWorkspacePlans, - // pro - z.literal('plus'), - z.literal('business') -]) - -export type PaidWorkspacePlans = z.infer - -// these are not publicly exposed for general use on billing enabled servers -export const unpaidWorkspacePlans = z.union([ - z.literal('unlimited'), - z.literal('academia'), - z.literal('starterInvoiced'), - z.literal('plusInvoiced'), - z.literal('businessInvoiced') -]) - -export type UnpaidWorkspacePlans = z.infer - -export const workspacePlans = z.union([paidWorkspacePlans, unpaidWorkspacePlans]) - -// this includes the plans your workspace can be on -export type WorkspacePlans = z.infer - -// this includes the pricing plans a customer can sub to -export type WorkspacePricingPlans = PaidWorkspacePlans | 'guest' - -export const workspacePlanBillingIntervals = z.union([ - z.literal('monthly'), - z.literal('yearly') -]) -export type WorkspacePlanBillingIntervals = z.infer< - typeof workspacePlanBillingIntervals -> - const starter: WorkspacePlanFeaturesAndLimits = { ...baseFeatures, name: 'starter', diff --git a/packages/server/modules/gatekeeper/repositories/billing.ts b/packages/server/modules/gatekeeper/repositories/billing.ts index ec76b42cc..1cc772846 100644 --- a/packages/server/modules/gatekeeper/repositories/billing.ts +++ b/packages/server/modules/gatekeeper/repositories/billing.ts @@ -8,7 +8,6 @@ import { UpsertWorkspacePlan, UpsertWorkspaceSubscription, WorkspaceSubscription, - WorkspacePlan, UpsertPaidWorkspacePlan, DeleteCheckoutSession, GetWorkspaceCheckoutSession, @@ -23,6 +22,7 @@ import { GetWorkspacesByPlanDaysTillExpiry, GetWorkspacePlanByProjectId } from '@/modules/gatekeeper/domain/operations' +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' import { Workspace } from '@/modules/workspacesCore/domain/types' import { Workspaces } from '@/modules/workspacesCore/helpers/db' import { Knex } from 'knex' @@ -47,7 +47,7 @@ export const getWorkspacePlanFactory = return workspacePlan ?? null } -const upsertWorkspacePlanFactory = +export const upsertWorkspacePlanFactory = ({ db }: { db: Knex }): UpsertWorkspacePlan => async ({ workspacePlan }) => { await tables diff --git a/packages/server/modules/gatekeeper/rest/billing.ts b/packages/server/modules/gatekeeper/rest/billing.ts index a042d9c12..5aefe102e 100644 --- a/packages/server/modules/gatekeeper/rest/billing.ts +++ b/packages/server/modules/gatekeeper/rest/billing.ts @@ -21,6 +21,7 @@ import { WorkspaceAlreadyPaidError } from '@/modules/gatekeeper/errors/billing' import { withTransaction } from '@/modules/shared/helpers/dbHelper' import { getStripeClient } from '@/modules/gatekeeper/stripe' import { handleSubscriptionUpdateFactory } from '@/modules/gatekeeper/services/subscriptions' +import { getEventBus } from '@/modules/shared/services/eventBus' export const getBillingRouter = (): Router => { const router = Router() @@ -96,7 +97,8 @@ export const getBillingRouter = (): Router => { }), getSubscriptionData: getSubscriptionDataFactory({ stripe - }) + }), + emitEvent: getEventBus().emit }) try { diff --git a/packages/server/modules/gatekeeper/services/checkout.ts b/packages/server/modules/gatekeeper/services/checkout.ts index a5bd6f377..4ea57a446 100644 --- a/packages/server/modules/gatekeeper/services/checkout.ts +++ b/packages/server/modules/gatekeeper/services/checkout.ts @@ -11,15 +11,16 @@ import { GetWorkspaceCheckoutSession, DeleteCheckoutSession } from '@/modules/gatekeeper/domain/billing' -import { - PaidWorkspacePlans, - WorkspacePlanBillingIntervals -} from '@/modules/gatekeeper/domain/workspacePricing' import { CheckoutSessionNotFoundError, WorkspaceAlreadyPaidError, WorkspaceCheckoutSessionInProgressError } from '@/modules/gatekeeper/errors/billing' +import { + PaidWorkspacePlans, + WorkspacePlanBillingIntervals +} from '@/modules/gatekeeperCore/domain/billing' +import { EventBusEmit } from '@/modules/shared/services/eventBus' import { CountWorkspaceRoleWithOptionalProjectRole } from '@/modules/workspaces/domain/operations' import { Roles, throwUncoveredError } from '@speckle/shared' @@ -135,13 +136,15 @@ export const completeCheckoutSessionFactory = updateCheckoutSessionStatus, upsertWorkspaceSubscription, upsertPaidWorkspacePlan, - getSubscriptionData + getSubscriptionData, + emitEvent }: { getCheckoutSession: GetCheckoutSession updateCheckoutSessionStatus: UpdateCheckoutSessionStatus upsertWorkspaceSubscription: UpsertWorkspaceSubscription upsertPaidWorkspacePlan: UpsertPaidWorkspacePlan getSubscriptionData: GetSubscriptionData + emitEvent: EventBusEmit }) => /** * Complete a paid checkout session @@ -169,13 +172,14 @@ export const completeCheckoutSessionFactory = await updateCheckoutSessionStatus({ sessionId, paymentStatus: 'paid' }) // a plan determines the workspace feature set + const workspacePlan = { + createdAt: new Date(), + workspaceId: checkoutSession.workspaceId, + name: checkoutSession.workspacePlan, + status: 'valid' + } as const await upsertPaidWorkspacePlan({ - workspacePlan: { - createdAt: new Date(), - workspaceId: checkoutSession.workspaceId, - name: checkoutSession.workspacePlan, - status: 'valid' - } + workspacePlan }) const subscriptionData = await getSubscriptionData({ subscriptionId @@ -205,4 +209,14 @@ export const completeCheckoutSessionFactory = await upsertWorkspaceSubscription({ workspaceSubscription }) + await emitEvent({ + eventName: 'gatekeeper.workspace-plan-updated', + payload: { + workspacePlan: { + workspaceId: workspacePlan.workspaceId, + status: workspacePlan.status, + name: workspacePlan.name + } + } + }) } diff --git a/packages/server/modules/gatekeeper/services/readOnly.ts b/packages/server/modules/gatekeeper/services/readOnly.ts index eda0359e2..827f8c12d 100644 --- a/packages/server/modules/gatekeeper/services/readOnly.ts +++ b/packages/server/modules/gatekeeper/services/readOnly.ts @@ -1,5 +1,6 @@ -import { GetWorkspacePlan, WorkspacePlan } from '@/modules/gatekeeper/domain/billing' +import { GetWorkspacePlan } from '@/modules/gatekeeper/domain/billing' import { GetWorkspacePlanByProjectId } from '@/modules/gatekeeper/domain/operations' +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' import { Workspace } from '@/modules/workspacesCore/domain/types' import { throwUncoveredError } from '@speckle/shared' diff --git a/packages/server/modules/gatekeeper/services/subscriptions.ts b/packages/server/modules/gatekeeper/services/subscriptions.ts index ba1daf151..eb2e7f9ca 100644 --- a/packages/server/modules/gatekeeper/services/subscriptions.ts +++ b/packages/server/modules/gatekeeper/services/subscriptions.ts @@ -6,7 +6,6 @@ import { GetWorkspaceSubscription, GetWorkspaceSubscriptionBySubscriptionId, GetWorkspaceSubscriptions, - PaidWorkspacePlanStatuses, ReconcileSubscriptionData, SubscriptionData, SubscriptionDataInput, @@ -14,11 +13,6 @@ import { UpsertWorkspaceSubscription, WorkspaceSubscription } from '@/modules/gatekeeper/domain/billing' -import { - PaidWorkspacePlans, - WorkspacePlanBillingIntervals, - WorkspacePricingPlans -} from '@/modules/gatekeeper/domain/workspacePricing' import { WorkspaceNotPaidPlanError, WorkspacePlanDowngradeError, @@ -26,6 +20,12 @@ import { WorkspacePlanNotFoundError, WorkspaceSubscriptionNotFoundError } from '@/modules/gatekeeper/errors/billing' +import { + PaidWorkspacePlans, + PaidWorkspacePlanStatuses, + WorkspacePlanBillingIntervals, + WorkspacePricingPlans +} from '@/modules/gatekeeperCore/domain/billing' import { LogicError } from '@/modules/shared/errors' import { CountWorkspaceRoleWithOptionalProjectRole } from '@/modules/workspaces/domain/operations' import { throwUncoveredError, WorkspaceRoles } from '@speckle/shared' diff --git a/packages/server/modules/gatekeeper/services/workspacePlans.ts b/packages/server/modules/gatekeeper/services/workspacePlans.ts new file mode 100644 index 000000000..06915d3b1 --- /dev/null +++ b/packages/server/modules/gatekeeper/services/workspacePlans.ts @@ -0,0 +1,95 @@ +import { UpsertWorkspacePlan } from '@/modules/gatekeeper/domain/billing' +import { InvalidWorkspacePlanStatus } from '@/modules/gatekeeper/errors/billing' +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' +import { EventBusEmit } from '@/modules/shared/services/eventBus' +import { GetWorkspace } from '@/modules/workspaces/domain/operations' +import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace' +import { throwUncoveredError } from '@speckle/shared' + +export const updateWorkspacePlanFactory = + ({ + getWorkspace, + upsertWorkspacePlan, + emitEvent + }: { + getWorkspace: GetWorkspace + // im using the generic function here, cause the service is + // responsible for protecting the permutations + upsertWorkspacePlan: UpsertWorkspacePlan + emitEvent: EventBusEmit + }) => + async ({ + workspaceId, + name, + status + }: Pick): Promise => { + const workspace = await getWorkspace({ + workspaceId + }) + if (!workspace) throw new WorkspaceNotFoundError() + const createdAt = new Date() + switch (name) { + case 'starter': + switch (status) { + case 'trial': + case 'expired': + case 'valid': + case 'cancelationScheduled': + case 'canceled': + case 'paymentFailed': + await upsertWorkspacePlan({ + workspacePlan: { workspaceId, status, name, createdAt } + }) + break + default: + throwUncoveredError(status) + } + break + case 'business': + case 'plus': + switch (status) { + case 'trial': + case 'expired': + throw new InvalidWorkspacePlanStatus() + case 'valid': + case 'cancelationScheduled': + case 'canceled': + case 'paymentFailed': + await upsertWorkspacePlan({ + workspacePlan: { workspaceId, status, name, createdAt } + }) + break + default: + throwUncoveredError(status) + } + break + + case 'academia': + case 'unlimited': + case 'starterInvoiced': + case 'plusInvoiced': + case 'businessInvoiced': + switch (status) { + case 'valid': + await upsertWorkspacePlan({ + workspacePlan: { workspaceId, status, name, createdAt } + }) + break + case 'cancelationScheduled': + case 'canceled': + case 'expired': + case 'paymentFailed': + case 'trial': + throw new InvalidWorkspacePlanStatus() + default: + throwUncoveredError(status) + } + break + default: + throwUncoveredError(name) + } + await emitEvent({ + eventName: 'gatekeeper.workspace-plan-updated', + payload: { workspacePlan: { name, status, workspaceId } } + }) + } diff --git a/packages/server/modules/gatekeeper/stripe.ts b/packages/server/modules/gatekeeper/stripe.ts index 694e03048..c753c8fac 100644 --- a/packages/server/modules/gatekeeper/stripe.ts +++ b/packages/server/modules/gatekeeper/stripe.ts @@ -5,7 +5,7 @@ import { import { WorkspacePlanBillingIntervals, WorkspacePricingPlans -} from '@/modules/gatekeeper/domain/workspacePricing' +} from '@/modules/gatekeeperCore/domain/billing' import { getStringFromEnv, getStripeApiKey } from '@/modules/shared/helpers/envHelper' import { Stripe } from 'stripe' diff --git a/packages/server/modules/gatekeeper/tests/unit/checkout.spec.ts b/packages/server/modules/gatekeeper/tests/unit/checkout.spec.ts index 86de6ece3..eb6879f22 100644 --- a/packages/server/modules/gatekeeper/tests/unit/checkout.spec.ts +++ b/packages/server/modules/gatekeeper/tests/unit/checkout.spec.ts @@ -12,15 +12,15 @@ import { expect } from 'chai' import cryptoRandomString from 'crypto-random-string' import { CheckoutSession, - PaidWorkspacePlan, SubscriptionData, WorkspaceSubscription } from '@/modules/gatekeeper/domain/billing' +import { omit } from 'lodash' import { + PaidWorkspacePlan, PaidWorkspacePlans, WorkspacePlanBillingIntervals -} from '@/modules/gatekeeper/domain/workspacePricing' -import { omit } from 'lodash' +} from '@/modules/gatekeeperCore/domain/billing' describe('checkout @gatekeeper', () => { describe('startCheckoutSessionFactory creates a function, that', () => { @@ -509,6 +509,9 @@ describe('checkout @gatekeeper', () => { getSubscriptionData: async () => { expect.fail() }, + emitEvent: async () => { + expect.fail() + }, upsertWorkspaceSubscription: async () => { expect.fail() } @@ -541,6 +544,9 @@ describe('checkout @gatekeeper', () => { getSubscriptionData: async () => { expect.fail() }, + emitEvent: async () => { + expect.fail() + }, upsertWorkspaceSubscription: async () => { expect.fail() } @@ -585,6 +591,10 @@ describe('checkout @gatekeeper', () => { let storedWorkspaceSubscriptionData: WorkspaceSubscription | undefined = undefined + let emittedEventName: string | undefined = undefined + + let eventWorkspacePlan: unknown + await completeCheckoutSessionFactory({ getCheckoutSession: async () => storedCheckoutSession, updateCheckoutSessionStatus: async ({ paymentStatus }) => { @@ -596,6 +606,10 @@ describe('checkout @gatekeeper', () => { getSubscriptionData: async () => subscriptionData, upsertWorkspaceSubscription: async ({ workspaceSubscription }) => { storedWorkspaceSubscriptionData = workspaceSubscription + }, + emitEvent: async ({ eventName, payload }) => { + emittedEventName = eventName + eventWorkspacePlan = payload } })({ sessionId, subscriptionId }) @@ -605,6 +619,14 @@ describe('checkout @gatekeeper', () => { name: storedCheckoutSession.workspacePlan, status: 'valid' }) + expect(emittedEventName).to.equal('gatekeeper.workspace-plan-updated') + expect(eventWorkspacePlan).to.deep.equal({ + workspacePlan: { + workspaceId, + name: storedCheckoutSession.workspacePlan, + status: 'valid' + } + }) expect(storedWorkspaceSubscriptionData!.billingInterval).to.equal( storedCheckoutSession.billingInterval ) diff --git a/packages/server/modules/gatekeeper/tests/unit/featureAuthorization.spec.ts b/packages/server/modules/gatekeeper/tests/unit/featureAuthorization.spec.ts index a64890998..b32a3c597 100644 --- a/packages/server/modules/gatekeeper/tests/unit/featureAuthorization.spec.ts +++ b/packages/server/modules/gatekeeper/tests/unit/featureAuthorization.spec.ts @@ -1,5 +1,5 @@ -import { WorkspacePlan } from '@/modules/gatekeeper/domain/billing' import { canWorkspaceAccessFeatureFactory } from '@/modules/gatekeeper/services/featureAuthorization' +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' import { expect } from 'chai' import cryptoRandomString from 'crypto-random-string' diff --git a/packages/server/modules/gatekeeper/tests/unit/subscriptions.spec.ts b/packages/server/modules/gatekeeper/tests/unit/subscriptions.spec.ts index 795ab5a07..9cddbb4ef 100644 --- a/packages/server/modules/gatekeeper/tests/unit/subscriptions.spec.ts +++ b/packages/server/modules/gatekeeper/tests/unit/subscriptions.spec.ts @@ -2,7 +2,6 @@ import { logger } from '@/logging/logging' import { SubscriptionData, SubscriptionDataInput, - WorkspacePlan, WorkspaceSubscription } from '@/modules/gatekeeper/domain/billing' import { @@ -23,6 +22,7 @@ import { createTestSubscriptionData, createTestWorkspaceSubscription } from '@/modules/gatekeeper/tests/helpers' +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' import { expectToThrow } from '@/test/assertionHelper' import { throwUncoveredError } from '@speckle/shared' import { expect } from 'chai' diff --git a/packages/server/modules/gatekeeper/tests/unit/workspacePlans.spec.ts b/packages/server/modules/gatekeeper/tests/unit/workspacePlans.spec.ts new file mode 100644 index 000000000..97dd5f897 --- /dev/null +++ b/packages/server/modules/gatekeeper/tests/unit/workspacePlans.spec.ts @@ -0,0 +1,201 @@ +import { InvalidWorkspacePlanStatus } from '@/modules/gatekeeper/errors/billing' +import { updateWorkspacePlanFactory } from '@/modules/gatekeeper/services/workspacePlans' +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' +import { EventBusEmit } from '@/modules/shared/services/eventBus' +import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace' +import { WorkspaceWithOptionalRole } from '@/modules/workspacesCore/domain/types' +import { expectToThrow } from '@/test/assertionHelper' +import { expect } from 'chai' +import cryptoRandomString from 'crypto-random-string' +import { omit } from 'lodash' + +describe('workspacePlan services @gatekeeper', () => { + describe('updateWorkspacePlanFactory creates a function, that', () => { + it('throws if the workspace is not found', async () => { + const updateWorkspacePlan = updateWorkspacePlanFactory({ + getWorkspace: async () => null, + upsertWorkspacePlan: () => { + expect.fail() + }, + emitEvent: () => { + expect.fail() + } + }) + const err = await expectToThrow(async () => { + await updateWorkspacePlan({ + workspaceId: cryptoRandomString({ length: 10 }), + name: 'business', + status: 'expired' + }) + }) + expect(err.message).to.equal(new WorkspaceNotFoundError().message) + }) + const uncoveredErrorMessage = 'Uncovered error case' + const invalidPlanMessage = new InvalidWorkspacePlanStatus().message + ;( + [ + { planName: 'foobar', cases: [['trial', uncoveredErrorMessage]] }, + { + planName: 'starter', + cases: [ + ['trial', null], + ['expired', null], + ['valid', null], + ['cancelationScheduled', null], + ['canceled', null], + ['paymentFailed', null], + ['foobar', uncoveredErrorMessage] + ] + }, + { + planName: 'business', + cases: [ + ['trial', invalidPlanMessage], + ['expired', invalidPlanMessage], + ['valid', null], + ['cancelationScheduled', null], + ['canceled', null], + ['paymentFailed', null], + ['foobar', uncoveredErrorMessage] + ] + }, + { + planName: 'plus', + cases: [ + ['trial', invalidPlanMessage], + ['expired', invalidPlanMessage], + ['valid', null], + ['cancelationScheduled', null], + ['canceled', null], + ['paymentFailed', null], + ['foobar', uncoveredErrorMessage] + ] + }, + { + planName: 'academia', + cases: [ + ['valid', null], + ['trial', invalidPlanMessage], + ['expired', invalidPlanMessage], + ['cancelationScheduled', invalidPlanMessage], + ['canceled', invalidPlanMessage], + ['paymentFailed', invalidPlanMessage], + ['foobar', uncoveredErrorMessage] + ] + }, + { + planName: 'unlimited', + cases: [ + ['valid', null], + ['trial', invalidPlanMessage], + ['expired', invalidPlanMessage], + ['cancelationScheduled', invalidPlanMessage], + ['canceled', invalidPlanMessage], + ['paymentFailed', invalidPlanMessage], + ['foobar', uncoveredErrorMessage] + ] + }, + { + planName: 'starterInvoiced', + cases: [ + ['valid', null], + ['trial', invalidPlanMessage], + ['expired', invalidPlanMessage], + ['cancelationScheduled', invalidPlanMessage], + ['canceled', invalidPlanMessage], + ['paymentFailed', invalidPlanMessage], + ['foobar', uncoveredErrorMessage] + ] + }, + { + planName: 'plusInvoiced', + cases: [ + ['valid', null], + ['trial', invalidPlanMessage], + ['expired', invalidPlanMessage], + ['cancelationScheduled', invalidPlanMessage], + ['canceled', invalidPlanMessage], + ['paymentFailed', invalidPlanMessage], + ['foobar', uncoveredErrorMessage] + ] + }, + { + planName: 'businessInvoiced', + cases: [ + ['valid', null], + ['trial', invalidPlanMessage], + ['expired', invalidPlanMessage], + ['cancelationScheduled', invalidPlanMessage], + ['canceled', invalidPlanMessage], + ['paymentFailed', invalidPlanMessage], + ['foobar', uncoveredErrorMessage] + ] + } + ] as const + ).forEach(({ planName, cases }) => { + return cases.forEach(([status, expectedErrorMessage]) => + it(`${ + expectedErrorMessage ? 'Throws an error' : 'Succeeds' + } when changing to plan ${planName} with status ${status}`, async () => { + const fail = () => { + expect.fail() + } + const workspaceId = cryptoRandomString({ length: 10 }) + if (expectedErrorMessage) { + const err = await expectToThrow(async () => { + const updateWorkspacePlan = updateWorkspacePlanFactory({ + getWorkspace: async () => { + return { id: workspaceId } as WorkspaceWithOptionalRole + }, + upsertWorkspacePlan: fail, + emitEvent: fail + }) + await updateWorkspacePlan({ + workspaceId, + //@ts-expect-error we need to test the runtime error checks too + name: planName, + //@ts-expect-error we need to test the runtime error checks too + status + }) + }) + expect(err.message.startsWith(expectedErrorMessage)).to.be.true + } else { + let storedWorkspacePlan: WorkspacePlan | undefined = undefined + let emittedEventName: string | undefined = undefined + let eventPayload: unknown = undefined + + const upsertWorkspacePlan = async ({ + workspacePlan + }: { + workspacePlan: WorkspacePlan + }) => { + storedWorkspacePlan = workspacePlan + } + const emitEvent: EventBusEmit = async ({ eventName, payload }) => { + emittedEventName = eventName + eventPayload = payload + } + const updateWorkspacePlan = updateWorkspacePlanFactory({ + getWorkspace: async () => { + return { id: workspaceId } as WorkspaceWithOptionalRole + }, + upsertWorkspacePlan, + emitEvent + }) + await updateWorkspacePlan({ + workspaceId, + //@ts-expect-error we need to test the runtime error checks too + name: planName, + //@ts-expect-error we need to test the runtime error checks too + status + }) + const expectedPlan = { workspaceId, name: planName, status } + expect(omit(storedWorkspacePlan, 'createdAt')).to.deep.equal(expectedPlan) + expect(emittedEventName).to.equal('gatekeeper.workspace-plan-updated') + expect(eventPayload).to.deep.equal({ workspacePlan: expectedPlan }) + } + }) + ) + }) + }) +}) diff --git a/packages/server/modules/gatekeeperCore/domain/billing.ts b/packages/server/modules/gatekeeperCore/domain/billing.ts new file mode 100644 index 000000000..422e2d2bb --- /dev/null +++ b/packages/server/modules/gatekeeperCore/domain/billing.ts @@ -0,0 +1,86 @@ +import { z } from 'zod' + +// team +export const trialWorkspacePlans = z.literal('starter') + +export type TrialWorkspacePlans = z.infer + +export const paidWorkspacePlans = z.union([ + trialWorkspacePlans, + // pro + z.literal('plus'), + z.literal('business') +]) + +export type PaidWorkspacePlans = z.infer + +// these are not publicly exposed for general use on billing enabled servers +export const unpaidWorkspacePlans = z.union([ + z.literal('unlimited'), + z.literal('academia'), + z.literal('starterInvoiced'), + z.literal('plusInvoiced'), + z.literal('businessInvoiced') +]) + +// export const freeWorkspacePlans = z + +// export const newPaidWorkspacePlans = z.union([ +// z.literal('starter2'), +// z.literal('business2') +// ]) + +export type UnpaidWorkspacePlans = z.infer + +export const workspacePlans = z.union([paidWorkspacePlans, unpaidWorkspacePlans]) + +// this includes the plans your workspace can be on +export type WorkspacePlans = z.infer + +// this includes the pricing plans a customer can sub to +export type WorkspacePricingPlans = PaidWorkspacePlans | 'guest' + +export const workspacePlanBillingIntervals = z.union([ + z.literal('monthly'), + z.literal('yearly') +]) +export type WorkspacePlanBillingIntervals = z.infer< + typeof workspacePlanBillingIntervals +> + +export type UnpaidWorkspacePlanStatuses = 'valid' + +export type PaidWorkspacePlanStatuses = + | UnpaidWorkspacePlanStatuses + // | 'paymentNeeded' // unsure if this is needed + | 'paymentFailed' + | 'cancelationScheduled' + | 'canceled' + +export type TrialWorkspacePlanStatuses = 'trial' | 'expired' + +export type PlanStatuses = + | PaidWorkspacePlanStatuses + | TrialWorkspacePlanStatuses + | UnpaidWorkspacePlanStatuses + +type BaseWorkspacePlan = { + workspaceId: string + createdAt: Date +} + +export type PaidWorkspacePlan = BaseWorkspacePlan & { + name: PaidWorkspacePlans + status: PaidWorkspacePlanStatuses +} + +export type TrialWorkspacePlan = BaseWorkspacePlan & { + name: TrialWorkspacePlans + status: TrialWorkspacePlanStatuses +} + +export type UnpaidWorkspacePlan = BaseWorkspacePlan & { + name: UnpaidWorkspacePlans + status: UnpaidWorkspacePlanStatuses +} +export type WorkspacePlan = PaidWorkspacePlan | TrialWorkspacePlan | UnpaidWorkspacePlan diff --git a/packages/server/modules/gatekeeperCore/domain/events.ts b/packages/server/modules/gatekeeperCore/domain/events.ts index 4f7511bdc..b9da15e84 100644 --- a/packages/server/modules/gatekeeperCore/domain/events.ts +++ b/packages/server/modules/gatekeeperCore/domain/events.ts @@ -1,11 +1,17 @@ +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' + export const gatekeeperEventNamespace = 'gatekeeper' as const const eventPrefix = `${gatekeeperEventNamespace}.` as const export const GatekeeperEvents = { - WorkspaceTrialExpired: `${eventPrefix}workspace-trial-expired` + WorkspaceTrialExpired: `${eventPrefix}workspace-trial-expired`, + WorkspacePlanUpdated: `${eventPrefix}workspace-plan-updated` } as const export type GatekeeperEventPayloads = { [GatekeeperEvents.WorkspaceTrialExpired]: { workspaceId: string } + [GatekeeperEvents.WorkspacePlanUpdated]: { + workspacePlan: Pick + } } diff --git a/packages/server/modules/index.ts b/packages/server/modules/index.ts index 1d5a2b325..bc002f0a1 100644 --- a/packages/server/modules/index.ts +++ b/packages/server/modules/index.ts @@ -84,8 +84,9 @@ const getEnabledModuleNames = () => { if (FF_AUTOMATE_MODULE_ENABLED) moduleNames.push('automate') if (FF_GENDOAI_MODULE_ENABLED) moduleNames.push('gendo') - if (FF_WORKSPACES_MODULE_ENABLED) moduleNames.push('workspaces') + // the order of the event listeners matters if (FF_GATEKEEPER_MODULE_ENABLED) moduleNames.push('gatekeeper') + if (FF_WORKSPACES_MODULE_ENABLED) moduleNames.push('workspaces') return moduleNames } diff --git a/packages/server/modules/shared/helpers/envHelper.ts b/packages/server/modules/shared/helpers/envHelper.ts index 04a521883..d7198e3f3 100644 --- a/packages/server/modules/shared/helpers/envHelper.ts +++ b/packages/server/modules/shared/helpers/envHelper.ts @@ -206,7 +206,7 @@ export function getServerOrigin() { const err = ensureError(e) if (e instanceof TypeError && e.message === 'Invalid URL') { throw new MisconfiguredEnvironmentError( - `Server origin environment variable (CANONICAL_URL) is not a valid URL: ${err.message}`, + `Server origin environment variable (CANONICAL_URL) is not a valid URL: ${process.env.CANONICAL_URL} ${err.message}`, { cause: e, info: { diff --git a/packages/server/modules/shared/utils/mixpanel.ts b/packages/server/modules/shared/utils/mixpanel.ts index c231afef1..4b722ea20 100644 --- a/packages/server/modules/shared/utils/mixpanel.ts +++ b/packages/server/modules/shared/utils/mixpanel.ts @@ -13,7 +13,7 @@ import { mixpanelLogger } from '@/logging/logging' let client: Optional = undefined let baseTrackingProperties: Optional> = undefined -function getBaseTrackingProperties() { +export function getBaseTrackingProperties() { if (baseTrackingProperties) return baseTrackingProperties baseTrackingProperties = MixpanelUtils.buildBasePropertiesPayload({ hostApp: 'serverside', diff --git a/packages/server/modules/workspaces/domain/constants.ts b/packages/server/modules/workspaces/domain/constants.ts deleted file mode 100644 index 987f2685d..000000000 --- a/packages/server/modules/workspaces/domain/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants' - -export const WorkspaceEarlyAdopterDiscount = { - name: '50% Early Adopter Discount', - amount: 0.5 -} diff --git a/packages/server/modules/workspaces/domain/operations.ts b/packages/server/modules/workspaces/domain/operations.ts index 66f0983c3..7f8de1419 100644 --- a/packages/server/modules/workspaces/domain/operations.ts +++ b/packages/server/modules/workspaces/domain/operations.ts @@ -68,7 +68,7 @@ export type GetWorkspaceBySlugOrId = (args: { }) => Promise export type GetWorkspaces = (args: { - workspaceIds: string[] + workspaceIds?: string[] userId?: string }) => Promise diff --git a/packages/server/modules/workspaces/domain/types.ts b/packages/server/modules/workspaces/domain/types.ts index d2311646b..4889904e3 100644 --- a/packages/server/modules/workspaces/domain/types.ts +++ b/packages/server/modules/workspaces/domain/types.ts @@ -1,6 +1,6 @@ export { WorkspaceInviteResourceTarget } from '@/modules/workspacesCore/domain/types' import { LimitedUserRecord, UserWithRole } from '@/modules/core/helpers/types' -import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants' +import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants' import { StreamRoles, WorkspaceRoles } from '@speckle/shared' declare module '@/modules/serverinvites/domain/types' { diff --git a/packages/server/modules/workspaces/events/eventListener.ts b/packages/server/modules/workspaces/events/eventListener.ts index 0154542c2..95363e54e 100644 --- a/packages/server/modules/workspaces/events/eventListener.ts +++ b/packages/server/modules/workspaces/events/eventListener.ts @@ -5,6 +5,8 @@ import { upsertProjectRoleFactory } from '@/modules/core/repositories/streams' import { + CountWorkspaceRoleWithOptionalProjectRole, + GetDefaultRegion, GetWorkspace, GetWorkspaceRoleForUser, GetWorkspaceRoles, @@ -22,8 +24,8 @@ import { import { logger, moduleLogger } from '@/logging/logging' import { updateWorkspaceRoleFactory } from '@/modules/workspaces/services/management' import { EventPayload, getEventBus } from '@/modules/shared/services/eventBus' -import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants' -import { Roles, WorkspaceRoles } from '@speckle/shared' +import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants' +import { Roles, throwUncoveredError, WorkspaceRoles } from '@speckle/shared' import { DeleteProjectRole, UpsertProjectRole @@ -31,6 +33,7 @@ import { import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events' import { Knex } from 'knex' import { + countWorkspaceRoleWithOptionalProjectRoleFactory, getWorkspaceFactory, getWorkspaceRoleForUserFactory, getWorkspaceRolesFactory, @@ -42,7 +45,10 @@ import { getWorkspaceRoleToDefaultProjectRoleMappingFactory } from '@/modules/workspaces/services/projects' import { withTransaction } from '@/modules/shared/helpers/dbHelper' -import { findVerifiedEmailsByUserIdFactory } from '@/modules/core/repositories/userEmails' +import { + findEmailsByUserIdFactory, + findVerifiedEmailsByUserIdFactory +} from '@/modules/core/repositories/userEmails' import { GetStream } from '@/modules/core/domain/streams/operations' import { GetUserSsoSession, @@ -61,6 +67,20 @@ import { ProjectEvents, ProjectEventsPayloads } from '@/modules/core/domain/projects/events' +import { getBaseTrackingProperties, getClient } from '@/modules/shared/utils/mixpanel' +import { + calculateSubscriptionSeats, + GetWorkspacePlan, + GetWorkspaceSubscription +} from '@/modules/gatekeeper/domain/billing' +import { getWorkspacePlanProductId } from '@/modules/gatekeeper/stripe' +import { Workspace } from '@/modules/workspacesCore/domain/types' +import { FindEmailsByUserId } from '@/modules/core/domain/userEmails/operations' +import { getDefaultRegionFactory } from '@/modules/workspaces/repositories/regions' +import { + getWorkspacePlanFactory, + getWorkspaceSubscriptionFactory +} from '@/modules/gatekeeper/repositories/billing' export const onProjectCreatedFactory = ({ @@ -258,6 +278,135 @@ export const onWorkspaceRoleUpdatedFactory = } } +export const workspaceTrackingFactory = + ({ + getWorkspace, + countWorkspaceRole, + getDefaultRegion, + getWorkspacePlan, + getWorkspaceSubscription, + getUserEmails + }: { + getWorkspace: GetWorkspace + countWorkspaceRole: CountWorkspaceRoleWithOptionalProjectRole + getDefaultRegion: GetDefaultRegion + getWorkspacePlan: GetWorkspacePlan + getWorkspaceSubscription: GetWorkspaceSubscription + getUserEmails: FindEmailsByUserId + }) => + async (params: EventPayload<'workspace.*'> | EventPayload<'gatekeeper.*'>) => { + const { eventName, payload } = params + const mixpanel = getClient() + if (!mixpanel) return + const calculateProperties = async (workspace: Workspace) => { + const workspaceId = workspace.id + const [adminCount, memberCount, guestCount, defaultRegion, plan, subscription] = + await Promise.all([ + countWorkspaceRole({ workspaceId, workspaceRole: Roles.Workspace.Admin }), + countWorkspaceRole({ workspaceId, workspaceRole: Roles.Workspace.Member }), + countWorkspaceRole({ workspaceId, workspaceRole: Roles.Workspace.Guest }), + getDefaultRegion({ workspaceId }), + getWorkspacePlan({ workspaceId }), + getWorkspaceSubscription({ workspaceId }) + ]) + const seats = subscription?.subscriptionData + ? calculateSubscriptionSeats({ + subscriptionData: subscription?.subscriptionData, + guestSeatProductId: getWorkspacePlanProductId({ workspacePlan: 'guest' }) + }) + : { plan: 0, guest: 0 } + return { + name: workspace.name, + description: workspace.description, + domainBasedMembershipProtectionEnabled: + workspace.domainBasedMembershipProtectionEnabled, + discoverabilityEnabled: workspace.discoverabilityEnabled, + defaultRegionKey: defaultRegion?.key, + teamTotalCount: adminCount + memberCount + guestCount, + teamAdminCount: adminCount, + teamMemberCount: memberCount, + teamGuestCount: guestCount, + planName: plan?.name || '', + planStatus: plan?.status || '', + planCreatedAt: plan?.createdAt, + subscriptionBillingInterval: subscription?.billingInterval, + subscriptionCurrentBillingCycleEnd: subscription?.currentBillingCycleEnd, + seats: seats.plan, + seatsGuest: seats.guest, + ...getBaseTrackingProperties() + } + } + const checkForSpeckleMembers = async ({ + userId + }: { + userId: string + }): Promise<{ hasSpeckleMembers: boolean }> => { + const userEmails = await getUserEmails({ userId }) + return { + hasSpeckleMembers: userEmails.some((e) => e.email.endsWith('@speckle.systems')) + } + } + switch (eventName) { + case 'gatekeeper.workspace-plan-updated': + const updatedPlanWorkspace = await getWorkspace({ + workspaceId: payload.workspacePlan.workspaceId + }) + if (!updatedPlanWorkspace) break + mixpanel.groups.set( + 'workspace_id', + payload.workspacePlan.workspaceId, + await calculateProperties(updatedPlanWorkspace) + ) + break + case 'gatekeeper.workspace-trial-expired': + break + case 'workspace.authorized': + break + case 'workspace.created': + payload.createdByUserId + + // we're setting workspace props and attributing to speckle users + mixpanel.groups.set('workspace_id', payload.workspace.id, { + ...(await calculateProperties(payload.workspace)), + ...(await checkForSpeckleMembers({ userId: payload.createdByUserId })) + }) + break + case 'workspace.updated': + case 'workspace.metrics': + // just updating workspace props + mixpanel.groups.set( + 'workspace_id', + payload.workspace.id, + await calculateProperties(payload.workspace) + ) + break + case 'workspace.deleted': + // just marking workspace deleted + mixpanel.groups.set('workspace_id', payload.workspaceId, { + isDeleted: true, + ...getBaseTrackingProperties() + }) + break + case 'workspace.role-deleted': + case 'workspace.role-updated': + const speckleMembers = await checkForSpeckleMembers({ userId: payload.userId }) + const workspace = await getWorkspace({ workspaceId: payload.workspaceId }) + if (!workspace) break + mixpanel.groups.set('workspace_id', payload.workspaceId, { + ...(await calculateProperties(workspace)), + // only marking has speckle members to true + // calculating this for speckle member removal would require getting all users + // that is too costly in here imho + ...(speckleMembers.hasSpeckleMembers ? speckleMembers : {}) + }) + break + case 'workspace.joined-from-discovery': + break + default: + throwUncoveredError(eventName) + } + } + const emitWorkspaceGraphqlSubscriptionsFactory = (deps: { getWorkspace: GetWorkspace }) => async (params: EventPayload<'**'>) => { const { eventName, payload } = params @@ -342,6 +491,26 @@ export const initializeEventListenersFactory = }) await onInviteFinalized(payload) }), + eventBus.listen('workspace.*', async (payload) => { + await workspaceTrackingFactory({ + countWorkspaceRole: countWorkspaceRoleWithOptionalProjectRoleFactory({ db }), + getDefaultRegion: getDefaultRegionFactory({ db }), + getUserEmails: findEmailsByUserIdFactory({ db }), + getWorkspace: getWorkspaceFactory({ db }), + getWorkspacePlan: getWorkspacePlanFactory({ db }), + getWorkspaceSubscription: getWorkspaceSubscriptionFactory({ db }) + })(payload) + }), + eventBus.listen('gatekeeper.*', async (payload) => { + await workspaceTrackingFactory({ + countWorkspaceRole: countWorkspaceRoleWithOptionalProjectRoleFactory({ db }), + getDefaultRegion: getDefaultRegionFactory({ db }), + getUserEmails: findEmailsByUserIdFactory({ db }), + getWorkspace: getWorkspaceFactory({ db }), + getWorkspacePlan: getWorkspacePlanFactory({ db }), + getWorkspaceSubscription: getWorkspaceSubscriptionFactory({ db }) + })(payload) + }), eventBus.listen(WorkspaceEvents.Authorized, async ({ payload }) => { const onWorkspaceAuthorized = onWorkspaceAuthorizedFactory({ getWorkspace, diff --git a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts index 9f6b5a95f..2d60e5f9f 100644 --- a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts +++ b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts @@ -1,9 +1,5 @@ import { db } from '@/db/knex' -import { - Resolvers, - WorkspacePlans, - WorkspacePlanStatuses -} from '@/modules/core/graph/generated/graphql' +import { Resolvers } from '@/modules/core/graph/generated/graphql' import { removePrivateFields } from '@/modules/core/helpers/userHelper' import { getProjectCollaboratorsFactory, @@ -47,7 +43,7 @@ import { getInvitationTargetUsersFactory } from '@/modules/serverinvites/service import { authorizeResolver } from '@/modules/shared' import { getFeatureFlags, getServerOrigin } from '@/modules/shared/helpers/envHelper' import { getEventBus } from '@/modules/shared/services/eventBus' -import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants' +import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants' import { WorkspaceInvalidRoleError, WorkspaceJoinNotAllowedError, @@ -194,13 +190,10 @@ import { getGenericRedis } from '@/modules/shared/redis/redis' import { convertFunctionToGraphQLReturn } from '@/modules/automate/services/functionManagement' import { getWorkspacePlanFactory, - upsertPaidWorkspacePlanFactory, - upsertTrialWorkspacePlanFactory, - upsertUnpaidWorkspacePlanFactory + upsertWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing' import { Knex } from 'knex' import { getPaginatedItemsFactory } from '@/modules/shared/services/paginatedItems' -import { InvalidWorkspacePlanStatus } from '@/modules/gatekeeper/errors/billing' import { BadRequestError } from '@/modules/shared/errors' import { dismissWorkspaceJoinRequestFactory, @@ -212,6 +205,7 @@ import { } from '@/modules/workspaces/repositories/workspaceJoinRequests' import { sendWorkspaceJoinRequestReceivedEmailFactory } from '@/modules/workspaces/services/workspaceJoinRequestEmails/received' import { getProjectFactory } from '@/modules/core/repositories/projects' +import { updateWorkspacePlanFactory } from '@/modules/gatekeeper/services/workspacePlans' const eventBus = getEventBus() const getServerInfo = getServerInfoFactory({ db }) @@ -439,73 +433,13 @@ export = FF_WORKSPACES_MODULE_ENABLED AdminMutations: { updateWorkspacePlan: async (_parent, { input }) => { const { workspaceId, plan: name, status } = input - const workspace = await getWorkspaceFactory({ db })({ - workspaceId - }) - const createdAt = new Date() - if (!workspace) throw new WorkspaceNotFoundError() - switch (name) { - case WorkspacePlans.Starter: - switch (status) { - case WorkspacePlanStatuses.Trial: - case WorkspacePlanStatuses.Expired: - await upsertTrialWorkspacePlanFactory({ db })({ - workspacePlan: { workspaceId, status, name, createdAt } - }) - return true - case WorkspacePlanStatuses.Valid: - case WorkspacePlanStatuses.CancelationScheduled: - case WorkspacePlanStatuses.Canceled: - case WorkspacePlanStatuses.PaymentFailed: - await upsertPaidWorkspacePlanFactory({ db })({ - workspacePlan: { workspaceId, status, name, createdAt } - }) - return true - default: - throwUncoveredError(status) - } - case WorkspacePlans.Business: - case WorkspacePlans.Plus: - switch (status) { - case WorkspacePlanStatuses.Trial: - case WorkspacePlanStatuses.Expired: - throw new InvalidWorkspacePlanStatus() - case WorkspacePlanStatuses.Valid: - case WorkspacePlanStatuses.CancelationScheduled: - case WorkspacePlanStatuses.Canceled: - case WorkspacePlanStatuses.PaymentFailed: - await upsertPaidWorkspacePlanFactory({ db })({ - workspacePlan: { workspaceId, status, name, createdAt } - }) - return true - default: - throwUncoveredError(status) - } - case WorkspacePlans.Academia: - case WorkspacePlans.Unlimited: - case WorkspacePlans.StarterInvoiced: - case WorkspacePlans.PlusInvoiced: - case WorkspacePlans.BusinessInvoiced: - switch (status) { - case WorkspacePlanStatuses.Valid: - await upsertUnpaidWorkspacePlanFactory({ db })({ - workspacePlan: { workspaceId, status, name, createdAt } - }) - - return true - case WorkspacePlanStatuses.CancelationScheduled: - case WorkspacePlanStatuses.Canceled: - case WorkspacePlanStatuses.Expired: - case WorkspacePlanStatuses.PaymentFailed: - case WorkspacePlanStatuses.Trial: - throw new InvalidWorkspacePlanStatus() - default: - throwUncoveredError(status) - } - default: - throwUncoveredError(name) - } + await updateWorkspacePlanFactory({ + getWorkspace: getWorkspaceFactory({ db }), + upsertWorkspacePlan: upsertWorkspacePlanFactory({ db }), + emitEvent: getEventBus().emit + })({ workspaceId, name, status }) + return true } }, WorkspaceMutations: { @@ -584,7 +518,8 @@ export = FF_WORKSPACES_MODULE_ENABLED queryAllWorkspaceProjects: queryAllWorkspaceProjectsFactory({ getStreams: legacyGetStreamsFactory({ db }) }), - deleteSsoProvider: deleteSsoProviderFactory({ db }) + deleteSsoProvider: deleteSsoProviderFactory({ db }), + emitWorkspaceEvent: getEventBus().emit }) // this should be turned into a get all regions and map over the regions... diff --git a/packages/server/modules/workspaces/index.ts b/packages/server/modules/workspaces/index.ts index fb838a643..9ba02167a 100644 --- a/packages/server/modules/workspaces/index.ts +++ b/packages/server/modules/workspaces/index.ts @@ -1,3 +1,4 @@ +import cron from 'node-cron' import { moduleLogger } from '@/logging/logging' import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' import { registerOrUpdateScopeFactory } from '@/modules/shared/repositories/scopes' @@ -10,9 +11,39 @@ import { initializeEventListenersFactory } from '@/modules/workspaces/events/eve import { validateModuleLicense } from '@/modules/gatekeeper/services/validateLicense' import { getSsoRouter } from '@/modules/workspaces/rest/sso' import { InvalidLicenseError } from '@/modules/gatekeeper/errors/license' +import { ScheduleExecution } from '@/modules/core/domain/scheduledTasks/operations' +import { getWorkspacesFactory } from '@/modules/workspaces/repositories/workspaces' +import { EventBusEmit, getEventBus } from '@/modules/shared/services/eventBus' +import { scheduleExecutionFactory } from '@/modules/core/services/taskScheduler' +import { + acquireTaskLockFactory, + releaseTaskLockFactory +} from '@/modules/core/repositories/scheduledTasks' +import { GetWorkspaces } from '@/modules/workspaces/domain/operations' const { FF_WORKSPACES_MODULE_ENABLED, FF_WORKSPACES_SSO_ENABLED } = getFeatureFlags() +const scheduleWorkspaceMetricsUpdate = ({ + scheduleExecution, + getWorkspaces, + emit +}: { + scheduleExecution: ScheduleExecution + getWorkspaces: GetWorkspaces + emit: EventBusEmit +}) => { + // run this every hour + // but its ok, we're removing this code after the first run + const cronExpression = '0 * * * *' + return scheduleExecution(cronExpression, 'WorkspaceMetricsUpdate', async () => { + const workspaces = await getWorkspaces({ workspaceIds: undefined }) + for (const workspace of workspaces) { + await emit({ eventName: 'workspace.metrics', payload: { workspace } }) + } + }) +} + +let scheduledTasks: cron.ScheduledTask[] = [] let quitListeners: Optional<() => void> = undefined const initScopes = async () => { @@ -42,12 +73,27 @@ const workspacesModule: SpeckleModule = { if (isInitial) { quitListeners = initializeEventListenersFactory({ db })() + const scheduleExecution = scheduleExecutionFactory({ + acquireTaskLock: acquireTaskLockFactory({ db }), + releaseTaskLock: releaseTaskLockFactory({ db }) + }) + + scheduledTasks = [ + scheduleWorkspaceMetricsUpdate({ + scheduleExecution, + getWorkspaces: getWorkspacesFactory({ db }), + emit: getEventBus().emit + }) + ] } await Promise.all([initScopes(), initRoles()]) }, shutdown() { if (!FF_WORKSPACES_MODULE_ENABLED) return quitListeners?.() + scheduledTasks.forEach((task) => { + task.stop() + }) } } diff --git a/packages/server/modules/workspaces/repositories/workspaces.ts b/packages/server/modules/workspaces/repositories/workspaces.ts index fdd6ff9e8..fb93fecf7 100644 --- a/packages/server/modules/workspaces/repositories/workspaces.ts +++ b/packages/server/modules/workspaces/repositories/workspaces.ts @@ -54,7 +54,7 @@ import { filterByResource, InvitesRetrievalValidityFilter } from '@/modules/serverinvites/repositories/serverInvites' -import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants' +import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants' import { clamp } from 'lodash' import { WorkspaceCreationState, @@ -136,17 +136,10 @@ const workspaceWithRoleBaseQuery = ({ export const getWorkspacesFactory = ({ db }: { db: Knex }): GetWorkspaces => - async (params: { - workspaceIds: string[] - /** - * Optionally - for each workspace, return the user's role in that workspace - */ - userId?: string - }) => { - const { workspaceIds, userId } = params - + async ({ workspaceIds, userId }) => { const q = workspaceWithRoleBaseQuery({ db, userId }) - const results = await q.whereIn(Workspaces.col.id, workspaceIds) + if (workspaceIds !== undefined) q.whereIn(Workspaces.col.id, workspaceIds) + const results = await q return results } diff --git a/packages/server/modules/workspaces/services/invites.ts b/packages/server/modules/workspaces/services/invites.ts index d6bfb9ecc..9c8624fa2 100644 --- a/packages/server/modules/workspaces/services/invites.ts +++ b/packages/server/modules/workspaces/services/invites.ts @@ -53,7 +53,7 @@ import { } from '@/modules/serverinvites/services/operations' import { authorizeResolver } from '@/modules/shared' import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper' -import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants' +import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants' import { GetWorkspace, GetWorkspaceBySlug, diff --git a/packages/server/modules/workspaces/services/management.ts b/packages/server/modules/workspaces/services/management.ts index 5acb3bfbb..1aef7473e 100644 --- a/packages/server/modules/workspaces/services/management.ts +++ b/packages/server/modules/workspaces/services/management.ts @@ -57,7 +57,7 @@ import { FindVerifiedEmailsByUserId } from '@/modules/core/domain/userEmails/operations' import { DeleteAllResourceInvites } from '@/modules/serverinvites/domain/operations' -import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants' +import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants' import { ProjectInviteResourceType } from '@/modules/serverinvites/domain/constants' import { chunk, isEmpty, omit } from 'lodash' import { userEmailsCompliantWithWorkspaceDomains } from '@/modules/workspaces/domain/logic' @@ -291,13 +291,15 @@ export const deleteWorkspaceFactory = deleteProject, queryAllWorkspaceProjects, deleteAllResourceInvites, - deleteSsoProvider + deleteSsoProvider, + emitWorkspaceEvent }: { deleteWorkspace: DeleteWorkspace deleteProject: DeleteStreamRecord queryAllWorkspaceProjects: QueryAllWorkspaceProjects deleteAllResourceInvites: DeleteAllResourceInvites deleteSsoProvider: DeleteSsoProvider + emitWorkspaceEvent: EventBus['emit'] }) => async ({ workspaceId }: WorkspaceDeleteArgs): Promise => { // Delete workspace SSO provider, if present @@ -328,6 +330,10 @@ export const deleteWorkspaceFactory = for (const projectIdsChunk of chunk(projectIds, 25)) { await Promise.all(projectIdsChunk.map((projectId) => deleteProject(projectId))) } + await emitWorkspaceEvent({ + eventName: WorkspaceEvents.Deleted, + payload: { workspaceId } + }) } type WorkspaceRoleDeleteArgs = { diff --git a/packages/server/modules/workspaces/tests/helpers/creation.ts b/packages/server/modules/workspaces/tests/helpers/creation.ts index 90062488d..b7e0ef94c 100644 --- a/packages/server/modules/workspaces/tests/helpers/creation.ts +++ b/packages/server/modules/workspaces/tests/helpers/creation.ts @@ -74,7 +74,7 @@ import { upsertRegionAssignmentFactory } from '@/modules/workspaces/repositories/regions' import { getDb } from '@/modules/multiregion/utils/dbSelector' -import { WorkspacePlan } from '@/modules/gatekeeper/domain/billing' +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags() diff --git a/packages/server/modules/workspacesCore/domain/events.ts b/packages/server/modules/workspacesCore/domain/events.ts index 40700be33..c60744e8b 100644 --- a/packages/server/modules/workspacesCore/domain/events.ts +++ b/packages/server/modules/workspacesCore/domain/events.ts @@ -6,9 +6,11 @@ export const workspaceEventNamespace = 'workspace' as const const eventPrefix = `${workspaceEventNamespace}.` as const export const WorkspaceEvents = { + Metrics: `${eventPrefix}metrics`, Authorized: `${eventPrefix}authorized`, Created: `${eventPrefix}created`, Updated: `${eventPrefix}updated`, + Deleted: `${eventPrefix}deleted`, RoleDeleted: `${eventPrefix}role-deleted`, RoleUpdated: `${eventPrefix}role-updated`, JoinedFromDiscovery: `${eventPrefix}joined-from-discovery` @@ -37,9 +39,11 @@ type WorkspaceJoinedFromDiscoveryPayload = { } export type WorkspaceEventsPayloads = { + [WorkspaceEvents.Metrics]: { workspace: Workspace } [WorkspaceEvents.Authorized]: WorkspaceAuthorizedPayload [WorkspaceEvents.Created]: WorkspaceCreatedPayload [WorkspaceEvents.Updated]: WorkspaceUpdatedPayload + [WorkspaceEvents.Deleted]: { workspaceId: string } [WorkspaceEvents.RoleDeleted]: WorkspaceRoleDeletedPayload [WorkspaceEvents.RoleUpdated]: WorkspaceRoleUpdatedPayload [WorkspaceEvents.JoinedFromDiscovery]: WorkspaceJoinedFromDiscoveryPayload From 9c587884c8821f03dd3d579b52a8ae4aa1470ef2 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 17 Feb 2025 10:42:59 +0100 Subject: [PATCH 45/78] Fix: Settings workspace sidebar groups styling (#3996) --- .../components/settings/Sidebar.vue | 1 + .../layout/sidebar/menu/group/Group.vue | 38 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/frontend-2/components/settings/Sidebar.vue b/packages/frontend-2/components/settings/Sidebar.vue index e652fbc9b..4852b0497 100644 --- a/packages/frontend-2/components/settings/Sidebar.vue +++ b/packages/frontend-2/components/settings/Sidebar.vue @@ -81,6 +81,7 @@ ? 'TRIAL' : undefined " + nested >