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

This commit is contained in:
Björn Steinhagen
2026-01-30 13:18:53 +02:00
parent 5399022afb
commit 877a702aab
3 changed files with 50 additions and 84 deletions
+6 -46
View File
@@ -4,10 +4,6 @@
:model-card="modelCard"
:project="project"
:can-edit="canEdit"
:cta-disabled="!canCreateVersion?.authorized"
:cta-disabled-message="
canCreateVersion?.authorized ? undefined : canCreateVersion?.message
"
@manual-publish-or-load="sendOrCancel"
>
<div class="flex max-[275px]:w-full overflow-hidden my-2">
@@ -39,15 +35,9 @@
<FormButton size="sm" color="outline" @click.stop="saveFilter()">
Save
</FormButton>
<div v-tippy="canCreateVersion?.authorized ? '' : canCreateVersion?.message">
<FormButton
size="sm"
:disabled="!canCreateVersion?.authorized"
@click.stop="saveFilterAndSend()"
>
Save & Publish
</FormButton>
</div>
<FormButton size="sm" @click.stop="saveFilterAndSend()">
Save & Publish
</FormButton>
</div>
</CommonDialog>
@@ -127,13 +117,9 @@ 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, useQuery } from '@vue/apollo-composable'
import { provideApolloClient, useMutation } from '@vue/apollo-composable'
import { useAccountStore, type DUIAccount } from '~/store/accounts'
import {
canCreateVersionQuery,
setVersionMessageMutation
} from '~/lib/graphql/mutationsAndQueries'
import { useIntervalFn } from '@vueuse/core'
import { setVersionMessageMutation } from '~/lib/graphql/mutationsAndQueries'
const store = useHostAppStore()
const accountStore = useAccountStore()
@@ -157,25 +143,6 @@ app.$baseBinding?.on('documentChanged', () => {
openFilterDialog.value = false
})
const { result: canCreateVersionResult, refetch: refetchCanCreateVersion } = useQuery(
canCreateVersionQuery,
() => ({ projectId: props.modelCard.projectId, modelId: props.modelCard.modelId }),
() => ({
clientId: account.accountInfo.id,
fetchPolicy: 'network-only'
})
)
const canCreateVersion = computed(() => {
return canCreateVersionResult.value?.project.model.permissions.canCreateVersion
})
// poll canCreateVersion every 1s to reflect workspace limit changes.
// No subscription available on a workspace level, so polling probably easiest approach?
useIntervalFn(() => {
refetchCanCreateVersion()
}, 1000)
const sendOrCancel = () => {
if (!props.canEdit) {
return
@@ -299,19 +266,12 @@ const expiredNotification = computed(() => {
notification.cta = {
name: ctaType,
action: async () => {
if (!canCreateVersion.value?.authorized) {
return
}
hasSetVersionMessage.value = false
if (props.modelCard.progress) {
await store.sendModelCancel(props.modelCard.modelCardId)
}
store.sendModel(props.modelCard.modelCardId, ctaType)
},
disabled: !canCreateVersion.value?.authorized,
tooltipText: canCreateVersion.value?.authorized
? undefined
: canCreateVersion.value?.message
}
}
return notification
})
+1 -37
View File
@@ -45,15 +45,7 @@
"
/>
<div class="mt-2">
<div v-tippy="canCreateVersion?.authorized ? '' : canCreateVersion?.message">
<FormButton
full-width
:disabled="!canCreateVersion?.authorized"
@click="addModel"
>
Publish
</FormButton>
</div>
<FormButton full-width @click="addModel">Publish</FormButton>
</div>
</div>
<div v-if="urlParseError" class="p-2 text-danger">
@@ -75,9 +67,6 @@ 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 { useQuery } from '@vue/apollo-composable'
import { canCreateVersionQuery } from '~/lib/graphql/mutationsAndQueries'
import { useIntervalFn } from '@vueuse/core'
const { trackEvent } = useMixpanel()
const { trackSettingsChange } = useSettingsTracking()
@@ -150,31 +139,6 @@ watch(step, (newVal, oldVal) => {
const hostAppStore = useHostAppStore()
// check canCreateVersion permission when model is selected
const { result: canCreateVersionResult, refetch: refetchCanCreateVersion } = useQuery(
canCreateVersionQuery,
() => ({
projectId: selectedProject.value?.id as string,
modelId: selectedModel.value?.id as string
}),
() => ({
enabled: !!selectedProject.value?.id && !!selectedModel.value?.id,
clientId: selectedAccountId.value,
fetchPolicy: 'network-only'
})
)
const canCreateVersion = computed(() => {
return canCreateVersionResult.value?.project.model.permissions.canCreateVersion
})
useIntervalFn(() => {
// poll on step 3 (where the Publish button is visible)
if (step.value === 3) {
refetchCanCreateVersion()
}
}, 1000)
// accountId, serverUrl, projectId, modelId, sendFilter, settings
const addModel = async () => {
void trackEvent('DUI3 Action', {
+43 -1
View File
@@ -14,6 +14,7 @@ import type {
SendFilterSelect
} from '~/lib/models/card/send'
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'
@@ -26,7 +27,10 @@ import {
type Version
} from '~/lib/core/composables/updateConnector'
import { provideApolloClient, useMutation } from '@vue/apollo-composable'
import { createVersionMutation } from '~/lib/graphql/mutationsAndQueries'
import {
canCreateVersionQuery,
createVersionMutation
} from '~/lib/graphql/mutationsAndQueries'
import type { BaseBridge } from '~/lib/bridge/base'
import { useModelIngestion } from '~/lib/ingestion/composables/useModelIngestion'
@@ -325,6 +329,31 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
* Send functionality
*/
/**
* Checks if user can create a version for the given model.
* Used to validate before starting a publish operation.
*/
const checkCanCreateVersion = async (model: ISenderModelCard) => {
const client = accountsStore.getAccountClient(model.accountId)
try {
const result = await client.query({
query: canCreateVersionQuery,
variables: {
projectId: model.projectId,
modelId: model.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 }
}
}
/**
* Tells the host app to start sending a specific model card. This will reach inside the host application.
* @param modelId
@@ -333,6 +362,19 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
const model = documentModelStore.value.models.find(
(m) => m.modelCardId === modelCardId
) as ISenderModelCard
// Check if user can create version before starting publish.
// We do this check on action rather than polling to avoid going ott on the server.
const canCreate = await checkCanCreateVersion(model)
if (!canCreate.authorized) {
setNotification({
type: ToastNotificationType.Warning,
title: 'Cannot publish',
description: canCreate.message || 'Workspace limits have been reached'
})
return
}
if (model.expired) {
// user sends via "Update" button
void trackEvent(