Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 553d3c3d7c | |||
| b026659460 | |||
| 009cc77bab | |||
| 82b9c3a545 | |||
| 04faa51c45 | |||
| 4fdf61bf1e | |||
| d870fdcb30 | |||
| 0f7c902711 | |||
| 9ab8f311c8 | |||
| c52caa0838 | |||
| 889ec0e3be | |||
| 80a6d5501b | |||
| 92f6d61c5d | |||
| 244e4236a5 | |||
| a8b802b7e3 | |||
| 6fc3df4a0d | |||
| f47f19c02d | |||
| 85f806368a | |||
| 35ddce1f90 | |||
| a37b3389d6 | |||
| ed4aa92ce1 | |||
| 60f3bed254 | |||
| 2f412df64a | |||
| c7e0929eca | |||
| eef0a59719 |
@@ -27,16 +27,17 @@
|
||||
>
|
||||
{{ notification.secondaryCta.name }}
|
||||
</FormButton>
|
||||
<FormButton
|
||||
v-if="notification.cta"
|
||||
v-tippy="notification.cta.tooltipText"
|
||||
size="sm"
|
||||
color="primary"
|
||||
full-width
|
||||
@click.stop="notification.cta?.action"
|
||||
>
|
||||
{{ notification.cta.name }}
|
||||
</FormButton>
|
||||
<div v-if="notification.cta" v-tippy="notification.cta.tooltipText">
|
||||
<FormButton
|
||||
:disabled="notification.cta.disabled"
|
||||
size="sm"
|
||||
color="primary"
|
||||
full-width
|
||||
@click.stop="notification.cta?.action"
|
||||
>
|
||||
{{ notification.cta.name }}
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -49,9 +49,26 @@
|
||||
>
|
||||
<span class="">Update</span>
|
||||
</FormButton> -->
|
||||
<div class="text-[8px] text-foreground-disabled max-[150px]:hidden">
|
||||
|
||||
<div
|
||||
class="text-[8px] text-foreground-disabled max-[150px]:hidden"
|
||||
:class="{ 'mr-2': !hostAppStore.isDistributedBySpeckle }"
|
||||
>
|
||||
{{ hostAppStore.connectorVersion }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!hostAppStore.isDistributedBySpeckle && hostAppStore.hostAppName"
|
||||
v-tippy="
|
||||
`${hostAppStore.hostAppName
|
||||
.charAt(0)
|
||||
.toUpperCase()}${hostAppStore.hostAppName.slice(
|
||||
1
|
||||
)} connector is not distributed by Speckle.`
|
||||
"
|
||||
class="text-xs text-foreground-disabled max-[150px]:hidden mr-1"
|
||||
>
|
||||
<CommonBadge color="secondary">Partner</CommonBadge>
|
||||
</div>
|
||||
<HeaderButton
|
||||
v-if="hostAppStore.isDistributedBySpeckle"
|
||||
v-tippy="'Documentation and help'"
|
||||
@@ -65,7 +82,11 @@
|
||||
class="w-4 text-foreground-disabled group-hover:text-foreground-2"
|
||||
/>
|
||||
</HeaderButton>
|
||||
<HeaderButton v-tippy="'Send us feedback'" @click="openFeedbackDialog()">
|
||||
<HeaderButton
|
||||
v-if="hostAppStore.isDistributedBySpeckle"
|
||||
v-tippy="'Send us feedback'"
|
||||
@click="openFeedbackDialog()"
|
||||
>
|
||||
<ChatBubbleLeftIcon
|
||||
class="w-4 text-foreground-disabled group-hover:text-foreground-2"
|
||||
/>
|
||||
@@ -106,8 +127,9 @@ const { $intercom } = useNuxtApp()
|
||||
|
||||
const openFeedbackDialog = () => {
|
||||
if (
|
||||
hostAppStore.hostAppName?.toLowerCase() === 'revit' &&
|
||||
hostAppStore.hostAppVersion?.includes('2022')
|
||||
(hostAppStore.hostAppName?.toLowerCase() === 'revit' &&
|
||||
hostAppStore.hostAppVersion?.includes('2022')) ||
|
||||
!hostAppStore.isDistributedBySpeckle
|
||||
) {
|
||||
showFeedbackDialog.value = true
|
||||
} else {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<IssuesSelectedItem :issue="selectedIssue" />
|
||||
<IssuesSelectedItem :issue="selectedIssue" :model-card="modelCard" />
|
||||
</div>
|
||||
|
||||
<div v-if="!selectedIssue" class="flex flex-col space-y-2">
|
||||
|
||||
@@ -6,9 +6,21 @@
|
||||
</div>
|
||||
<IssuesBasicTiptap
|
||||
v-if="issue.description?.doc"
|
||||
class="border rounded-xl border-outline-3"
|
||||
class="border rounded-xl border-outline-3 w-full"
|
||||
:doc="issue.description?.doc"
|
||||
></IssuesBasicTiptap>
|
||||
|
||||
<div v-if="app.$parametersBinding && hasObjectDeltas" class="w-full pt-1 pb-1">
|
||||
<FormButton
|
||||
class="w-full justify-center"
|
||||
:disabled="isApplying || isResolved"
|
||||
@click="applyChanges"
|
||||
>
|
||||
{{
|
||||
isApplying ? 'Applying...' : isResolved ? 'Issue resolved' : 'Apply changes'
|
||||
}}
|
||||
</FormButton>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
||||
<IssuesStatusIcon :status="issue.status" show-label />
|
||||
<IssuesPriorityIcon :priority="issue.priority" show-label />
|
||||
@@ -84,14 +96,85 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { ResourceMetaType, IssueStatus } from '~/lib/common/generated/gql/graphql'
|
||||
import { issueResourceMetaSearchQuery } from '~/lib/issues/graphql/queries'
|
||||
import type { IssuesItemFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import type { IModelCard } from '~/lib/models/card'
|
||||
import dayjs from 'dayjs'
|
||||
import { Calendar } from 'lucide-vue-next'
|
||||
|
||||
const props = defineProps<{
|
||||
issue: IssuesItemFragment
|
||||
modelCard: IModelCard
|
||||
}>()
|
||||
|
||||
const app = useNuxtApp()
|
||||
const isApplying = ref(false)
|
||||
|
||||
const isResolved = computed(() => {
|
||||
return props.issue.status === IssueStatus.Resolved
|
||||
})
|
||||
|
||||
const queryVariables = computed(() => ({
|
||||
workspaceId: props.modelCard.workspaceId!,
|
||||
projectId: props.modelCard.projectId,
|
||||
resourceType: ResourceMetaType.Issue,
|
||||
resourceId: props.issue.id,
|
||||
metaType: 'objectDeltas'
|
||||
}))
|
||||
|
||||
const queryOptions = computed(() => ({
|
||||
fetchPolicy: 'cache-and-network' as const,
|
||||
enabled: !!props.modelCard.workspaceId,
|
||||
clientId: props.modelCard.accountId
|
||||
}))
|
||||
|
||||
const { result: resourceMetaResult } = useQuery(
|
||||
issueResourceMetaSearchQuery,
|
||||
queryVariables,
|
||||
queryOptions
|
||||
)
|
||||
|
||||
const hasObjectDeltas = computed<boolean>(() => {
|
||||
const metadata = resourceMetaResult.value?.resourceMetaSearch
|
||||
return Array.isArray(metadata) && metadata.length > 0
|
||||
})
|
||||
|
||||
const objectDeltasPayload = computed<unknown>(() => {
|
||||
if (!hasObjectDeltas.value) return null
|
||||
const metadata = resourceMetaResult.value?.resourceMetaSearch
|
||||
|
||||
if (Array.isArray(metadata) && metadata.length > 0) {
|
||||
return metadata[0]?.data as unknown
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
const applyChanges = async () => {
|
||||
if (!objectDeltasPayload.value) return
|
||||
|
||||
isApplying.value = true
|
||||
try {
|
||||
const payload =
|
||||
typeof objectDeltasPayload.value === 'string'
|
||||
? objectDeltasPayload.value
|
||||
: JSON.stringify(objectDeltasPayload.value)
|
||||
|
||||
if (app.$parametersBinding) {
|
||||
await app.$parametersBinding.update(payload)
|
||||
} else {
|
||||
console.warn('IParametersBinding not available in this host app')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to apply changes:', error)
|
||||
} finally {
|
||||
isApplying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const formattedDate = computed((): string | null => {
|
||||
try {
|
||||
const date = props.issue.dueDate ? dayjs(props.issue.dueDate).toDate() : null
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
:icon-left="Bars3Icon"
|
||||
hide-text
|
||||
size="sm"
|
||||
:disabled="!!props.modelCard.progress"
|
||||
@click.stop="openModelCardActionsDialog = true"
|
||||
/>
|
||||
<CommonDialog
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
>
|
||||
<div v-if="modelData" class="relative px-1 py-1">
|
||||
<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 -->
|
||||
<FormButton
|
||||
v-tippy="buttonTooltip"
|
||||
color="outline"
|
||||
:icon-left="
|
||||
modelCard.progress
|
||||
@@ -19,7 +21,9 @@
|
||||
"
|
||||
hide-text
|
||||
class=""
|
||||
:disabled="!canEdit || isSettingsMissing"
|
||||
:disabled="
|
||||
(!canEdit || isSettingsMissing || ctaDisabled) && !modelCard.progress
|
||||
"
|
||||
@click.stop="$emit('manual-publish-or-load')"
|
||||
></FormButton>
|
||||
</div>
|
||||
@@ -260,11 +264,18 @@ const store = useHostAppStore()
|
||||
const accStore = useAccountStore()
|
||||
const { trackEvent } = useMixpanel()
|
||||
|
||||
const props = defineProps<{
|
||||
modelCard: IModelCard
|
||||
project: ProjectModelGroup
|
||||
canEdit: boolean
|
||||
}>()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelCard: IModelCard
|
||||
project: ProjectModelGroup
|
||||
canEdit: boolean
|
||||
ctaDisabled?: boolean
|
||||
ctaDisabledMessage?: string
|
||||
}>(),
|
||||
{
|
||||
ctaDisabled: false
|
||||
}
|
||||
)
|
||||
|
||||
defineEmits<{
|
||||
(e: 'manual-publish-or-load'): void
|
||||
@@ -275,11 +286,9 @@ const isSender = computed(() => {
|
||||
})
|
||||
|
||||
const buttonTooltip = computed(() => {
|
||||
return props.modelCard.progress
|
||||
? 'Cancel'
|
||||
: isSender.value
|
||||
? 'Publish model'
|
||||
: 'Load selected version'
|
||||
if (props.modelCard.progress) return 'Cancel'
|
||||
if (props.ctaDisabled) return props.ctaDisabledMessage
|
||||
return isSender.value ? 'Publish model' : 'Load selected version'
|
||||
})
|
||||
|
||||
const projectAccount = computed(() =>
|
||||
|
||||
+98
-25
@@ -4,6 +4,8 @@
|
||||
:model-card="modelCard"
|
||||
:project="project"
|
||||
:can-edit="canEdit"
|
||||
:cta-disabled="ctaDisabled"
|
||||
:cta-disabled-message="ctaDisabledMessage"
|
||||
@manual-publish-or-load="sendOrCancel"
|
||||
>
|
||||
<div class="flex max-[275px]:w-full overflow-hidden my-2">
|
||||
@@ -17,7 +19,6 @@
|
||||
full-width
|
||||
@click.stop="openFilterDialog = true"
|
||||
>
|
||||
<!-- Sending -->
|
||||
<span class="font-bold">{{ modelCard.sendFilter?.name }}: </span>
|
||||
<span class="truncate">{{ modelCard.sendFilter?.summary }}</span>
|
||||
</FormButton>
|
||||
@@ -31,13 +32,23 @@
|
||||
<FilterListSelect :filter="modelCard.sendFilter" @update:filter="updateFilter" />
|
||||
|
||||
<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"
|
||||
:disabled="isSaveDisabled"
|
||||
@click.stop="saveFilter()"
|
||||
>
|
||||
Save
|
||||
</FormButton>
|
||||
<FormButton size="sm" @click.stop="saveFilterAndSend()">
|
||||
Save & Publish
|
||||
</FormButton>
|
||||
<div v-tippy="!canCreateVersionPerm ? canCreateVersionMessage : ''">
|
||||
<FormButton
|
||||
size="sm"
|
||||
:disabled="!canCreateVersionPerm || isSaveDisabled"
|
||||
@click.stop="saveFilterAndSend()"
|
||||
>
|
||||
Save & Publish
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</CommonDialog>
|
||||
|
||||
@@ -108,7 +119,7 @@
|
||||
</ModelCardBase>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import ModelCardBase from '~/components/model/CardBase.vue'
|
||||
import { Square3Stack3DIcon } from '@heroicons/vue/20/solid'
|
||||
import type { ModelCardNotification } from '~/lib/models/card/notification'
|
||||
@@ -117,13 +128,22 @@ import type { ProjectModelGroup } from '~/store/hostApp'
|
||||
import { useHostAppStore } from '~/store/hostApp'
|
||||
import { useMixpanel } from '~/lib/core/composables/mixpanel'
|
||||
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 { 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 app = useNuxtApp()
|
||||
const { canCreateModelIngestion } = useCheckGraphql()
|
||||
|
||||
const cardBase = ref<InstanceType<typeof ModelCardBase>>()
|
||||
const props = defineProps<{
|
||||
@@ -132,37 +152,87 @@ const props = defineProps<{
|
||||
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)
|
||||
app.$baseBinding?.on('documentChanged', () => {
|
||||
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 = () => {
|
||||
if (!props.canEdit) {
|
||||
// check for progress first to allow cancelling even if permissions changed
|
||||
if (props.modelCard.progress) {
|
||||
store.sendModelCancel(props.modelCard.modelCardId)
|
||||
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
|
||||
}
|
||||
|
||||
let newFilter: ISendFilter
|
||||
const newFilter = ref<ISendFilter>()
|
||||
const updateFilter = (filter: ISendFilter) => {
|
||||
newFilter = filter
|
||||
newFilter.value = filter
|
||||
}
|
||||
|
||||
const isSaveDisabled = computed(() => {
|
||||
const filterToCheck = newFilter.value || props.modelCard.sendFilter
|
||||
return !store.validateSendFilter(filterToCheck).valid
|
||||
})
|
||||
|
||||
const saveFilter = async () => {
|
||||
if (!newFilter.value) return // Safety check
|
||||
void trackEvent('DUI3 Action', {
|
||||
name: 'Publish Card Filter Change',
|
||||
filter: newFilter.typeDiscriminator
|
||||
filter: newFilter.value.typeDiscriminator
|
||||
})
|
||||
|
||||
// do not reset idmap while creating a new one because it is managed by host app
|
||||
newFilter.idMap = props.modelCard.sendFilter?.idMap
|
||||
newFilter.value.idMap = props.modelCard.sendFilter?.idMap
|
||||
|
||||
await store.patchModel(props.modelCard.modelCardId, {
|
||||
sendFilter: newFilter,
|
||||
sendFilter: newFilter.value,
|
||||
expired: true
|
||||
})
|
||||
openFilterDialog.value = false
|
||||
@@ -173,11 +243,6 @@ const isUpdatingVersionMessage = ref(false)
|
||||
const hasSetVersionMessage = ref(false)
|
||||
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) => {
|
||||
if (!props.modelCard.latestCreatedVersionId) {
|
||||
return
|
||||
@@ -203,14 +268,14 @@ const setVersionMessage = async (message: string) => {
|
||||
if (res?.data?.versionMutations.update.id) {
|
||||
// seemed to noisy, and autoclose does not work for some reason.
|
||||
// nicer ux to just close the dialog
|
||||
// hostAppStore.setNotification({
|
||||
// store.setNotification({
|
||||
// type: ToastNotificationType.Info,
|
||||
// title: 'Version message saved',
|
||||
// autoClose: true
|
||||
// })
|
||||
hasSetVersionMessage.value = true
|
||||
} else {
|
||||
hostAppStore.setNotification({
|
||||
store.setNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Request failed',
|
||||
description: 'Failed to update version message.',
|
||||
@@ -261,6 +326,10 @@ const expiredNotification = computed(() => {
|
||||
const ctaType = props.modelCard.progress ? 'Restart' : 'Update'
|
||||
notification.cta = {
|
||||
name: ctaType,
|
||||
disabled: !canCreateVersionPerm.value,
|
||||
tooltipText: !canCreateVersionPerm.value
|
||||
? canCreateVersionMessage.value || 'Publish limit reached'
|
||||
: undefined,
|
||||
action: async () => {
|
||||
hasSetVersionMessage.value = false
|
||||
if (props.modelCard.progress) {
|
||||
@@ -337,4 +406,8 @@ const latestVersionNotification = computed(() => {
|
||||
}
|
||||
return notification
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void checkPermissions()
|
||||
})
|
||||
</script>
|
||||
|
||||
+99
-18
@@ -21,7 +21,6 @@
|
||||
@search-text-update="updateSearchText"
|
||||
/>
|
||||
</div>
|
||||
<!-- Model selector wizard -->
|
||||
<div v-if="step === 2 && selectedProject && selectedAccountId">
|
||||
<WizardModelSelector
|
||||
:project="selectedProject"
|
||||
@@ -32,7 +31,6 @@
|
||||
@next="selectModel"
|
||||
/>
|
||||
</div>
|
||||
<!-- Version selector wizard -->
|
||||
<div v-if="step === 3">
|
||||
<SendFiltersAndSettings
|
||||
v-model="filter"
|
||||
@@ -44,8 +42,15 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div class="mt-2">
|
||||
<FormButton full-width @click="addModel">Publish</FormButton>
|
||||
<div v-tippy="publishTooltipMessage" class="mt-2">
|
||||
<FormButton
|
||||
full-width
|
||||
:disabled="isPublishDisabled"
|
||||
:loading="isLoadingPermissions"
|
||||
@click="addModel"
|
||||
>
|
||||
Publish
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="urlParseError" class="p-2 text-danger">
|
||||
@@ -55,6 +60,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSubscription } from '@vue/apollo-composable'
|
||||
import type {
|
||||
ModelListModelItemFragment,
|
||||
ProjectListProjectItemFragment
|
||||
@@ -63,10 +69,13 @@ import type { ISendFilter } from '~/lib/models/card/send'
|
||||
import { SenderModelCard } from '~/lib/models/card/send'
|
||||
import { useHostAppStore } from '~/store/hostApp'
|
||||
import { useAccountStore } from '~/store/accounts'
|
||||
import { useSelectionStore } from '~/store/selection'
|
||||
import { useMixpanel } from '~/lib/core/composables/mixpanel'
|
||||
import { useSettingsTracking } from '~/lib/core/composables/trackSettings'
|
||||
import type { CardSetting } from '~/lib/models/card/setting'
|
||||
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 { trackSettingsChange } = useSettingsTracking()
|
||||
@@ -87,6 +96,29 @@ const settings = ref<CardSetting[] | undefined>(undefined)
|
||||
const settingsWereChanged = ref(false)
|
||||
|
||||
const { tryParseUrl, urlParsedData, urlParseError } = useAddByUrl()
|
||||
const { canCreateModelIngestion, canCreateVersion } = useCheckGraphql()
|
||||
|
||||
const canPublish = ref(false)
|
||||
const publishLimitMessage = ref<string | undefined>(undefined)
|
||||
const isLoadingPermissions = ref(false)
|
||||
const hostAppStore = useHostAppStore()
|
||||
const selectionStore = useSelectionStore()
|
||||
|
||||
const publishValidation = computed(() => hostAppStore.validateSendFilter(filter.value))
|
||||
|
||||
const isPublishDisabled = computed(() => {
|
||||
return (
|
||||
!canPublish.value || isLoadingPermissions.value || !publishValidation.value.valid
|
||||
)
|
||||
})
|
||||
|
||||
const publishTooltipMessage = computed(() => {
|
||||
if (!publishValidation.value.valid) return publishValidation.value.reason
|
||||
if (!canPublish.value && !isLoadingPermissions.value)
|
||||
return publishLimitMessage.value || ''
|
||||
return ''
|
||||
})
|
||||
|
||||
const updateSearchText = (text: string | undefined) => {
|
||||
urlParseError.value = undefined
|
||||
if (!text) return
|
||||
@@ -102,9 +134,72 @@ watch(urlParsedData, (newVal) => {
|
||||
watch(showSendDialog, (newVal) => {
|
||||
if (newVal) {
|
||||
urlParseError.value = undefined
|
||||
void selectionStore.refreshSelectionFromHostApp()
|
||||
}
|
||||
})
|
||||
|
||||
const checkPermissions = async () => {
|
||||
if (!selectedProject.value || !selectedModel.value) return
|
||||
|
||||
isLoadingPermissions.value = true
|
||||
|
||||
try {
|
||||
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
|
||||
}
|
||||
} finally {
|
||||
isLoadingPermissions.value = false
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
step.value++
|
||||
selectedAccountId.value = accountId
|
||||
@@ -125,20 +220,6 @@ const selectModel = (model: ModelListModelItemFragment) => {
|
||||
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()
|
||||
|
||||
// accountId, serverUrl, projectId, modelId, sendFilter, settings
|
||||
const addModel = async () => {
|
||||
void trackEvent('DUI3 Action', {
|
||||
|
||||
@@ -32,33 +32,6 @@
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
canCreateModelResult &&
|
||||
!canCreateModelResult.project.permissions.canCreateModel.authorized
|
||||
"
|
||||
>
|
||||
<CommonAlert title="Cannot create new models" color="info" hide-icon>
|
||||
<template #description>
|
||||
{{ canCreateModelResult.project.permissions.canCreateModel.message }}
|
||||
|
||||
<FormButton
|
||||
v-if="workspaceSlug"
|
||||
full-width
|
||||
color="primary"
|
||||
size="sm"
|
||||
class="mt-2"
|
||||
@click="
|
||||
$openUrl(
|
||||
`${account.accountInfo.serverInfo.url}/settings/workspaces/${workspaceSlug}/billing`
|
||||
)
|
||||
"
|
||||
>
|
||||
Explore Plans
|
||||
</FormButton>
|
||||
</template>
|
||||
</CommonAlert>
|
||||
</div>
|
||||
<div class="relative grid grid-cols-1 gap-2">
|
||||
<CommonLoadingBar v-if="loading" loading />
|
||||
|
||||
|
||||
@@ -3,12 +3,18 @@
|
||||
<div class="space-y-2 relative">
|
||||
<div v-if="workspacesEnabled && workspaces" class="flex items-center space-x-2">
|
||||
<div class="flex-grow min-w-0">
|
||||
<!-- NO WORKSPACE YET -->
|
||||
<div v-if="workspaces.length === 0">
|
||||
<FormButton
|
||||
full-width
|
||||
class="flex items-center"
|
||||
@click="$openUrl('https://app.speckle.systems/workspaces/actions/create')"
|
||||
@click="
|
||||
$openUrl(
|
||||
`${activeAccount.accountInfo.serverInfo.url.replace(
|
||||
/\/$/,
|
||||
''
|
||||
)}/workspaces/actions/create`
|
||||
)
|
||||
"
|
||||
>
|
||||
<div class="min-w-0 truncate flex-grow">
|
||||
<span>{{ 'Create a workspace' }}</span>
|
||||
@@ -72,13 +78,7 @@
|
||||
color="foundation"
|
||||
/>
|
||||
<div class="flex justify-between items-center space-x-2">
|
||||
<div
|
||||
v-tippy="
|
||||
canCreateProject
|
||||
? 'Create new project'
|
||||
: canCreateProjectPermissionCheck?.message
|
||||
"
|
||||
>
|
||||
<div v-if="canCreateProject" v-tippy="'Create new project'">
|
||||
<FormButton
|
||||
color="outline"
|
||||
:disabled="!canCreateProject"
|
||||
@@ -88,6 +88,22 @@
|
||||
<PlusIcon class="w-4 -mx-2" />
|
||||
</FormButton>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
v-tippy="
|
||||
canCreateProject
|
||||
? 'Create new project'
|
||||
: canCreateProjectPermissionCheck?.message
|
||||
"
|
||||
>
|
||||
<FormButton
|
||||
color="primary"
|
||||
:class="`p-1.5 bg-foundation rounded text-foreground border`"
|
||||
@click="upgradePlanButtonAction"
|
||||
>
|
||||
<ArrowUpCircleIcon class="w-4 -mx-2" />
|
||||
</FormButton>
|
||||
</div>
|
||||
<CommonDialog
|
||||
v-model:open="showProjectCreateDialog"
|
||||
:title="`Create new project`"
|
||||
@@ -132,28 +148,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
canCreateProjectPermissionCheck &&
|
||||
!canCreateProjectPermissionCheck.authorized &&
|
||||
showUpgradePlanButton
|
||||
"
|
||||
>
|
||||
<CommonAlert color="info" hide-icon>
|
||||
<template #description>
|
||||
{{ canCreateProjectPermissionCheck.message }}
|
||||
<FormButton
|
||||
full-width
|
||||
class="mt-2"
|
||||
color="primary"
|
||||
size="sm"
|
||||
@click="upgradePlanButtonAction()"
|
||||
>
|
||||
Upgrade now
|
||||
</FormButton>
|
||||
</template>
|
||||
</CommonAlert>
|
||||
</div>
|
||||
|
||||
<WizardPersonalProjectsWarning v-if="isPersonalProjectsAsWorkspace" />
|
||||
|
||||
@@ -202,7 +196,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ChevronDownIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { PlusIcon } from '@heroicons/vue/20/solid'
|
||||
import { PlusIcon, ArrowUpCircleIcon } from '@heroicons/vue/20/solid'
|
||||
import type { DUIAccount } from '~/store/accounts'
|
||||
import { useAccountStore } from '~/store/accounts'
|
||||
import {
|
||||
@@ -341,11 +335,7 @@ const activeWorkspace = computed(() => {
|
||||
if (activeWorkspace) return activeWorkspace
|
||||
}
|
||||
|
||||
// if activeWorkspace is null will mean that it is personal projects - this fallback wont be the case soon
|
||||
return {
|
||||
id: 'personalProject',
|
||||
name: 'Personal Projects'
|
||||
} as WorkspaceListWorkspaceItemFragment
|
||||
return workspaces.value?.[0] // fallback to first workspace if none is active
|
||||
})
|
||||
|
||||
const selectedWorkspace = ref<WorkspaceListWorkspaceItemFragment | undefined>(
|
||||
@@ -497,35 +487,6 @@ const canCreateProjectPermissionCheck = computed(() => {
|
||||
return null
|
||||
})
|
||||
|
||||
const upgradePlanButtonAction = () => {
|
||||
if (!canCreateProjectPermissionCheck.value) return
|
||||
if (canCreateProjectPermissionCheck.value.code === 'WorkspaceNoEditorSeat') {
|
||||
// open url to workspace/settings/users
|
||||
$openUrl(
|
||||
`${account.value.accountInfo.serverInfo.url}/settings/workspaces/${selectedWorkspace.value?.slug}/members`
|
||||
)
|
||||
return
|
||||
}
|
||||
if (canCreateProjectPermissionCheck.value.code === 'WorkspaceLimitsReached') {
|
||||
// open url to workspace/billing
|
||||
$openUrl(
|
||||
`${account.value.accountInfo.serverInfo.url}/settings/workspaces/${selectedWorkspace.value?.slug}/billing`
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const showUpgradePlanButton = computed(() => {
|
||||
if (!canCreateProjectPermissionCheck.value) return false
|
||||
if (
|
||||
canCreateProjectPermissionCheck.value.code === 'WorkspaceNoEditorSeat' ||
|
||||
canCreateProjectPermissionCheck.value.code === 'WorkspaceLimitsReached'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
const isCreatingProject = ref(false)
|
||||
const showProjectCreateDialog = ref(false)
|
||||
|
||||
@@ -614,6 +575,24 @@ const createNewPersonalProject = async (name: string) => {
|
||||
isCreatingProject.value = false
|
||||
}
|
||||
|
||||
const upgradePlanButtonAction = () => {
|
||||
if (!canCreateProjectPermissionCheck.value) return
|
||||
if (canCreateProjectPermissionCheck.value.code === 'WorkspaceNoEditorSeat') {
|
||||
// open url to workspace/settings/users
|
||||
$openUrl(
|
||||
`${account.value.accountInfo.serverInfo.url}/settings/workspaces/${selectedWorkspace.value?.slug}/members`
|
||||
)
|
||||
return
|
||||
}
|
||||
if (canCreateProjectPermissionCheck.value.code === 'WorkspaceLimitsReached') {
|
||||
// open url to workspace/billing
|
||||
$openUrl(
|
||||
`${account.value.accountInfo.serverInfo.url}/settings/workspaces/${selectedWorkspace.value?.slug}/billing`
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const loadMore = () => {
|
||||
fetchMore({
|
||||
variables: { cursor: projectsResult.value?.activeUser?.projects.cursor },
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface IBasicConnectorBinding
|
||||
highlightObjects: (objectIds: string[]) => Promise<void>
|
||||
removeModel: (model: IModelCard) => Promise<void>
|
||||
removeModels: (models: IModelCard[]) => Promise<void>
|
||||
updateParameters: (payload: string) => Promise<void>
|
||||
}
|
||||
|
||||
export interface IBasicConnectorBindingHostEvents
|
||||
@@ -107,6 +108,10 @@ export class MockedBaseBinding implements IBasicConnectorBinding {
|
||||
await console.log('no way dude')
|
||||
}
|
||||
|
||||
public async updateParameters(payload: string) {
|
||||
await console.log('Mock: updateParameters called with payload:', payload)
|
||||
}
|
||||
|
||||
public async showDevTools() {
|
||||
await console.log('No way dude')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface IParametersBinding {
|
||||
update: (payload: string) => Promise<void>
|
||||
}
|
||||
@@ -26,6 +26,7 @@ export interface ISendBindingEvents
|
||||
modelCardId: string
|
||||
versionId: string
|
||||
sendConversionResults: ConversionResult[]
|
||||
ingestionId?: string
|
||||
}) => void
|
||||
setIdMap: (args: {
|
||||
modelCardId: string
|
||||
|
||||
+69
-15
@@ -16,6 +16,9 @@ import type { Emitter } from 'nanoevents'
|
||||
import { useDesktopService } from '~/lib/core/composables/desktopService'
|
||||
import type { ToastNotification } 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 = {
|
||||
modelCardId: string
|
||||
@@ -466,24 +469,75 @@ export class ArchicadBridge {
|
||||
}
|
||||
|
||||
private async createVersion(args: CreateVersionArgs) {
|
||||
const accountStore = useAccountStore()
|
||||
const { accounts } = storeToRefs(accountStore)
|
||||
const account = accounts.value.find((acc) => acc.accountInfo.id === args.accountId)
|
||||
const hostAppStore = useHostAppStore()
|
||||
const { completeIngestionWithVersion } = useModelIngestion()
|
||||
const { canCreateModelIngestion } = useCheckGraphql()
|
||||
|
||||
const createVersion = provideApolloClient((account as DUIAccount).client)(() =>
|
||||
useMutation(createVersionMutation)
|
||||
const modelCard = hostAppStore.models.find(
|
||||
(model) => model.modelCardId === args.modelCardId
|
||||
) as ISenderModelCard
|
||||
|
||||
const canCreateIngestion = await canCreateModelIngestion(
|
||||
modelCard.projectId,
|
||||
modelCard.modelId,
|
||||
modelCard.accountId
|
||||
)
|
||||
|
||||
const hostAppStore = useHostAppStore()
|
||||
|
||||
const result = await createVersion.mutate({
|
||||
input: {
|
||||
modelId: args.modelId,
|
||||
objectId: args.referencedObjectId,
|
||||
sourceApplication: hostAppStore.hostAppName,
|
||||
projectId: args.projectId
|
||||
if (canCreateIngestion.queryAvailable) {
|
||||
const ingestionId = hostAppStore.activeIngestions[args.modelCardId]
|
||||
if (!ingestionId) {
|
||||
hostAppStore.setNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Ingestion Failed',
|
||||
description: 'Ingestion ID not found to create version.'
|
||||
})
|
||||
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
@@ -19,6 +19,9 @@ import type {
|
||||
ReceiveViaBrowserArgs,
|
||||
CreateVersionArgs
|
||||
} 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: {
|
||||
exec: (data: Record<string, unknown>) => void
|
||||
@@ -297,40 +300,89 @@ export class SketchupBridge extends BaseBridge {
|
||||
sourceApplication: 'sketchup',
|
||||
message: message || 'send from sketchup'
|
||||
}
|
||||
const versionId = await this.createVersion(args)
|
||||
|
||||
const hostAppStore = useHostAppStore()
|
||||
// TODO: Alignment needed
|
||||
hostAppStore.setModelSendResult({
|
||||
modelCardId: args.modelCardId,
|
||||
versionId: versionId as string,
|
||||
sendConversionResults
|
||||
})
|
||||
try {
|
||||
const versionId = await this.createVersion(args)
|
||||
hostAppStore.setModelSendResult({
|
||||
modelCardId: args.modelCardId,
|
||||
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) {
|
||||
const accountStore = useAccountStore()
|
||||
const hostAppStore = useHostAppStore()
|
||||
const accountStore = useAccountStore()
|
||||
const { accounts } = storeToRefs(accountStore)
|
||||
const account = accounts.value.find((acc) => acc.accountInfo.id === args.accountId)
|
||||
const { completeIngestionWithVersion } = useModelIngestion()
|
||||
|
||||
const createVersion = provideApolloClient((account as DUIAccount).client)(() =>
|
||||
useMutation(createVersionMutation)
|
||||
const modelCard = hostAppStore.models.find(
|
||||
(model) => model.modelCardId === args.modelCardId
|
||||
)
|
||||
|
||||
// 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}`
|
||||
if (!modelCard) {
|
||||
throw new Error('Model card not found') // ctor
|
||||
}
|
||||
|
||||
const result = await createVersion.mutate({
|
||||
input: {
|
||||
modelId: args.modelId,
|
||||
objectId: args.referencedObjectId,
|
||||
sourceApplication: hostAppName,
|
||||
projectId: args.projectId
|
||||
const { canCreateModelIngestion } = useCheckGraphql()
|
||||
const canCreateIngestion = await canCreateModelIngestion(
|
||||
modelCard.projectId,
|
||||
modelCard.modelId,
|
||||
modelCard.accountId
|
||||
)
|
||||
|
||||
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> {
|
||||
|
||||
@@ -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 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 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 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,
|
||||
@@ -52,10 +53,19 @@ 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 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 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 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 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 query IssueResourceMetaSearch(\n $workspaceId: String!\n $resourceType: ResourceMetaType!\n $resourceId: String!\n $projectId: String\n $metaType: String\n ) {\n resourceMetaSearch(\n workspaceId: $workspaceId\n resourceType: $resourceType\n resourceId: $resourceId\n projectId: $projectId\n metaType: $metaType\n ) {\n data\n }\n }\n": typeof types.IssueResourceMetaSearchDocument,
|
||||
"\n subscription WorkspacePlanUsageUpdated($input: WorkspacePlanUsageSubscriptionInput!) {\n workspacePlanUsageUpdated(input: $input)\n }\n": typeof types.WorkspacePlanUsageUpdatedDocument,
|
||||
};
|
||||
const documents: Documents = {
|
||||
"\n mutation SetActiveWorkspaceMutation($slug: String) {\n activeUserMutations {\n setActiveWorkspace(slug: $slug) {\n id\n }\n }\n }\n": types.SetActiveWorkspaceMutationDocument,
|
||||
@@ -77,6 +87,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 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 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 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,
|
||||
@@ -96,10 +107,19 @@ 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 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 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 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 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 query IssueResourceMetaSearch(\n $workspaceId: String!\n $resourceType: ResourceMetaType!\n $resourceId: String!\n $projectId: String\n $metaType: String\n ) {\n resourceMetaSearch(\n workspaceId: $workspaceId\n resourceType: $resourceType\n resourceId: $resourceId\n projectId: $projectId\n metaType: $metaType\n ) {\n data\n }\n }\n": types.IssueResourceMetaSearchDocument,
|
||||
"\n subscription WorkspacePlanUsageUpdated($input: WorkspacePlanUsageSubscriptionInput!) {\n workspacePlanUsageUpdated(input: $input)\n }\n": types.WorkspacePlanUsageUpdatedDocument,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -192,6 +212,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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -271,11 +295,39 @@ 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -284,6 +336,14 @@ 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.
|
||||
*/
|
||||
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 query IssueResourceMetaSearch(\n $workspaceId: String!\n $resourceType: ResourceMetaType!\n $resourceId: String!\n $projectId: String\n $metaType: String\n ) {\n resourceMetaSearch(\n workspaceId: $workspaceId\n resourceType: $resourceType\n resourceId: $resourceId\n projectId: $projectId\n metaType: $metaType\n ) {\n data\n }\n }\n"): (typeof documents)["\n query IssueResourceMetaSearch(\n $workspaceId: String!\n $resourceType: ResourceMetaType!\n $resourceId: String!\n $projectId: String\n $metaType: String\n ) {\n resourceMetaSearch(\n workspaceId: $workspaceId\n resourceType: $resourceType\n resourceId: $resourceId\n projectId: $projectId\n metaType: $metaType\n ) {\n data\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) {
|
||||
return (documents as any)[source] ?? {};
|
||||
|
||||
+685
-164
File diff suppressed because one or more lines are too long
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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(`
|
||||
query ActiveWorkspace {
|
||||
activeUser {
|
||||
@@ -606,7 +623,7 @@ export const projectUpdatedSubscription = graphql(`
|
||||
`)
|
||||
|
||||
export const modelViewingSubscription = graphql(`
|
||||
subscription Subscription($target: ViewerUpdateTrackingTarget!) {
|
||||
subscription ModelViewingSubscription($target: ViewerUpdateTrackingTarget!) {
|
||||
viewerUserActivityBroadcasted(target: $target) {
|
||||
userName
|
||||
userId
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
import {
|
||||
provideApolloClient,
|
||||
useMutation,
|
||||
useSubscription
|
||||
} from '@vue/apollo-composable'
|
||||
import { useAccountStore } from '~/store/accounts'
|
||||
import { useHostAppStore } from '~/store/hostApp'
|
||||
import {
|
||||
completeModelIngestionWithVersion,
|
||||
createModelIngestion,
|
||||
updateModelIngestionProgress,
|
||||
failModelIngestionWithError,
|
||||
failModelIngestionWithCancel
|
||||
} from '../graphql/mutations'
|
||||
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'
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
// 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,
|
||||
subscribeToIngestion,
|
||||
unsubscribeFromIngestion
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
@@ -13,3 +13,23 @@ export const issuesListQuery = graphql(`
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const issueResourceMetaSearchQuery = graphql(`
|
||||
query IssueResourceMetaSearch(
|
||||
$workspaceId: String!
|
||||
$resourceType: ResourceMetaType!
|
||||
$resourceId: String!
|
||||
$projectId: String
|
||||
$metaType: String
|
||||
) {
|
||||
resourceMetaSearch(
|
||||
workspaceId: $workspaceId
|
||||
resourceType: $resourceType
|
||||
resourceId: $resourceId
|
||||
projectId: $projectId
|
||||
metaType: $metaType
|
||||
) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -15,6 +15,7 @@ export type ModelCardNotification = {
|
||||
name: string
|
||||
tooltipText?: string
|
||||
action: () => void
|
||||
disabled?: boolean
|
||||
}
|
||||
/**
|
||||
* If set, will display a view report button next to cta
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
import { computed } from 'vue'
|
||||
import type {
|
||||
ISendFilter,
|
||||
SendFilterSelect,
|
||||
RevitCategoriesSendFilter,
|
||||
RevitViewsSendFilter
|
||||
} from '~/lib/models/card/send'
|
||||
import { ValidationHelpers } from '@speckle/ui-components'
|
||||
import type { GenericValidateFunction } from 'vee-validate'
|
||||
|
||||
export const isSelectFilter = (f: ISendFilter): f is SendFilterSelect =>
|
||||
f.type === 'Select' || 'selectedItems' in f
|
||||
|
||||
export const isRevitCategoriesFilter = (
|
||||
f: ISendFilter
|
||||
): f is RevitCategoriesSendFilter =>
|
||||
f.id === 'revitCategories' || f.id === 'archicadLayers'
|
||||
|
||||
export const isRevitViewsFilter = (f: ISendFilter): f is RevitViewsSendFilter =>
|
||||
f.id === 'revitViews'
|
||||
|
||||
export const isEmail = ValidationHelpers.isEmail
|
||||
|
||||
export const isOneOrMultipleEmails = ValidationHelpers.isOneOrMultipleEmails
|
||||
@@ -42,3 +60,42 @@ export function useModelNameValidationRules() {
|
||||
isValidModelName
|
||||
])
|
||||
}
|
||||
|
||||
export type FilterValidationResult = { valid: boolean; reason?: string }
|
||||
|
||||
export function validateFilter(
|
||||
filter: ISendFilter | undefined,
|
||||
context: { selectionCount: number }
|
||||
): FilterValidationResult {
|
||||
if (!filter) return { valid: false, reason: 'No filter selected' }
|
||||
|
||||
// Selection Filter check
|
||||
if (filter.name === 'Selection' || filter.id === 'selection') {
|
||||
return context.selectionCount > 0
|
||||
? { valid: true }
|
||||
: { valid: false, reason: 'No objects selected to publish' }
|
||||
}
|
||||
|
||||
// List-based filters (Rhino Layers, etc.)
|
||||
if (isSelectFilter(filter)) {
|
||||
return (filter.selectedItems?.length ?? 0) > 0
|
||||
? { valid: true }
|
||||
: { valid: false, reason: 'No items selected to publish' }
|
||||
}
|
||||
|
||||
// Category-based filters
|
||||
if (isRevitCategoriesFilter(filter)) {
|
||||
return (filter.selectedCategories?.length ?? 0) > 0
|
||||
? { valid: true }
|
||||
: { valid: false, reason: 'No categories selected to publish' }
|
||||
}
|
||||
|
||||
// View-based filters
|
||||
if (isRevitViewsFilter(filter)) {
|
||||
return filter.selectedView?.trim()
|
||||
? { valid: true }
|
||||
: { valid: false, reason: 'No view selected to publish' }
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
|
||||
export const workspacePlanUsageUpdatedSubscription = graphql(`
|
||||
subscription WorkspacePlanUsageUpdated($input: WorkspacePlanUsageSubscriptionInput!) {
|
||||
workspacePlanUsageUpdated(input: $input)
|
||||
}
|
||||
`)
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
IAccountBindingKey,
|
||||
MockedAccountBinding
|
||||
} from '~/lib/bindings/definitions/IAccountBinding'
|
||||
import type { IParametersBinding } from '~/lib/bindings/definitions/IParametersBinding'
|
||||
|
||||
import type { ITestBinding } from '~/lib/bindings/definitions/ITestBinding'
|
||||
import {
|
||||
@@ -132,6 +133,10 @@ export default defineNuxtPlugin(async () => {
|
||||
ITopLevelExpectionHandlerBindingKey
|
||||
)
|
||||
|
||||
const parametersBinding = await tryHoistBinding<IParametersBinding>(
|
||||
'parametersBinding'
|
||||
)
|
||||
|
||||
// Any binding implments these two methods below, we just choose one to
|
||||
// expose globally to the app.
|
||||
const showDevTools = () => {
|
||||
@@ -157,7 +162,8 @@ export default defineNuxtPlugin(async () => {
|
||||
topLevelExceptionHandlerBinding,
|
||||
showDevTools,
|
||||
openUrl,
|
||||
revitMapperBinding
|
||||
revitMapperBinding,
|
||||
parametersBinding
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import Intercom, {
|
||||
trackEvent
|
||||
} from '@intercom/messenger-js-sdk'
|
||||
import { useAccountStore } from '~/store/accounts'
|
||||
import { useHostAppStore } from '~/store/hostApp'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const disabledRoutes: string[] = []
|
||||
@@ -15,7 +16,9 @@ export const useIntercom = () => {
|
||||
const route = useRoute()
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const hostAppStore = useHostAppStore()
|
||||
const { activeAccount } = storeToRefs(accountStore)
|
||||
const { isDistributedBySpeckle } = storeToRefs(hostAppStore)
|
||||
|
||||
const isInitialized = ref(false)
|
||||
|
||||
@@ -80,6 +83,13 @@ export const useIntercom = () => {
|
||||
}
|
||||
})
|
||||
|
||||
// we listen to changes in the host app distribution status that fetched on updateConnector composable after the intercom is initialized, we cant simply rely on activeAccount watcher
|
||||
watch(isDistributedBySpeckle, (newValue) => {
|
||||
if (!newValue) {
|
||||
shutdownIntercom()
|
||||
}
|
||||
})
|
||||
|
||||
watch(activeAccount, (newValue) => {
|
||||
if (newValue) {
|
||||
if (!isInitialized.value) {
|
||||
|
||||
@@ -294,6 +294,14 @@ export const useAccountStore = defineStore('accountStore', () => {
|
||||
if (accountMatchWithServerUrl) return accountMatchWithServerUrl
|
||||
}
|
||||
|
||||
const getAccountClient = (accountId: string) => {
|
||||
return (
|
||||
accounts.value.find(
|
||||
(account) => account.accountInfo.id === accountId
|
||||
) as DUIAccount
|
||||
).client
|
||||
}
|
||||
|
||||
const provideClients = () => {
|
||||
provideApolloClients(apolloClients)
|
||||
}
|
||||
@@ -320,6 +328,7 @@ export const useAccountStore = defineStore('accountStore', () => {
|
||||
return {
|
||||
isLoading,
|
||||
accounts,
|
||||
getAccountClient,
|
||||
defaultAccount,
|
||||
activeAccount,
|
||||
userSelectedAccount,
|
||||
|
||||
+186
-25
@@ -13,7 +13,10 @@ import type {
|
||||
RevitViewsSendFilter,
|
||||
SendFilterSelect
|
||||
} from '~/lib/models/card/send'
|
||||
import { useSelectionStore } from '~/store/selection'
|
||||
import { validateFilter } from '~/lib/validation'
|
||||
import type { ToastNotification } from '@speckle/ui-components'
|
||||
import { ToastNotificationType } from '@speckle/ui-components'
|
||||
import type { Nullable } from '@speckle/shared'
|
||||
import type { HostAppError } from '~/lib/bridge/errorHandler'
|
||||
import type { ConversionResult } from '~/lib/conversions/conversionResult'
|
||||
@@ -28,6 +31,8 @@ import {
|
||||
import { provideApolloClient, useMutation } from '@vue/apollo-composable'
|
||||
import { createVersionMutation } from '~/lib/graphql/mutationsAndQueries'
|
||||
import type { BaseBridge } from '~/lib/bridge/base'
|
||||
import { useModelIngestion } from '~/lib/ingestion/composables/useModelIngestion'
|
||||
import { useCheckGraphql } from '~/lib/core/composables/useCheckGraphql'
|
||||
|
||||
export type ProjectModelGroup = {
|
||||
projectId: string
|
||||
@@ -43,7 +48,15 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
const { $openUrl } = useNuxtApp()
|
||||
const accountsStore = useAccountStore()
|
||||
const { checkUpdate } = useUpdateConnector()
|
||||
|
||||
const {
|
||||
startIngestion,
|
||||
updateIngestion,
|
||||
failIngestion,
|
||||
cancelIngestion,
|
||||
completeIngestionWithVersion,
|
||||
subscribeToIngestion,
|
||||
unsubscribeFromIngestion
|
||||
} = useModelIngestion()
|
||||
const isDistributedBySpeckle = ref<boolean>(true)
|
||||
const latestAvailableVersion = ref<Version | null>(null)
|
||||
|
||||
@@ -65,6 +78,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`
|
||||
const availableSelectSendFilters = ref<Record<string, SendFilterSelect>>({})
|
||||
|
||||
// kvp for modelCardId - ingestionId
|
||||
const activeIngestions = ref<Record<string, string>>({})
|
||||
|
||||
const dismissNotification = () => {
|
||||
currentNotification.value = null
|
||||
}
|
||||
@@ -95,6 +111,11 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
isDistributedBySpeckle.value = val
|
||||
}
|
||||
|
||||
const shouldHandleIngestion = computed(() => {
|
||||
const hostAppsThatUsesDUIForGraphql = ['sketchup', 'archicad', 'Vectorworks']
|
||||
return hostAppsThatUsesDUIForGraphql.includes(hostAppName.value as string)
|
||||
})
|
||||
|
||||
/**
|
||||
* Model Card Operations
|
||||
*/
|
||||
@@ -281,20 +302,59 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
const account = accountStore.accounts.find(
|
||||
(acc) => acc.accountInfo.id === args.accountId
|
||||
)
|
||||
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
|
||||
|
||||
// Check if we have an ingestion ID for this model.
|
||||
// If so, we are in the "New Business Model" flow and should use completeIngestionWithVersion.
|
||||
const modelCard = documentModelStore.value.models.find(
|
||||
(m) => m.modelId === args.modelId && m.projectId === args.projectId
|
||||
) as ISenderModelCard
|
||||
|
||||
const { canCreateModelIngestion } = useCheckGraphql()
|
||||
const canCreateIngestion = await canCreateModelIngestion(
|
||||
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}`)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(`triggerCreateVersion is failed: ${err}`)
|
||||
} else {
|
||||
setNotification({
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -310,6 +370,14 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
*/
|
||||
app.$sendBinding?.on('refreshSendFilters', () => void refreshSendFilters())
|
||||
|
||||
const validateSendFilter = (filter?: ISendFilter) => {
|
||||
const selectionStore = useSelectionStore()
|
||||
|
||||
return validateFilter(filter, {
|
||||
selectionCount: selectionStore.selectionInfo.selectedObjectIds?.length ?? 0
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send functionality
|
||||
*/
|
||||
@@ -318,10 +386,57 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
* Tells the host app to start sending a specific model card. This will reach inside the host application.
|
||||
* @param modelId
|
||||
*/
|
||||
const sendModel = (modelCardId: string, actionSource: string) => {
|
||||
const sendModel = async (modelCardId: string, actionSource: string) => {
|
||||
const model = documentModelStore.value.models.find(
|
||||
(m) => m.modelCardId === modelCardId
|
||||
) 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) {
|
||||
// user sends via "Update" button
|
||||
void trackEvent(
|
||||
@@ -346,11 +461,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
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..
|
||||
// It solves the press "escape" issue.
|
||||
// Because probably we don't give enough time to acad complete it's previos task and it stucks.
|
||||
@@ -377,6 +488,17 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
model.error = undefined
|
||||
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]
|
||||
if (ingestionId) {
|
||||
await cancelIngestion(model, ingestionId, 'Cancelled by user')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.$sendBinding?.on('setModelsExpired', (modelCardIds) => {
|
||||
@@ -393,13 +515,22 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
modelCardId: string
|
||||
versionId: string
|
||||
sendConversionResults: ConversionResult[]
|
||||
ingestionId?: string
|
||||
}) => {
|
||||
const model = documentModelStore.value.models.find(
|
||||
(m) => m.modelCardId === args.modelCardId
|
||||
) as ISenderModelCard
|
||||
model.latestCreatedVersionId = args.versionId
|
||||
// Conversion results are always valid regardless of ingestion state
|
||||
model.report = args.sendConversionResults
|
||||
model.progress = undefined
|
||||
|
||||
if (args.ingestionId) {
|
||||
// Connector handled ingestion via SDK — composable subscribes and manages model card state to 'Version created' bla bla
|
||||
subscribeToIngestion(model, args.ingestionId)
|
||||
} else {
|
||||
// Legacy path or no ingestion — behave as before
|
||||
model.latestCreatedVersionId = args.versionId
|
||||
model.progress = undefined
|
||||
}
|
||||
}
|
||||
|
||||
app.$sendBinding?.on('setModelSendResult', setModelSendResult)
|
||||
@@ -479,7 +610,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
app.$receiveBinding?.on('setModelReceiveResult', setModelReceiveResult)
|
||||
|
||||
// GENERIC STUFF
|
||||
const handleModelProgressEvents = (args: {
|
||||
const handleModelProgressEvents = async (args: {
|
||||
modelCardId: string
|
||||
progress?: ModelCardProgress
|
||||
}) => {
|
||||
@@ -487,9 +618,24 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
(m) => m.modelCardId === args.modelCardId
|
||||
) as IModelCard
|
||||
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
|
||||
error: string | { errorMessage: string; dismissible?: boolean }
|
||||
}) => {
|
||||
@@ -505,6 +651,19 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
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.
|
||||
@@ -750,6 +909,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
hostAppName,
|
||||
hostAppVersion,
|
||||
connectorVersion,
|
||||
activeIngestions,
|
||||
isConnectorUpToDate,
|
||||
latestAvailableVersion,
|
||||
documentInfo,
|
||||
@@ -788,6 +948,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
|
||||
getSendSettings,
|
||||
setModelSendResult,
|
||||
setModelReceiveResult,
|
||||
handleModelProgressEvents
|
||||
handleModelProgressEvents,
|
||||
validateSendFilter
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user