Merge branch 'main' into iain/file-import-more-fixes
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
Looking for V2 connectors? Get them
|
||||
<NuxtLink
|
||||
class="text-foreground-3 hover:text-foreground-2 underline"
|
||||
to="https://releases.speckle.systems"
|
||||
to="https://releases.speckle.systems/legacy-connectors"
|
||||
>
|
||||
here.
|
||||
</NuxtLink>
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</MenuItem>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 pt-1">
|
||||
<div class="p-2 pt-1 max-h-96 overflow-y-auto simple-scrollbar">
|
||||
<LayoutSidebarMenuGroup
|
||||
title="Workspaces"
|
||||
:icon-click="isGuest ? undefined : handlePlusClick"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div ref="rendererparent" class="absolute w-full h-full"></div>
|
||||
<div
|
||||
ref="rendererparent"
|
||||
class="absolute w-full h-full"
|
||||
data-dd-action-name="Viewer Canvas"
|
||||
></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useInjectedViewer } from '~~/lib/viewer/composables/setup'
|
||||
|
||||
@@ -97,7 +97,11 @@
|
||||
:url="route.path"
|
||||
/>
|
||||
<Portal to="primary-actions">
|
||||
<HeaderNavShare v-if="project" :resource-id-string="modelId" :project="project" />
|
||||
<HeaderNavShare
|
||||
v-if="project"
|
||||
:resource-id-string="resourceIdString"
|
||||
:project="project"
|
||||
/>
|
||||
</Portal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -113,17 +117,28 @@ import { useFilterUtilities } from '~/lib/viewer/composables/ui'
|
||||
import { projectsRoute } from '~~/lib/common/helpers/route'
|
||||
import { workspaceRoute } from '~/lib/common/helpers/route'
|
||||
import { useMixpanel } from '~/lib/core/composables/mp'
|
||||
import { writableAsyncComputed } from '~/lib/common/composables/async'
|
||||
|
||||
const emit = defineEmits<{
|
||||
setup: [InjectableViewerState]
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const isWorkspacesEnabled = useIsWorkspacesEnabled()
|
||||
|
||||
const modelId = computed(() => route.params.modelId as string)
|
||||
|
||||
const projectId = computed(() => route.params.id as string)
|
||||
const resourceIdString = computed(() => route.params.modelId as string)
|
||||
const projectId = writableAsyncComputed({
|
||||
get: () => route.params.id as string,
|
||||
set: async (value: string) => {
|
||||
// Just rewrite route id param
|
||||
await router.push({
|
||||
params: { id: value }
|
||||
})
|
||||
},
|
||||
initialState: route.params.id as string,
|
||||
asyncRead: false
|
||||
})
|
||||
|
||||
const state = useSetupViewer({
|
||||
projectId
|
||||
|
||||
@@ -48,9 +48,13 @@ export const useBillingActions = () => {
|
||||
const { mutate: cancelCheckoutSessionMutation } = useMutation(
|
||||
settingsBillingCancelCheckoutSessionMutation
|
||||
)
|
||||
const logger = useLogger()
|
||||
|
||||
const billingPortalRedirect = async (workspaceId: MaybeNullOrUndefined<string>) => {
|
||||
if (!workspaceId) return
|
||||
if (!workspaceId) {
|
||||
logger.error('[Billing Portal] No workspaceId provided, returning early')
|
||||
return
|
||||
}
|
||||
|
||||
mixpanel.track('Workspace Billing Portal Button Clicked', {
|
||||
// eslint-disable-next-line camelcase
|
||||
@@ -66,6 +70,11 @@ export const useBillingActions = () => {
|
||||
|
||||
if (result.data?.workspace.customerPortalUrl) {
|
||||
window.open(result.data.workspace.customerPortalUrl, '_blank')
|
||||
} else {
|
||||
logger.warn(
|
||||
'[Billing Portal] No portal URL returned, full response:',
|
||||
result.data
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
useSelectionEvents,
|
||||
useViewerCameraControlEndTracker
|
||||
} from '~~/lib/viewer/composables/viewer'
|
||||
import { SpeckleViewer } from '@speckle/shared'
|
||||
import type { Nullable } from '@speckle/shared'
|
||||
import { SpeckleViewer, xor } from '@speckle/shared'
|
||||
import type { Nullable, Optional } from '@speckle/shared'
|
||||
import { Vector3 } from 'three'
|
||||
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
|
||||
import { broadcastViewerUserActivityMutation } from '~~/lib/viewer/graphql/mutations'
|
||||
@@ -82,8 +82,16 @@ export function useViewerUserActivityBroadcasting(
|
||||
const apollo = useApolloClient().client
|
||||
const { isEnabled: isEmbedEnabled } = useEmbed()
|
||||
|
||||
const isSameMessage = (
|
||||
previousSerializedMessage: Optional<string>,
|
||||
newMessage: ViewerUserActivityMessageInput
|
||||
) => {
|
||||
if (xor(previousSerializedMessage, newMessage)) return false
|
||||
if (!previousSerializedMessage && !newMessage) return false
|
||||
return previousSerializedMessage === JSON.stringify(newMessage)
|
||||
}
|
||||
|
||||
const invokeMutation = async (message: ViewerUserActivityMessageInput) => {
|
||||
if (!isLoggedIn.value || isEmbedEnabled.value) return false
|
||||
const result = await apollo
|
||||
.mutate({
|
||||
mutation: broadcastViewerUserActivityMutation,
|
||||
@@ -98,14 +106,33 @@ export function useViewerUserActivityBroadcasting(
|
||||
return result.data?.broadcastViewerUserActivity || false
|
||||
}
|
||||
|
||||
let serializedPreviousMessage: Optional<string> = undefined
|
||||
const invokeObservabilityEvent = async (message: ViewerUserActivityMessageInput) => {
|
||||
const dd = window.DD_RUM
|
||||
if (!dd || !('addAction' in dd)) return
|
||||
|
||||
if (isSameMessage(serializedPreviousMessage, message)) return
|
||||
|
||||
serializedPreviousMessage = JSON.stringify(message)
|
||||
dd.addAction('Viewer User Activity', { message })
|
||||
}
|
||||
|
||||
const invoke = async (message: ViewerUserActivityMessageInput) => {
|
||||
if (!isLoggedIn.value || isEmbedEnabled.value) return false
|
||||
return await Promise.all([
|
||||
invokeMutation(message),
|
||||
invokeObservabilityEvent(message)
|
||||
])
|
||||
}
|
||||
|
||||
return {
|
||||
emitDisconnected: async () =>
|
||||
invokeMutation({
|
||||
await invoke({
|
||||
...getMainMetadata(),
|
||||
status: ViewerUserActivityStatus.Disconnected
|
||||
}),
|
||||
emitViewing: async () => {
|
||||
await invokeMutation({
|
||||
await invoke({
|
||||
...getMainMetadata(),
|
||||
status: ViewerUserActivityStatus.Viewing
|
||||
})
|
||||
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
useFilterUtilities,
|
||||
useSelectionUtilities
|
||||
} from '~~/lib/viewer/composables/ui'
|
||||
import { CameraController, ViewMode } from '@speckle/viewer'
|
||||
import { CameraController, ViewMode, VisualDiffMode } from '@speckle/viewer'
|
||||
import type { NumericPropertyInfo } from '@speckle/viewer'
|
||||
import type { PartialDeep } from 'type-fest'
|
||||
|
||||
type SerializedViewerState = SpeckleViewer.ViewerState.SerializedViewerState
|
||||
|
||||
@@ -136,6 +137,7 @@ export enum StateApplyMode {
|
||||
|
||||
export function useApplySerializedState() {
|
||||
const {
|
||||
projectId,
|
||||
ui: {
|
||||
camera: { position, target, isOrthoProjection },
|
||||
sectionBox,
|
||||
@@ -165,61 +167,71 @@ export function useApplySerializedState() {
|
||||
const { setSelectionFromObjectIds } = useSelectionUtilities()
|
||||
const logger = useLogger()
|
||||
|
||||
return async (state: SerializedViewerState, mode: StateApplyMode) => {
|
||||
return async (state: PartialDeep<SerializedViewerState>, mode: StateApplyMode) => {
|
||||
if (mode === StateApplyMode.Reset) {
|
||||
resetState()
|
||||
return
|
||||
}
|
||||
|
||||
if (state.projectId && state.projectId !== projectId.value) {
|
||||
await projectId.update(state.projectId)
|
||||
}
|
||||
|
||||
if (
|
||||
[StateApplyMode.Spotlight, StateApplyMode.TheadFullContextOpen].includes(mode)
|
||||
) {
|
||||
await resourceIdString.update(state.resources?.request?.resourceIdString || '')
|
||||
}
|
||||
|
||||
position.value = new Vector3(
|
||||
state.ui.camera.position[0],
|
||||
state.ui.camera.position[1],
|
||||
state.ui.camera.position[2]
|
||||
state.ui?.camera?.position?.[0],
|
||||
state.ui?.camera?.position?.[1],
|
||||
state.ui?.camera?.position?.[2]
|
||||
)
|
||||
target.value = new Vector3(
|
||||
state.ui.camera.target[0],
|
||||
state.ui.camera.target[1],
|
||||
state.ui.camera.target[2]
|
||||
state.ui?.camera?.target?.[0],
|
||||
state.ui?.camera?.target?.[1],
|
||||
state.ui?.camera?.target?.[2]
|
||||
)
|
||||
|
||||
isOrthoProjection.value = state.ui.camera.isOrthoProjection
|
||||
isOrthoProjection.value = !!state.ui?.camera?.isOrthoProjection
|
||||
|
||||
sectionBox.value = state.ui.sectionBox
|
||||
sectionBox.value = state.ui?.sectionBox
|
||||
? new Box3(
|
||||
new Vector3(
|
||||
state.ui.sectionBox.min[0],
|
||||
state.ui.sectionBox.min[1],
|
||||
state.ui.sectionBox.min[2]
|
||||
state.ui.sectionBox.min?.[0],
|
||||
state.ui.sectionBox.min?.[1],
|
||||
state.ui.sectionBox.min?.[2]
|
||||
),
|
||||
new Vector3(
|
||||
state.ui.sectionBox.max[0],
|
||||
state.ui.sectionBox.max[1],
|
||||
state.ui.sectionBox.max[2]
|
||||
state.ui.sectionBox.max?.[0],
|
||||
state.ui.sectionBox.max?.[1],
|
||||
state.ui.sectionBox.max?.[2]
|
||||
)
|
||||
)
|
||||
: null
|
||||
|
||||
const filters = state.ui.filters
|
||||
if (filters.hiddenObjectIds.length) {
|
||||
const filters = state.ui?.filters || {}
|
||||
if (filters.hiddenObjectIds?.length) {
|
||||
resetFilters()
|
||||
hideObjects(filters.hiddenObjectIds, { replace: true })
|
||||
} else if (filters.isolatedObjectIds.length) {
|
||||
} else if (filters.isolatedObjectIds?.length) {
|
||||
resetFilters()
|
||||
isolateObjects(filters.isolatedObjectIds, { replace: true })
|
||||
} else {
|
||||
resetFilters()
|
||||
}
|
||||
|
||||
const propertyFilterApplied = state.ui.filters.propertyFilter.isApplied
|
||||
const propertyFilterApplied = filters.propertyFilter?.isApplied
|
||||
if (propertyFilterApplied) {
|
||||
applyPropertyFilter()
|
||||
} else {
|
||||
unApplyPropertyFilter()
|
||||
}
|
||||
|
||||
const propertyInfoKey = state.ui.filters.propertyFilter.key
|
||||
const passMin = state.viewer.metadata.filteringState?.passMin
|
||||
const passMax = state.viewer.metadata.filteringState?.passMax
|
||||
const propertyInfoKey = filters.propertyFilter?.key
|
||||
const passMin = state.viewer?.metadata?.filteringState?.passMin
|
||||
const passMax = state.viewer?.metadata?.filteringState?.passMax
|
||||
if (propertyInfoKey) {
|
||||
removePropertyFilter()
|
||||
|
||||
@@ -249,30 +261,26 @@ export function useApplySerializedState() {
|
||||
}
|
||||
|
||||
if (mode === StateApplyMode.Spotlight) {
|
||||
highlightedObjectIds.value = filters.selectedObjectIds.slice()
|
||||
highlightedObjectIds.value = (filters.selectedObjectIds || []).slice()
|
||||
} else {
|
||||
if (filters.selectedObjectIds.length) {
|
||||
if (filters.selectedObjectIds?.length) {
|
||||
setSelectionFromObjectIds(filters.selectedObjectIds)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
[StateApplyMode.Spotlight, StateApplyMode.TheadFullContextOpen].includes(mode)
|
||||
) {
|
||||
await resourceIdString.update(state.resources.request.resourceIdString)
|
||||
}
|
||||
|
||||
if ([StateApplyMode.Spotlight].includes(mode)) {
|
||||
await urlHashState.focusedThreadId.update(state.ui.threads.openThread.threadId)
|
||||
await urlHashState.focusedThreadId.update(
|
||||
state.ui?.threads?.openThread?.threadId || null
|
||||
)
|
||||
}
|
||||
|
||||
const command = state.ui.diff.command
|
||||
const command = state.ui?.diff?.command
|
||||
? deserializeDiffCommand(state.ui.diff.command)
|
||||
: null
|
||||
const activeDiffEnabled = !!diff.enabled.value
|
||||
if (command && command.diffs.length) {
|
||||
diff.time.value = state.ui.diff.time
|
||||
diff.mode.value = state.ui.diff.mode
|
||||
if (command && command.diffs.length && state.ui?.diff) {
|
||||
diff.time.value = state.ui.diff.time || 0.5
|
||||
diff.mode.value = state.ui?.diff.mode || VisualDiffMode.COLORED
|
||||
|
||||
const instruction = command.diffs[0]
|
||||
await diffModelVersions(
|
||||
@@ -285,16 +293,16 @@ export function useApplySerializedState() {
|
||||
}
|
||||
|
||||
// Restore view mode
|
||||
if (state.ui.viewMode) {
|
||||
if (state.ui?.viewMode) {
|
||||
viewMode.value = state.ui.viewMode
|
||||
} else {
|
||||
viewMode.value = ViewMode.DEFAULT
|
||||
}
|
||||
|
||||
explodeFactor.value = state.ui.explodeFactor
|
||||
explodeFactor.value = state.ui?.explodeFactor || 0
|
||||
lightConfig.value = {
|
||||
...lightConfig.value,
|
||||
...state.ui.lightConfig
|
||||
...(state.ui?.lightConfig || {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
type VisualDiffMode,
|
||||
ViewMode
|
||||
} from '@speckle/viewer'
|
||||
import type { MaybeRef } from '@vueuse/shared'
|
||||
import { inject, ref, provide } from 'vue'
|
||||
import type { ComputedRef, WritableComputedRef, Raw, Ref, ShallowRef } from 'vue'
|
||||
import { useScopedState } from '~~/lib/common/composables/scopedState'
|
||||
@@ -82,7 +81,7 @@ export type InjectableViewerState = Readonly<{
|
||||
/**
|
||||
* The project which we're opening in the viewer (all loaded models should belong to it)
|
||||
*/
|
||||
projectId: ComputedRef<string>
|
||||
projectId: AsyncWritableComputedRef<string>
|
||||
/**
|
||||
* User viewer session ID. The same user will have different IDs in different tabs if multiple are open.
|
||||
* This is used to ignore user activity messages from the same tab.
|
||||
@@ -400,8 +399,6 @@ function setupInitialState(params: UseSetupViewerParams): InitialSetupState {
|
||||
public: { viewerDebug }
|
||||
} = useRuntimeConfig()
|
||||
|
||||
const projectId = computed(() => unref(params.projectId))
|
||||
|
||||
const sessionId = computed(() => nanoid())
|
||||
const isInitialized = ref(false)
|
||||
const { instance, initPromise, container } = useScopedState(
|
||||
@@ -412,7 +409,7 @@ function setupInitialState(params: UseSetupViewerParams): InitialSetupState {
|
||||
const hasDoneInitialLoad = ref(false)
|
||||
|
||||
return {
|
||||
projectId,
|
||||
projectId: params.projectId,
|
||||
sessionId,
|
||||
viewer: import.meta.server
|
||||
? ({
|
||||
@@ -1030,7 +1027,7 @@ function setupInterfaceState(
|
||||
}
|
||||
}
|
||||
|
||||
type UseSetupViewerParams = { projectId: MaybeRef<string> }
|
||||
type UseSetupViewerParams = { projectId: AsyncWritableComputedRef<string> }
|
||||
|
||||
export function useSetupViewer(params: UseSetupViewerParams): InjectableViewerState {
|
||||
// Initialize full state object - each subsequent state initialization depends on
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { ViewerEvent } from '@speckle/viewer'
|
||||
import {
|
||||
StateApplyMode,
|
||||
useApplySerializedState,
|
||||
useStateSerialization
|
||||
} from '~/lib/viewer/composables/serialization'
|
||||
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
|
||||
import { useViewerEventListener } from '~~/lib/viewer/composables/viewer'
|
||||
import type { SpeckleViewer } from '@speckle/shared'
|
||||
import { get, isString } from 'lodash-es'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function useDebugViewerEvents() {
|
||||
@@ -12,22 +19,67 @@ function useDebugViewerEvents() {
|
||||
}
|
||||
|
||||
function useDebugViewer() {
|
||||
const state = useInjectedViewerState()
|
||||
const fullViewerState = useInjectedViewerState()
|
||||
const apply = useApplySerializedState()
|
||||
const { serialize } = useStateSerialization()
|
||||
const {
|
||||
viewer: { instance }
|
||||
} = state
|
||||
} = fullViewerState
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
const ensureObj = <O>(obj: O | string): O => {
|
||||
return isString(obj) ? JSON.parse(obj) : obj
|
||||
}
|
||||
|
||||
const applyState = (
|
||||
state: SpeckleViewer.ViewerState.SerializedViewerState | string
|
||||
) => {
|
||||
return apply(ensureObj(state), StateApplyMode.TheadFullContextOpen)
|
||||
}
|
||||
|
||||
// Get current viewer instance
|
||||
window.VIEWER = instance
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
window.VIEWER_STATE = () => state
|
||||
|
||||
// Get current viewer state
|
||||
window.VIEWER_STATE = () => fullViewerState
|
||||
|
||||
// Get serialized version of current state
|
||||
window.VIEWER_SERIALIZED_STATE = (...args: Parameters<typeof serialize>) => {
|
||||
const serialized = serialize(...args)
|
||||
return JSON.stringify(serialized)
|
||||
}
|
||||
|
||||
// Apply viewer state
|
||||
window.APPLY_VIEWER_STATE = (
|
||||
state: SpeckleViewer.ViewerState.SerializedViewerState
|
||||
) => applyState(state)
|
||||
|
||||
// Apply DD user activity event
|
||||
window.APPLY_VIEWER_DD_EVENT = (
|
||||
event:
|
||||
| {
|
||||
content: {
|
||||
attributes: {
|
||||
context: {
|
||||
message: { state: SpeckleViewer.ViewerState.SerializedViewerState }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
| string
|
||||
) => {
|
||||
event = ensureObj(event)
|
||||
const path = 'content.attributes.context.message.state'
|
||||
const state = get(event, path)
|
||||
if (!state) {
|
||||
throw new Error('Cant find serialized state at path: ' + path)
|
||||
}
|
||||
|
||||
return applyState(state)
|
||||
}
|
||||
}
|
||||
|
||||
export function setupDebugMode() {
|
||||
if (import.meta.server) return
|
||||
if (!import.meta.dev) return
|
||||
|
||||
// useDebugViewerEvents()
|
||||
useDebugViewer()
|
||||
|
||||
@@ -7,6 +7,13 @@ declare global {
|
||||
* Start a new DD RUM view. Function is idempotent and can be safely called multiple times.
|
||||
*/
|
||||
DD_RUM_START_VIEW?: (path: string, name: string) => void
|
||||
|
||||
// Debug keys, don't need to type properly cause we only use them manually from dev tools
|
||||
VIEWER?: any
|
||||
VIEWER_STATE?: any
|
||||
VIEWER_SERIALIZED_STATE?: any
|
||||
APPLY_VIEWER_STATE?: any
|
||||
APPLY_VIEWER_DD_EVENT?: any
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,6 @@ extend type WorkspaceProjectMutations {
|
||||
- TODO: Eventually delete data in previous region
|
||||
"""
|
||||
moveToRegion(projectId: String!, regionKey: String!): String!
|
||||
@hasServerRole(role: SERVER_ADMIN)
|
||||
@hasStreamRole(role: STREAM_OWNER)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'
|
||||
import { RelativeURL } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import type { Express } from 'express'
|
||||
import { has, isString, random } from 'lodash'
|
||||
import { has, isString } from 'lodash'
|
||||
import request from 'supertest'
|
||||
|
||||
export const appId = 'spklwebapp' // same values as on FE
|
||||
@@ -233,7 +233,7 @@ export type LocalAuthRestApiHelpers = ReturnType<typeof localAuthRestApi>
|
||||
export const generateRegistrationParams = (): RegisterParams => ({
|
||||
challenge: faker.string.uuid(),
|
||||
user: {
|
||||
email: `${random(0, 1000)}@example.org`.toLowerCase(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password: faker.internet.password(),
|
||||
name: faker.person.fullName()
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
getAvailableRegionsFactory
|
||||
} from '@/modules/workspaces/services/regions'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { getFeatureFlags, isTestEnv } from '@/modules/shared/helpers/envHelper'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { WorkspacesNotYetImplementedError } from '@/modules/workspaces/errors/workspace'
|
||||
import { scheduleJob } from '@/modules/multiregion/services/queue'
|
||||
|
||||
@@ -61,7 +61,7 @@ export default {
|
||||
},
|
||||
WorkspaceProjectMutations: {
|
||||
moveToRegion: async (_parent, args, context) => {
|
||||
if (!FF_MOVE_PROJECT_REGION_ENABLED && !isTestEnv()) {
|
||||
if (!FF_MOVE_PROJECT_REGION_ENABLED) {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
}
|
||||
|
||||
|
||||
@@ -522,7 +522,8 @@ isMultiRegionTestMode()
|
||||
const adminUser: BasicTestUser = {
|
||||
id: '',
|
||||
name: 'John Speckle',
|
||||
email: createRandomEmail()
|
||||
email: createRandomEmail(),
|
||||
role: Roles.Server.Admin
|
||||
}
|
||||
|
||||
const testWorkspace: SetOptional<BasicTestWorkspace, 'slug'> = {
|
||||
|
||||
@@ -572,6 +572,9 @@ Generate the environment variables for Speckle server and Speckle objects deploy
|
||||
- name: FF_WORKSPACES_NEW_PLANS_ENABLED
|
||||
value: {{ .Values.featureFlags.workspacesNewPlanEnabled | quote }}
|
||||
|
||||
- name: FF_MOVE_PROJECT_REGION_ENABLED
|
||||
value: {{ .Values.featureFlags.moveProjectRegionEnabled | quote }}
|
||||
|
||||
{{- if .Values.featureFlags.workspacesModuleEnabled }}
|
||||
- name: LICENSE_TOKEN
|
||||
valueFrom:
|
||||
|
||||
@@ -99,6 +99,11 @@
|
||||
"type": "boolean",
|
||||
"description": "Toggles whether the new (Q1 2025) plans for workspaces are available. workspacesModuleEnabled must also be enabled for this to take effect.",
|
||||
"default": false
|
||||
},
|
||||
"moveProjectRegionEnabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enables the ability to move a project region (manually or automatically)",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -61,6 +61,8 @@ featureFlags:
|
||||
noPersonalEmailsEnabled: false
|
||||
## @param featureFlags.workspacesNewPlanEnabled Toggles whether the new (Q1 2025) plans for workspaces are available. workspacesModuleEnabled must also be enabled for this to take effect.
|
||||
workspacesNewPlanEnabled: false
|
||||
## @param featureFlags.moveProjectRegionEnabled Enables the ability to move a project region (manually or automatically)
|
||||
moveProjectRegionEnabled: false
|
||||
|
||||
analytics:
|
||||
## @param analytics.enabled Enable or disable analytics
|
||||
|
||||
Reference in New Issue
Block a user