From 85f806368ae43e9ee5416534da0a5f17f7eb0720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Koral?= <45078678+oguzhankoral@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:00:59 +0300 Subject: [PATCH] feat: handle model card state according to given ingestion id (#89) * feat: handle model card state according to given ingestion id * chore: linting --- lib/bindings/definitions/ISendBinding.ts | 1 + lib/common/generated/gql/gql.ts | 6 + lib/common/generated/gql/graphql.ts | 406 +++++++++++++----- .../composables/useModelIngestion.ts | 98 ++++- lib/ingestion/graphql/subscriptions.ts | 38 ++ store/hostApp.ts | 20 +- 6 files changed, 459 insertions(+), 110 deletions(-) create mode 100644 lib/ingestion/graphql/subscriptions.ts diff --git a/lib/bindings/definitions/ISendBinding.ts b/lib/bindings/definitions/ISendBinding.ts index a331034..2a1ee93 100644 --- a/lib/bindings/definitions/ISendBinding.ts +++ b/lib/bindings/definitions/ISendBinding.ts @@ -26,6 +26,7 @@ export interface ISendBindingEvents modelCardId: string versionId: string sendConversionResults: ConversionResult[] + ingestionId?: string }) => void setIdMap: (args: { modelCardId: string diff --git a/lib/common/generated/gql/gql.ts b/lib/common/generated/gql/gql.ts index f65d2a4..3eac05d 100644 --- a/lib/common/generated/gql/gql.ts +++ b/lib/common/generated/gql/gql.ts @@ -61,6 +61,7 @@ type Documents = { "\n mutation FailModelIngestionWithError($input: ModelIngestionFailedInput!) {\n projectMutations {\n modelIngestionMutations {\n failWithError(input: $input) {\n id\n }\n }\n }\n }\n": typeof types.FailModelIngestionWithErrorDocument, "\n mutation FailModelIngestionWithCancel($input: ModelIngestionCancelledInput!) {\n projectMutations {\n modelIngestionMutations {\n failWithCancel(input: $input) {\n id\n }\n }\n }\n }\n": typeof types.FailModelIngestionWithCancelDocument, "\n query CanCreateIngestion($modelId: String!, $projectId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n permissions {\n canCreateIngestion {\n authorized\n code\n message\n }\n }\n }\n }\n }\n": typeof types.CanCreateIngestionDocument, + "\n subscription ProjectModelIngestionUpdated(\n $input: ProjectModelIngestionSubscriptionInput!\n ) {\n projectModelIngestionUpdated(input: $input) {\n type\n modelIngestion {\n id\n statusData {\n __typename\n ... on ModelIngestionSuccessStatus {\n status\n versionId\n }\n ... on ModelIngestionProcessingStatus {\n status\n progressMessage\n progress\n }\n ... on ModelIngestionFailedStatus {\n status\n errorReason\n }\n ... on ModelIngestionCancelledStatus {\n status\n cancellationMessage\n }\n ... on ModelIngestionQueuedStatus {\n status\n progressMessage\n }\n }\n }\n }\n }\n": typeof types.ProjectModelIngestionUpdatedDocument, "\n fragment IssuesItem on Issue {\n id\n status\n title\n priority\n viewerState\n identifier\n resourceIdString\n activities(input: { limit: 1, sortDirection: asc }) {\n totalCount\n items {\n actor {\n id\n user {\n name\n id\n avatar\n }\n }\n eventType\n createdAt\n }\n }\n replies {\n totalCount\n items {\n id\n author {\n id\n user {\n name\n id\n avatar\n }\n }\n createdAt\n description {\n doc\n }\n }\n }\n description {\n doc\n }\n labels {\n hexColor\n id\n name\n }\n author {\n id\n user {\n id\n name\n avatar\n }\n }\n dueDate\n assignee {\n id\n user {\n id\n avatar\n name\n }\n }\n }\n": typeof types.IssuesItemFragmentDoc, "\n query IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\n }\n }\n }\n }\n": typeof types.IssuesListDocument, "\n subscription WorkspacePlanUsageUpdated($input: WorkspacePlanUsageSubscriptionInput!) {\n workspacePlanUsageUpdated(input: $input)\n }\n": typeof types.WorkspacePlanUsageUpdatedDocument, @@ -113,6 +114,7 @@ const documents: Documents = { "\n mutation FailModelIngestionWithError($input: ModelIngestionFailedInput!) {\n projectMutations {\n modelIngestionMutations {\n failWithError(input: $input) {\n id\n }\n }\n }\n }\n": types.FailModelIngestionWithErrorDocument, "\n mutation FailModelIngestionWithCancel($input: ModelIngestionCancelledInput!) {\n projectMutations {\n modelIngestionMutations {\n failWithCancel(input: $input) {\n id\n }\n }\n }\n }\n": types.FailModelIngestionWithCancelDocument, "\n query CanCreateIngestion($modelId: String!, $projectId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n permissions {\n canCreateIngestion {\n authorized\n code\n message\n }\n }\n }\n }\n }\n": types.CanCreateIngestionDocument, + "\n subscription ProjectModelIngestionUpdated(\n $input: ProjectModelIngestionSubscriptionInput!\n ) {\n projectModelIngestionUpdated(input: $input) {\n type\n modelIngestion {\n id\n statusData {\n __typename\n ... on ModelIngestionSuccessStatus {\n status\n versionId\n }\n ... on ModelIngestionProcessingStatus {\n status\n progressMessage\n progress\n }\n ... on ModelIngestionFailedStatus {\n status\n errorReason\n }\n ... on ModelIngestionCancelledStatus {\n status\n cancellationMessage\n }\n ... on ModelIngestionQueuedStatus {\n status\n progressMessage\n }\n }\n }\n }\n }\n": types.ProjectModelIngestionUpdatedDocument, "\n fragment IssuesItem on Issue {\n id\n status\n title\n priority\n viewerState\n identifier\n resourceIdString\n activities(input: { limit: 1, sortDirection: asc }) {\n totalCount\n items {\n actor {\n id\n user {\n name\n id\n avatar\n }\n }\n eventType\n createdAt\n }\n }\n replies {\n totalCount\n items {\n id\n author {\n id\n user {\n name\n id\n avatar\n }\n }\n createdAt\n description {\n doc\n }\n }\n }\n description {\n doc\n }\n labels {\n hexColor\n id\n name\n }\n author {\n id\n user {\n id\n name\n avatar\n }\n }\n dueDate\n assignee {\n id\n user {\n id\n avatar\n name\n }\n }\n }\n": types.IssuesItemFragmentDoc, "\n query IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\n }\n }\n }\n }\n": types.IssuesListDocument, "\n subscription WorkspacePlanUsageUpdated($input: WorkspacePlanUsageSubscriptionInput!) {\n workspacePlanUsageUpdated(input: $input)\n }\n": types.WorkspacePlanUsageUpdatedDocument, @@ -320,6 +322,10 @@ export function graphql(source: "\n mutation FailModelIngestionWithCancel($inpu * 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 CanCreateIngestion($modelId: String!, $projectId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n permissions {\n canCreateIngestion {\n authorized\n code\n message\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query CanCreateIngestion($modelId: String!, $projectId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n permissions {\n canCreateIngestion {\n authorized\n code\n message\n }\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 subscription ProjectModelIngestionUpdated(\n $input: ProjectModelIngestionSubscriptionInput!\n ) {\n projectModelIngestionUpdated(input: $input) {\n type\n modelIngestion {\n id\n statusData {\n __typename\n ... on ModelIngestionSuccessStatus {\n status\n versionId\n }\n ... on ModelIngestionProcessingStatus {\n status\n progressMessage\n progress\n }\n ... on ModelIngestionFailedStatus {\n status\n errorReason\n }\n ... on ModelIngestionCancelledStatus {\n status\n cancellationMessage\n }\n ... on ModelIngestionQueuedStatus {\n status\n progressMessage\n }\n }\n }\n }\n }\n"): (typeof documents)["\n subscription ProjectModelIngestionUpdated(\n $input: ProjectModelIngestionSubscriptionInput!\n ) {\n projectModelIngestionUpdated(input: $input) {\n type\n modelIngestion {\n id\n statusData {\n __typename\n ... on ModelIngestionSuccessStatus {\n status\n versionId\n }\n ... on ModelIngestionProcessingStatus {\n status\n progressMessage\n progress\n }\n ... on ModelIngestionFailedStatus {\n status\n errorReason\n }\n ... on ModelIngestionCancelledStatus {\n status\n cancellationMessage\n }\n ... on ModelIngestionQueuedStatus {\n status\n progressMessage\n }\n }\n }\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/lib/common/generated/gql/graphql.ts b/lib/common/generated/gql/graphql.ts index 3f33621..0f2dfe4 100644 --- a/lib/common/generated/gql/graphql.ts +++ b/lib/common/generated/gql/graphql.ts @@ -249,6 +249,12 @@ export type AddWorkspaceDomainInput = { workspaceId: Scalars['ID']['input']; }; +export type AdditionalRequestHeader = { + __typename?: 'AdditionalRequestHeader'; + header: Scalars['String']['output']; + value: Scalars['String']['output']; +}; + export type AdminExternalSyncMutations = { __typename?: 'AdminExternalSyncMutations'; /** Sync versions from external Speckle servers into local projects */ @@ -383,11 +389,6 @@ export type AdminQueriesWorkspaceListArgs = { query?: InputMaybe; }; -export type AdminSupportMutations = { - __typename?: 'AdminSupportMutations'; - externalSync: AdminExternalSyncMutations; -}; - export type AdminUpdateEmailVerificationInput = { email: Scalars['String']['input']; /** Defaults to true. If set to false, the email will be marked as unverified. */ @@ -499,6 +500,10 @@ export type ApproveWorkspaceJoinRequestInput = { workspaceId: Scalars['String']['input']; }; +export type ApproveWorkspaceSupportAccessInput = { + sessionId: Scalars['ID']['input']; +}; + export type ArchiveCommentInput = { archived: Scalars['Boolean']['input']; commentId: Scalars['String']['input']; @@ -1253,6 +1258,14 @@ export type CreateModelInput = { projectId: Scalars['ID']['input']; }; +export type CreateProjectResourceMetaInput = { + data: Scalars['JSON']['input']; + metaType: Scalars['String']['input']; + projectId: Scalars['String']['input']; + resourceId: Scalars['String']['input']; + resourceType: ResourceMetaType; +}; + export type CreateSavedViewGroupInput = { /** Will default to auto-generated group name otherwise */ groupName?: InputMaybe; @@ -1302,6 +1315,12 @@ export type CreateVersionInput = { totalChildrenCount?: InputMaybe; }; +export type CreateWorkspaceResourceMetaInput = { + data: Scalars['JSON']['input']; + metaType: Scalars['String']['input']; + workspaceId: Scalars['String']['input']; +}; + export enum Currency { Gbp = 'gbp', Usd = 'usd' @@ -1402,6 +1421,7 @@ export type DashboardMutationsUpdateArgs = { export type DashboardPermissionChecks = { __typename?: 'DashboardPermissionChecks'; + canAccessModelValidation: PermissionCheckResult; canCreateToken: PermissionCheckResult; canDelete: PermissionCheckResult; canDuplicate: PermissionCheckResult; @@ -1633,14 +1653,14 @@ export type FileUploadCollection = { export type FileUploadMutations = { __typename?: 'FileUploadMutations'; /** + * **For internal service usage** * Marks the file import flow as completed for that specific job * recording the provided status, and emitting the needed subscriptions. - * Mostly for internal service usage. */ finishFileImport: Scalars['Boolean']['output']; /** * Generate a pre-signed url to which a file can be uploaded. - * After uploading the file, call mutation startFileImport to register the completed upload. + * After uploading the file, call mutation startFileIngestion to register the completed upload. */ generateUploadUrl: GenerateFileUploadUrlOutput; /** @@ -1648,6 +1668,7 @@ export type FileUploadMutations = { * pre-signed url and blobId. Then upload the file to that url. * Once the upload to the pre-signed url is completed, this mutation should be * called to register the completed upload and create the blob metadata. + * @deprecated We now offer a common interface for all data ingestion. Use the startModelIngestion mutation instead. Will be removed on or after 2026-06-01 */ startFileImport: FileUpload; /** @@ -1692,41 +1713,6 @@ export type FinishFileImportInput = { warnings?: InputMaybe>; }; -export type GendoAiRender = { - __typename?: 'GendoAIRender'; - camera?: Maybe; - createdAt: Scalars['DateTime']['output']; - gendoGenerationId?: Maybe; - id: Scalars['ID']['output']; - modelId: Scalars['String']['output']; - projectId: Scalars['String']['output']; - prompt: Scalars['String']['output']; - /** This is a blob id. */ - responseImage?: Maybe; - status: Scalars['String']['output']; - updatedAt: Scalars['DateTime']['output']; - user?: Maybe; - userId: Scalars['String']['output']; - versionId: Scalars['String']['output']; -}; - -export type GendoAiRenderCollection = { - __typename?: 'GendoAIRenderCollection'; - items: Array>; - totalCount: Scalars['Int']['output']; -}; - -export type GendoAiRenderInput = { - /** Base64 encoded image of the depthmap. */ - baseImage: Scalars['String']['input']; - camera: Scalars['JSONObject']['input']; - modelId: Scalars['ID']['input']; - projectId: Scalars['ID']['input']; - /** The generation prompt. */ - prompt: Scalars['String']['input']; - versionId: Scalars['ID']['input']; -}; - export type GenerateFileUploadUrlInput = { fileName: Scalars['String']['input']; projectId: Scalars['String']['input']; @@ -1734,7 +1720,11 @@ export type GenerateFileUploadUrlInput = { export type GenerateFileUploadUrlOutput = { __typename?: 'GenerateFileUploadUrlOutput'; + /** The additional headers which must be sent with the PUT upload request to the returned url. */ + additionalRequestHeaders: Array; + /** The id of the file upload. This id must be used in subsequent calls to startFileImport. */ fileId: Scalars['String']['output']; + /** The pre-signed url to which the file must be uploaded. */ url: Scalars['String']['output']; }; @@ -2583,7 +2573,6 @@ export type Mutation = { activeUserMutations: ActiveUserMutations; admin: AdminMutations; adminDeleteUser: Scalars['Boolean']['output']; - adminSupport: AdminSupportMutations; /** Creates an personal api token. */ apiTokenCreate: Scalars['String']['output']; /** Revokes (deletes) an personal api token/app token. */ @@ -2644,10 +2633,12 @@ export type Mutation = { notificationMutations: NotificationMutations; /** @deprecated Part of the old API surface and will be removed in the future. */ objectCreate: Array; + powerTools: PowerToolsMutations; projectMutations: ProjectMutations; /** (Re-)send the account verification e-mail */ requestVerification: SentEmailInfo; requestVerificationByEmail: SentEmailInfo; + resourceMetaMutations: ResourceMetaMutations; serverInfoMutations: ServerInfoMutations; serverInfoUpdate?: Maybe; /** Note: The required scope to invoke this is not given out to app or personal access tokens */ @@ -3070,18 +3061,13 @@ export type ObjectCreateInput = { }; export type OnboardingCompletionInput = { - plans?: InputMaybe>; - role?: InputMaybe; - source?: InputMaybe; + /** The primary use case for how Speckle will help the user in their role */ + useCase?: InputMaybe; }; export enum PaidWorkspacePlans { Business = 'business', - Legacy = 'legacy', - Pro = 'pro', - ProUnlimited = 'proUnlimited', - Team = 'team', - TeamUnlimited = 'teamUnlimited' + Legacy = 'legacy' } export type PasswordStrengthCheckFeedback = { @@ -3173,6 +3159,11 @@ export type PermissionCheckResult = { payload?: Maybe; }; +export type PowerToolsMutations = { + __typename?: 'PowerToolsMutations'; + externalSync: AdminExternalSyncMutations; +}; + export type Price = { __typename?: 'Price'; amount: Scalars['Float']['output']; @@ -4007,11 +3998,6 @@ export type ProjectMutations = { /** Create new project */ create: Project; createEmbedToken: CreateEmbedTokenReturn; - /** - * Create onboarding/tutorial project. If one is already created for the active user, that - * one will be returned instead. - */ - createForOnboarding: Project; /** Delete an existing project */ delete: Scalars['Boolean']['output']; /** Invite related mutations */ @@ -4109,14 +4095,17 @@ export enum ProjectPendingVersionsUpdatedMessageType { export type ProjectPermissionChecks = { __typename?: 'ProjectPermissionChecks'; canAccessIssuesFeature: PermissionCheckResult; + canAccessViewerTableFeature: PermissionCheckResult; canBroadcastActivity: PermissionCheckResult; canCreateAutomation: PermissionCheckResult; /** @deprecated Comments were moved to issues. Use canCreateIssue instead. This check will be removed after 01 Jun 2026. */ canCreateComment: PermissionCheckResult; + canCreateDashboards: PermissionCheckResult; canCreateEmbedTokens: PermissionCheckResult; canCreateIngestion: PermissionCheckResult; canCreateIssue: PermissionCheckResult; canCreateModel: PermissionCheckResult; + canCreateResourceMeta: PermissionCheckResult; canCreateSavedView: PermissionCheckResult; canDelete: PermissionCheckResult; canInvite: PermissionCheckResult; @@ -4149,6 +4138,19 @@ export type ProjectPermissionChecksCanUseInviteArgs = { type?: InputMaybe; }; +export type ProjectResourceMeta = { + __typename?: 'ProjectResourceMeta'; + authorId?: Maybe; + createdAt: Scalars['DateTime']['output']; + data: Scalars['JSON']['output']; + id: Scalars['ID']['output']; + metaType: Scalars['String']['output']; + projectId: Scalars['String']['output']; + resourceId: Scalars['String']['output']; + resourceType: ResourceMetaType; + updatedAt: Scalars['DateTime']['output']; +}; + export type ProjectRole = { __typename?: 'ProjectRole'; project: Project; @@ -4342,6 +4344,8 @@ export type Query = { * isn't specified, the server will look for any valid invite. */ projectInvite?: Maybe; + projectResourceMeta: ProjectResourceMeta; + projectResourceMetaSearch: Array; serverInfo: ServerInfo; /** Receive metadata about an invite by the invite token */ serverInviteByToken?: Maybe; @@ -4407,6 +4411,8 @@ export type Query = { * Either token or workspaceId must be specified, or both */ workspaceInvite?: Maybe; + workspaceResourceMeta: WorkspaceResourceMeta; + workspaceResourceMetaSearch: Array; /** Find workspaces a given user email can use SSO to sign with */ workspaceSsoByEmail: Array; }; @@ -4472,6 +4478,20 @@ export type QueryProjectInviteArgs = { }; +export type QueryProjectResourceMetaArgs = { + id: Scalars['ID']['input']; + projectId: Scalars['String']['input']; +}; + + +export type QueryProjectResourceMetaSearchArgs = { + metaType?: InputMaybe; + projectId: Scalars['String']['input']; + resourceId: Scalars['String']['input']; + resourceType: ResourceMetaType; +}; + + export type QueryServerInviteByTokenArgs = { token?: InputMaybe; }; @@ -4551,6 +4571,18 @@ export type QueryWorkspaceInviteArgs = { }; +export type QueryWorkspaceResourceMetaArgs = { + id: Scalars['ID']['input']; + workspaceId: Scalars['String']['input']; +}; + + +export type QueryWorkspaceResourceMetaSearchArgs = { + metaType?: InputMaybe; + workspaceId: Scalars['String']['input']; +}; + + export type QueryWorkspaceSsoByEmailArgs = { email: Scalars['String']['input']; }; @@ -4560,12 +4592,42 @@ export type RemoveWorkspaceDomainInput = { workspaceId: Scalars['ID']['input']; }; +export type RequestWorkspaceSupportAccessInput = { + /** Optional expiry timestamp. Null means no expiration. */ + validUntil?: InputMaybe; + workspaceId: Scalars['ID']['input']; +}; + export type ResourceIdentifier = { __typename?: 'ResourceIdentifier'; resourceId: Scalars['String']['output']; resourceType: ResourceType; }; +export type ResourceMetaMutations = { + __typename?: 'ResourceMetaMutations'; + createProjectResourceMeta: ProjectResourceMeta; + createWorkspaceResourceMeta: WorkspaceResourceMeta; +}; + + +export type ResourceMetaMutationsCreateProjectResourceMetaArgs = { + input: CreateProjectResourceMetaInput; +}; + + +export type ResourceMetaMutationsCreateWorkspaceResourceMetaArgs = { + input: CreateWorkspaceResourceMetaInput; +}; + +export enum ResourceMetaType { + Issue = 'issue', + Model = 'model', + Object = 'object', + Project = 'project', + Version = 'version' +} + export enum ResourceType { Comment = 'comment', Commit = 'commit', @@ -4573,6 +4635,10 @@ export enum ResourceType { Stream = 'stream' } +export type RevokeWorkspaceSupportAccessInput = { + sessionId: Scalars['ID']['input']; +}; + export type Role = { __typename?: 'Role'; description: Scalars['String']['output']; @@ -4584,7 +4650,8 @@ export type RootPermissionChecks = { __typename?: 'RootPermissionChecks'; canCreatePersonalProject: PermissionCheckResult; canCreateWorkspace: PermissionCheckResult; - canUseAdminSupportTools: PermissionCheckResult; + canManageServerWorkspaces: PermissionCheckResult; + canUsePowerTools: PermissionCheckResult; }; export type SavedView = { @@ -5392,8 +5459,6 @@ export type Subscription = { projectTriggeredAutomationsStatusUpdated: ProjectTriggeredAutomationsStatusUpdatedMessage; /** Track updates to a specific project */ projectUpdated: ProjectUpdatedMessage; - projectVersionGendoAIRenderCreated: GendoAiRender; - projectVersionGendoAIRenderUpdated: GendoAiRender; /** Subscribe to when a project's versions get their preview image fully generated. */ projectVersionsPreviewGenerated: ProjectVersionsPreviewGeneratedMessage; /** Subscribe to changes to a project's versions. */ @@ -5431,6 +5496,11 @@ export type Subscription = { * Either slug or id must be set. */ workspaceProjectsUpdated: WorkspaceProjectsUpdatedMessage; + /** + * Track support session changes for a specific workspace. + * Fires when sessions are requested, approved, revoked, or expire. + */ + workspaceSupportSessionUpdated: WorkspaceSupportSessionUpdatedMessage; /** * Track updates to a specific workspace. * Either slug or id must be set. @@ -5540,18 +5610,6 @@ export type SubscriptionProjectUpdatedArgs = { }; -export type SubscriptionProjectVersionGendoAiRenderCreatedArgs = { - id: Scalars['String']['input']; - versionId: Scalars['String']['input']; -}; - - -export type SubscriptionProjectVersionGendoAiRenderUpdatedArgs = { - id: Scalars['String']['input']; - versionId: Scalars['String']['input']; -}; - - export type SubscriptionProjectVersionsPreviewGeneratedArgs = { id: Scalars['String']['input']; }; @@ -5589,6 +5647,11 @@ export type SubscriptionWorkspaceProjectsUpdatedArgs = { }; +export type SubscriptionWorkspaceSupportSessionUpdatedArgs = { + workspaceId: Scalars['String']['input']; +}; + + export type SubscriptionWorkspaceUpdatedArgs = { workspaceId?: InputMaybe; workspaceSlug?: InputMaybe; @@ -5762,6 +5825,11 @@ export type UpgradeToPaidlPlanInput = { */ export type User = { __typename?: 'User'; + /** + * All active support sessions for the current user across all workspaces. + * Used by the frontend to show support mode banners globally. + */ + activeSupportSessions: Array; /** The last-visited workspace for the given user */ activeWorkspace?: Maybe; /** @@ -5805,7 +5873,6 @@ export type User = { * @deprecated Part of the old API surface and will be removed in the future. Field will be deleted on January 1st, 2026. */ favoriteStreams: StreamCollection; - gendoAICredits: UserGendoAiCredits; /** Whether the user has a pending/active email verification token */ hasPendingVerification?: Maybe; id: Scalars['ID']['output']; @@ -6038,13 +6105,6 @@ export type UserEmailMutationsVerifyArgs = { input: VerifyUserEmailInput; }; -export type UserGendoAiCredits = { - __typename?: 'UserGendoAICredits'; - limit: Scalars['Int']['output']; - resetDate: Scalars['DateTime']['output']; - used: Scalars['Int']['output']; -}; - export type UserMeta = { __typename?: 'UserMeta'; flag: Scalars['Boolean']['output']; @@ -6207,8 +6267,6 @@ export type Version = { */ commentThreads: CommentCollection; createdAt: Scalars['DateTime']['output']; - gendoAIRender: GendoAiRender; - gendoAIRenders: GendoAiRenderCollection; id: Scalars['ID']['output']; message?: Maybe; model: Model; @@ -6226,11 +6284,6 @@ export type VersionCommentThreadsArgs = { limit?: Scalars['Int']['input']; }; - -export type VersionGendoAiRenderArgs = { - id: Scalars['String']['input']; -}; - export type VersionCollection = { __typename?: 'VersionCollection'; cursor?: Maybe; @@ -6257,7 +6310,6 @@ export type VersionMutations = { delete: Scalars['Boolean']['output']; markReceived: Scalars['Boolean']['output']; moveToModel: Model; - requestGendoAIRender: Scalars['Boolean']['output']; update: Version; }; @@ -6282,11 +6334,6 @@ export type VersionMutationsMoveToModelArgs = { }; -export type VersionMutationsRequestGendoAiRenderArgs = { - input: GendoAiRenderInput; -}; - - export type VersionMutationsUpdateArgs = { input: UpdateVersionInput; }; @@ -6444,6 +6491,12 @@ export type WebhookUpdateInput = { export type Workspace = { __typename?: 'Workspace'; + /** + * The current user's active or pending support session for this workspace. + * Null if the current user has no active/pending session. + * Only relevant for server admins — used to show the support mode CTA state. + */ + activeSupportSession?: Maybe; /** Get all join requests for all the workspaces the user is an admin of */ adminWorkspacesJoinRequests?: Maybe; automateFunctions: AutomateFunctionCollection; @@ -6512,6 +6565,12 @@ export type Workspace = { /** Information about the workspace's SSO configuration and the current user's SSO session, if present */ sso?: Maybe; subscription?: Maybe; + /** + * Paginated list of support sessions for this workspace. + * Includes pending, active, revoked, and expired sessions. + * Only accessible to workspace admins. + */ + supportSessions: WorkspaceSupportSessionCollection; team: WorkspaceCollaboratorCollection; teamByRole: WorkspaceTeamByRole; updatedAt: Scalars['DateTime']['output']; @@ -6561,6 +6620,13 @@ export type WorkspaceProjectsArgs = { }; +export type WorkspaceSupportSessionsArgs = { + cursor?: InputMaybe; + filter?: InputMaybe; + limit?: Scalars['Int']['input']; +}; + + export type WorkspaceTeamArgs = { cursor?: InputMaybe; filter?: InputMaybe; @@ -6689,6 +6755,7 @@ export enum WorkspaceFeatureName { Presentations = 'presentations', ProjectDashboards = 'projectDashboards', SavedViews = 'savedViews', + ViewerTable = 'viewerTable', WorkspaceDataRegionSpecificity = 'workspaceDataRegionSpecificity' } @@ -6919,6 +6986,8 @@ export type WorkspaceMutations = { requestToJoin: Scalars['Boolean']['output']; /** Set the default region where project data will be stored. Only available to admins. */ setDefaultRegion: Workspace; + /** Support session management mutations */ + support: WorkspaceSupportMutations; update: Workspace; /** @deprecated workspaces no longer have creation state, is always created true. Field will be removed after 01 Jun 2026 */ updateCreationState: Scalars['Boolean']['output']; @@ -7002,10 +7071,6 @@ export type WorkspacePaidPlanPrices = { __typename?: 'WorkspacePaidPlanPrices'; business: WorkspacePlanPrice; legacy: WorkspacePlanPrice; - pro: WorkspacePlanPrice; - proUnlimited: WorkspacePlanPrice; - team: WorkspacePlanPrice; - teamUnlimited: WorkspacePlanPrice; }; export enum WorkspacePaymentMethod { @@ -7018,7 +7083,6 @@ export type WorkspacePermissionChecks = { __typename?: 'WorkspacePermissionChecks'; canAcceptJoinRequest: PermissionCheckResult; canAccessDashboards: PermissionCheckResult; - canAccessModelValidation: PermissionCheckResult; canAccessSso: PermissionCheckResult; canChangeSeatType: PermissionCheckResult; canCreateDashboards: PermissionCheckResult; @@ -7035,24 +7099,33 @@ export type WorkspacePermissionChecks = { canManageInvites: PermissionCheckResult; canManageJoinRequests: PermissionCheckResult; canManageSso: PermissionCheckResult; + /** Whether the current user can approve/manage support sessions (workspace admins only) */ + canManageSupportSessions: PermissionCheckResult; canMoveProjectToWorkspace: PermissionCheckResult; + canReadAutomateFunctions: PermissionCheckResult; canReadAutomateSettings: PermissionCheckResult; canReadBillingSettings: PermissionCheckResult; canReadDataResidencySettings: PermissionCheckResult; canReadIntegrationsSettings: PermissionCheckResult; canReadMemberEmail: PermissionCheckResult; + canReadMemberRole: PermissionCheckResult; canReadPeopleSettings: PermissionCheckResult; canReadProjectsSettings: PermissionCheckResult; canReadSecuritySettings: PermissionCheckResult; + /** Whether the current user can read/list support sessions for this workspace */ + canReadSupportSessions: PermissionCheckResult; canReadWorkspaceIssueLabels: PermissionCheckResult; canRejectJoinRequest: PermissionCheckResult; canRemoveUser: PermissionCheckResult; + /** Whether the current user can request support access to this workspace (server admins only) */ + canRequestSupportAccess: PermissionCheckResult; canResendInvite: PermissionCheckResult; canSendJoinRequest: PermissionCheckResult; + canUpdateRole: PermissionCheckResult; canUpgradePlan: PermissionCheckResult; - canUseAdminSupportTools: PermissionCheckResult; canUseExperimentalDashboardFeatures: PermissionCheckResult; canUseInvite: PermissionCheckResult; + canUsePowerTools: PermissionCheckResult; }; @@ -7067,6 +7140,12 @@ export type WorkspacePermissionChecksCanMoveProjectToWorkspaceArgs = { }; +export type WorkspacePermissionChecksCanUpdateRoleArgs = { + targetRole: WorkspaceRole; + targetUserId: Scalars['String']['input']; +}; + + export type WorkspacePermissionChecksCanUpgradePlanArgs = { input: CanUpgradePlanInput; }; @@ -7130,12 +7209,6 @@ export enum WorkspacePlans { Enterprise = 'enterprise', Free = 'free', Legacy = 'legacy', - Pro = 'pro', - ProUnlimited = 'proUnlimited', - ProUnlimitedInvoiced = 'proUnlimitedInvoiced', - Team = 'team', - TeamUnlimited = 'teamUnlimited', - TeamUnlimitedInvoiced = 'teamUnlimitedInvoiced', Unlimited = 'unlimited' } @@ -7228,6 +7301,17 @@ export type WorkspaceRequestToJoinInput = { workspaceId: Scalars['ID']['input']; }; +export type WorkspaceResourceMeta = { + __typename?: 'WorkspaceResourceMeta'; + authorId?: Maybe; + createdAt: Scalars['DateTime']['output']; + data: Scalars['JSON']['output']; + id: Scalars['ID']['output']; + metaType: Scalars['String']['output']; + updatedAt: Scalars['DateTime']['output']; + workspaceId: Scalars['String']['output']; +}; + export enum WorkspaceRole { Admin = 'ADMIN', Guest = 'GUEST', @@ -7312,6 +7396,112 @@ export type WorkspaceSubscriptionSeats = { viewers: WorkspaceSubscriptionSeatCount; }; +export type WorkspaceSupportMutations = { + __typename?: 'WorkspaceSupportMutations'; + /** + * Approve a pending support access request. Only workspace admins can approve. + * Activates the session and notifies the requesting server admin. + */ + approveAccess: WorkspaceSupportSession; + /** + * Request access to a workspace for support purposes. Only server admins can request access. + * Creates a pending session and notifies workspace admins for approval. + */ + requestAccess: WorkspaceSupportSession; + /** + * Revoke/stop support access. Works on both pending (denial) and active (revocation) sessions. + * Can be called by the requesting server admin or a workspace admin. + */ + revokeAccess: WorkspaceSupportSession; +}; + + +export type WorkspaceSupportMutationsApproveAccessArgs = { + input: ApproveWorkspaceSupportAccessInput; +}; + + +export type WorkspaceSupportMutationsRequestAccessArgs = { + input: RequestWorkspaceSupportAccessInput; +}; + + +export type WorkspaceSupportMutationsRevokeAccessArgs = { + input: RevokeWorkspaceSupportAccessInput; +}; + +/** + * A support session grants a server admin temporary access to a workspace for support purposes. + * Sessions are requested by server admins and must be approved by workspace admins. + * The session is tied to the admin user (not a specific token), so any auth token the admin + * uses will have support access for the workspace while the session is active. + */ +export type WorkspaceSupportSession = { + __typename?: 'WorkspaceSupportSession'; + /** The server admin who requested/holds this support session */ + adminUser: LimitedUser; + /** When the session was approved by a workspace admin. Null if still pending. */ + approvedAt?: Maybe; + /** The workspace admin who approved the session. Null if still pending. */ + approvedBy?: Maybe; + /** When the session request was created */ + createdAt: Scalars['DateTime']['output']; + id: Scalars['ID']['output']; + /** When the session was revoked/denied. Null if not revoked. */ + revokedAt?: Maybe; + /** Current status of the session */ + status: WorkspaceSupportSessionStatus; + /** + * When the session expires. Null means no expiration. + * A session with a past validUntil is considered expired regardless of stored status. + */ + validUntil?: Maybe; + /** The workspace this session grants access to */ + workspace: LimitedWorkspace; +}; + +export type WorkspaceSupportSessionCollection = { + __typename?: 'WorkspaceSupportSessionCollection'; + cursor?: Maybe; + items: Array; + totalCount: Scalars['Int']['output']; +}; + +export type WorkspaceSupportSessionFilter = { + /** Filter by session status(es). Returns all statuses if not specified. */ + status?: InputMaybe>; +}; + +/** + * Support session status lifecycle: + * pending → active → revoked/expired + */ +export enum WorkspaceSupportSessionStatus { + /** Session has been approved and support access is active */ + Active = 'active', + /** Session has passed its validUntil timestamp */ + Expired = 'expired', + /** Session has been requested by a server admin, awaiting workspace admin approval */ + Pending = 'pending', + /** Session was manually stopped/revoked by either party (covers both denial and revocation) */ + Revoked = 'revoked' +} + +export type WorkspaceSupportSessionUpdatedMessage = { + __typename?: 'WorkspaceSupportSessionUpdatedMessage'; + /** The affected support session */ + session: WorkspaceSupportSession; + /** The type of update that occurred */ + type: WorkspaceSupportSessionUpdatedMessageType; +}; + +export enum WorkspaceSupportSessionUpdatedMessageType { + Approved = 'APPROVED', + Expired = 'EXPIRED', + Requested = 'REQUESTED', + Revoked = 'REVOKED' +} + export type WorkspaceSyncUsage = { __typename?: 'WorkspaceSyncUsage'; versionSyncsMonthly: Scalars['Int']['output']; @@ -7688,6 +7878,13 @@ export type CanCreateIngestionQueryVariables = Exact<{ export type CanCreateIngestionQuery = { __typename?: 'Query', project: { __typename?: 'Project', model: { __typename?: 'Model', permissions: { __typename?: 'ModelPermissionChecks', canCreateIngestion: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string } } } } }; +export type ProjectModelIngestionUpdatedSubscriptionVariables = Exact<{ + input: ProjectModelIngestionSubscriptionInput; +}>; + + +export type ProjectModelIngestionUpdatedSubscription = { __typename?: 'Subscription', projectModelIngestionUpdated: { __typename?: 'ProjectModelIngestionUpdatedMessage', type: ProjectModelIngestionUpdatedMessageType, modelIngestion: { __typename?: 'ModelIngestion', id: string, statusData: { __typename: 'ModelIngestionCancelledStatus', status: ModelIngestionStatus, cancellationMessage: string } | { __typename: 'ModelIngestionFailedStatus', status: ModelIngestionStatus, errorReason: string } | { __typename: 'ModelIngestionProcessingStatus', status: ModelIngestionStatus, progressMessage: string, progress?: number | null } | { __typename: 'ModelIngestionQueuedStatus', status: ModelIngestionStatus, progressMessage: string } | { __typename: 'ModelIngestionSuccessStatus', status: ModelIngestionStatus, versionId: string } } } }; + export type IssuesItemFragment = { __typename?: 'Issue', id: string, status: IssueStatus, title?: string | null, priority: IssuePriority, viewerState?: {} | null, identifier: string, resourceIdString?: string | null, dueDate?: string | null, activities?: { __typename?: 'IssueActivityCollection', totalCount: number, items: Array<{ __typename?: 'IssueActivity', eventType: IssueActivityEventType, createdAt: string, actor?: { __typename?: 'IssueParticipant', id: string, user: { __typename?: 'LimitedUser', name: string, id: string, avatar?: string | null } } | null }> } | null, replies: { __typename?: 'IssueReplyCollection', totalCount: number, items: Array<{ __typename?: 'IssueReply', id: string, createdAt: string, author?: { __typename?: 'IssueParticipant', id: string, user: { __typename?: 'LimitedUser', name: string, id: string, avatar?: string | null } } | null, description?: { __typename?: 'SmartTextEditorValue', doc?: {} | null } | null }> }, description?: { __typename?: 'SmartTextEditorValue', doc?: {} | null } | null, labels: Array<{ __typename?: 'AssignedLabel', hexColor: string, id: string, name: string }>, author?: { __typename?: 'IssueParticipant', id: string, user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } } | null, assignee?: { __typename?: 'IssueParticipant', id: string, user: { __typename?: 'LimitedUser', id: string, avatar?: string | null, name: string } } | null }; export type IssuesListQueryVariables = Exact<{ @@ -7752,5 +7949,6 @@ export const CompleteModelIngestionWithVersionDocument = {"kind":"Document","def export const FailModelIngestionWithErrorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"FailModelIngestionWithError"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ModelIngestionFailedInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelIngestionMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"failWithError"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const FailModelIngestionWithCancelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"FailModelIngestionWithCancel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ModelIngestionCancelledInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelIngestionMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"failWithCancel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const CanCreateIngestionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CanCreateIngestion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}},"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":"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":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canCreateIngestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const ProjectModelIngestionUpdatedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"ProjectModelIngestionUpdated"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectModelIngestionSubscriptionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectModelIngestionUpdated"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"modelIngestion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"statusData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ModelIngestionSuccessStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"versionId"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ModelIngestionProcessingStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"progressMessage"}},{"kind":"Field","name":{"kind":"Name","value":"progress"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ModelIngestionFailedStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"errorReason"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ModelIngestionCancelledStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"cancellationMessage"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ModelIngestionQueuedStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"progressMessage"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const IssuesListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"IssuesList"},"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":"issues"},"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":"IssuesItem"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"IssuesItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Issue"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"priority"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"activities"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}},{"kind":"ObjectField","name":{"kind":"Name","value":"sortDirection"},"value":{"kind":"EnumValue","value":"asc"}}]}}],"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":"actor"},"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"eventType"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"replies"},"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":"author"},"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"description"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"description"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}}]}},{"kind":"Field","name":{"kind":"Name","value":"labels"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hexColor"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"author"},"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":"Field","name":{"kind":"Name","value":"dueDate"}},{"kind":"Field","name":{"kind":"Name","value":"assignee"},"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":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; export const WorkspacePlanUsageUpdatedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"WorkspacePlanUsageUpdated"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspacePlanUsageSubscriptionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspacePlanUsageUpdated"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/lib/ingestion/composables/useModelIngestion.ts b/lib/ingestion/composables/useModelIngestion.ts index dc46bd0..fd5cf1d 100644 --- a/lib/ingestion/composables/useModelIngestion.ts +++ b/lib/ingestion/composables/useModelIngestion.ts @@ -1,4 +1,8 @@ -import { provideApolloClient, useMutation } from '@vue/apollo-composable' +import { + provideApolloClient, + useMutation, + useSubscription +} from '@vue/apollo-composable' import { useAccountStore } from '~/store/accounts' import { useHostAppStore } from '~/store/hostApp' import { @@ -8,7 +12,11 @@ import { failModelIngestionWithError, failModelIngestionWithCancel } from '../graphql/mutations' -import type { SourceDataInput } from '~~/lib/common/generated/gql/graphql' +import { projectModelIngestionUpdatedSubscription } from '../graphql/subscriptions' +import type { + SourceDataInput, + ProjectModelIngestionUpdatedSubscription +} from '~~/lib/common/generated/gql/graphql' import type { ISenderModelCard } from '~/lib/models/card/send' import { storeToRefs } from 'pinia' import { ToastNotificationType } from '@speckle/ui-components' @@ -217,11 +225,95 @@ export const useModelIngestion = () => { return res?.data?.projectMutations.modelIngestionMutations.completeWithVersion } + // Tracks active ingestion subscriptions so they can be stopped on cancel or terminal state + const activeSubscriptions: Record void> = {} + + /** + * Subscribes to ingestion status updates for a given ingestionId. + * Used when the connector (.NET SDK) handles the ingestion and passes the ingestionId + * back to the DUI via setModelSendResult. The DUI then subscribes to track + * the server-side processing state until a terminal status is reached. + * + * Manages model card state directly: updates progress, sets versionId on success, + * sets error on failure, and clears progress on terminal states. + */ + const subscribeToIngestion = ( + senderModelCard: ISenderModelCard, + ingestionId: string + ) => { + const client = accountStore.getAccountClient(senderModelCard.accountId) + + senderModelCard.progress = { status: 'Remote processing...' } + + const { onResult, onError, stop } = provideApolloClient(client)(() => + useSubscription(projectModelIngestionUpdatedSubscription, () => ({ + input: { + projectId: senderModelCard.projectId, + ingestionReference: { ingestionId } + } + })) + ) + + activeSubscriptions[senderModelCard.modelCardId] = stop + + onResult((result) => { + const data = result.data as ProjectModelIngestionUpdatedSubscription | undefined + const statusData = data?.projectModelIngestionUpdated?.modelIngestion?.statusData + if (!statusData) return + + switch (statusData.__typename) { + case 'ModelIngestionSuccessStatus': + senderModelCard.latestCreatedVersionId = statusData.versionId + senderModelCard.progress = undefined + unsubscribeFromIngestion(senderModelCard.modelCardId) + break + case 'ModelIngestionProcessingStatus': + senderModelCard.progress = { + status: statusData.progressMessage, + progress: statusData.progress ?? undefined + } + break + case 'ModelIngestionFailedStatus': + senderModelCard.error = { + errorMessage: statusData.errorReason, + dismissible: true + } + senderModelCard.progress = undefined + unsubscribeFromIngestion(senderModelCard.modelCardId) + break + case 'ModelIngestionCancelledStatus': + senderModelCard.progress = undefined + unsubscribeFromIngestion(senderModelCard.modelCardId) + break + case 'ModelIngestionQueuedStatus': + senderModelCard.progress = { + status: statusData.progressMessage + } + break + } + }) + + onError((err) => { + console.error('Ingestion subscription error:', err) + unsubscribeFromIngestion(senderModelCard.modelCardId) + }) + } + + const unsubscribeFromIngestion = (modelCardId: string) => { + const stop = activeSubscriptions[modelCardId] + if (stop) { + stop() + delete activeSubscriptions[modelCardId] + } + } + return { startIngestion, updateIngestion, failIngestion, cancelIngestion, - completeIngestionWithVersion + completeIngestionWithVersion, + subscribeToIngestion, + unsubscribeFromIngestion } } diff --git a/lib/ingestion/graphql/subscriptions.ts b/lib/ingestion/graphql/subscriptions.ts new file mode 100644 index 0000000..b12e326 --- /dev/null +++ b/lib/ingestion/graphql/subscriptions.ts @@ -0,0 +1,38 @@ +import { graphql } from '~~/lib/common/generated/gql' + +export const projectModelIngestionUpdatedSubscription = graphql(` + subscription ProjectModelIngestionUpdated( + $input: ProjectModelIngestionSubscriptionInput! + ) { + projectModelIngestionUpdated(input: $input) { + type + modelIngestion { + id + statusData { + __typename + ... on ModelIngestionSuccessStatus { + status + versionId + } + ... on ModelIngestionProcessingStatus { + status + progressMessage + progress + } + ... on ModelIngestionFailedStatus { + status + errorReason + } + ... on ModelIngestionCancelledStatus { + status + cancellationMessage + } + ... on ModelIngestionQueuedStatus { + status + progressMessage + } + } + } + } + } +`) diff --git a/store/hostApp.ts b/store/hostApp.ts index 32677cc..cfdf7d0 100644 --- a/store/hostApp.ts +++ b/store/hostApp.ts @@ -51,7 +51,9 @@ export const useHostAppStore = defineStore('hostAppStore', () => { updateIngestion, failIngestion, cancelIngestion, - completeIngestionWithVersion + completeIngestionWithVersion, + subscribeToIngestion, + unsubscribeFromIngestion } = useModelIngestion() const isDistributedBySpeckle = ref(true) const latestAvailableVersion = ref(null) @@ -477,6 +479,9 @@ export const useHostAppStore = defineStore('hostAppStore', () => { void trackEvent('DUI3 Action', { name: 'Send Cancel' }, model.accountId) model.latestCreatedVersionId = undefined + // Clean up any active ingestion subscription from SDK-based connectors + unsubscribeFromIngestion(modelCardId) + // Cancel the ingestion if applicable if (shouldHandleIngestion.value) { const ingestionId = activeIngestions.value[modelCardId] @@ -500,13 +505,22 @@ export const useHostAppStore = defineStore('hostAppStore', () => { modelCardId: string versionId: string sendConversionResults: ConversionResult[] + ingestionId?: string }) => { const model = documentModelStore.value.models.find( (m) => m.modelCardId === args.modelCardId ) as ISenderModelCard - model.latestCreatedVersionId = args.versionId + // Conversion results are always valid regardless of ingestion state model.report = args.sendConversionResults - model.progress = undefined + + if (args.ingestionId) { + // Connector handled ingestion via SDK — composable subscribes and manages model card state to 'Version created' bla bla + subscribeToIngestion(model, args.ingestionId) + } else { + // Legacy path or no ingestion — behave as before + model.latestCreatedVersionId = args.versionId + model.progress = undefined + } } app.$sendBinding?.on('setModelSendResult', setModelSendResult)