feat: handle version limits in publish flows via subscription
This commit is contained in:
@@ -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,18 @@
|
||||
<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()">
|
||||
Save
|
||||
</FormButton>
|
||||
<FormButton size="sm" @click.stop="saveFilterAndSend()">
|
||||
Save & Publish
|
||||
</FormButton>
|
||||
<div v-tippy="!canCreateVersionPerm ? canCreateVersionMessage : ''">
|
||||
<FormButton
|
||||
size="sm"
|
||||
:disabled="!canCreateVersionPerm"
|
||||
@click.stop="saveFilterAndSend()"
|
||||
>
|
||||
Save & Publish
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</CommonDialog>
|
||||
|
||||
@@ -108,7 +114,7 @@
|
||||
</ModelCardBase>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import ModelCardBase from '~/components/model/CardBase.vue'
|
||||
import { Square3Stack3DIcon } from '@heroicons/vue/20/solid'
|
||||
import type { ModelCardNotification } from '~/lib/models/card/notification'
|
||||
@@ -125,12 +131,14 @@ import {
|
||||
import { useAccountStore, type DUIAccount } from '~/store/accounts'
|
||||
import { setVersionMessageMutation } from '~/lib/graphql/mutationsAndQueries'
|
||||
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<{
|
||||
@@ -149,6 +157,26 @@ 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,
|
||||
() => ({
|
||||
@@ -160,11 +188,15 @@ const { onResult: onWorkspacePlanUsageUpdated } = useSubscription(
|
||||
)
|
||||
|
||||
onWorkspacePlanUsageUpdated(() => {
|
||||
// TODO: refetch canCreateVersion and disable CTAs, and potentialls on other CTAs like `Save & Publish` etc. basically same as first approach
|
||||
void checkPermissions()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void checkPermissions()
|
||||
})
|
||||
|
||||
const sendOrCancel = () => {
|
||||
if (!props.canEdit) {
|
||||
if (!props.canEdit || !canCreateVersionPerm.value) {
|
||||
return
|
||||
}
|
||||
if (props.modelCard.progress) {
|
||||
@@ -246,6 +278,7 @@ const setVersionMessage = async (message: string) => {
|
||||
}
|
||||
|
||||
const saveFilterAndSend = async () => {
|
||||
if (!canCreateVersionPerm.value) return
|
||||
await saveFilter()
|
||||
store.sendModel(props.modelCard.modelCardId, 'Filter')
|
||||
hasSetVersionMessage.value = false
|
||||
@@ -285,6 +318,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) {
|
||||
|
||||
+64
-16
@@ -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,10 @@
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div class="mt-2">
|
||||
<FormButton full-width @click="addModel">Publish</FormButton>
|
||||
<div v-tippy="!canPublish ? publishLimitMessage : ''" class="mt-2">
|
||||
<FormButton full-width :disabled="!canPublish" @click="addModel">
|
||||
Publish
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="urlParseError" class="p-2 text-danger">
|
||||
@@ -55,6 +55,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSubscription } from '@vue/apollo-composable'
|
||||
import type {
|
||||
ModelListModelItemFragment,
|
||||
ProjectListProjectItemFragment
|
||||
@@ -67,6 +68,8 @@ 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 +90,11 @@ const settings = ref<CardSetting[] | undefined>(undefined)
|
||||
const settingsWereChanged = ref(false)
|
||||
|
||||
const { tryParseUrl, urlParsedData, urlParseError } = useAddByUrl()
|
||||
const { canCreateModelIngestion } = useCheckGraphql()
|
||||
|
||||
const canPublish = ref(true)
|
||||
const publishLimitMessage = ref<string | null>(null)
|
||||
|
||||
const updateSearchText = (text: string | undefined) => {
|
||||
urlParseError.value = undefined
|
||||
if (!text) return
|
||||
@@ -105,6 +113,56 @@ watch(showSendDialog, (newVal) => {
|
||||
}
|
||||
})
|
||||
|
||||
const checkPermissions = async () => {
|
||||
if (!selectedProject.value || !selectedModel.value) return
|
||||
|
||||
const res = await canCreateModelIngestion(
|
||||
selectedProject.value.id,
|
||||
selectedModel.value.id,
|
||||
selectedAccountId.value
|
||||
)
|
||||
if (res.queryAvailable) {
|
||||
canPublish.value = res.authorized
|
||||
publishLimitMessage.value = res.message || null
|
||||
} else {
|
||||
canPublish.value = true
|
||||
publishLimitMessage.value = null
|
||||
}
|
||||
}
|
||||
|
||||
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,22 +183,12 @@ 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 () => {
|
||||
if (!canPublish.value) return
|
||||
|
||||
void trackEvent('DUI3 Action', {
|
||||
name: 'Publish Wizard',
|
||||
step: 'objects selected',
|
||||
|
||||
@@ -3105,6 +3105,8 @@ export type PendingStreamCollaborator = {
|
||||
id: Scalars['String']['output'];
|
||||
inviteId: Scalars['String']['output'];
|
||||
invitedBy: LimitedUser;
|
||||
/** The project this invite is for */
|
||||
project?: Maybe<LimitedProject>;
|
||||
projectId: Scalars['String']['output'];
|
||||
projectName: Scalars['String']['output'];
|
||||
role: Scalars['String']['output'];
|
||||
@@ -6664,6 +6666,7 @@ export enum WorkspaceFeatureName {
|
||||
HideSpeckleBranding = 'hideSpeckleBranding',
|
||||
Issues = 'issues',
|
||||
Markup = 'markup',
|
||||
ModelValidation = 'modelValidation',
|
||||
OidcSso = 'oidcSso',
|
||||
PortfolioDashboards = 'portfolioDashboards',
|
||||
Presentation = 'presentation',
|
||||
@@ -7001,6 +7004,7 @@ export type WorkspacePermissionChecks = {
|
||||
canAcceptInvite: PermissionCheckResult;
|
||||
canAcceptJoinRequest: PermissionCheckResult;
|
||||
canAccessDashboards: PermissionCheckResult;
|
||||
canAccessModelValidation: PermissionCheckResult;
|
||||
canAccessSso: PermissionCheckResult;
|
||||
canChangeSeatType: PermissionCheckResult;
|
||||
canCreateDashboards: PermissionCheckResult;
|
||||
|
||||
Reference in New Issue
Block a user