feat: handle model card state according to given ingestion id (#89)
* feat: handle model card state according to given ingestion id * chore: linting
This commit is contained in:
@@ -26,6 +26,7 @@ export interface ISendBindingEvents
|
||||
modelCardId: string
|
||||
versionId: string
|
||||
sendConversionResults: ConversionResult[]
|
||||
ingestionId?: string
|
||||
}) => void
|
||||
setIdMap: (args: {
|
||||
modelCardId: string
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
+302
-104
File diff suppressed because one or more lines are too long
@@ -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<string, () => 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
+16
-2
@@ -51,7 +51,9 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
updateIngestion,
|
||||
failIngestion,
|
||||
cancelIngestion,
|
||||
completeIngestionWithVersion
|
||||
completeIngestionWithVersion,
|
||||
subscribeToIngestion,
|
||||
unsubscribeFromIngestion
|
||||
} = useModelIngestion()
|
||||
const isDistributedBySpeckle = ref<boolean>(true)
|
||||
const latestAvailableVersion = ref<Version | null>(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,14 +505,23 @@ 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
|
||||
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user