feat: new business model changes (#85)

* feat: initial can create version implementation on model card

* feat: disable model card CTAs for send

* feat: initial model ingestion tests

* fix: apply ingestion send to all CTAs

* feat: sketchup bridge

* feat: centeralize the start ingestion logic in host app store

* fix: sketchup is handling via model ingestion

* chore: cosmetics

* feat(ingestion): add failWithError and failWithCancel GraphQL mutations

* feat(ingestion): add failIngestion and cancelIngestion methods to useModelIngestion composable

* feat(ingestion): handle ingestion failure and cancellation in hostAppStore

* fix: reviewers comments

* fix: don't know where the f that came from

* refactor(ingestion): remove unused statusData and fix lint errors

* feat(wizard): add canCreateVersion permission check to publish wizard

* TODOs

* feat(permissions): add 1s polling for canCreateVersion to reflect workspace limit changes

* fix(tooltip): undefined doesnt refresh v-tippy

* fix(wizard): too much ctrl z lol

* refactor(permissions): check canCreateVersion on action instead of polling

* feat(hostApp): adds fallback for model ingestion on older servers

* fix: ingestion available check and rock'n roll

* feat: workspace plan updated subscription boilerplate

* fix: bump the timeout to 2h

* feat: handle version limits in publish flows via subscription

* feat: align Archicad and Vectorworks with new ingestion flow

* chore: onMounted at end of file

* fix: logic and ui adjustments

* fix: refactoring and permissions

* refactor: ingestionStatus renamed to activeIngestions

* fix: error handling and notifications

* fix: global error handling

* chore: general alignment and clean up

* fix(vectorworks): now uses capital V

* chore: revert codegen

---------

Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
This commit is contained in:
Oğuzhan Koral
2026-02-03 14:43:16 +03:00
committed by GitHub
parent eef0a59719
commit c7e0929eca
18 changed files with 1230 additions and 187 deletions
+11 -10
View File
@@ -27,16 +27,17 @@
> >
{{ notification.secondaryCta.name }} {{ notification.secondaryCta.name }}
</FormButton> </FormButton>
<FormButton <div v-if="notification.cta" v-tippy="notification.cta.tooltipText">
v-if="notification.cta" <FormButton
v-tippy="notification.cta.tooltipText" :disabled="notification.cta.disabled"
size="sm" size="sm"
color="primary" color="primary"
full-width full-width
@click.stop="notification.cta?.action" @click.stop="notification.cta?.action"
> >
{{ notification.cta.name }} {{ notification.cta.name }}
</FormButton> </FormButton>
</div>
</div> </div>
</div> </div>
<div <div
+18 -8
View File
@@ -5,10 +5,12 @@
> >
<div v-if="modelData" class="relative px-1 py-1"> <div v-if="modelData" class="relative px-1 py-1">
<div class="relative flex items-center space-x-2 min-w-0"> <div class="relative flex items-center space-x-2 min-w-0">
<div class="text-foreground-2 mt-[2px] flex items-center -space-x-2 relative"> <div
v-tippy="buttonTooltip"
class="text-foreground-2 mt-[2px] flex items-center -space-x-2 relative"
>
<!-- CTA button --> <!-- CTA button -->
<FormButton <FormButton
v-tippy="buttonTooltip"
color="outline" color="outline"
:icon-left=" :icon-left="
modelCard.progress modelCard.progress
@@ -19,7 +21,7 @@
" "
hide-text hide-text
class="" class=""
:disabled="!canEdit || isSettingsMissing" :disabled="!canEdit || isSettingsMissing || ctaDisabled"
@click.stop="$emit('manual-publish-or-load')" @click.stop="$emit('manual-publish-or-load')"
></FormButton> ></FormButton>
</div> </div>
@@ -260,11 +262,18 @@ const store = useHostAppStore()
const accStore = useAccountStore() const accStore = useAccountStore()
const { trackEvent } = useMixpanel() const { trackEvent } = useMixpanel()
const props = defineProps<{ const props = withDefaults(
modelCard: IModelCard defineProps<{
project: ProjectModelGroup modelCard: IModelCard
canEdit: boolean project: ProjectModelGroup
}>() canEdit: boolean
ctaDisabled?: boolean
ctaDisabledMessage?: string
}>(),
{
ctaDisabled: false
}
)
defineEmits<{ defineEmits<{
(e: 'manual-publish-or-load'): void (e: 'manual-publish-or-load'): void
@@ -275,6 +284,7 @@ const isSender = computed(() => {
}) })
const buttonTooltip = computed(() => { const buttonTooltip = computed(() => {
if (props.ctaDisabled) return props.ctaDisabledMessage
return props.modelCard.progress return props.modelCard.progress
? 'Cancel' ? 'Cancel'
: isSender.value : isSender.value
+81 -19
View File
@@ -4,6 +4,8 @@
:model-card="modelCard" :model-card="modelCard"
:project="project" :project="project"
:can-edit="canEdit" :can-edit="canEdit"
:cta-disabled="ctaDisabled"
:cta-disabled-message="ctaDisabledMessage"
@manual-publish-or-load="sendOrCancel" @manual-publish-or-load="sendOrCancel"
> >
<div class="flex max-[275px]:w-full overflow-hidden my-2"> <div class="flex max-[275px]:w-full overflow-hidden my-2">
@@ -17,7 +19,6 @@
full-width full-width
@click.stop="openFilterDialog = true" @click.stop="openFilterDialog = true"
> >
<!-- Sending&nbsp; -->
<span class="font-bold">{{ modelCard.sendFilter?.name }}:&nbsp;</span> <span class="font-bold">{{ modelCard.sendFilter?.name }}:&nbsp;</span>
<span class="truncate">{{ modelCard.sendFilter?.summary }}</span> <span class="truncate">{{ modelCard.sendFilter?.summary }}</span>
</FormButton> </FormButton>
@@ -31,13 +32,18 @@
<FilterListSelect :filter="modelCard.sendFilter" @update:filter="updateFilter" /> <FilterListSelect :filter="modelCard.sendFilter" @update:filter="updateFilter" />
<div class="mt-4 flex justify-end items-center space-x-2"> <div class="mt-4 flex justify-end items-center space-x-2">
<!-- TODO: Ux wise, users might want to just save the selection and publish it later. -->
<FormButton size="sm" color="outline" @click.stop="saveFilter()"> <FormButton size="sm" color="outline" @click.stop="saveFilter()">
Save Save
</FormButton> </FormButton>
<FormButton size="sm" @click.stop="saveFilterAndSend()"> <div v-tippy="!canCreateVersionPerm ? canCreateVersionMessage : ''">
Save & Publish <FormButton
</FormButton> size="sm"
:disabled="!canCreateVersionPerm"
@click.stop="saveFilterAndSend()"
>
Save & Publish
</FormButton>
</div>
</div> </div>
</CommonDialog> </CommonDialog>
@@ -108,7 +114,7 @@
</ModelCardBase> </ModelCardBase>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import ModelCardBase from '~/components/model/CardBase.vue' import ModelCardBase from '~/components/model/CardBase.vue'
import { Square3Stack3DIcon } from '@heroicons/vue/20/solid' import { Square3Stack3DIcon } from '@heroicons/vue/20/solid'
import type { ModelCardNotification } from '~/lib/models/card/notification' import type { ModelCardNotification } from '~/lib/models/card/notification'
@@ -117,13 +123,22 @@ import type { ProjectModelGroup } from '~/store/hostApp'
import { useHostAppStore } from '~/store/hostApp' import { useHostAppStore } from '~/store/hostApp'
import { useMixpanel } from '~/lib/core/composables/mixpanel' import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { ToastNotificationType, ValidationHelpers } from '@speckle/ui-components' import { ToastNotificationType, ValidationHelpers } from '@speckle/ui-components'
import { provideApolloClient, useMutation } from '@vue/apollo-composable' import {
provideApolloClient,
useMutation,
useSubscription
} from '@vue/apollo-composable'
import { useAccountStore, type DUIAccount } from '~/store/accounts' import { useAccountStore, type DUIAccount } from '~/store/accounts'
import { setVersionMessageMutation } from '~/lib/graphql/mutationsAndQueries' import { setVersionMessageMutation } from '~/lib/graphql/mutationsAndQueries'
const hostAppStore = useHostAppStore() import { workspacePlanUsageUpdatedSubscription } from '~/lib/workspaces/graphql/subscriptions'
import { useCheckGraphql } from '~/lib/core/composables/useCheckGraphql'
const store = useHostAppStore()
const accountStore = useAccountStore()
const { trackEvent } = useMixpanel() const { trackEvent } = useMixpanel()
const app = useNuxtApp() const app = useNuxtApp()
const { canCreateModelIngestion } = useCheckGraphql()
const cardBase = ref<InstanceType<typeof ModelCardBase>>() const cardBase = ref<InstanceType<typeof ModelCardBase>>()
const props = defineProps<{ const props = defineProps<{
@@ -132,18 +147,62 @@ const props = defineProps<{
canEdit: boolean canEdit: boolean
}>() }>()
const store = useHostAppStore() const account = accountStore.accounts.find(
(acc) => acc.accountInfo.id === props.project.accountId
) as DUIAccount
const clientId = account.accountInfo.id
const openFilterDialog = ref(false) const openFilterDialog = ref(false)
app.$baseBinding?.on('documentChanged', () => { app.$baseBinding?.on('documentChanged', () => {
openFilterDialog.value = false openFilterDialog.value = false
}) })
const canCreateVersionPerm = ref(true)
const canCreateVersionMessage = ref<string | null>(null)
const checkPermissions = async () => {
const res = await canCreateModelIngestion(
props.modelCard.projectId,
props.modelCard.modelId,
props.modelCard.accountId
)
if (res.queryAvailable) {
canCreateVersionPerm.value = res.authorized
canCreateVersionMessage.value = res.message || null
}
}
const ctaDisabled = computed(
() => !canCreateVersionPerm.value || !!props.modelCard.progress
)
const ctaDisabledMessage = computed(() => canCreateVersionMessage.value || undefined)
const { onResult: onWorkspacePlanUsageUpdated } = useSubscription(
workspacePlanUsageUpdatedSubscription,
() => ({
input: {
workspaceId: props.modelCard.workspaceId as string
}
}),
() => ({ clientId })
)
onWorkspacePlanUsageUpdated(() => {
void checkPermissions()
})
const sendOrCancel = () => { const sendOrCancel = () => {
if (!props.canEdit) { // check for progress first to allow cancelling even if permissions changed
if (props.modelCard.progress) {
store.sendModelCancel(props.modelCard.modelCardId)
return return
} }
if (props.modelCard.progress) store.sendModelCancel(props.modelCard.modelCardId)
else store.sendModel(props.modelCard.modelCardId, 'ModelCardButton') if (!props.canEdit || !canCreateVersionPerm.value) {
return
}
store.sendModel(props.modelCard.modelCardId, 'ModelCardButton')
hasSetVersionMessage.value = false hasSetVersionMessage.value = false
} }
@@ -173,11 +232,6 @@ const isUpdatingVersionMessage = ref(false)
const hasSetVersionMessage = ref(false) const hasSetVersionMessage = ref(false)
const versionMessage = ref<string>() const versionMessage = ref<string>()
const accountStore = useAccountStore()
const account = accountStore.accounts.find(
(acc) => acc.accountInfo.id === props.project.accountId
) as DUIAccount
const setVersionMessage = async (message: string) => { const setVersionMessage = async (message: string) => {
if (!props.modelCard.latestCreatedVersionId) { if (!props.modelCard.latestCreatedVersionId) {
return return
@@ -203,14 +257,14 @@ const setVersionMessage = async (message: string) => {
if (res?.data?.versionMutations.update.id) { if (res?.data?.versionMutations.update.id) {
// seemed to noisy, and autoclose does not work for some reason. // seemed to noisy, and autoclose does not work for some reason.
// nicer ux to just close the dialog // nicer ux to just close the dialog
// hostAppStore.setNotification({ // store.setNotification({
// type: ToastNotificationType.Info, // type: ToastNotificationType.Info,
// title: 'Version message saved', // title: 'Version message saved',
// autoClose: true // autoClose: true
// }) // })
hasSetVersionMessage.value = true hasSetVersionMessage.value = true
} else { } else {
hostAppStore.setNotification({ store.setNotification({
type: ToastNotificationType.Danger, type: ToastNotificationType.Danger,
title: 'Request failed', title: 'Request failed',
description: 'Failed to update version message.', description: 'Failed to update version message.',
@@ -261,6 +315,10 @@ const expiredNotification = computed(() => {
const ctaType = props.modelCard.progress ? 'Restart' : 'Update' const ctaType = props.modelCard.progress ? 'Restart' : 'Update'
notification.cta = { notification.cta = {
name: ctaType, name: ctaType,
disabled: !canCreateVersionPerm.value,
tooltipText: !canCreateVersionPerm.value
? canCreateVersionMessage.value || 'Publish limit reached'
: undefined,
action: async () => { action: async () => {
hasSetVersionMessage.value = false hasSetVersionMessage.value = false
if (props.modelCard.progress) { if (props.modelCard.progress) {
@@ -337,4 +395,8 @@ const latestVersionNotification = computed(() => {
} }
return notification return notification
}) })
onMounted(() => {
void checkPermissions()
})
</script> </script>
+68 -16
View File
@@ -21,7 +21,6 @@
@search-text-update="updateSearchText" @search-text-update="updateSearchText"
/> />
</div> </div>
<!-- Model selector wizard -->
<div v-if="step === 2 && selectedProject && selectedAccountId"> <div v-if="step === 2 && selectedProject && selectedAccountId">
<WizardModelSelector <WizardModelSelector
:project="selectedProject" :project="selectedProject"
@@ -32,7 +31,6 @@
@next="selectModel" @next="selectModel"
/> />
</div> </div>
<!-- Version selector wizard -->
<div v-if="step === 3"> <div v-if="step === 3">
<SendFiltersAndSettings <SendFiltersAndSettings
v-model="filter" v-model="filter"
@@ -44,8 +42,10 @@
} }
" "
/> />
<div class="mt-2"> <div v-tippy="!canPublish ? publishLimitMessage : ''" class="mt-2">
<FormButton full-width @click="addModel">Publish</FormButton> <FormButton full-width :disabled="!canPublish" @click="addModel">
Publish
</FormButton>
</div> </div>
</div> </div>
<div v-if="urlParseError" class="p-2 text-danger"> <div v-if="urlParseError" class="p-2 text-danger">
@@ -55,6 +55,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useSubscription } from '@vue/apollo-composable'
import type { import type {
ModelListModelItemFragment, ModelListModelItemFragment,
ProjectListProjectItemFragment ProjectListProjectItemFragment
@@ -67,6 +68,8 @@ import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useSettingsTracking } from '~/lib/core/composables/trackSettings' import { useSettingsTracking } from '~/lib/core/composables/trackSettings'
import type { CardSetting } from '~/lib/models/card/setting' import type { CardSetting } from '~/lib/models/card/setting'
import { useAddByUrl } from '~/lib/core/composables/addByUrl' import { useAddByUrl } from '~/lib/core/composables/addByUrl'
import { useCheckGraphql } from '~/lib/core/composables/useCheckGraphql'
import { workspacePlanUsageUpdatedSubscription } from '~/lib/workspaces/graphql/subscriptions'
const { trackEvent } = useMixpanel() const { trackEvent } = useMixpanel()
const { trackSettingsChange } = useSettingsTracking() const { trackSettingsChange } = useSettingsTracking()
@@ -87,6 +90,11 @@ const settings = ref<CardSetting[] | undefined>(undefined)
const settingsWereChanged = ref(false) const settingsWereChanged = ref(false)
const { tryParseUrl, urlParsedData, urlParseError } = useAddByUrl() const { tryParseUrl, urlParsedData, urlParseError } = useAddByUrl()
const { canCreateModelIngestion, canCreateVersion } = useCheckGraphql()
const canPublish = ref(true)
const publishLimitMessage = ref<string | undefined>(undefined)
const updateSearchText = (text: string | undefined) => { const updateSearchText = (text: string | undefined) => {
urlParseError.value = undefined urlParseError.value = undefined
if (!text) return if (!text) return
@@ -105,6 +113,62 @@ watch(showSendDialog, (newVal) => {
} }
}) })
const checkPermissions = async () => {
if (!selectedProject.value || !selectedModel.value) return
const res = await canCreateModelIngestion(
selectedProject.value.id,
selectedModel.value.id,
selectedAccountId.value
)
if (res.queryAvailable) {
canPublish.value = res.authorized
publishLimitMessage.value = res.message || undefined
} else {
// check legacy canCreateVersion in else block
const legacyRes = await canCreateVersion(
selectedProject.value.id,
selectedModel.value.id,
selectedAccountId.value
)
canPublish.value = legacyRes.authorized
publishLimitMessage.value = legacyRes.message || undefined
}
}
watch(step, async (newVal, oldVal) => {
if (newVal > oldVal) {
if (newVal === 3) {
await checkPermissions()
}
return // exit fast on forward
}
if (newVal === 1) {
selectedProject.value = undefined
selectedModel.value = undefined
}
if (newVal === 2) selectedModel.value = undefined
})
const workspaceId = computed(() => selectedProject.value?.workspace?.id)
const { onResult: onUsageUpdate } = useSubscription(
workspacePlanUsageUpdatedSubscription,
() => ({
input: {
workspaceId: workspaceId.value || ''
}
}),
() => ({
enabled: !!workspaceId.value && step.value === 3,
clientId: selectedAccountId.value
})
)
onUsageUpdate(() => {
void checkPermissions()
})
const selectProject = (accountId: string, project: ProjectListProjectItemFragment) => { const selectProject = (accountId: string, project: ProjectListProjectItemFragment) => {
step.value++ step.value++
selectedAccountId.value = accountId selectedAccountId.value = accountId
@@ -125,18 +189,6 @@ const selectModel = (model: ModelListModelItemFragment) => {
void trackEvent('DUI3 Action', { name: 'Publish Wizard', step: 'model selected' }) void trackEvent('DUI3 Action', { name: 'Publish Wizard', step: 'model selected' })
} }
// Clears data if going backwards in the wizard
watch(step, (newVal, oldVal) => {
if (newVal > oldVal) {
return // exit fast on forward
}
if (newVal === 1) {
selectedProject.value = undefined
selectedModel.value = undefined
}
if (newVal === 2) selectedModel.value = undefined
})
const hostAppStore = useHostAppStore() const hostAppStore = useHostAppStore()
// accountId, serverUrl, projectId, modelId, sendFilter, settings // accountId, serverUrl, projectId, modelId, sendFilter, settings
View File
+69 -15
View File
@@ -16,6 +16,9 @@ import type { Emitter } from 'nanoevents'
import { useDesktopService } from '~/lib/core/composables/desktopService' import { useDesktopService } from '~/lib/core/composables/desktopService'
import type { ToastNotification } from '@speckle/ui-components' import type { ToastNotification } from '@speckle/ui-components'
import { ToastNotificationType } from '@speckle/ui-components' import { ToastNotificationType } from '@speckle/ui-components'
import { useModelIngestion } from '../ingestion/composables/useModelIngestion'
import type { ISenderModelCard } from '../models/card/send'
import { useCheckGraphql } from '~/lib/core/composables/useCheckGraphql'
export type SendBatchViaBrowserArgs = { export type SendBatchViaBrowserArgs = {
modelCardId: string modelCardId: string
@@ -466,24 +469,75 @@ export class ArchicadBridge {
} }
private async createVersion(args: CreateVersionArgs) { private async createVersion(args: CreateVersionArgs) {
const accountStore = useAccountStore() const hostAppStore = useHostAppStore()
const { accounts } = storeToRefs(accountStore) const { completeIngestionWithVersion } = useModelIngestion()
const account = accounts.value.find((acc) => acc.accountInfo.id === args.accountId) const { canCreateModelIngestion } = useCheckGraphql()
const createVersion = provideApolloClient((account as DUIAccount).client)(() => const modelCard = hostAppStore.models.find(
useMutation(createVersionMutation) (model) => model.modelCardId === args.modelCardId
) as ISenderModelCard
const canCreateIngestion = await canCreateModelIngestion(
modelCard.projectId,
modelCard.modelId,
modelCard.accountId
) )
const hostAppStore = useHostAppStore() if (canCreateIngestion.queryAvailable) {
const ingestionId = hostAppStore.activeIngestions[args.modelCardId]
const result = await createVersion.mutate({ if (!ingestionId) {
input: { hostAppStore.setNotification({
modelId: args.modelId, type: ToastNotificationType.Danger,
objectId: args.referencedObjectId, title: 'Ingestion Failed',
sourceApplication: hostAppStore.hostAppName, description: 'Ingestion ID not found to create version.'
projectId: args.projectId })
throw new Error(`Ingestion failed: Ingestion ID not found to create version.`)
} }
})
return result?.data?.versionMutations?.create?.id const res = await completeIngestionWithVersion(
modelCard,
ingestionId,
args.referencedObjectId
)
if (res?.statusData.__typename === 'ModelIngestionSuccessStatus') {
return res?.statusData.versionId
}
if (res?.statusData.__typename === 'ModelIngestionFailedStatus') {
const errorReason = res?.statusData.errorReason || 'Unknown error'
hostAppStore.setNotification({
type: ToastNotificationType.Danger,
title: 'Ingestion Failed',
description: errorReason
})
throw new Error(`Ingestion failed: ${errorReason}.`)
}
hostAppStore.setNotification({
type: ToastNotificationType.Danger,
title: 'Ingestion Error',
description: 'Ingestion status does not match expected types.'
})
throw new Error(
`Ingestion status does not match with the expected types as success or failure.`
)
} else {
const accountStore = useAccountStore()
const account = accountStore.getAccountClient(args.accountId)
const { mutate } = provideApolloClient(account)(() =>
useMutation(createVersionMutation)
)
const result = await mutate({
input: {
modelId: args.modelId,
objectId: args.referencedObjectId,
sourceApplication: args.sourceApplication || 'Archicad',
projectId: args.projectId
}
})
return result?.data?.versionMutations?.create?.id
}
} }
} }
+74 -22
View File
@@ -19,6 +19,9 @@ import type {
ReceiveViaBrowserArgs, ReceiveViaBrowserArgs,
CreateVersionArgs CreateVersionArgs
} from '~/lib/bridge/server' } from '~/lib/bridge/server'
import { useModelIngestion } from '../ingestion/composables/useModelIngestion'
import type { ISenderModelCard } from '../models/card/send'
import { useCheckGraphql } from '~/lib/core/composables/useCheckGraphql'
declare let sketchup: { declare let sketchup: {
exec: (data: Record<string, unknown>) => void exec: (data: Record<string, unknown>) => void
@@ -297,40 +300,89 @@ export class SketchupBridge extends BaseBridge {
sourceApplication: 'sketchup', sourceApplication: 'sketchup',
message: message || 'send from sketchup' message: message || 'send from sketchup'
} }
const versionId = await this.createVersion(args)
const hostAppStore = useHostAppStore() const hostAppStore = useHostAppStore()
// TODO: Alignment needed try {
hostAppStore.setModelSendResult({ const versionId = await this.createVersion(args)
modelCardId: args.modelCardId, hostAppStore.setModelSendResult({
versionId: versionId as string, modelCardId: args.modelCardId,
sendConversionResults versionId: versionId as string,
}) sendConversionResults
})
} catch (err) {
hostAppStore.setHostAppError({
message: (err as Error).message || 'Unknown error occurred',
error: (err as Error).toString(),
stackTrace: (err as Error).stack || ''
})
}
} }
public async createVersion(args: CreateVersionArgs) { public async createVersion(args: CreateVersionArgs) {
const accountStore = useAccountStore()
const hostAppStore = useHostAppStore() const hostAppStore = useHostAppStore()
const accountStore = useAccountStore()
const { accounts } = storeToRefs(accountStore) const { accounts } = storeToRefs(accountStore)
const account = accounts.value.find((acc) => acc.accountInfo.id === args.accountId) const account = accounts.value.find((acc) => acc.accountInfo.id === args.accountId)
const { completeIngestionWithVersion } = useModelIngestion()
const createVersion = provideApolloClient((account as DUIAccount).client)(() => const modelCard = hostAppStore.models.find(
useMutation(createVersionMutation) (model) => model.modelCardId === args.modelCardId
) )
// sketchup versions are provided as 2 digit. i.e. 22, 23, 24 if (!modelCard) {
// we are safe with this string concatanation for 77 years throw new Error('Model card not found') // ctor
const hostAppName = `SketchUp 20${hostAppStore.hostAppVersion}` }
const result = await createVersion.mutate({ const { canCreateModelIngestion } = useCheckGraphql()
input: { const canCreateIngestion = await canCreateModelIngestion(
modelId: args.modelId, modelCard.projectId,
objectId: args.referencedObjectId, modelCard.modelId,
sourceApplication: hostAppName, modelCard.accountId
projectId: args.projectId )
if (canCreateIngestion.queryAvailable) {
const ingestionId = hostAppStore.activeIngestions[args.modelCardId]
if (!ingestionId) {
throw new Error(`Ingestion failed: Ingestion ID not found to create version.`)
} }
})
return result?.data?.versionMutations?.create?.id const res = await completeIngestionWithVersion(
modelCard as ISenderModelCard,
ingestionId,
args.referencedObjectId
)
if (res?.statusData.__typename === 'ModelIngestionSuccessStatus') {
return res?.statusData.versionId
}
if (res?.statusData.__typename === 'ModelIngestionFailedStatus') {
throw new Error(
`Ingestion failed: ${res?.statusData.errorReason || 'Unknown error'}.`
)
}
throw new Error(
`Ingestion status does not match with the expected types as success or failure.`
)
} else {
// for the self hosters that does not have available graphql for ingestions
const createVersion = provideApolloClient((account as DUIAccount).client)(() =>
useMutation(createVersionMutation)
)
// sketchup versions are provided as 2 digit. i.e. 22, 23, 24
// we are safe with this string concatanation for 77 years
const hostAppName = `SketchUp 20${hostAppStore.hostAppVersion}`
const result = await createVersion.mutate({
input: {
modelId: args.modelId,
objectId: args.referencedObjectId,
sourceApplication: hostAppName,
projectId: args.projectId
}
})
return result?.data?.versionMutations?.create?.id
}
} }
public async create(): Promise<boolean> { public async create(): Promise<boolean> {
+51 -3
View File
@@ -33,6 +33,7 @@ type Documents = {
"\n query CanCreatePersonalProject {\n activeUser {\n permissions {\n canCreatePersonalProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": typeof types.CanCreatePersonalProjectDocument, "\n query CanCreatePersonalProject {\n activeUser {\n permissions {\n canCreatePersonalProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": typeof types.CanCreatePersonalProjectDocument,
"\n query CanCreateProjectInWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n permissions {\n canCreateProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": typeof types.CanCreateProjectInWorkspaceDocument, "\n query CanCreateProjectInWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n permissions {\n canCreateProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": typeof types.CanCreateProjectInWorkspaceDocument,
"\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n": typeof types.CanCreateModelInProjectDocument, "\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n": typeof types.CanCreateModelInProjectDocument,
"\n query CanCreateVersion($projectId: String!, $modelId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n permissions {\n canCreateVersion {\n authorized\n code\n message\n errorMessage\n }\n }\n }\n }\n }\n": typeof types.CanCreateVersionDocument,
"\n query ActiveWorkspace {\n activeUser {\n activeWorkspace {\n id\n name\n }\n }\n }\n": typeof types.ActiveWorkspaceDocument, "\n query ActiveWorkspace {\n activeUser {\n activeWorkspace {\n id\n name\n }\n }\n }\n": typeof types.ActiveWorkspaceDocument,
"\n fragment ProjectListProjectItem on Project {\n id\n name\n role\n updatedAt\n workspaceId\n workspace {\n id\n name\n slug\n role\n }\n models {\n totalCount\n }\n permissions {\n canLoad {\n authorized\n code\n message\n }\n canPublish {\n authorized\n code\n message\n }\n }\n }\n": typeof types.ProjectListProjectItemFragmentDoc, "\n fragment ProjectListProjectItem on Project {\n id\n name\n role\n updatedAt\n workspaceId\n workspace {\n id\n name\n slug\n role\n }\n models {\n totalCount\n }\n permissions {\n canLoad {\n authorized\n code\n message\n }\n canPublish {\n authorized\n code\n message\n }\n }\n }\n": typeof types.ProjectListProjectItemFragmentDoc,
"\n query ProjectListQuery($limit: Int!, $filter: UserProjectsFilter, $cursor: String) {\n activeUser {\n id\n projects(limit: $limit, filter: $filter, cursor: $cursor) {\n totalCount\n cursor\n items {\n ...ProjectListProjectItem\n }\n }\n }\n }\n": typeof types.ProjectListQueryDocument, "\n query ProjectListQuery($limit: Int!, $filter: UserProjectsFilter, $cursor: String) {\n activeUser {\n id\n projects(limit: $limit, filter: $filter, cursor: $cursor) {\n totalCount\n cursor\n items {\n ...ProjectListProjectItem\n }\n }\n }\n }\n": typeof types.ProjectListQueryDocument,
@@ -52,10 +53,17 @@ type Documents = {
"\n subscription ProjectTriggeredAutomationsStatusUpdated($projectId: String!) {\n projectTriggeredAutomationsStatusUpdated(projectId: $projectId) {\n type\n version {\n id\n }\n model {\n id\n }\n project {\n id\n }\n run {\n ...AutomationRunItem\n }\n }\n }\n": typeof types.ProjectTriggeredAutomationsStatusUpdatedDocument, "\n subscription ProjectTriggeredAutomationsStatusUpdated($projectId: String!) {\n projectTriggeredAutomationsStatusUpdated(projectId: $projectId) {\n type\n version {\n id\n }\n model {\n id\n }\n project {\n id\n }\n run {\n ...AutomationRunItem\n }\n }\n }\n": typeof types.ProjectTriggeredAutomationsStatusUpdatedDocument,
"\n subscription OnUserProjectsUpdated {\n userProjectsUpdated {\n id\n project {\n id\n visibility\n team {\n id\n role\n }\n }\n }\n }\n": typeof types.OnUserProjectsUpdatedDocument, "\n subscription OnUserProjectsUpdated {\n userProjectsUpdated {\n id\n project {\n id\n visibility\n team {\n id\n role\n }\n }\n }\n }\n": typeof types.OnUserProjectsUpdatedDocument,
"\n subscription ProjectUpdated($projectId: String!) {\n projectUpdated(id: $projectId) {\n id\n project {\n visibility\n }\n }\n }\n": typeof types.ProjectUpdatedDocument, "\n subscription ProjectUpdated($projectId: String!) {\n projectUpdated(id: $projectId) {\n id\n project {\n visibility\n }\n }\n }\n": typeof types.ProjectUpdatedDocument,
"\n subscription Subscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n": typeof types.SubscriptionDocument, "\n subscription ModelViewingSubscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n": typeof types.ModelViewingSubscriptionDocument,
"\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n": typeof types.ProjectCommentsUpdatedDocument, "\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n": typeof types.ProjectCommentsUpdatedDocument,
"\n mutation CreateModelIngestion($input: ModelIngestionCreateInput!) {\n projectMutations {\n modelIngestionMutations {\n create(input: $input) {\n id\n }\n }\n }\n }\n": typeof types.CreateModelIngestionDocument,
"\n mutation UpdateModelIngestionProgress($input: ModelIngestionUpdateInput!) {\n projectMutations {\n modelIngestionMutations {\n updateProgress(input: $input) {\n id\n }\n }\n }\n }\n": typeof types.UpdateModelIngestionProgressDocument,
"\n mutation CompleteModelIngestionWithVersion($input: ModelIngestionSuccessInput!) {\n projectMutations {\n modelIngestionMutations {\n completeWithVersion(input: $input) {\n id\n statusData {\n __typename\n ... on ModelIngestionProcessingStatus {\n status\n progressMessage\n progress\n }\n ... on ModelIngestionSuccessStatus {\n status\n versionId\n }\n ... on ModelIngestionFailedStatus {\n errorStacktrace\n errorReason\n status\n }\n ... on ModelIngestionCancelledStatus {\n cancellationMessage\n status\n }\n ... on ModelIngestionQueuedStatus {\n progressMessage\n status\n }\n }\n }\n }\n }\n }\n": typeof types.CompleteModelIngestionWithVersionDocument,
"\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 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 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 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,
}; };
const documents: Documents = { const documents: Documents = {
"\n mutation SetActiveWorkspaceMutation($slug: String) {\n activeUserMutations {\n setActiveWorkspace(slug: $slug) {\n id\n }\n }\n }\n": types.SetActiveWorkspaceMutationDocument, "\n mutation SetActiveWorkspaceMutation($slug: String) {\n activeUserMutations {\n setActiveWorkspace(slug: $slug) {\n id\n }\n }\n }\n": types.SetActiveWorkspaceMutationDocument,
@@ -77,6 +85,7 @@ const documents: Documents = {
"\n query CanCreatePersonalProject {\n activeUser {\n permissions {\n canCreatePersonalProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": types.CanCreatePersonalProjectDocument, "\n query CanCreatePersonalProject {\n activeUser {\n permissions {\n canCreatePersonalProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": types.CanCreatePersonalProjectDocument,
"\n query CanCreateProjectInWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n permissions {\n canCreateProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": types.CanCreateProjectInWorkspaceDocument, "\n query CanCreateProjectInWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n permissions {\n canCreateProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": types.CanCreateProjectInWorkspaceDocument,
"\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n": types.CanCreateModelInProjectDocument, "\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n": types.CanCreateModelInProjectDocument,
"\n query CanCreateVersion($projectId: String!, $modelId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n permissions {\n canCreateVersion {\n authorized\n code\n message\n errorMessage\n }\n }\n }\n }\n }\n": types.CanCreateVersionDocument,
"\n query ActiveWorkspace {\n activeUser {\n activeWorkspace {\n id\n name\n }\n }\n }\n": types.ActiveWorkspaceDocument, "\n query ActiveWorkspace {\n activeUser {\n activeWorkspace {\n id\n name\n }\n }\n }\n": types.ActiveWorkspaceDocument,
"\n fragment ProjectListProjectItem on Project {\n id\n name\n role\n updatedAt\n workspaceId\n workspace {\n id\n name\n slug\n role\n }\n models {\n totalCount\n }\n permissions {\n canLoad {\n authorized\n code\n message\n }\n canPublish {\n authorized\n code\n message\n }\n }\n }\n": types.ProjectListProjectItemFragmentDoc, "\n fragment ProjectListProjectItem on Project {\n id\n name\n role\n updatedAt\n workspaceId\n workspace {\n id\n name\n slug\n role\n }\n models {\n totalCount\n }\n permissions {\n canLoad {\n authorized\n code\n message\n }\n canPublish {\n authorized\n code\n message\n }\n }\n }\n": types.ProjectListProjectItemFragmentDoc,
"\n query ProjectListQuery($limit: Int!, $filter: UserProjectsFilter, $cursor: String) {\n activeUser {\n id\n projects(limit: $limit, filter: $filter, cursor: $cursor) {\n totalCount\n cursor\n items {\n ...ProjectListProjectItem\n }\n }\n }\n }\n": types.ProjectListQueryDocument, "\n query ProjectListQuery($limit: Int!, $filter: UserProjectsFilter, $cursor: String) {\n activeUser {\n id\n projects(limit: $limit, filter: $filter, cursor: $cursor) {\n totalCount\n cursor\n items {\n ...ProjectListProjectItem\n }\n }\n }\n }\n": types.ProjectListQueryDocument,
@@ -96,10 +105,17 @@ const documents: Documents = {
"\n subscription ProjectTriggeredAutomationsStatusUpdated($projectId: String!) {\n projectTriggeredAutomationsStatusUpdated(projectId: $projectId) {\n type\n version {\n id\n }\n model {\n id\n }\n project {\n id\n }\n run {\n ...AutomationRunItem\n }\n }\n }\n": types.ProjectTriggeredAutomationsStatusUpdatedDocument, "\n subscription ProjectTriggeredAutomationsStatusUpdated($projectId: String!) {\n projectTriggeredAutomationsStatusUpdated(projectId: $projectId) {\n type\n version {\n id\n }\n model {\n id\n }\n project {\n id\n }\n run {\n ...AutomationRunItem\n }\n }\n }\n": types.ProjectTriggeredAutomationsStatusUpdatedDocument,
"\n subscription OnUserProjectsUpdated {\n userProjectsUpdated {\n id\n project {\n id\n visibility\n team {\n id\n role\n }\n }\n }\n }\n": types.OnUserProjectsUpdatedDocument, "\n subscription OnUserProjectsUpdated {\n userProjectsUpdated {\n id\n project {\n id\n visibility\n team {\n id\n role\n }\n }\n }\n }\n": types.OnUserProjectsUpdatedDocument,
"\n subscription ProjectUpdated($projectId: String!) {\n projectUpdated(id: $projectId) {\n id\n project {\n visibility\n }\n }\n }\n": types.ProjectUpdatedDocument, "\n subscription ProjectUpdated($projectId: String!) {\n projectUpdated(id: $projectId) {\n id\n project {\n visibility\n }\n }\n }\n": types.ProjectUpdatedDocument,
"\n subscription Subscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n": types.SubscriptionDocument, "\n subscription ModelViewingSubscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n": types.ModelViewingSubscriptionDocument,
"\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n": types.ProjectCommentsUpdatedDocument, "\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n": types.ProjectCommentsUpdatedDocument,
"\n mutation CreateModelIngestion($input: ModelIngestionCreateInput!) {\n projectMutations {\n modelIngestionMutations {\n create(input: $input) {\n id\n }\n }\n }\n }\n": types.CreateModelIngestionDocument,
"\n mutation UpdateModelIngestionProgress($input: ModelIngestionUpdateInput!) {\n projectMutations {\n modelIngestionMutations {\n updateProgress(input: $input) {\n id\n }\n }\n }\n }\n": types.UpdateModelIngestionProgressDocument,
"\n mutation CompleteModelIngestionWithVersion($input: ModelIngestionSuccessInput!) {\n projectMutations {\n modelIngestionMutations {\n completeWithVersion(input: $input) {\n id\n statusData {\n __typename\n ... on ModelIngestionProcessingStatus {\n status\n progressMessage\n progress\n }\n ... on ModelIngestionSuccessStatus {\n status\n versionId\n }\n ... on ModelIngestionFailedStatus {\n errorStacktrace\n errorReason\n status\n }\n ... on ModelIngestionCancelledStatus {\n cancellationMessage\n status\n }\n ... on ModelIngestionQueuedStatus {\n progressMessage\n status\n }\n }\n }\n }\n }\n }\n": types.CompleteModelIngestionWithVersionDocument,
"\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 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 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 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,
}; };
/** /**
@@ -192,6 +208,10 @@ export function graphql(source: "\n query CanCreateProjectInWorkspace($workspac
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * 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 CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n"): (typeof documents)["\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n"]; export function graphql(source: "\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n"): (typeof documents)["\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\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 query CanCreateVersion($projectId: String!, $modelId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n permissions {\n canCreateVersion {\n authorized\n code\n message\n errorMessage\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query CanCreateVersion($projectId: String!, $modelId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n permissions {\n canCreateVersion {\n authorized\n code\n message\n errorMessage\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. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
@@ -271,11 +291,35 @@ export function graphql(source: "\n subscription ProjectUpdated($projectId: Str
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * 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 Subscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n"): (typeof documents)["\n subscription Subscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n"]; export function graphql(source: "\n subscription ModelViewingSubscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n"): (typeof documents)["\n subscription ModelViewingSubscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * 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 ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n"): (typeof documents)["\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n"]; export function graphql(source: "\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n"): (typeof documents)["\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\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 mutation CreateModelIngestion($input: ModelIngestionCreateInput!) {\n projectMutations {\n modelIngestionMutations {\n create(input: $input) {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n mutation CreateModelIngestion($input: ModelIngestionCreateInput!) {\n projectMutations {\n modelIngestionMutations {\n create(input: $input) {\n id\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 mutation UpdateModelIngestionProgress($input: ModelIngestionUpdateInput!) {\n projectMutations {\n modelIngestionMutations {\n updateProgress(input: $input) {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n mutation UpdateModelIngestionProgress($input: ModelIngestionUpdateInput!) {\n projectMutations {\n modelIngestionMutations {\n updateProgress(input: $input) {\n id\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 mutation CompleteModelIngestionWithVersion($input: ModelIngestionSuccessInput!) {\n projectMutations {\n modelIngestionMutations {\n completeWithVersion(input: $input) {\n id\n statusData {\n __typename\n ... on ModelIngestionProcessingStatus {\n status\n progressMessage\n progress\n }\n ... on ModelIngestionSuccessStatus {\n status\n versionId\n }\n ... on ModelIngestionFailedStatus {\n errorStacktrace\n errorReason\n status\n }\n ... on ModelIngestionCancelledStatus {\n cancellationMessage\n status\n }\n ... on ModelIngestionQueuedStatus {\n progressMessage\n status\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n mutation CompleteModelIngestionWithVersion($input: ModelIngestionSuccessInput!) {\n projectMutations {\n modelIngestionMutations {\n completeWithVersion(input: $input) {\n id\n statusData {\n __typename\n ... on ModelIngestionProcessingStatus {\n status\n progressMessage\n progress\n }\n ... on ModelIngestionSuccessStatus {\n status\n versionId\n }\n ... on ModelIngestionFailedStatus {\n errorStacktrace\n errorReason\n status\n }\n ... on ModelIngestionCancelledStatus {\n cancellationMessage\n status\n }\n ... on ModelIngestionQueuedStatus {\n progressMessage\n status\n }\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 mutation FailModelIngestionWithError($input: ModelIngestionFailedInput!) {\n projectMutations {\n modelIngestionMutations {\n failWithError(input: $input) {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n mutation FailModelIngestionWithError($input: ModelIngestionFailedInput!) {\n projectMutations {\n modelIngestionMutations {\n failWithError(input: $input) {\n id\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 mutation FailModelIngestionWithCancel($input: ModelIngestionCancelledInput!) {\n projectMutations {\n modelIngestionMutations {\n failWithCancel(input: $input) {\n id\n }\n }\n }\n }\n"): (typeof documents)["\n mutation FailModelIngestionWithCancel($input: ModelIngestionCancelledInput!) {\n projectMutations {\n modelIngestionMutations {\n failWithCancel(input: $input) {\n id\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 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. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
@@ -284,6 +328,10 @@ export function graphql(source: "\n fragment IssuesItem on Issue {\n id\n
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * 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 IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\n }\n }\n }\n }\n"): (typeof documents)["\n query IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\n }\n }\n }\n }\n"]; export function graphql(source: "\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 documents)["\n query IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\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 WorkspacePlanUsageUpdated($input: WorkspacePlanUsageSubscriptionInput!) {\n workspacePlanUsageUpdated(input: $input)\n }\n"): (typeof documents)["\n subscription WorkspacePlanUsageUpdated($input: WorkspacePlanUsageSubscriptionInput!) {\n workspacePlanUsageUpdated(input: $input)\n }\n"];
export function graphql(source: string) { export function graphql(source: string) {
return (documents as any)[source] ?? {}; return (documents as any)[source] ?? {};
File diff suppressed because one or more lines are too long
+71
View File
@@ -0,0 +1,71 @@
import { canCreateVersionQuery } from '~/lib/graphql/mutationsAndQueries'
import { canCreateModelIngestionQuery } from '~/lib/ingestion/graphql/queries'
import { useAccountStore } from '~/store/accounts'
// use this composable whenever we need to check against available graphqls over servers
export function useCheckGraphql() {
/**
* Checks the ingestions available for the server,
* if available, returns with respond by appending `queryAvailable = true`
* otherwise, returns fake result object with `queryAvailable = false`
*/
const canCreateModelIngestion = async (
projectId: string,
modelId: string,
accountId: string
) => {
const accountsStore = useAccountStore()
const client = accountsStore.getAccountClient(accountId)
try {
const result = await client.query({
query: canCreateModelIngestionQuery,
variables: {
projectId,
modelId
},
fetchPolicy: 'network-only'
})
return {
...result.data.project.model.permissions.canCreateIngestion,
queryAvailable: true
}
} catch {
return { queryAvailable: false, authorized: false, message: undefined }
}
}
/**
* Checks if user can create a version for the given model.
* Used to validate before starting a publish operation.
*/
const canCreateVersion = async (
projectId: string,
modelId: string,
accountId: string
) => {
const accountsStore = useAccountStore()
const client = accountsStore.getAccountClient(accountId)
try {
const result = await client.query({
query: canCreateVersionQuery,
variables: {
projectId,
modelId
},
fetchPolicy: 'network-only'
})
return result.data.project.model.permissions.canCreateVersion
} catch (error) {
// If we can't check, allow the attempt - server will reject if not allowed
console.error('Failed to check canCreateVersion:', error)
return { authorized: true, message: null }
}
}
return {
canCreateVersion,
canCreateModelIngestion
}
}
+18 -1
View File
@@ -246,6 +246,23 @@ export const canCreateModelInProjectQuery = graphql(`
} }
`) `)
export const canCreateVersionQuery = graphql(`
query CanCreateVersion($projectId: String!, $modelId: String!) {
project(id: $projectId) {
model(id: $modelId) {
permissions {
canCreateVersion {
authorized
code
message
errorMessage
}
}
}
}
}
`)
export const activeWorkspaceQuery = graphql(` export const activeWorkspaceQuery = graphql(`
query ActiveWorkspace { query ActiveWorkspace {
activeUser { activeUser {
@@ -606,7 +623,7 @@ export const projectUpdatedSubscription = graphql(`
`) `)
export const modelViewingSubscription = graphql(` export const modelViewingSubscription = graphql(`
subscription Subscription($target: ViewerUpdateTrackingTarget!) { subscription ModelViewingSubscription($target: ViewerUpdateTrackingTarget!) {
viewerUserActivityBroadcasted(target: $target) { viewerUserActivityBroadcasted(target: $target) {
userName userName
userId userId
@@ -0,0 +1,227 @@
import { provideApolloClient, useMutation } from '@vue/apollo-composable'
import { useAccountStore } from '~/store/accounts'
import { useHostAppStore } from '~/store/hostApp'
import {
completeModelIngestionWithVersion,
createModelIngestion,
updateModelIngestionProgress,
failModelIngestionWithError,
failModelIngestionWithCancel
} from '../graphql/mutations'
import type { SourceDataInput } from '~~/lib/common/generated/gql/graphql'
import type { ISenderModelCard } from '~/lib/models/card/send'
import { storeToRefs } from 'pinia'
import { ToastNotificationType } from '@speckle/ui-components'
/**
* New way of creating versions.
* It is essential for server to track limits on versions.
* The flow is as follows:
* 0. Check if the user has enough limits to create a new version (this is handled outside of this composable)
* 1. Start a new ingestion
* 2. Update the ingestion with the new data when connector throws progress via 'setModelProgress' event
* 3. Complete the version with the root object id that passed by connector or server/sketchup bridges in JS
*/
export const useModelIngestion = () => {
const store = useHostAppStore()
const accountStore = useAccountStore()
const startIngestion = async (
senderModelCard: ISenderModelCard,
progressMessage: string,
sourceData: SourceDataInput
) => {
const { activeIngestions } = storeToRefs(store)
const client = accountStore.getAccountClient(senderModelCard.accountId)
const { mutate } = provideApolloClient(client)(() =>
useMutation(createModelIngestion)
)
const res = await mutate({
input: {
projectId: senderModelCard.projectId,
modelId: senderModelCard.modelId,
progressMessage,
sourceData,
maxIdleTimeoutSeconds: 7200 // 2h
}
})
if (res?.errors?.length) {
const msg = res.errors[0].message
store.setNotification({
type: ToastNotificationType.Danger,
title: 'Ingestion Error',
description: msg
})
throw new Error(msg)
}
const ingestionId = res?.data?.projectMutations.modelIngestionMutations.create.id
if (ingestionId) {
activeIngestions.value[senderModelCard.modelCardId] = ingestionId
}
return res?.data?.projectMutations.modelIngestionMutations.create
}
const updateIngestion = async (
senderModelCard: ISenderModelCard,
ingestionId: string,
progressMessage: string,
progress?: number
) => {
const client = accountStore.getAccountClient(senderModelCard.accountId)
const { mutate } = provideApolloClient(client)(() =>
useMutation(updateModelIngestionProgress)
)
const res = await mutate({
input: {
projectId: senderModelCard.projectId,
ingestionId,
progressMessage,
progress
}
})
if (res?.errors?.length) {
const msg = res.errors[0].message
store.setNotification({
type: ToastNotificationType.Danger,
title: 'Ingestion Error',
description: msg
})
throw new Error(msg)
}
return res?.data?.projectMutations.modelIngestionMutations.updateProgress
}
const failIngestion = async (
senderModelCard: ISenderModelCard,
ingestionId: string,
errorReason: string,
errorStacktrace?: string
) => {
const client = accountStore.getAccountClient(senderModelCard.accountId)
const { mutate } = provideApolloClient(client)(() =>
useMutation(failModelIngestionWithError)
)
const res = await mutate({
input: {
projectId: senderModelCard.projectId,
ingestionId,
errorReason,
errorStacktrace
}
})
if (res?.errors?.length) {
const msg = res.errors[0].message
store.setNotification({
type: ToastNotificationType.Danger,
title: 'Ingestion Error',
description: msg
})
throw new Error(msg)
}
const { activeIngestions } = storeToRefs(store)
// clean the failed ingestion
activeIngestions.value = Object.fromEntries(
Object.entries(activeIngestions.value).filter(
([key]) => key !== senderModelCard.modelCardId
)
)
}
const cancelIngestion = async (
senderModelCard: ISenderModelCard,
ingestionId: string,
cancellationMessage: string = 'Cancelled by user'
) => {
const client = accountStore.getAccountClient(senderModelCard.accountId)
const { mutate } = provideApolloClient(client)(() =>
useMutation(failModelIngestionWithCancel)
)
const res = await mutate({
input: {
projectId: senderModelCard.projectId,
ingestionId,
cancellationMessage
}
})
if (res?.errors?.length) {
const msg = res.errors[0].message
store.setNotification({
type: ToastNotificationType.Danger,
title: 'Ingestion Error',
description: msg
})
throw new Error(msg)
}
const { activeIngestions } = storeToRefs(store)
// clean the cancelled ingestion
activeIngestions.value = Object.fromEntries(
Object.entries(activeIngestions.value).filter(
([key]) => key !== senderModelCard.modelCardId
)
)
}
const completeIngestionWithVersion = async (
senderModelCard: ISenderModelCard,
ingestionId: string,
rootObjectId: string
) => {
const client = accountStore.getAccountClient(senderModelCard.accountId)
const { mutate } = provideApolloClient(client)(() =>
useMutation(completeModelIngestionWithVersion)
)
const res = await mutate({
input: {
projectId: senderModelCard.projectId,
ingestionId,
rootObjectId
}
})
if (res?.errors?.length) {
const msg = res.errors[0].message
store.setNotification({
type: ToastNotificationType.Danger,
title: 'Ingestion Error',
description: msg
})
throw new Error(msg)
}
const { activeIngestions } = storeToRefs(store)
// clean the completed ingestion
activeIngestions.value = Object.fromEntries(
Object.entries(activeIngestions.value).filter(
([key]) => key !== senderModelCard.modelCardId
)
)
return res?.data?.projectMutations.modelIngestionMutations.completeWithVersion
}
return {
startIngestion,
updateIngestion,
failIngestion,
cancelIngestion,
completeIngestionWithVersion
}
}
+86
View File
@@ -0,0 +1,86 @@
import { graphql } from '~~/lib/common/generated/gql'
export const createModelIngestion = graphql(`
mutation CreateModelIngestion($input: ModelIngestionCreateInput!) {
projectMutations {
modelIngestionMutations {
create(input: $input) {
id
}
}
}
}
`)
export const updateModelIngestionProgress = graphql(`
mutation UpdateModelIngestionProgress($input: ModelIngestionUpdateInput!) {
projectMutations {
modelIngestionMutations {
updateProgress(input: $input) {
id
}
}
}
}
`)
export const completeModelIngestionWithVersion = graphql(`
mutation CompleteModelIngestionWithVersion($input: ModelIngestionSuccessInput!) {
projectMutations {
modelIngestionMutations {
completeWithVersion(input: $input) {
id
statusData {
__typename
... on ModelIngestionProcessingStatus {
status
progressMessage
progress
}
... on ModelIngestionSuccessStatus {
status
versionId
}
... on ModelIngestionFailedStatus {
errorStacktrace
errorReason
status
}
... on ModelIngestionCancelledStatus {
cancellationMessage
status
}
... on ModelIngestionQueuedStatus {
progressMessage
status
}
}
}
}
}
}
`)
export const failModelIngestionWithError = graphql(`
mutation FailModelIngestionWithError($input: ModelIngestionFailedInput!) {
projectMutations {
modelIngestionMutations {
failWithError(input: $input) {
id
}
}
}
}
`)
export const failModelIngestionWithCancel = graphql(`
mutation FailModelIngestionWithCancel($input: ModelIngestionCancelledInput!) {
projectMutations {
modelIngestionMutations {
failWithCancel(input: $input) {
id
}
}
}
}
`)
+17
View File
@@ -0,0 +1,17 @@
import { graphql } from '~~/lib/common/generated/gql'
export const canCreateModelIngestionQuery = graphql(`
query CanCreateIngestion($modelId: String!, $projectId: String!) {
project(id: $projectId) {
model(id: $modelId) {
permissions {
canCreateIngestion {
authorized
code
message
}
}
}
}
}
`)
+1
View File
@@ -15,6 +15,7 @@ export type ModelCardNotification = {
name: string name: string
tooltipText?: string tooltipText?: string
action: () => void action: () => void
disabled?: boolean
} }
/** /**
* If set, will display a view report button next to cta * If set, will display a view report button next to cta
+7
View File
@@ -0,0 +1,7 @@
import { graphql } from '~~/lib/common/generated/gql'
export const workspacePlanUsageUpdatedSubscription = graphql(`
subscription WorkspacePlanUsageUpdated($input: WorkspacePlanUsageSubscriptionInput!) {
workspacePlanUsageUpdated(input: $input)
}
`)
+9
View File
@@ -294,6 +294,14 @@ export const useAccountStore = defineStore('accountStore', () => {
if (accountMatchWithServerUrl) return accountMatchWithServerUrl if (accountMatchWithServerUrl) return accountMatchWithServerUrl
} }
const getAccountClient = (accountId: string) => {
return (
accounts.value.find(
(account) => account.accountInfo.id === accountId
) as DUIAccount
).client
}
const provideClients = () => { const provideClients = () => {
provideApolloClients(apolloClients) provideApolloClients(apolloClients)
} }
@@ -320,6 +328,7 @@ export const useAccountStore = defineStore('accountStore', () => {
return { return {
isLoading, isLoading,
accounts, accounts,
getAccountClient,
defaultAccount, defaultAccount,
activeAccount, activeAccount,
userSelectedAccount, userSelectedAccount,
+158 -22
View File
@@ -14,6 +14,7 @@ import type {
SendFilterSelect SendFilterSelect
} from '~/lib/models/card/send' } from '~/lib/models/card/send'
import type { ToastNotification } from '@speckle/ui-components' import type { ToastNotification } from '@speckle/ui-components'
import { ToastNotificationType } from '@speckle/ui-components'
import type { Nullable } from '@speckle/shared' import type { Nullable } from '@speckle/shared'
import type { HostAppError } from '~/lib/bridge/errorHandler' import type { HostAppError } from '~/lib/bridge/errorHandler'
import type { ConversionResult } from '~/lib/conversions/conversionResult' import type { ConversionResult } from '~/lib/conversions/conversionResult'
@@ -28,6 +29,8 @@ import {
import { provideApolloClient, useMutation } from '@vue/apollo-composable' import { provideApolloClient, useMutation } from '@vue/apollo-composable'
import { createVersionMutation } from '~/lib/graphql/mutationsAndQueries' import { createVersionMutation } from '~/lib/graphql/mutationsAndQueries'
import type { BaseBridge } from '~/lib/bridge/base' import type { BaseBridge } from '~/lib/bridge/base'
import { useModelIngestion } from '~/lib/ingestion/composables/useModelIngestion'
import { useCheckGraphql } from '~/lib/core/composables/useCheckGraphql'
export type ProjectModelGroup = { export type ProjectModelGroup = {
projectId: string projectId: string
@@ -43,7 +46,13 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
const { $openUrl } = useNuxtApp() const { $openUrl } = useNuxtApp()
const accountsStore = useAccountStore() const accountsStore = useAccountStore()
const { checkUpdate } = useUpdateConnector() const { checkUpdate } = useUpdateConnector()
const {
startIngestion,
updateIngestion,
failIngestion,
cancelIngestion,
completeIngestionWithVersion
} = useModelIngestion()
const isDistributedBySpeckle = ref<boolean>(true) const isDistributedBySpeckle = ref<boolean>(true)
const latestAvailableVersion = ref<Version | null>(null) const latestAvailableVersion = ref<Version | null>(null)
@@ -65,6 +74,9 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
// Different host apps can have different kind of ISendFilterSelect send filters, and we collect them here to generalize the component we use in `ListSelect` // Different host apps can have different kind of ISendFilterSelect send filters, and we collect them here to generalize the component we use in `ListSelect`
const availableSelectSendFilters = ref<Record<string, SendFilterSelect>>({}) const availableSelectSendFilters = ref<Record<string, SendFilterSelect>>({})
// kvp for modelCardId - ingestionId
const activeIngestions = ref<Record<string, string>>({})
const dismissNotification = () => { const dismissNotification = () => {
currentNotification.value = null currentNotification.value = null
} }
@@ -95,6 +107,11 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
isDistributedBySpeckle.value = val isDistributedBySpeckle.value = val
} }
const shouldHandleIngestion = computed(() => {
const hostAppsThatUsesDUIForGraphql = ['sketchup', 'archicad', 'Vectorworks']
return hostAppsThatUsesDUIForGraphql.includes(hostAppName.value as string)
})
/** /**
* Model Card Operations * Model Card Operations
*/ */
@@ -281,20 +298,59 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
const account = accountStore.accounts.find( const account = accountStore.accounts.find(
(acc) => acc.accountInfo.id === args.accountId (acc) => acc.accountInfo.id === args.accountId
) )
try {
const createVersion = provideApolloClient((account as DUIAccount).client)(() => // Check if we have an ingestion ID for this model.
useMutation(createVersionMutation) // If so, we are in the "New Business Model" flow and should use completeIngestionWithVersion.
) const modelCard = documentModelStore.value.models.find(
await createVersion.mutate({ (m) => m.modelId === args.modelId && m.projectId === args.projectId
input: { ) as ISenderModelCard
modelId: args.modelId,
objectId: args.referencedObjectId, const { canCreateModelIngestion } = useCheckGraphql()
sourceApplication: args.sourceApplication, const canCreateIngestion = await canCreateModelIngestion(
projectId: args.projectId args.projectId,
args.modelId,
args.accountId
)
if (canCreateIngestion.queryAvailable) {
const ingestionId = modelCard
? activeIngestions.value[modelCard.modelCardId]
: undefined
if (ingestionId && modelCard) {
try {
await completeIngestionWithVersion(
modelCard,
ingestionId,
args.referencedObjectId
)
} catch (err) {
console.error(`completeIngestionWithVersion failed: ${err}`)
} }
}) } else {
} catch (err) { setNotification({
console.error(`triggerCreateVersion is failed: ${err}`) type: ToastNotificationType.Danger,
title: 'Publish Error',
description: 'Could not complete publish: Ingestion ID missing.'
})
}
} else {
// Fallback to legacy flow (Old Server)
try {
const createVersion = provideApolloClient((account as DUIAccount).client)(() =>
useMutation(createVersionMutation)
)
await createVersion.mutate({
input: {
modelId: args.modelId,
objectId: args.referencedObjectId,
sourceApplication: args.sourceApplication,
projectId: args.projectId
}
})
} catch (err) {
console.error(`triggerCreateVersion is failed: ${err}`)
}
} }
}) })
@@ -318,10 +374,57 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
* Tells the host app to start sending a specific model card. This will reach inside the host application. * Tells the host app to start sending a specific model card. This will reach inside the host application.
* @param modelId * @param modelId
*/ */
const sendModel = (modelCardId: string, actionSource: string) => { const sendModel = async (modelCardId: string, actionSource: string) => {
const model = documentModelStore.value.models.find( const model = documentModelStore.value.models.find(
(m) => m.modelCardId === modelCardId (m) => m.modelCardId === modelCardId
) as ISenderModelCard ) as ISenderModelCard
const { canCreateModelIngestion, canCreateVersion } = useCheckGraphql()
const canCreateIngestion = await canCreateModelIngestion(
model.projectId,
model.modelId,
model.accountId
)
// for the connectors that don't have SDK to handle graqhql
if (shouldHandleIngestion.value && canCreateIngestion.queryAvailable) {
const sourceData = {
sourceApplicationSlug: hostAppName.value || 'unknown',
sourceApplicationVersion: hostAppVersion.value?.toString() || 'unknown'
}
if (canCreateIngestion.authorized) {
await startIngestion(model, 'Starting to publish', sourceData)
model.progress = { status: 'Converting the objects...' }
} else {
setNotification({
type: ToastNotificationType.Warning,
title: 'Cannot publish',
description: canCreateIngestion.message
})
return
}
} else {
// for the self hosters that does not have available graphql for ingestions
const canCreate = await canCreateVersion(
model.projectId,
model.modelId,
model.accountId
)
if (!canCreate.authorized) {
setNotification({
type: ToastNotificationType.Warning,
title: 'Cannot publish',
description: canCreate.message || 'Workspace limits have been reached'
})
return
}
}
model.latestCreatedVersionId = undefined
model.error = undefined
model.progress = { status: 'Starting to send...' }
model.expired = false
model.report = undefined
if (model.expired) { if (model.expired) {
// user sends via "Update" button // user sends via "Update" button
void trackEvent( void trackEvent(
@@ -346,11 +449,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
model.accountId model.accountId
) )
} }
model.latestCreatedVersionId = undefined
model.error = undefined
model.progress = { status: 'Starting to send...' }
model.expired = false
model.report = undefined
// You should stop asking why if you saw anything related autocad.. // You should stop asking why if you saw anything related autocad..
// It solves the press "escape" issue. // It solves the press "escape" issue.
// Because probably we don't give enough time to acad complete it's previos task and it stucks. // Because probably we don't give enough time to acad complete it's previos task and it stucks.
@@ -377,6 +476,14 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
model.error = undefined model.error = undefined
void trackEvent('DUI3 Action', { name: 'Send Cancel' }, model.accountId) void trackEvent('DUI3 Action', { name: 'Send Cancel' }, model.accountId)
model.latestCreatedVersionId = undefined model.latestCreatedVersionId = undefined
// Cancel the ingestion if applicable
if (shouldHandleIngestion.value) {
const ingestionId = activeIngestions.value[modelCardId]
if (ingestionId) {
await cancelIngestion(model, ingestionId, 'Cancelled by user')
}
}
} }
app.$sendBinding?.on('setModelsExpired', (modelCardIds) => { app.$sendBinding?.on('setModelsExpired', (modelCardIds) => {
@@ -479,7 +586,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
app.$receiveBinding?.on('setModelReceiveResult', setModelReceiveResult) app.$receiveBinding?.on('setModelReceiveResult', setModelReceiveResult)
// GENERIC STUFF // GENERIC STUFF
const handleModelProgressEvents = (args: { const handleModelProgressEvents = async (args: {
modelCardId: string modelCardId: string
progress?: ModelCardProgress progress?: ModelCardProgress
}) => { }) => {
@@ -487,9 +594,24 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
(m) => m.modelCardId === args.modelCardId (m) => m.modelCardId === args.modelCardId
) as IModelCard ) as IModelCard
model.progress = args.progress model.progress = args.progress
if (
model.typeDiscriminator.includes('SenderModelCard') &&
shouldHandleIngestion.value // for the connectors that don't have SDK to handle graqhql
) {
const ingestionId = activeIngestions.value[args.modelCardId]
if (ingestionId) {
await updateIngestion(
model,
ingestionId,
args.progress?.status || 'Progressing',
args.progress?.progress || 0
)
}
}
} }
const setModelError = (args: { const setModelError = async (args: {
modelCardId: string modelCardId: string
error: string | { errorMessage: string; dismissible?: boolean } error: string | { errorMessage: string; dismissible?: boolean }
}) => { }) => {
@@ -505,6 +627,19 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
dismissible: boolean dismissible: boolean
} }
} }
// Fail the ingestion if applicable
if (
model.typeDiscriminator.includes('SenderModelCard') &&
shouldHandleIngestion.value
) {
const ingestionId = activeIngestions.value[args.modelCardId]
if (ingestionId) {
const errorMessage =
typeof args.error === 'string' ? args.error : args.error.errorMessage
await failIngestion(model as ISenderModelCard, ingestionId, errorMessage)
}
}
} }
// NOTE: all bindings that need to send these model events should register. // NOTE: all bindings that need to send these model events should register.
@@ -750,6 +885,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
hostAppName, hostAppName,
hostAppVersion, hostAppVersion,
connectorVersion, connectorVersion,
activeIngestions,
isConnectorUpToDate, isConnectorUpToDate,
latestAvailableVersion, latestAvailableVersion,
documentInfo, documentInfo,