Merge branch 'main' of github.com:specklesystems/speckle-server into alessandro/web-2728-full-seat-products-for-new-paid-plans

This commit is contained in:
Alessandro Magionami
2025-02-28 09:37:57 +01:00
179 changed files with 5445 additions and 2410 deletions
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -eo pipefail
GIT_ROOT="$(git rev-parse --show-toplevel)"
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# shellcheck disable=SC1090,SC1091
source "${SCRIPT_DIR}/common.sh"
FE2_DIR_PATH="${FE2_DIR_PATH:-"packages/frontend-2"}"
FE2_DATADOG_SERVICE="${FE2_DATADOG_SERVICE:-"web-app-2"}"
DATADOG_SITE="${DATADOG_SITE:-"datadoghq.eu"}"
if [[ -z "${DATADOG_API_KEY}" ]]; then
echo "DATADOG_API_KEY is not set"
exit 1
fi
# Build same prod docker image just w/ sourcemaps enabled
export DOCKER_BUILDKIT=1
docker build --build-arg BUILD_SOURCEMAPS=true --build-arg SPECKLE_SERVER_VERSION="${IMAGE_VERSION_TAG}" --tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}-sourcemaps" --file "${FE2_DIR_PATH}/Dockerfile" .
container_id=$(docker create "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}-sourcemaps")
# Clean target location and copy sourcemaps into it
rm -rf "${GIT_ROOT}/${FE2_DIR_PATH}/.output"
docker cp "$container_id":/speckle-server "${GIT_ROOT}/${FE2_DIR_PATH}/.output"
docker rm "$container_id"
# Publish sourcemaps
pushd "${GIT_ROOT}/${FE2_DIR_PATH}"
DATADOG_SITE="${DATADOG_SITE}" npx --yes @datadog/datadog-ci sourcemaps upload ./.output/public/_nuxt \
--service="${FE2_DATADOG_SERVICE}" \
--release-version="${IMAGE_VERSION_TAG}" \
--minified-path-prefix=/_nuxt
popd
# Clean up
rm -rf "${GIT_ROOT}/${FE2_DIR_PATH}/.output"
+68 -43
View File
@@ -44,6 +44,9 @@ workflows:
- test-objectsender:
filters: *filters-allow-all
- test-shared:
filters: *filters-allow-all
- test-preview-service:
filters: *filters-allow-all
@@ -184,6 +187,7 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-server
- test-server-no-ff
- test-server-multiregion
@@ -200,11 +204,20 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-server
- test-server-no-ff
- test-server-multiregion
- test-preview-service
- docker-publish-frontend-2-sourcemaps:
context:
- github-readonly-public-repos
- datadog-sourcemaps-publish
filters: *filters-publish
requires:
- get-version
- docker-publish-webhooks:
context: *docker-hub-context
filters: *filters-publish
@@ -216,6 +229,7 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-server
- test-server-no-ff
- test-server-multiregion
@@ -232,6 +246,7 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-server
- test-server-no-ff
- test-server-multiregion
@@ -248,6 +263,7 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-server
- test-server-no-ff
- test-server-multiregion
@@ -264,6 +280,7 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-server
- test-server-no-ff
- test-server-multiregion
@@ -286,6 +303,7 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-server
- test-server-no-ff
- test-server-multiregion
@@ -302,6 +320,7 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-server
- test-server-no-ff
- test-server-multiregion
@@ -349,6 +368,7 @@ workflows:
- test-frontend-2
- test-viewer
- test-objectsender
- test-shared
- test-preview-service
- publish-viewer-sandbox-cloudflare-pages:
@@ -358,16 +378,6 @@ workflows:
requires:
- test-viewer
- frontend-2-sourcemaps:
context:
- datadog-sourcemaps-publish
filters: *filters-publish
requires:
- get-version
- docker-build-frontend-2
- test-frontend-2
- publish-helm-chart
jobs:
get-version:
docker: &docker-base-image
@@ -729,6 +739,36 @@ jobs:
command: yarn test
working_directory: 'packages/preview-service'
test-shared:
docker: *docker-node-browsers-image
resource_class: medium+
steps:
- checkout
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-server-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-server-{{ checksum "yarn.lock" }}
paths:
- .yarn/cache
- .yarn/unplugged
- run:
name: Lint
command: yarn lint:ci
working_directory: 'packages/shared'
- run:
name: Run tests
command: yarn test:single-run
working_directory: 'packages/shared'
test-objectsender:
docker: *docker-node-browsers-image
resource_class: large
@@ -1005,6 +1045,24 @@ jobs:
environment:
SPECKLE_SERVER_PACKAGE: frontend-2
docker-publish-frontend-2-sourcemaps:
docker: *docker-node-image
resource_class: xlarge
working_directory: *work-dir
environment:
SPECKLE_SERVER_PACKAGE: frontend-2
steps:
- checkout
- attach_workspace:
at: /tmp/ci/workspace
- run: cat workspace/env-vars >> $BASH_ENV
- setup_remote_docker:
version: default
docker_layer_caching: true
- run:
name: Build and Save
command: ./.circleci/build_publish_fe2_sourcemaps.sh
docker-build-previews:
<<: *build-job
environment:
@@ -1216,36 +1274,3 @@ jobs:
environment:
CLOUDFLARE_PAGES_PROJECT_NAME: viewer
VIEWER_SANDBOX_DIR_PATH: packages/viewer-sandbox
frontend-2-sourcemaps:
docker: *docker-node-image
resource_class: large
working_directory: *work-dir
steps:
- checkout
- attach_workspace:
at: /tmp/ci/workspace
- run: cat workspace/env-vars >> $BASH_ENV
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-server-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-server-{{ checksum "yarn.lock" }}
paths:
- .yarn/cache
- .yarn/unplugged
- run:
name: Build public packages
command: yarn build:public
- run:
name: Build FE2
command: NODE_ENV=production SPECKLE_SERVER_VERSION="${IMAGE_VERSION_TAG}" yarn build:sourcemaps
working_directory: 'packages/frontend-2'
- run:
name: Upload source maps
command: ./.circleci/publish_fe2_sourcemaps.sh
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
GIT_ROOT="$(git rev-parse --show-toplevel)"
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# shellcheck disable=SC1090,SC1091
source "${SCRIPT_DIR}/common.sh"
FE2_DIR_PATH="${FE2_DIR_PATH:-"packages/frontend-2"}"
FE2_DATADOG_SERVICE="${FE2_DATADOG_SERVICE:-"web-app-2"}"
if [[ -z "${DATADOG_API_KEY}" ]]; then
echo "DATADOG_API_KEY is not set"
exit 1
fi
pushd "${GIT_ROOT}/${FE2_DIR_PATH}"
DATADOG_SITE="${DATADOG_SITE:-"datadoghq.eu"}" yarn datadog-ci sourcemaps upload ./.output/public/_nuxt \
--service="${FE2_DATADOG_SERVICE}" \
--release-version="${IMAGE_VERSION_TAG}" \
--minified-path-prefix=/_nuxt
popd
+3 -1
View File
@@ -63,6 +63,7 @@
"prettier": "^2.5.1",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.0.0",
"vitest": "^3.0.7",
"zx": "^8.1.2"
},
"resolutions": {
@@ -91,7 +92,8 @@
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0",
"wait-on": ">=7.2.0",
"vue-tsc@npm:2.2.2/@vue/language-core": "2.2.0"
"vue-tsc@npm:2.2.2/@vue/language-core": "2.2.0",
"vitest": "^3.0.7"
},
"config": {
"commitizen": {
-3
View File
@@ -37,8 +37,5 @@ NUXT_PUBLIC_ENABLE_AUTOMATE_MODULE=false
# Survicate
NUXT_PUBLIC_SURVICATE_WORKSPACE_KEY=
# Enable direct preview image loading - way quicker, but requres server & frontend to be on the same origin
NUXT_PUBLIC_ENABLE_DIRECT_PREVIEWS=true
# Ghost API
NUXT_PUBLIC_GHOST_API_KEY=
+1 -2
View File
@@ -1,6 +1,7 @@
FROM node:18-bookworm-slim@sha256:408f8cbbb7b33a5bb94bdb8862795a94d2b64c2d516856824fd86c4a5594a443 AS build-stage
ARG NODE_ENV=production
ARG SPECKLE_SERVER_VERSION=custom
ARG BUILD_SOURCEMAPS=false
WORKDIR /speckle-server
@@ -30,8 +31,6 @@ COPY packages/frontend-2 ./packages/frontend-2/
RUN yarn workspaces focus -A
# hadolint ignore=DL3059
RUN yarn workspaces foreach -W run build
# hadolint ignore=DL3059
RUN find ./packages/frontend-2/.output/ -type f \( -name "*.js.map" -o -name "*.mjs.map" -o -name "*.cjs.map" \) -exec rm -f {} \;
ENV TINI_VERSION v0.19.0
RUN apt-get update -y \
+1
View File
@@ -23,6 +23,7 @@ const config: CodegenConfig = {
'./lib/common/generated/gql/': {
preset: 'client',
config: {
enumsAsConst: true,
useTypeImports: true,
fragmentMasking: false,
dedupeFragments: true,
@@ -6,7 +6,7 @@
v-model="email"
type="email"
name="email"
label="Email"
label="Work email"
placeholder="Email"
size="lg"
color="foundation"
@@ -69,17 +69,14 @@ import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables
import { ensureError } from '@speckle/shared'
import { useAuthManager } from '~~/lib/auth/composables/auth'
import { loginRoute } from '~~/lib/common/helpers/route'
import { passwordRules } from '~~/lib/auth/helpers/validation'
import {
passwordRules,
doesNotContainBlockedDomain
} from '~~/lib/auth/helpers/validation'
import { graphql } from '~~/lib/common/generated/gql'
import type { ServerTermsOfServicePrivacyPolicyFragmentFragment } from '~~/lib/common/generated/gql/graphql'
import { useMounted } from '@vueuse/core'
/**
* TODO:
* - (BE) Password strength check? Do we want to use it anymore?
* - Dim's answer: no, `passwordRules` are legit enough for now.
*/
graphql(`
fragment ServerTermsOfServicePrivacyPolicyFragment on ServerInfo {
termsOfService
@@ -99,13 +96,18 @@ const router = useRouter()
const { signUpWithEmail, inviteToken } = useAuthManager()
const { triggerNotification } = useGlobalToast()
const isMounted = useMounted()
const isNoPersonalEmailsEnabled = useIsNoPersonalEmailsEnabled()
const newsletterConsent = defineModel<boolean>('newsletterConsent', { required: true })
const loading = ref(false)
const password = ref('')
const email = ref('')
const emailRules = [isEmail]
const emailRules = computed(() =>
inviteToken.value || !isNoPersonalEmailsEnabled.value
? [isEmail]
: [isEmail, doesNotContainBlockedDomain]
)
const nameRules = [isRequired]
const isEmailDisabled = computed(() => !!props.inviteEmail?.length || loading.value)
@@ -22,13 +22,7 @@
<div class="flex flex-col sm:flex-row gap-2 sm:items-center truncate">
<div class="sm:truncate">
<div
v-if="
[
AutomateRunStatus.Initializing,
AutomateRunStatus.Running,
AutomateRunStatus.Pending
].includes(functionRun.status)
"
v-if="isStartingOrRunning"
class="text-body-2xs text-foreground-2 italic whitespace-normal sm:truncate"
>
Function is {{ functionRun.status.toLowerCase() }}.
@@ -141,4 +135,14 @@ const results = useAutomationFunctionRunResults({
const showAttachmentDialog = ref(false)
const attachments = computed(() => results.value?.values.blobIds || [])
const isStartingOrRunning = computed(() =>
(
[
AutomateRunStatus.Initializing,
AutomateRunStatus.Running,
AutomateRunStatus.Pending
] as string[]
).includes(props.functionRun.status)
)
</script>
@@ -156,11 +156,13 @@ const hasValidContextView = computed(() => {
})
const statusMessage = computed(() => {
const isFinished = ![
AutomateRunStatus.Initializing,
AutomateRunStatus.Running,
AutomateRunStatus.Pending
].includes(props.functionRun.status)
const isFinished = !(
[
AutomateRunStatus.Initializing,
AutomateRunStatus.Running,
AutomateRunStatus.Pending
] as string[]
).includes(props.functionRun.status)
return isFinished
? props.functionRun.statusMessage ?? 'No status message'
@@ -3,7 +3,8 @@ import {
waitIntervalUntil,
type Nullable,
timeoutAt,
WaitIntervalUntilCanceledError
WaitIntervalUntilCanceledError,
TimeoutError
} from '@speckle/shared'
import { until } from '@vueuse/core'
import type { CSSProperties } from 'vue'
@@ -26,6 +27,8 @@ export default defineComponent({
}
},
setup(props, { slots, expose }) {
const logger = useLogger()
const transitioning = ref(false)
const newWrapperRef = ref(null as Nullable<HTMLDivElement>)
const oldWrapperRef = ref(null as Nullable<HTMLDivElement>)
@@ -102,6 +105,8 @@ export default defineComponent({
* Cause default slot to update with an opacity transition
*/
const updateContents = async () => {
transitioning.value = true
// Stage 1: Just move new -> old w/o any transitions (visually should look the same)
oldContents.value = newContents.value
newContents.value = slots.default?.()
@@ -155,15 +160,23 @@ export default defineComponent({
const triggerTransition = async () => {
if (!transitioning.value) {
transitioning.value = true
await updateContents()
return
}
await Promise.race([
until(transitioning).toBe(false),
timeoutAt(props.duration + 1000)
])
try {
await Promise.race([
until(transitioning).toBe(false),
timeoutAt(props.duration + 1000, 'Waiting for transition to finish timed out')
])
} catch (e) {
if (!(e instanceof TimeoutError)) {
throw e
} else {
logger.warn(e)
}
}
await updateContents()
}
@@ -36,6 +36,8 @@
class="md:min-w-80"
allow-unset
:items="categories"
size="base"
color="foundation"
>
<template #something-selected="{ value }">
{{ isArray(value) ? value[0].name : value.name }}
@@ -38,4 +38,7 @@ const dialogButtons = computed((): LayoutDialogButton[] => [
}
}
])
// testing deployments in prod, ignore this
markUsed('a')
</script>
@@ -26,11 +26,7 @@
<script setup lang="ts">
import { useForm } from 'vee-validate'
import type {
OnboardingRole,
OnboardingPlan,
OnboardingSource
} from '~/lib/auth/helpers/onboarding'
import type { OnboardingRole, OnboardingPlan, OnboardingSource } from '@speckle/shared'
import { useProcessOnboarding } from '~~/lib/auth/composables/onboarding'
import { homeRoute } from '~/lib/common/helpers/route'
@@ -50,7 +46,11 @@ const onSubmit = handleSubmit(async () => {
if (values.role) {
setMixpanelSegments({ role: values.role })
}
await setUserOnboardingComplete()
await setUserOnboardingComplete({
role: values.role,
plans: values.plan,
source: values.source
})
navigateTo(homeRoute)
})
</script>
@@ -30,7 +30,8 @@
<script setup lang="ts">
import { useFormSelectChildInternals } from '@speckle/ui-components'
import { OnboardingPlan, PlanTitleMap } from '~/lib/auth/helpers/onboarding'
import { PlanTitleMap } from '~/lib/auth/helpers/onboarding'
import { OnboardingPlan } from '@speckle/shared'
import { isRequired } from '~~/lib/common/helpers/validation'
const props = defineProps<{
@@ -26,7 +26,8 @@
<script setup lang="ts">
import { useFormSelectChildInternals } from '@speckle/ui-components'
import { OnboardingRole, RoleTitleMap } from '~/lib/auth/helpers/onboarding'
import { RoleTitleMap } from '~/lib/auth/helpers/onboarding'
import { OnboardingRole } from '@speckle/shared'
import { isRequired } from '~~/lib/common/helpers/validation'
const props = defineProps<{
@@ -26,7 +26,8 @@
<script setup lang="ts">
import { useFormSelectChildInternals } from '@speckle/ui-components'
import { OnboardingSource, SourceTitleMap } from '~/lib/auth/helpers/onboarding'
import { SourceTitleMap } from '~/lib/auth/helpers/onboarding'
import { OnboardingSource } from '@speckle/shared'
import { isRequired } from '~~/lib/common/helpers/validation'
const props = defineProps<{
@@ -93,14 +93,20 @@
<NuxtLink
v-for="workspaceMenuItem in workspaceMenuItems"
:key="`workspace-menu-item-${workspaceMenuItem.name}-${workspaceItem.slug}`"
:to="workspaceMenuItem.route(workspaceItem.slug)"
:to="
!isAdmin &&
(workspaceMenuItem.disabled ||
needsSsoSession(workspaceItem, workspaceMenuItem.name))
? undefined
: workspaceMenuItem.route(workspaceItem.slug)
"
@click="isOpenMobile = false"
>
<LayoutSidebarMenuGroupItem
v-if="workspaceMenuItem.permission?.includes(workspaceItem.role as WorkspaceRoles)"
:label="workspaceMenuItem.title"
:active="
route.name === workspaceMenuItem.name &&
route.name?.toString().startsWith(workspaceMenuItem.name) &&
route.params.slug === workspaceItem.slug
"
:tooltip-text="
@@ -117,13 +123,6 @@
/>
</NuxtLink>
</LayoutSidebarMenuGroup>
<NuxtLink v-if="!isGuest" :to="workspacesRoute">
<LayoutSidebarMenuGroupItem label="Create workspace">
<template #icon>
<PlusIcon class="h-4 w-4 text-foreground-2" />
</template>
</LayoutSidebarMenuGroupItem>
</NuxtLink>
</LayoutSidebarMenuGroup>
</LayoutSidebarMenu>
</LayoutSidebar>
@@ -132,11 +131,10 @@
</template>
<script setup lang="ts">
import { Roles } from '@speckle/shared'
import { useIsWorkspacesEnabled } from '~/composables/globals'
import { useQuery } from '@vue/apollo-composable'
import { settingsSidebarQuery } from '~/lib/settings/graphql/queries'
import { PlusIcon, ChevronLeftIcon } from '@heroicons/vue/24/outline'
import { ChevronLeftIcon } from '@heroicons/vue/24/outline'
import { useActiveUser } from '~/lib/auth/composables/activeUser'
import { useSettingsMenu, useSettingsMenuState } from '~/lib/settings/composables/menu'
import {
@@ -146,11 +144,7 @@ import {
} from '@speckle/ui-components'
import { graphql } from '~~/lib/common/generated/gql'
import type { WorkspaceRoles } from '@speckle/shared'
import {
workspacesRoute,
homeRoute,
settingsWorkspaceRoutes
} from '~/lib/common/helpers/route'
import { homeRoute, settingsWorkspaceRoutes } from '~/lib/common/helpers/route'
import {
WorkspacePlanStatuses,
type SettingsMenu_WorkspaceFragment
@@ -187,7 +181,7 @@ graphql(`
`)
const settingsMenuState = useSettingsMenuState()
const { activeUser: user } = useActiveUser()
const { isAdmin } = useActiveUser()
const route = useRoute()
const isWorkspacesEnabled = useIsWorkspacesEnabled()
const { result: workspaceResult } = useQuery(settingsSidebarQuery, null, {
@@ -206,8 +200,6 @@ const workspaceItems = computed(
(item) => item.creationState?.completed !== false // Removed workspaces that are not completely created
) || []
)
const isAdmin = computed(() => user.value?.role === Roles.Server.Admin)
const isGuest = computed(() => user.value?.role === Roles.Server.Guest)
const needsSsoSession = (
workspace: SettingsMenu_WorkspaceFragment,
@@ -1,14 +1,14 @@
<template>
<LayoutDialog
v-model:open="isOpen"
:title="cancel ? 'Cancel adding email' : 'Delete email address'"
:title="isAdding ? 'Stop adding email?' : 'Delete email address'"
max-width="xs"
:buttons="dialogButtons"
>
<p class="text-body-xs text-foreground mb-2">
{{
cancel
? `Are you sure you want to cancel adding ${email?.email} to your account?`
isAdding
? `Do you want to stop adding ${email?.email}? Any progress will be discarded.`
: `Are you sure you want to delete ${email?.email} from your account?`
}}
</p>
@@ -22,7 +22,7 @@ import { useUserEmails } from '~/lib/user/composables/emails'
const props = defineProps<{
email?: UserEmail
cancel?: boolean
isAdding?: boolean
}>()
const isOpen = defineModel<boolean>('open', { required: true })
@@ -31,14 +31,14 @@ const { deleteUserEmail } = useUserEmails()
const dialogButtons = computed((): LayoutDialogButton[] => [
{
text: 'Cancel',
text: props.isAdding ? 'No' : 'Cancel',
props: { color: 'outline' },
onClick: () => {
isOpen.value = false
}
},
{
text: props.cancel ? 'Confirm' : 'Delete',
text: props.isAdding ? 'Yes' : 'Delete',
props: { color: 'primary' },
onClick: () => {
onDeleteEmail()
@@ -48,7 +48,10 @@ const dialogButtons = computed((): LayoutDialogButton[] => [
const onDeleteEmail = async () => {
if (!props.email) return
const success = await deleteUserEmail(props.email)
const success = await deleteUserEmail({
email: props.email,
hideToast: props.isAdding
})
if (success) {
isOpen.value = false
}
@@ -62,11 +62,7 @@
:email="emailData.email"
/>
<SettingsUserEmailDeleteDialog
v-model:open="showDeleteDialog"
:email="emailData"
:is-verifying="!emailData.verified"
/>
<SettingsUserEmailDeleteDialog v-model:open="showDeleteDialog" :email="emailData" />
</div>
</template>
@@ -5,7 +5,7 @@
<div class="flex items-center gap-x-2">
<h4 class="text-body font-medium">
Workspace
<span class="capitalize">{{ plan.name }}</span>
<span class="capitalize">{{ plan }}</span>
</h4>
<CommonBadge v-if="badgeText" rounded>
{{ badgeText }}
@@ -15,8 +15,8 @@
<span class="font-medium">
£{{
yearlyIntervalSelected
? plan.cost.yearly[Roles.Workspace.Member]
: plan.cost.monthly[Roles.Workspace.Member]
? planPrices.yearly[Roles.Workspace.Member]
: planPrices.monthly[Roles.Workspace.Member]
}}
</span>
per seat/month
@@ -40,7 +40,7 @@
<div v-else>
<!-- Key to fix tippy reactivity -->
<div
:key="`tooltip-${yearlyIntervalSelected}-${plan.name}-${currentPlan?.name}`"
:key="`tooltip-${yearlyIntervalSelected}-${plan}-${currentPlan?.name}`"
v-tippy="buttonTooltip"
>
<FormButton
@@ -56,32 +56,35 @@
</div>
<ul class="flex flex-col gap-y-2 mt-4 pt-3 border-t border-outline-3">
<li
v-for="feature in features"
:key="feature.name"
v-for="(featureMetadata, feature) in WorkspacePlanFeaturesMetadata"
:key="feature"
class="flex items-center text-body-xs"
:class="{
'lg:hidden': !plan.features.includes(feature.name as PlanFeaturesList)
}"
'lg:hidden': !planFeatures.includes(feature)
}"
>
<IconCheck
v-if="plan.features.includes(feature.name as PlanFeaturesList)"
v-if="planFeatures.includes(feature)"
class="w-4 h-4 text-foreground mx-2"
/>
<XMarkIcon v-else class="w-4 h-4 mx-2 text-foreground-3" />
<span
v-tippy="
feature.description(
yearlyIntervalSelected
? plan.cost.yearly[Roles.Workspace.Guest]
: plan.cost.monthly[Roles.Workspace.Guest]
)
isFunction(featureMetadata.description)
? featureMetadata.description({
price: (yearlyIntervalSelected
? planPrices.yearly[Roles.Workspace.Guest]
: planPrices.monthly[Roles.Workspace.Guest]
).toString()
})
: featureMetadata.description
"
class="underline decoration-outline-5 decoration-dashed underline-offset-4 cursor-help"
:class="{
'text-foreground-2': !plan.features.includes(feature.name as PlanFeaturesList)
'text-foreground-2': !planFeatures.includes(feature)
}"
>
{{ feature.name }}
{{ featureMetadata.displayName }}
</span>
</li>
</ul>
@@ -89,7 +92,7 @@
<SettingsWorkspacesBillingUpgradeDialog
v-if="currentPlan?.name && workspaceId"
v-model:open="isUpgradeDialogOpen"
:plan="plan.name"
:plan="plan"
:billing-interval="
yearlyIntervalSelected ? BillingInterval.Yearly : BillingInterval.Monthly
"
@@ -99,28 +102,30 @@
</template>
<script setup lang="ts">
import type { PricingPlan } from '@/lib/billing/helpers/types'
import { Roles } from '@speckle/shared'
import {
type PaidWorkspacePlansOld,
type MaybeNullOrUndefined,
WorkspacePlans,
WorkspacePlanFeaturesMetadata
} from '@speckle/shared'
import { Roles, WorkspacePlanConfigs } from '@speckle/shared'
import {
type WorkspacePlan,
WorkspacePlanStatuses,
WorkspacePlans,
BillingInterval
} from '~/lib/common/generated/gql/graphql'
import type { MaybeNullOrUndefined } from '@speckle/shared'
import { startCase } from 'lodash'
import { pricingPlansConfig } from '~/lib/billing/helpers/constants'
import type { PlanFeaturesList } from '~/lib/billing/helpers/types'
import { startCase, isFunction } from 'lodash'
import { XMarkIcon } from '@heroicons/vue/24/outline'
import type { SetupContext } from 'vue'
import { WorkspaceOldPaidPlanPrices } from '~/lib/billing/helpers/constants'
const emit = defineEmits<{
(e: 'onYearlyIntervalSelected', value: boolean): void
(e: 'onPlanSelected', value: WorkspacePlans): void
(e: 'onPlanSelected', value: PaidWorkspacePlansOld): void
}>()
const props = defineProps<{
plan: PricingPlan
plan: PaidWorkspacePlansOld
yearlyIntervalSelected: boolean
badgeText?: string
// The following props are optional if the table is for informational purposes
@@ -132,10 +137,12 @@ const props = defineProps<{
const slots: SetupContext['slots'] = useSlots()
const features = ref(pricingPlansConfig.features)
const isUpgradeDialogOpen = ref(false)
const isYearlyIntervalSelected = ref(props.yearlyIntervalSelected)
const planPrices = computed(() => WorkspaceOldPaidPlanPrices[props.plan])
const planFeatures = computed(() => WorkspacePlanConfigs[props.plan].features)
const hasCta = computed(() => !!slots.cta)
const canUpgradeToPlan = computed(() => {
if (!props.currentPlan) return false
@@ -149,10 +156,13 @@ const canUpgradeToPlan = computed(() => {
[WorkspacePlans.StarterInvoiced]: [],
[WorkspacePlans.PlusInvoiced]: [],
[WorkspacePlans.BusinessInvoiced]: [],
[WorkspacePlans.Free]: []
// New
[WorkspacePlans.Free]: [],
[WorkspacePlans.Team]: [],
[WorkspacePlans.Pro]: []
}
return allowedUpgrades[props.currentPlan.name].includes(props.plan.name)
return allowedUpgrades[props.currentPlan.name].includes(props.plan)
})
const statusIsTrial = computed(
@@ -166,17 +176,17 @@ const isMatchingInterval = computed(
)
const isDowngrade = computed(() => {
return !canUpgradeToPlan.value && props.currentPlan?.name !== props.plan.name
return !canUpgradeToPlan.value && props.currentPlan?.name !== props.plan
})
const isCurrentPlan = computed(
() => isMatchingInterval.value && props.currentPlan?.name === props.plan.name
() => isMatchingInterval.value && props.currentPlan?.name === props.plan
)
const isAnnualToMonthly = computed(() => {
return (
!isMatchingInterval.value &&
props.currentPlan?.name === props.plan.name &&
props.currentPlan?.name === props.plan &&
!props.yearlyIntervalSelected
)
})
@@ -184,7 +194,7 @@ const isAnnualToMonthly = computed(() => {
const isMonthlyToAnnual = computed(() => {
return (
!isMatchingInterval.value &&
props.currentPlan?.name === props.plan.name &&
props.currentPlan?.name === props.plan &&
props.yearlyIntervalSelected
)
})
@@ -200,8 +210,7 @@ const isSelectable = computed(() => {
return true
// Allow selection if switching from monthly to yearly for the same plan
if (isMonthlyToAnnual.value && props.currentPlan?.name === props.plan.name)
return true
if (isMonthlyToAnnual.value && props.currentPlan?.name === props.plan) return true
// Disable if current plan and intervals match
if (isCurrentPlan.value) return false
@@ -227,7 +236,7 @@ const buttonColor = computed(() => {
statusIsTrial.value ||
props.currentPlan?.status === WorkspacePlanStatuses.Expired
) {
return props.plan.name === WorkspacePlans.Starter ? 'primary' : 'outline'
return props.plan === WorkspacePlans.Starter ? 'primary' : 'outline'
}
return 'outline'
})
@@ -239,7 +248,7 @@ const buttonText = computed(() => {
props.currentPlan?.status === WorkspacePlanStatuses.Expired ||
props.currentPlan?.status === WorkspacePlanStatuses.Canceled
) {
return `Subscribe to ${startCase(props.plan.name)}`
return `Subscribe to ${startCase(props.plan)}`
}
// Current plan case
if (isCurrentPlan.value) {
@@ -247,7 +256,7 @@ const buttonText = computed(() => {
}
// Billing interval and lower plan case
if (isDowngrade.value) {
return `Downgrade to ${props.plan.name}`
return `Downgrade to ${props.plan}`
}
// Billing interval change and current plan
if (isAnnualToMonthly.value) {
@@ -257,7 +266,7 @@ const buttonText = computed(() => {
return 'Change to annual plan'
}
// Upgrade case
return canUpgradeToPlan.value ? `Upgrade to ${startCase(props.plan.name)}` : ''
return canUpgradeToPlan.value ? `Upgrade to ${startCase(props.plan)}` : ''
})
const buttonTooltip = computed(() => {
@@ -293,7 +302,7 @@ const buttonTooltip = computed(() => {
})
const onCtaClick = () => {
emit('onPlanSelected', props.plan.name)
emit('onPlanSelected', props.plan)
}
watch(
@@ -1,8 +1,8 @@
<template>
<div class="flex flex-col lg:grid lg:grid-cols-3 gap-4 w-full">
<SettingsWorkspacesBillingPricingTablePlan
v-for="plan in plans"
:key="plan.name"
v-for="plan in oldPlans"
:key="plan"
:plan="plan"
:yearly-interval-selected="isYearlySelected"
v-bind="$props"
@@ -13,16 +13,14 @@
</template>
<script setup lang="ts">
import {
type WorkspacePlan,
BillingInterval,
type WorkspacePlans
} from '~/lib/common/generated/gql/graphql'
import { pricingPlansConfig } from '~/lib/billing/helpers/constants'
import type { MaybeNullOrUndefined } from '@speckle/shared'
import { type WorkspacePlan, BillingInterval } from '~/lib/common/generated/gql/graphql'
import { type MaybeNullOrUndefined, PaidWorkspacePlansOld } from '@speckle/shared'
const emit = defineEmits<{
(e: 'onPlanSelected', value: { name: WorkspacePlans; cycle: BillingInterval }): void
(
e: 'onPlanSelected',
value: { name: PaidWorkspacePlansOld; cycle: BillingInterval }
): void
}>()
const props = defineProps<{
@@ -32,14 +30,14 @@ const props = defineProps<{
activeBillingInterval?: BillingInterval
}>()
const plans = ref(pricingPlansConfig.plans)
const isYearlySelected = ref(false)
const oldPlans = computed(() => Object.values(PaidWorkspacePlansOld))
const onYearlyIntervalSelected = (newValue: boolean) => {
isYearlySelected.value = newValue
}
const onPlanSelected = (value: WorkspacePlans) => {
const onPlanSelected = (value: PaidWorkspacePlansOld) => {
emit('onPlanSelected', {
name: value,
cycle: isYearlySelected.value ? BillingInterval.Yearly : BillingInterval.Monthly
@@ -22,18 +22,18 @@
<script setup lang="ts">
import type { LayoutDialogButton } from '@speckle/ui-components'
import {
type WorkspacePlans,
BillingInterval,
type PaidWorkspacePlans
} from '~/lib/common/generated/gql/graphql'
import { useBillingActions } from '~/lib/billing/composables/actions'
import { startCase } from 'lodash'
import { pricingPlansConfig } from '~/lib/billing/helpers/constants'
import type { PaidWorkspacePlansOld } from '@speckle/shared'
import { Roles } from '@speckle/shared'
import { isPaidPlan } from '~/lib/billing/helpers/types'
import { WorkspaceOldPaidPlanPrices } from '~/lib/billing/helpers/constants'
const props = defineProps<{
plan: WorkspacePlans
plan: PaidWorkspacePlansOld
billingInterval: BillingInterval
workspaceId: string
}>()
@@ -43,9 +43,8 @@ const { upgradePlan } = useBillingActions()
const seatPrice = computed(() => {
if (isPaidPlan(props.plan)) {
const planConfig =
pricingPlansConfig.plans[props.plan as unknown as PaidWorkspacePlans]
return planConfig.cost[props.billingInterval][Roles.Workspace.Member]
const prices = WorkspaceOldPaidPlanPrices[props.plan]
return prices[props.billingInterval][Roles.Workspace.Member]
}
return 0
@@ -11,9 +11,8 @@
:columns="[
{ id: 'name', header: 'Name', classes: 'col-span-3' },
{ id: 'company', header: 'Company', classes: 'col-span-3' },
{ id: 'verified', header: 'Status', classes: 'col-span-3' },
{ id: 'projects', header: 'Projects', classes: 'col-span-2' },
{ id: 'actions', header: '', classes: 'col-span-1 flex justify-end' }
{ id: 'actions', header: '', classes: 'col-span-4 flex justify-end' }
]"
:items="guests"
:loading="searchResultLoading"
@@ -36,11 +35,6 @@
{{ item.user.company ? item.user.company : '-' }}
</span>
</template>
<template #verified="{ item }">
<span class="text-body-xs text-foreground-2">
{{ item.user.verified ? 'Verified' : 'Unverified' }}
</span>
</template>
<template #projects="{ item }">
<span class="text-body-xs text-foreground-2">
<CommonBadge color-classes="bg-foundation-2 text-foreground-2" rounded>
@@ -121,7 +115,6 @@ graphql(`
avatar
name
company
verified
}
projectRoles {
role
@@ -108,7 +108,7 @@ graphql(`
fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {
id
...SettingsWorkspacesMembersTableHeader_Workspace
invitedTeam(filter: $invitesFilter) {
invitedTeam {
...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator
}
}
@@ -5,10 +5,11 @@
:columns="[
{ id: 'name', header: 'Name', classes: 'col-span-3' },
{ id: 'createdAt', header: 'Requested at', classes: 'col-span-3' },
{ id: 'status', header: 'Status', classes: 'col-span-3' },
{
id: 'actions',
header: '',
classes: 'col-span-3 lg:col-span-6 flex items-center justify-end'
classes: 'col-span-3 flex items-center justify-end'
}
]"
:items="joinRequests"
@@ -27,13 +28,22 @@
{{ formattedFullDate(item.createdAt) }}
</p>
</template>
<template #status="{ item }">
<span class="truncate text-body-xs text-foreground capitalize">
{{ item.status }}
</span>
</template>
<template #actions="{ item }">
<div class="flex items-center gap-x-2">
<div
v-if="item.status === WorkspaceJoinRequestStatus.Pending"
class="flex items-center gap-x-2"
>
<FormButton color="outline" size="sm" @click="onApprove(item)">
Approve
</FormButton>
<FormButton color="outline" size="sm" @click="onDeny(item)">Deny</FormButton>
</div>
<span v-else />
</template>
</LayoutTable>
@@ -45,10 +55,13 @@
</template>
<script setup lang="ts">
import { orderBy } from 'lodash-es'
import dayjs from 'dayjs'
import type { MaybeNullOrUndefined } from '@speckle/shared'
import type {
SettingsWorkspacesMembersRequestsTable_WorkspaceFragment,
WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment
import {
type SettingsWorkspacesMembersRequestsTable_WorkspaceFragment,
type WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment,
WorkspaceJoinRequestStatus
} from '~~/lib/common/generated/gql/graphql'
import { graphql } from '~/lib/common/generated/gql'
import { useWorkspaceJoinRequest } from '~/lib/workspaces/composables/joinRequests'
@@ -57,7 +70,7 @@ graphql(`
fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {
...SettingsWorkspacesMembersTableHeader_Workspace
id
adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {
adminWorkspacesJoinRequests {
totalCount
items {
...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest
@@ -84,9 +97,24 @@ const showApproveJoinRequestDialog = ref(false)
const requestToApprove =
ref<WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment>()
const joinRequests = computed(
() => props.workspace?.adminWorkspacesJoinRequests?.items || []
)
const joinRequests = computed(() => {
const thirtyDaysAgo = dayjs().subtract(30, 'days')
const filtered =
props.workspace?.adminWorkspacesJoinRequests?.items.filter((request) => {
if (request.status === WorkspaceJoinRequestStatus.Pending) return true
return dayjs(request.createdAt).isAfter(thirtyDaysAgo)
}) ?? []
return orderBy(
filtered,
[
(request) => (request.status === WorkspaceJoinRequestStatus.Pending ? 1 : 0),
'createdAt'
],
['desc', 'desc']
)
})
const onApprove = (
request: WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragment
@@ -13,12 +13,11 @@
:columns="[
{ id: 'name', header: 'Name', classes: 'col-span-3' },
{ id: 'company', header: 'Company', classes: 'col-span-3' },
{ id: 'verified', header: 'Status', classes: 'col-span-3' },
{ id: 'role', header: 'Role', classes: 'col-span-2' },
{
id: 'actions',
header: '',
classes: 'col-span-1 flex items-center justify-end'
classes: 'col-span-4 flex items-center justify-end'
}
]"
:items="members"
@@ -49,11 +48,6 @@
{{ item.company ? item.company : '-' }}
</span>
</template>
<template #verified="{ item }">
<span class="text-body-xs text-foreground-2">
{{ item.verified ? 'Verified' : 'Unverified' }}
</span>
</template>
<template #role="{ item }">
<span class="text-foreground-2">
<span>
@@ -105,7 +99,7 @@
import { Roles, type WorkspaceRoles, type MaybeNullOrUndefined } from '@speckle/shared'
import { settingsWorkspacesMembersSearchQuery } from '~~/lib/settings/graphql/queries'
import { useQuery } from '@vue/apollo-composable'
import type { SettingsWorkspacesMembersMembersTable_WorkspaceFragment } from '~~/lib/common/generated/gql/graphql'
import type { SettingsWorkspacesMembersTable_WorkspaceFragment } from '~~/lib/common/generated/gql/graphql'
import { graphql } from '~/lib/common/generated/gql'
import {
EllipsisHorizontalIcon,
@@ -120,7 +114,7 @@ import { getRoleLabel } from '~~/lib/settings/helpers/utils'
type UserItem = (typeof members)['value'][0]
graphql(`
fragment SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {
fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {
id
role
user {
@@ -128,14 +122,13 @@ graphql(`
avatar
name
company
verified
workspaceDomainPolicyCompliant
}
}
`)
graphql(`
fragment SettingsWorkspacesMembersMembersTable_Workspace on Workspace {
fragment SettingsWorkspacesMembersTable_Workspace on Workspace {
id
name
...SettingsSharedDeleteUserDialog_Workspace
@@ -144,7 +137,7 @@ graphql(`
team {
items {
id
...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator
...SettingsWorkspacesMembersTable_WorkspaceCollaborator
}
}
}
@@ -157,7 +150,7 @@ enum ActionTypes {
}
const props = defineProps<{
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersMembersTable_WorkspaceFragment>
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
workspaceSlug: string
}>()
@@ -24,7 +24,8 @@
navigateTo(settingsWorkspaceRoutes.members.route(workspaceInfo.slug))
"
>
{{ adminWorkspacesJoinRequestsCount }} join requests
{{ adminWorkspacesJoinRequestsCount }} join
{{ adminWorkspacesJoinRequestsCount > 1 ? 'requests' : 'request' }}
</button>
<button
v-if="invitedTeamCount && isWorkspaceAdmin"
@@ -49,24 +50,20 @@
</LayoutSidebarMenuGroup>
</template>
<script setup lang="ts">
import { graphql } from '~~/lib/common/generated/gql'
import type { WorkspaceSidebarMembers_WorkspaceFragment } from '~/lib/common/generated/gql/graphql'
import {
type WorkspaceTeam_WorkspaceFragment,
WorkspaceJoinRequestStatus
} from '~/lib/common/generated/gql/graphql'
import { settingsWorkspaceRoutes } from '~/lib/common/helpers/route'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import { useBreakpoints } from '@vueuse/core'
graphql(`
fragment WorkspaceSidebarMembers_Workspace on Workspace {
...WorkspaceTeam_Workspace
}
`)
defineEmits<{
(e: 'show-invite-dialog'): void
}>()
const props = defineProps<{
workspaceInfo: WorkspaceSidebarMembers_WorkspaceFragment
workspaceInfo: WorkspaceTeam_WorkspaceFragment
collapsible?: boolean
isWorkspaceAdmin?: boolean
}>()
@@ -94,6 +91,9 @@ const iconText = computed(() => {
const invitedTeamCount = computed(() => props.workspaceInfo?.invitedTeam?.length ?? 0)
const adminWorkspacesJoinRequestsCount = computed(
() => props.workspaceInfo?.adminWorkspacesJoinRequests?.totalCount
() =>
props.workspaceInfo?.adminWorkspacesJoinRequests?.items.filter(
(request) => request.status === WorkspaceJoinRequestStatus.Pending
).length
)
</script>
@@ -3,12 +3,12 @@
<div class="flex flex-col max-w-5xl w-full items-center">
<div class="grid lg:grid-cols-3 gap-y-2 gap-x-2 w-full">
<SettingsWorkspacesBillingPricingTablePlan
v-for="plan in plans"
:key="plan.name"
v-for="plan in oldPlans"
:key="plan"
:plan="plan"
:yearly-interval-selected="isYearlySelected"
:badge-text="
plan.name === WorkspacePlans.Starter && !isYearlySelected
plan === WorkspacePlans.Starter && !isYearlySelected
? '30-day free trial'
: undefined
"
@@ -16,14 +16,14 @@
>
<template #cta>
<FormButton
:color="plan.name === WorkspacePlans.Starter ? 'primary' : 'outline'"
:color="plan === WorkspacePlans.Starter ? 'primary' : 'outline'"
full-width
@click="onCtaClick(plan.name)"
@click="onCtaClick(plan)"
>
{{
plan.name === WorkspacePlans.Starter && !isYearlySelected
plan === WorkspacePlans.Starter && !isYearlySelected
? 'Start 30-day free trial'
: `Subscribe to ${startCase(plan.name)}`
: `Subscribe to ${startCase(plan)}`
}}
</FormButton>
</template>
@@ -45,16 +45,17 @@ import {
WorkspacePlans
} from '~/lib/common/generated/gql/graphql'
import { useWorkspacesWizard } from '~/lib/workspaces/composables/wizard'
import { pricingPlansConfig } from '~/lib/billing/helpers/constants'
import { useMixpanel } from '~/lib/core/composables/mp'
import { startCase } from 'lodash'
import { PaidWorkspacePlansOld } from '@speckle/shared'
const { goToNextStep, goToPreviousStep, state } = useWorkspacesWizard()
const mixpanel = useMixpanel()
const plans = ref(pricingPlansConfig.plans)
const isYearlySelected = ref(false)
const oldPlans = computed(() => Object.values(PaidWorkspacePlansOld))
const onCtaClick = (plan: WorkspacePlans) => {
state.value.plan = plan as unknown as PaidWorkspacePlans
state.value.billingInterval = isYearlySelected.value
@@ -71,4 +71,12 @@ export const useIsBillingIntegrationEnabled = () => {
return ref(FF_BILLING_INTEGRATION_ENABLED)
}
export const useIsNoPersonalEmailsEnabled = () => {
const {
public: { FF_NO_PERSONAL_EMAILS_ENABLED }
} = useRuntimeConfig()
return ref(FF_NO_PERSONAL_EMAILS_ENABLED)
}
export { useGlobalToast, useActiveUser, usePageQueryStandardFetchPolicy }
@@ -1,7 +1,12 @@
import { useApolloClient } from '@vue/apollo-composable'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { UnsupportedEnvironmentError } from '~~/lib/core/errors/base'
import type { OnboardingState } from '~~/lib/auth/helpers/onboarding'
import type {
OnboardingPlan,
OnboardingRole,
OnboardingSource,
OnboardingState
} from '@speckle/shared'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
import { OnboardingError } from '~~/lib/auth/errors/errors'
import { finishOnboardingMutation } from '~~/lib/auth/graphql/mutations'
@@ -86,14 +91,22 @@ export const useProcessOnboarding = () => {
/**
* Marks the current user as having completed the onboarding - we're using this as a flag to
* know that we've set up the sample project once.
* know that we've set up the sample project once. Also updates their Mailchimp tags.
*/
const setUserOnboardingComplete = async () => {
const setUserOnboardingComplete = async (onboardingData?: {
role?: OnboardingRole
plans?: OnboardingPlan[]
source?: OnboardingSource
}) => {
const user = activeUser.value
if (!user) throw new OnboardingError('Attempting to onboard unidentified user')
await apollo
.mutate({
mutation: finishOnboardingMutation,
variables: {
input: onboardingData
},
update: (cache, { data }) => {
if (!data?.activeUserMutations.finishOnboarding) return
@@ -1,9 +1,9 @@
import { graphql } from '~~/lib/common/generated/gql'
export const finishOnboardingMutation = graphql(`
mutation FinishOnboarding {
mutation FinishOnboarding($input: OnboardingCompletionInput) {
activeUserMutations {
finishOnboarding
finishOnboarding(input: $input)
}
}
`)
@@ -1,32 +1,4 @@
export enum OnboardingRole {
ComputationalDesign = 'computational-design',
BIM = 'bim',
ArchitecturePlanning = 'architecture-planning',
EngineeringAEC = 'engineering-aec',
EngineeringSoftware = 'engineering-software',
Education = 'education',
Management = 'management',
Other = 'other'
}
export enum OnboardingPlan {
Exploring = 'exploring',
DataExchange = 'data-exchange',
Analytics = 'analytics',
Collaboration = 'collaboration',
DataWarehouse = 'data-warehouse',
Development = 'development',
Other = 'other'
}
export enum OnboardingSource {
SocialMedia = 'social-media',
Search = 'internet-search',
Referral = 'friend-or-colleague',
Event = 'event-conference',
Education = 'university-course',
Other = 'other'
}
import { OnboardingRole, OnboardingPlan, OnboardingSource } from '@speckle/shared'
export const RoleTitleMap: Record<OnboardingRole, string> = {
[OnboardingRole.ComputationalDesign]: 'Computational Design',
@@ -58,9 +30,3 @@ export const SourceTitleMap: Record<OnboardingSource, string> = {
[OnboardingSource.Education]: 'University or course',
[OnboardingSource.Other]: 'Other'
}
export type OnboardingState = {
role?: OnboardingRole
plans?: OnboardingPlan[]
source?: OnboardingSource
}
@@ -1,4 +1,5 @@
import { isStringOfLength, stringContains } from '~~/lib/common/helpers/validation'
import { blockedDomains } from '@speckle/shared'
export const passwordLongEnough = isStringOfLength({ minLength: 8 })
export const passwordHasAtLeastOneNumber = stringContains({
@@ -20,3 +21,10 @@ export const passwordRules = [
passwordHasAtLeastOneLowercaseLetter,
passwordHasAtLeastOneUppercaseLetter
]
export const doesNotContainBlockedDomain = (val: string) => {
const domain = val.split('@')[1]?.toLowerCase()
return domain && blockedDomains.includes(domain)
? 'Please use your work email instead of a personal email address'
: true
}
@@ -62,12 +62,14 @@ export const useAutomationRunSummary = (params: {
?.functionRuns.filter(
(r): r is SetFullyRequired<typeof r, 'statusMessage'> =>
!!(
[
AutomateRunStatus.Failed,
AutomateRunStatus.Canceled,
AutomateRunStatus.Exception,
AutomateRunStatus.Timeout
].includes(r.status) && r.statusMessage?.length
(
[
AutomateRunStatus.Failed,
AutomateRunStatus.Canceled,
AutomateRunStatus.Exception,
AutomateRunStatus.Timeout
] as string[]
).includes(r.status) && r.statusMessage?.length
)
)
.map((r) => r.statusMessage) || []
@@ -141,9 +143,9 @@ export const useAutomationRunDetailsFns = () => {
const end =
run.status === AutomateRunStatus.Running
? now.value
: [AutomateRunStatus.Initializing, AutomateRunStatus.Pending].includes(
run.status
)
: (
[AutomateRunStatus.Initializing, AutomateRunStatus.Pending] as string[]
).includes(run.status)
? undefined
: run.updatedAt
if (!end) return undefined
@@ -1,114 +1,48 @@
import { WorkspacePlans, BillingInterval } from '~/lib/common/generated/gql/graphql'
import { Roles } from '@speckle/shared'
import { PlanFeaturesList, type PricingPlan } from '@/lib/billing/helpers/types'
import {
PaidWorkspacePlansOld,
Roles,
WorkspacePlanBillingIntervals,
type WorkspacePlanPriceStructure
} from '@speckle/shared'
const baseFeatures = [
PlanFeaturesList.Workspaces,
PlanFeaturesList.RoleManagement,
PlanFeaturesList.GuestUsers,
PlanFeaturesList.PrivateAutomateFunctions,
PlanFeaturesList.DomainSecurity
]
export const pricingPlansConfig: {
features: Record<
PlanFeaturesList,
{ name: string; description: (price?: number) => string }
>
plans: Record<
WorkspacePlans.Starter | WorkspacePlans.Plus | WorkspacePlans.Business,
PricingPlan
>
// TODO: Read these from API, especially for new plans
export const WorkspaceOldPaidPlanPrices: {
[plan in PaidWorkspacePlansOld]: WorkspacePlanPriceStructure
} = {
features: {
[PlanFeaturesList.Workspaces]: {
name: PlanFeaturesList.Workspaces,
description: () => `A shared space for your team and projects`
[PaidWorkspacePlansOld.Starter]: {
[WorkspacePlanBillingIntervals.Monthly]: {
[Roles.Workspace.Guest]: 15,
[Roles.Workspace.Member]: 15,
[Roles.Workspace.Admin]: 15
},
[PlanFeaturesList.RoleManagement]: {
name: PlanFeaturesList.RoleManagement,
description: () => `Control individual members' access and edit rights`
},
[PlanFeaturesList.GuestUsers]: {
name: PlanFeaturesList.GuestUsers,
description: (price?: number) =>
`Give guests access to specific projects in the workspace at £${price}/month/guest`
},
[PlanFeaturesList.PrivateAutomateFunctions]: {
name: PlanFeaturesList.PrivateAutomateFunctions,
description: () =>
`Create and manage private automation functions securely within your workspace`
},
[PlanFeaturesList.DomainSecurity]: {
name: PlanFeaturesList.DomainSecurity,
description: () => `Require workspace members to use a verified company email`
},
[PlanFeaturesList.SSO]: {
name: PlanFeaturesList.SSO,
description: () => `Require workspace members to log in with your SSO provider`
},
[PlanFeaturesList.CustomDataRegion]: {
name: PlanFeaturesList.CustomDataRegion,
description: () => `Store the workspace data in a custom region`
},
[PlanFeaturesList.PrioritySupport]: {
name: PlanFeaturesList.PrioritySupport,
description: () => `Personal and fast support`
[WorkspacePlanBillingIntervals.Yearly]: {
[Roles.Workspace.Guest]: 12,
[Roles.Workspace.Member]: 12,
[Roles.Workspace.Admin]: 12
}
},
plans: {
[WorkspacePlans.Starter]: {
name: WorkspacePlans.Starter,
features: [...baseFeatures],
cost: {
[BillingInterval.Monthly]: {
[Roles.Workspace.Guest]: 15,
[Roles.Workspace.Member]: 15,
[Roles.Workspace.Admin]: 15
},
[BillingInterval.Yearly]: {
[Roles.Workspace.Guest]: 12,
[Roles.Workspace.Member]: 12,
[Roles.Workspace.Admin]: 12
}
}
[PaidWorkspacePlansOld.Plus]: {
[WorkspacePlanBillingIntervals.Monthly]: {
[Roles.Workspace.Guest]: 15,
[Roles.Workspace.Member]: 50,
[Roles.Workspace.Admin]: 50
},
[WorkspacePlans.Plus]: {
name: WorkspacePlans.Plus,
features: [...baseFeatures, PlanFeaturesList.SSO],
cost: {
[BillingInterval.Monthly]: {
[Roles.Workspace.Guest]: 15,
[Roles.Workspace.Member]: 50,
[Roles.Workspace.Admin]: 50
},
[BillingInterval.Yearly]: {
[Roles.Workspace.Guest]: 12,
[Roles.Workspace.Member]: 40,
[Roles.Workspace.Admin]: 40
}
}
[WorkspacePlanBillingIntervals.Yearly]: {
[Roles.Workspace.Guest]: 12,
[Roles.Workspace.Member]: 40,
[Roles.Workspace.Admin]: 40
}
},
[PaidWorkspacePlansOld.Business]: {
[WorkspacePlanBillingIntervals.Monthly]: {
[Roles.Workspace.Guest]: 15,
[Roles.Workspace.Member]: 75,
[Roles.Workspace.Admin]: 75
},
[WorkspacePlans.Business]: {
name: WorkspacePlans.Business,
features: [
...baseFeatures,
PlanFeaturesList.SSO,
PlanFeaturesList.CustomDataRegion,
PlanFeaturesList.PrioritySupport
],
cost: {
[BillingInterval.Monthly]: {
[Roles.Workspace.Guest]: 15,
[Roles.Workspace.Member]: 75,
[Roles.Workspace.Admin]: 75
},
[BillingInterval.Yearly]: {
[Roles.Workspace.Guest]: 12,
[Roles.Workspace.Member]: 60,
[Roles.Workspace.Admin]: 60
}
}
[WorkspacePlanBillingIntervals.Yearly]: {
[Roles.Workspace.Guest]: 12,
[Roles.Workspace.Member]: 60,
[Roles.Workspace.Admin]: 60
}
}
}
@@ -1,31 +1,6 @@
import {
type BillingInterval,
type WorkspacePlans,
PaidWorkspacePlans
} from '~/lib/common/generated/gql/graphql'
import type { WorkspaceRoles } from '@speckle/shared'
export enum PlanFeaturesList {
Workspaces = 'Workspaces',
RoleManagement = 'Role management',
GuestUsers = 'Guest users',
PrivateAutomateFunctions = 'Private automate functions',
DomainSecurity = 'Domain security',
SSO = 'Single Sign-On (SSO)',
CustomDataRegion = 'Custom data residency',
PrioritySupport = 'Priority support'
}
export type PricingPlan = {
name: WorkspacePlans
features: PlanFeaturesList[]
cost: {
[I in BillingInterval]: Record<WorkspaceRoles, number>
}
}
import type { WorkspacePlans } from '@speckle/shared'
import { PaidWorkspacePlans } from '@speckle/shared'
// Check if the plan matches PaidWorkspacePlans
export const isPaidPlan = (plan?: WorkspacePlans): boolean =>
plan
? Object.values(PaidWorkspacePlans).includes(plan as unknown as PaidWorkspacePlans)
: false
plan ? (Object.values(PaidWorkspacePlans) as string[]).includes(plan) : false
@@ -13,7 +13,394 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-
* Therefore it is highly recommended to use the babel or swc plugin for production.
* Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size
*/
const documents = {
type Documents = {
"\n fragment AuthLoginWithEmailBlock_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n email\n user {\n id\n }\n }\n": typeof types.AuthLoginWithEmailBlock_PendingWorkspaceCollaboratorFragmentDoc,
"\n query AuthRegisterPanelWorkspaceInvite($token: String) {\n workspaceInvite(token: $token) {\n id\n ...AuthWorkspaceInviteHeader_PendingWorkspaceCollaborator\n }\n }\n": typeof types.AuthRegisterPanelWorkspaceInviteDocument,
"\n fragment ServerTermsOfServicePrivacyPolicyFragment on ServerInfo {\n termsOfService\n }\n": typeof types.ServerTermsOfServicePrivacyPolicyFragmentFragmentDoc,
"\n fragment AuthWorkspaceInviteHeader_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n workspaceName\n email\n user {\n id\n ...LimitedUserAvatar\n }\n }\n": typeof types.AuthWorkspaceInviteHeader_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment AuthSsoLogin_Workspace on LimitedWorkspace {\n id\n slug\n name\n logo\n }\n": typeof types.AuthSsoLogin_WorkspaceFragmentDoc,
"\n fragment AuthStategiesServerInfoFragment on ServerInfo {\n authStrategies {\n id\n name\n url\n }\n ...AuthThirdPartyLoginButtonOIDC_ServerInfo\n }\n": typeof types.AuthStategiesServerInfoFragmentFragmentDoc,
"\n fragment AuthThirdPartyLoginButtonOIDC_ServerInfo on ServerInfo {\n authStrategies {\n id\n name\n }\n }\n": typeof types.AuthThirdPartyLoginButtonOidc_ServerInfoFragmentDoc,
"\n fragment AutomateAutomationCreateDialog_AutomateFunction on AutomateFunction {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction\n }\n": typeof types.AutomateAutomationCreateDialog_AutomateFunctionFragmentDoc,
"\n fragment AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunction on AutomateFunction {\n id\n releases(limit: 1) {\n items {\n id\n inputSchema\n }\n }\n }\n": typeof types.AutomateAutomationCreateDialogFunctionParametersStep_AutomateFunctionFragmentDoc,
"\n query AutomationCreateDialogFunctionsSearch(\n $workspaceId: String!\n $search: String\n $cursor: String = null\n ) {\n workspace(id: $workspaceId) {\n automateFunctions(limit: 20, filter: { search: $search }, cursor: $cursor) {\n cursor\n totalCount\n items {\n id\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n }\n": typeof types.AutomationCreateDialogFunctionsSearchDocument,
"\n fragment AutomationsFunctionsCard_AutomateFunction on AutomateFunction {\n id\n name\n isFeatured\n description\n logo\n repo {\n id\n url\n owner\n name\n }\n }\n": typeof types.AutomationsFunctionsCard_AutomateFunctionFragmentDoc,
"\n fragment AutomateFunctionCreateDialog_Workspace on Workspace {\n id\n name\n slug\n }\n": typeof types.AutomateFunctionCreateDialog_WorkspaceFragmentDoc,
"\n fragment AutomateFunctionEditDialog_Workspace on Workspace {\n id\n name\n }\n": typeof types.AutomateFunctionEditDialog_WorkspaceFragmentDoc,
"\n fragment AutomateFunctionCreateDialogDoneStep_AutomateFunction on AutomateFunction {\n id\n repo {\n id\n url\n owner\n name\n }\n ...AutomationsFunctionsCard_AutomateFunction\n }\n": typeof types.AutomateFunctionCreateDialogDoneStep_AutomateFunctionFragmentDoc,
"\n fragment AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate on AutomateFunctionTemplate {\n id\n title\n logo\n url\n }\n": typeof types.AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplateFragmentDoc,
"\n fragment AutomateFunctionPageHeader_Function on AutomateFunction {\n id\n name\n logo\n repo {\n id\n url\n owner\n name\n }\n releases(limit: 1) {\n totalCount\n }\n workspaceIds\n }\n\n fragment AutomateFunctionPageHeader_Workspace on Workspace {\n id\n name\n slug\n }\n": typeof types.AutomateFunctionPageHeader_FunctionFragmentDoc,
"\n fragment AutomateFunctionPageInfo_AutomateFunction on AutomateFunction {\n id\n repo {\n id\n url\n owner\n name\n }\n description\n releases(limit: 1) {\n items {\n id\n inputSchema\n createdAt\n commitId\n ...AutomateFunctionPageParametersDialog_AutomateFunctionRelease\n }\n }\n }\n": typeof types.AutomateFunctionPageInfo_AutomateFunctionFragmentDoc,
"\n fragment AutomateFunctionPageParametersDialog_AutomateFunctionRelease on AutomateFunctionRelease {\n id\n inputSchema\n }\n": typeof types.AutomateFunctionPageParametersDialog_AutomateFunctionReleaseFragmentDoc,
"\n fragment AutomateFunctionsPageHeader_Query on Query {\n activeUser {\n id\n role\n automateInfo {\n hasAutomateGithubApp\n availableGithubOrgs\n }\n }\n serverInfo {\n automate {\n availableFunctionTemplates {\n ...AutomateFunctionCreateDialogTemplateStep_AutomateFunctionTemplate\n }\n }\n }\n }\n": typeof types.AutomateFunctionsPageHeader_QueryFragmentDoc,
"\n fragment AutomateFunctionsPageItems_Query on Query {\n automateFunctions(limit: 6, filter: { search: $search }, cursor: $cursor) {\n totalCount\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n cursor\n }\n }\n": typeof types.AutomateFunctionsPageItems_QueryFragmentDoc,
"\n fragment AutomateRunsTriggerStatus_TriggeredAutomationsStatus on TriggeredAutomationsStatus {\n id\n ...TriggeredAutomationsStatusSummary\n ...AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus\n }\n": typeof types.AutomateRunsTriggerStatus_TriggeredAutomationsStatusFragmentDoc,
"\n fragment AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus on TriggeredAutomationsStatus {\n id\n automationRuns {\n id\n ...AutomateRunsTriggerStatusDialogRunsRows_AutomateRun\n }\n }\n": typeof types.AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatusFragmentDoc,
"\n fragment AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun on AutomateFunctionRun {\n id\n results\n status\n statusMessage\n contextView\n function {\n id\n logo\n name\n }\n createdAt\n updatedAt\n }\n": typeof types.AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRunFragmentDoc,
"\n fragment AutomateRunsTriggerStatusDialogRunsRows_AutomateRun on AutomateRun {\n id\n functionRuns {\n id\n ...AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun\n }\n ...AutomationsStatusOrderedRuns_AutomationRun\n }\n": typeof types.AutomateRunsTriggerStatusDialogRunsRows_AutomateRunFragmentDoc,
"\n fragment AutomateViewerPanel_AutomateRun on AutomateRun {\n id\n functionRuns {\n id\n ...AutomateViewerPanelFunctionRunRow_AutomateFunctionRun\n }\n ...AutomationsStatusOrderedRuns_AutomationRun\n }\n": typeof types.AutomateViewerPanel_AutomateRunFragmentDoc,
"\n fragment AutomateViewerPanelFunctionRunRow_AutomateFunctionRun on AutomateFunctionRun {\n id\n results\n status\n statusMessage\n contextView\n function {\n id\n logo\n name\n }\n createdAt\n updatedAt\n }\n": typeof types.AutomateViewerPanelFunctionRunRow_AutomateFunctionRunFragmentDoc,
"\n fragment BillingAlert_Workspace on Workspace {\n id\n plan {\n name\n status\n createdAt\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n }\n }\n": typeof types.BillingAlert_WorkspaceFragmentDoc,
"\n fragment CommonModelSelectorModel on Model {\n id\n name\n }\n": typeof types.CommonModelSelectorModelFragmentDoc,
"\n fragment DashboardProjectCard_Project on Project {\n id\n name\n role\n updatedAt\n models {\n totalCount\n }\n team {\n user {\n ...LimitedUserAvatar\n }\n }\n workspace {\n id\n slug\n name\n logo\n }\n }\n": typeof types.DashboardProjectCard_ProjectFragmentDoc,
"\n fragment Sidebar_User on User {\n id\n automateFunctions {\n items {\n id\n name\n description\n logo\n }\n }\n }\n": typeof types.Sidebar_UserFragmentDoc,
"\n fragment FormSelectModels_Model on Model {\n id\n name\n }\n": typeof types.FormSelectModels_ModelFragmentDoc,
"\n fragment FormSelectProjects_Project on Project {\n id\n name\n }\n": typeof types.FormSelectProjects_ProjectFragmentDoc,
"\n fragment FormUsersSelectItem on LimitedUser {\n id\n name\n avatar\n }\n": typeof types.FormUsersSelectItemFragmentDoc,
"\n fragment HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\n }\n": typeof types.HeaderNavShare_ProjectFragmentDoc,
"\n fragment InviteDialogWorkspace_Workspace on Workspace {\n id\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n plan {\n status\n name\n }\n subscription {\n seats {\n guest\n plan\n }\n }\n }\n": typeof types.InviteDialogWorkspace_WorkspaceFragmentDoc,
"\n fragment InviteDialogProject_Project on Project {\n id\n name\n ...InviteDialogProjectWorkspaceMembers_Project\n workspace {\n id\n name\n defaultProjectRole\n role\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n plan {\n status\n name\n }\n subscription {\n seats {\n guest\n plan\n }\n }\n }\n }\n": typeof types.InviteDialogProject_ProjectFragmentDoc,
"\n fragment InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator on WorkspaceCollaborator {\n role\n id\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n": typeof types.InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaboratorFragmentDoc,
"\n fragment InviteDialogProjectWorkspaceMembers_Project on Project {\n id\n ...ProjectPageTeamInternals_Project\n workspace {\n team {\n items {\n ...InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator\n }\n }\n }\n }\n": typeof types.InviteDialogProjectWorkspaceMembers_ProjectFragmentDoc,
"\n fragment ProjectModelPageHeaderProject on Project {\n id\n name\n model(id: $modelId) {\n id\n name\n description\n }\n workspace {\n id\n slug\n name\n }\n }\n": typeof types.ProjectModelPageHeaderProjectFragmentDoc,
"\n fragment ProjectModelPageVersionsPagination on Project {\n id\n visibility\n model(id: $modelId) {\n id\n versions(limit: 16, cursor: $versionsCursor) {\n cursor\n totalCount\n items {\n ...ProjectModelPageVersionsCardVersion\n }\n }\n }\n ...ProjectsModelPageEmbed_Project\n }\n": typeof types.ProjectModelPageVersionsPaginationFragmentDoc,
"\n fragment ProjectModelPageVersionsProject on Project {\n ...ProjectPageProjectHeader\n model(id: $modelId) {\n id\n name\n pendingImportedVersions {\n ...PendingFileUpload\n }\n }\n ...ProjectModelPageVersionsPagination\n ...ProjectsModelPageEmbed_Project\n workspace {\n id\n readOnly\n }\n }\n": typeof types.ProjectModelPageVersionsProjectFragmentDoc,
"\n fragment ProjectModelPageDialogDeleteVersion on Version {\n id\n message\n }\n": typeof types.ProjectModelPageDialogDeleteVersionFragmentDoc,
"\n fragment ProjectModelPageDialogEditMessageVersion on Version {\n id\n message\n }\n": typeof types.ProjectModelPageDialogEditMessageVersionFragmentDoc,
"\n fragment ProjectModelPageDialogMoveToVersion on Version {\n id\n message\n }\n": typeof types.ProjectModelPageDialogMoveToVersionFragmentDoc,
"\n fragment ProjectsModelPageEmbed_Project on Project {\n id\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": typeof types.ProjectsModelPageEmbed_ProjectFragmentDoc,
"\n fragment ProjectModelPageVersionsCardVersion on Version {\n id\n message\n authorUser {\n ...LimitedUserAvatar\n }\n createdAt\n previewUrl\n sourceApplication\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n ...ProjectModelPageDialogDeleteVersion\n ...ProjectModelPageDialogMoveToVersion\n automationsStatus {\n ...AutomateRunsTriggerStatus_TriggeredAutomationsStatus\n }\n }\n": typeof types.ProjectModelPageVersionsCardVersionFragmentDoc,
"\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n workspace {\n id\n slug\n name\n logo\n }\n }\n": typeof types.ProjectPageProjectHeaderFragmentDoc,
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction on AutomationRevisionFunction {\n parameters\n release {\n id\n versionTag\n createdAt\n inputSchema\n function {\n id\n }\n }\n }\n": typeof types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunctionFragmentDoc,
"\n fragment ProjectPageAutomationFunctionSettingsDialog_AutomationRevision on AutomationRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n type\n model {\n id\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": typeof types.ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFragmentDoc,
"\n fragment ProjectPageAutomationFunctions_Automation on Automation {\n id\n currentRevision {\n id\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevision\n functions {\n release {\n id\n inputSchema\n function {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n releases(limit: 1) {\n items {\n id\n }\n }\n }\n }\n ...ProjectPageAutomationFunctionSettingsDialog_AutomationRevisionFunction\n }\n }\n }\n": typeof types.ProjectPageAutomationFunctions_AutomationFragmentDoc,
"\n fragment ProjectPageAutomationHeader_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n currentRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n model {\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n }\n }\n": typeof types.ProjectPageAutomationHeader_AutomationFragmentDoc,
"\n fragment ProjectPageAutomationHeader_Project on Project {\n id\n role\n workspaceId\n ...ProjectPageModelsCardProject\n }\n": typeof types.ProjectPageAutomationHeader_ProjectFragmentDoc,
"\n fragment ProjectPageAutomationModels_Project on Project {\n id\n ...ProjectPageModelsCardProject\n }\n": typeof types.ProjectPageAutomationModels_ProjectFragmentDoc,
"\n fragment ProjectPageAutomationRuns_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n runs(limit: 10) {\n items {\n ...AutomationRunDetails\n }\n totalCount\n cursor\n }\n }\n": typeof types.ProjectPageAutomationRuns_AutomationFragmentDoc,
"\n fragment ProjectPageAutomationsRow_Automation on Automation {\n id\n name\n enabled\n isTestAutomation\n currentRevision {\n id\n triggerDefinitions {\n ... on VersionCreatedTriggerDefinition {\n model {\n id\n name\n }\n }\n }\n }\n runs(limit: 10) {\n totalCount\n items {\n ...AutomationRunDetails\n }\n cursor\n }\n }\n": typeof types.ProjectPageAutomationsRow_AutomationFragmentDoc,
"\n fragment ProjectDiscussionsPageHeader_Project on Project {\n id\n name\n }\n": typeof types.ProjectDiscussionsPageHeader_ProjectFragmentDoc,
"\n fragment ProjectDiscussionsPageResults_Project on Project {\n id\n }\n": typeof types.ProjectDiscussionsPageResults_ProjectFragmentDoc,
"\n fragment ProjectPageModelsActions on Model {\n id\n name\n }\n": typeof types.ProjectPageModelsActionsFragmentDoc,
"\n fragment ProjectPageModelsActions_Project on Project {\n id\n ...ProjectsModelPageEmbed_Project\n }\n": typeof types.ProjectPageModelsActions_ProjectFragmentDoc,
"\n fragment ProjectPageModelsCardProject on Project {\n id\n role\n visibility\n ...ProjectPageModelsActions_Project\n workspace {\n id\n readOnly\n }\n }\n": typeof types.ProjectPageModelsCardProjectFragmentDoc,
"\n fragment ProjectModelsPageHeader_Project on Project {\n id\n name\n sourceApps\n role\n models {\n totalCount\n }\n team {\n id\n user {\n ...FormUsersSelectItem\n }\n }\n workspace {\n id\n readOnly\n }\n }\n": typeof types.ProjectModelsPageHeader_ProjectFragmentDoc,
"\n fragment ProjectModelsPageResults_Project on Project {\n ...ProjectPageLatestItemsModels\n }\n": typeof types.ProjectModelsPageResults_ProjectFragmentDoc,
"\n fragment ProjectPageModelsStructureItem_Project on Project {\n id\n workspace {\n id\n readOnly\n }\n ...ProjectPageModelsActions_Project\n }\n": typeof types.ProjectPageModelsStructureItem_ProjectFragmentDoc,
"\n fragment SingleLevelModelTreeItem on ModelsTreeItem {\n id\n name\n fullName\n model {\n ...ProjectPageLatestItemsModelItem\n }\n hasChildren\n updatedAt\n }\n": typeof types.SingleLevelModelTreeItemFragmentDoc,
"\n fragment ProjectPageModelsCardDeleteDialog on Model {\n id\n name\n }\n": typeof types.ProjectPageModelsCardDeleteDialogFragmentDoc,
"\n fragment ProjectPageModelsCardRenameDialog on Model {\n id\n name\n description\n }\n": typeof types.ProjectPageModelsCardRenameDialogFragmentDoc,
"\n query ProjectPageSettingsCollaborators($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageTeamInternals_Project\n ...InviteDialogProject_Project\n workspaceId\n }\n }\n": typeof types.ProjectPageSettingsCollaboratorsDocument,
"\n query ProjectPageSettingsCollaboratorsWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n ...ProjectPageTeamInternals_Workspace\n }\n }\n": typeof types.ProjectPageSettingsCollaboratorsWorkspaceDocument,
"\n query ProjectPageSettingsGeneral($projectId: String!) {\n project(id: $projectId) {\n id\n role\n ...ProjectPageSettingsGeneralBlockProjectInfo_Project\n ...ProjectPageSettingsGeneralBlockAccess_Project\n ...ProjectPageSettingsGeneralBlockDiscussions_Project\n ...ProjectPageSettingsGeneralBlockLeave_Project\n ...ProjectPageSettingsGeneralBlockDelete_Project\n ...ProjectPageTeamInternals_Project\n }\n }\n": typeof types.ProjectPageSettingsGeneralDocument,
"\n fragment ProjectPageSettingsGeneralBlockAccess_Project on Project {\n id\n visibility\n }\n": typeof types.ProjectPageSettingsGeneralBlockAccess_ProjectFragmentDoc,
"\n fragment ProjectPageSettingsGeneralBlockDelete_Project on Project {\n ...ProjectsDeleteDialog_Project\n }\n": typeof types.ProjectPageSettingsGeneralBlockDelete_ProjectFragmentDoc,
"\n fragment ProjectPageSettingsGeneralBlockDiscussions_Project on Project {\n id\n visibility\n allowPublicComments\n }\n": typeof types.ProjectPageSettingsGeneralBlockDiscussions_ProjectFragmentDoc,
"\n fragment ProjectPageSettingsGeneralBlockLeave_Project on Project {\n id\n name\n role\n team {\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n workspace {\n id\n }\n }\n": typeof types.ProjectPageSettingsGeneralBlockLeave_ProjectFragmentDoc,
"\n fragment ProjectPageSettingsGeneralBlockProjectInfo_Project on Project {\n id\n role\n name\n description\n }\n": typeof types.ProjectPageSettingsGeneralBlockProjectInfo_ProjectFragmentDoc,
"\n fragment ProjectPageTeamDialog on Project {\n id\n name\n role\n allowPublicComments\n visibility\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n invitedTeam {\n id\n title\n inviteId\n role\n user {\n ...LimitedUserAvatar\n role\n }\n }\n ...ProjectsPageTeamDialogManagePermissions_Project\n }\n": typeof types.ProjectPageTeamDialogFragmentDoc,
"\n fragment ProjectsPageTeamDialogManagePermissions_Project on Project {\n id\n visibility\n role\n }\n": typeof types.ProjectsPageTeamDialogManagePermissions_ProjectFragmentDoc,
"\n fragment ProjectsAddDialog_Workspace on Workspace {\n id\n ...ProjectsWorkspaceSelect_Workspace\n }\n": typeof types.ProjectsAddDialog_WorkspaceFragmentDoc,
"\n fragment ProjectsAddDialog_User on User {\n workspaces {\n items {\n ...ProjectsAddDialog_Workspace\n }\n }\n }\n": typeof types.ProjectsAddDialog_UserFragmentDoc,
"\n fragment ProjectsDashboard_UserProjectCollection on UserProjectCollection {\n numberOfHidden\n }\n": typeof types.ProjectsDashboard_UserProjectCollectionFragmentDoc,
"\n fragment ProjectsDashboardFilledProject on ProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": typeof types.ProjectsDashboardFilledProjectFragmentDoc,
"\n fragment ProjectsDashboardFilledUser on UserProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": typeof types.ProjectsDashboardFilledUserFragmentDoc,
"\n fragment ProjectsDashboardHeaderProjects_User on User {\n projectInvites {\n ...ProjectsInviteBanner\n }\n }\n": typeof types.ProjectsDashboardHeaderProjects_UserFragmentDoc,
"\n fragment ProjectsDashboardHeaderWorkspaces_User on User {\n discoverableWorkspaces {\n ...WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace\n }\n workspaceInvites {\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n }\n }\n": typeof types.ProjectsDashboardHeaderWorkspaces_UserFragmentDoc,
"\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n }\n": typeof types.ProjectsDeleteDialog_ProjectFragmentDoc,
"\n fragment ProjectsHiddenProjectWarning_User on User {\n id\n expiredSsoSessions {\n id\n slug\n name\n logo\n }\n }\n": typeof types.ProjectsHiddenProjectWarning_UserFragmentDoc,
"\n fragment ProjectsMoveToWorkspaceDialog_Workspace on Workspace {\n id\n role\n name\n logo\n ...WorkspaceHasCustomDataResidency_Workspace\n ...ProjectsWorkspaceSelect_Workspace\n }\n": typeof types.ProjectsMoveToWorkspaceDialog_WorkspaceFragmentDoc,
"\n fragment ProjectsMoveToWorkspaceDialog_User on User {\n workspaces {\n items {\n ...ProjectsMoveToWorkspaceDialog_Workspace\n }\n }\n }\n": typeof types.ProjectsMoveToWorkspaceDialog_UserFragmentDoc,
"\n fragment ProjectsMoveToWorkspaceDialog_Project on Project {\n id\n name\n modelCount: models(limit: 0) {\n totalCount\n }\n versions(limit: 0) {\n totalCount\n }\n }\n": typeof types.ProjectsMoveToWorkspaceDialog_ProjectFragmentDoc,
"\n query ProjectsMoveToWorkspaceDialog {\n activeUser {\n id\n ...ProjectsMoveToWorkspaceDialog_User\n }\n }\n": typeof types.ProjectsMoveToWorkspaceDialogDocument,
"\n fragment ProjectsWorkspaceSelect_Workspace on Workspace {\n id\n role\n name\n logo\n readOnly\n slug\n }\n": typeof types.ProjectsWorkspaceSelect_WorkspaceFragmentDoc,
"\n fragment ProjectsInviteBanner on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n": typeof types.ProjectsInviteBannerFragmentDoc,
"\n fragment SettingsDialog_Workspace on Workspace {\n ...SettingsMenu_Workspace\n id\n slug\n role\n name\n logo\n plan {\n status\n }\n creationState {\n completed\n }\n }\n": typeof types.SettingsDialog_WorkspaceFragmentDoc,
"\n fragment SettingsDialog_User on User {\n id\n workspaces {\n items {\n ...SettingsDialog_Workspace\n }\n }\n }\n": typeof types.SettingsDialog_UserFragmentDoc,
"\n fragment SettingsServerRegionsAddEditDialog_ServerRegionItem on ServerRegionItem {\n id\n name\n description\n key\n }\n": typeof types.SettingsServerRegionsAddEditDialog_ServerRegionItemFragmentDoc,
"\n fragment SettingsServerRegionsTable_ServerRegionItem on ServerRegionItem {\n id\n name\n key\n description\n }\n": typeof types.SettingsServerRegionsTable_ServerRegionItemFragmentDoc,
"\n fragment SettingsSharedDeleteUserDialog_Workspace on Workspace {\n id\n plan {\n status\n name\n }\n subscription {\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n }\n": typeof types.SettingsSharedDeleteUserDialog_WorkspaceFragmentDoc,
"\n fragment SettingsSharedProjects_Project on Project {\n ...ProjectsDeleteDialog_Project\n id\n name\n visibility\n createdAt\n updatedAt\n models(limit: 0) {\n totalCount\n }\n versions(limit: 0) {\n totalCount\n }\n team {\n id\n user {\n name\n id\n avatar\n }\n }\n }\n": typeof types.SettingsSharedProjects_ProjectFragmentDoc,
"\n fragment SettingsUserProfileChangePassword_User on User {\n id\n email\n }\n": typeof types.SettingsUserProfileChangePassword_UserFragmentDoc,
"\n fragment SettingsUserProfileDeleteAccount_User on User {\n id\n email\n }\n": typeof types.SettingsUserProfileDeleteAccount_UserFragmentDoc,
"\n fragment SettingsUserProfileDetails_User on User {\n id\n name\n company\n ...UserProfileEditDialogAvatar_User\n }\n": typeof types.SettingsUserProfileDetails_UserFragmentDoc,
"\n fragment UserProfileEditDialogAvatar_User on User {\n id\n avatar\n ...ActiveUserAvatar\n }\n": typeof types.UserProfileEditDialogAvatar_UserFragmentDoc,
"\n fragment SettingsWorkspaceGeneralDeleteDialog_Workspace on Workspace {\n id\n name\n }\n": typeof types.SettingsWorkspaceGeneralDeleteDialog_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesGeneralEditAvatar_Workspace on Workspace {\n id\n logo\n name\n }\n": typeof types.SettingsWorkspacesGeneralEditAvatar_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesGeneralEditSlugDialog_Workspace on Workspace {\n id\n name\n slug\n }\n": typeof types.SettingsWorkspacesGeneralEditSlugDialog_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersChangeRoleDialog_Workspace on Workspace {\n id\n plan {\n status\n name\n }\n subscription {\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersChangeRoleDialog_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n inviteId\n role\n title\n updatedAt\n user {\n id\n ...LimitedUserAvatar\n }\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersRequestsTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n workspaceDomainPolicyCompliant\n }\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n": typeof types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesRegionsSelect_ServerRegionItem on ServerRegionItem {\n id\n key\n name\n description\n }\n": typeof types.SettingsWorkspacesRegionsSelect_ServerRegionItemFragmentDoc,
"\n fragment SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain on WorkspaceDomain {\n id\n domain\n }\n": typeof types.SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragmentDoc,
"\n fragment SettingsWorkspacesSecurityDomainRemoveDialog_Workspace on Workspace {\n id\n domains {\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n }\n": typeof types.SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesSecuritySsoWrapper_Workspace on Workspace {\n id\n role\n slug\n sso {\n provider {\n id\n name\n clientId\n issuerUrl\n }\n }\n hasAccessToSSO: hasAccessToFeature(featureName: oidcSso)\n }\n": typeof types.SettingsWorkspacesSecuritySsoWrapper_WorkspaceFragmentDoc,
"\n fragment ModelPageProject on Project {\n id\n createdAt\n name\n visibility\n workspace {\n id\n slug\n name\n }\n }\n": typeof types.ModelPageProjectFragmentDoc,
"\n fragment ThreadCommentAttachment on Comment {\n text {\n attachments {\n id\n fileName\n fileType\n fileSize\n }\n }\n }\n": typeof types.ThreadCommentAttachmentFragmentDoc,
"\n fragment ViewerCommentsListItem on Comment {\n id\n rawText\n archived\n author {\n ...LimitedUserAvatar\n }\n createdAt\n viewedAt\n replies {\n totalCount\n cursor\n items {\n ...ViewerCommentsReplyItem\n }\n }\n replyAuthors(limit: 4) {\n totalCount\n items {\n ...FormUsersSelectItem\n }\n }\n resources {\n resourceId\n resourceType\n }\n }\n": typeof types.ViewerCommentsListItemFragmentDoc,
"\n fragment ViewerModelVersionCardItem on Version {\n id\n message\n referencedObject\n sourceApplication\n createdAt\n previewUrl\n authorUser {\n ...LimitedUserAvatar\n }\n }\n": typeof types.ViewerModelVersionCardItemFragmentDoc,
"\n fragment MoveProjectsDialog_Workspace on Workspace {\n id\n ...ProjectsMoveToWorkspaceDialog_Workspace\n projects {\n items {\n id\n modelCount: models(limit: 0) {\n totalCount\n }\n versions(limit: 0) {\n totalCount\n }\n }\n }\n }\n": typeof types.MoveProjectsDialog_WorkspaceFragmentDoc,
"\n fragment MoveProjectsDialog_User on User {\n projects {\n items {\n ...ProjectsMoveToWorkspaceDialog_Project\n role\n workspace {\n id\n }\n }\n }\n }\n": typeof types.MoveProjectsDialog_UserFragmentDoc,
"\n fragment WorkspaceProjectList_Workspace on Workspace {\n id\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n ...BillingAlert_Workspace\n ...MoveProjectsDialog_Workspace\n ...InviteDialogWorkspace_Workspace\n projects {\n ...WorkspaceProjectList_ProjectCollection\n }\n creationState {\n completed\n state\n }\n readOnly\n }\n": typeof types.WorkspaceProjectList_WorkspaceFragmentDoc,
"\n fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...ProjectDashboardItem\n }\n cursor\n }\n": typeof types.WorkspaceProjectList_ProjectCollectionFragmentDoc,
"\n fragment WorkspaceHeader_Workspace on Workspace {\n ...WorkspaceBase_Workspace\n ...WorkspaceTeam_Workspace\n ...BillingAlert_Workspace\n slug\n readOnly\n }\n": typeof types.WorkspaceHeader_WorkspaceFragmentDoc,
"\n fragment WorkspaceInviteBanner_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": typeof types.WorkspaceInviteBanner_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment WorkspaceInviteBlock_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n workspaceId\n workspaceName\n token\n user {\n id\n name\n ...LimitedUserAvatar\n }\n title\n email\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": typeof types.WorkspaceInviteBlock_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {\n id\n name\n slug\n description\n logo\n }\n": typeof types.WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspaceFragmentDoc,
"\n fragment WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest on WorkspaceJoinRequest {\n id\n user {\n id\n name\n }\n workspace {\n id\n }\n }\n": typeof types.WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragmentDoc,
"\n fragment WorkspaceSidebarAbout_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\n }\n": typeof types.WorkspaceSidebarAbout_WorkspaceFragmentDoc,
"\n fragment WorkspaceSidebarSecurity_Workspace on Workspace {\n ...WorkspaceSecurity_Workspace\n }\n": typeof types.WorkspaceSidebarSecurity_WorkspaceFragmentDoc,
"\n fragment WorkspaceSidebar_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n slug\n plan {\n status\n }\n }\n": typeof types.WorkspaceSidebar_WorkspaceFragmentDoc,
"\n fragment WorkspaceWizard_Workspace on Workspace {\n creationState {\n completed\n state\n }\n name\n slug\n }\n": typeof types.WorkspaceWizard_WorkspaceFragmentDoc,
"\n fragment WorkspaceWizardStepRegion_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": typeof types.WorkspaceWizardStepRegion_ServerInfoFragmentDoc,
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": typeof types.ActiveUserMainMetadataDocument,
"\n mutation CreateOnboardingProject {\n projectMutations {\n createForOnboarding {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n ": typeof types.CreateOnboardingProjectDocument,
"\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n": typeof types.FinishOnboardingDocument,
"\n mutation RequestVerificationByEmail($email: String!) {\n requestVerificationByEmail(email: $email)\n }\n": typeof types.RequestVerificationByEmailDocument,
"\n query AuthLoginPanel {\n serverInfo {\n authStrategies {\n id\n }\n ...AuthStategiesServerInfoFragment\n }\n }\n": typeof types.AuthLoginPanelDocument,
"\n query AuthRegisterPanel($token: String) {\n serverInfo {\n inviteOnly\n authStrategies {\n id\n }\n ...AuthStategiesServerInfoFragment\n ...ServerTermsOfServicePrivacyPolicyFragment\n }\n serverInviteByToken(token: $token) {\n id\n email\n }\n }\n": typeof types.AuthRegisterPanelDocument,
"\n query AuthLoginPanelWorkspaceInvite($token: String) {\n workspaceInvite(token: $token) {\n id\n email\n ...AuthWorkspaceInviteHeader_PendingWorkspaceCollaborator\n ...AuthLoginWithEmailBlock_PendingWorkspaceCollaborator\n }\n }\n": typeof types.AuthLoginPanelWorkspaceInviteDocument,
"\n query AuthorizableAppMetadata($id: String!) {\n app(id: $id) {\n id\n name\n description\n trustByDefault\n redirectUrl\n scopes {\n name\n description\n }\n author {\n name\n id\n avatar\n }\n }\n }\n": typeof types.AuthorizableAppMetadataDocument,
"\n fragment FunctionRunStatusForSummary on AutomateFunctionRun {\n id\n status\n }\n": typeof types.FunctionRunStatusForSummaryFragmentDoc,
"\n fragment TriggeredAutomationsStatusSummary on TriggeredAutomationsStatus {\n id\n automationRuns {\n id\n functionRuns {\n id\n ...FunctionRunStatusForSummary\n }\n }\n }\n": typeof types.TriggeredAutomationsStatusSummaryFragmentDoc,
"\n fragment AutomationRunDetails on AutomateRun {\n id\n status\n functionRuns {\n ...FunctionRunStatusForSummary\n statusMessage\n }\n trigger {\n ... on VersionCreatedTrigger {\n version {\n id\n }\n model {\n id\n }\n }\n }\n createdAt\n updatedAt\n }\n": typeof types.AutomationRunDetailsFragmentDoc,
"\n fragment AutomationsStatusOrderedRuns_AutomationRun on AutomateRun {\n id\n automation {\n id\n name\n }\n functionRuns {\n id\n updatedAt\n }\n }\n": typeof types.AutomationsStatusOrderedRuns_AutomationRunFragmentDoc,
"\n fragment SearchAutomateFunctionReleaseItem on AutomateFunctionRelease {\n id\n versionTag\n createdAt\n inputSchema\n }\n": typeof types.SearchAutomateFunctionReleaseItemFragmentDoc,
"\n mutation CreateAutomateFunction($input: CreateAutomateFunctionInput!) {\n automateMutations {\n createFunction(input: $input) {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateFunctionCreateDialogDoneStep_AutomateFunction\n }\n }\n }\n": typeof types.CreateAutomateFunctionDocument,
"\n mutation UpdateAutomateFunction($input: UpdateAutomateFunctionInput!) {\n automateMutations {\n updateFunction(input: $input) {\n id\n ...AutomateFunctionPage_AutomateFunction\n }\n }\n }\n": typeof types.UpdateAutomateFunctionDocument,
"\n query SearchAutomateFunctionReleases(\n $functionId: ID!\n $cursor: String\n $limit: Int\n $filter: AutomateFunctionReleasesFilter\n ) {\n automateFunction(id: $functionId) {\n id\n releases(cursor: $cursor, limit: $limit, filter: $filter) {\n cursor\n totalCount\n items {\n ...SearchAutomateFunctionReleaseItem\n }\n }\n }\n }\n": typeof types.SearchAutomateFunctionReleasesDocument,
"\n query FunctionAccessCheck($id: ID!) {\n automateFunction(id: $id) {\n id\n }\n }\n": typeof types.FunctionAccessCheckDocument,
"\n query ProjectAutomationCreationPublicKeys(\n $projectId: String!\n $automationId: String!\n ) {\n project(id: $projectId) {\n id\n automation(id: $automationId) {\n id\n creationPublicKeys\n }\n }\n }\n": typeof types.ProjectAutomationCreationPublicKeysDocument,
"\n query AutomateFunctionsPagePagination($search: String, $cursor: String) {\n ...AutomateFunctionsPageItems_Query\n }\n": typeof types.AutomateFunctionsPagePaginationDocument,
"\n query ActiveUserFunctions {\n activeUser {\n automateFunctions(limit: 2) {\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n }\n }\n }\n }\n": typeof types.ActiveUserFunctionsDocument,
"\n fragment BillingActions_Workspace on Workspace {\n id\n name\n invitedTeam(filter: $invitesFilter) {\n id\n }\n plan {\n name\n status\n }\n subscription {\n billingInterval\n }\n team {\n totalCount\n }\n defaultRegion {\n name\n }\n }\n": typeof types.BillingActions_WorkspaceFragmentDoc,
"\n mutation BillingCreateCheckoutSession($input: CheckoutSessionInput!) {\n workspaceMutations {\n billing {\n createCheckoutSession(input: $input) {\n url\n id\n }\n }\n }\n }\n": typeof types.BillingCreateCheckoutSessionDocument,
"\n mutation BillingUpgradePlan($input: UpgradePlanInput!) {\n workspaceMutations {\n billing {\n upgradePlan(input: $input)\n }\n }\n }\n": typeof types.BillingUpgradePlanDocument,
"\n mutation AdminUpdateWorkspacePlan($input: AdminUpdateWorkspacePlanInput!) {\n admin {\n updateWorkspacePlan(input: $input)\n }\n }\n": typeof types.AdminUpdateWorkspacePlanDocument,
"\n query MentionsUserSearch($query: String!, $projectId: String) {\n users(input: { query: $query, limit: 5, cursor: null, projectId: $projectId }) {\n items {\n id\n name\n company\n }\n }\n }\n": typeof types.MentionsUserSearchDocument,
"\n query UserSearch(\n $query: String!\n $limit: Int\n $cursor: String\n $archived: Boolean\n $workspaceId: String\n ) {\n userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived) {\n cursor\n items {\n id\n name\n bio\n company\n avatar\n verified\n role\n workspaceDomainPolicyCompliant(workspaceId: $workspaceId)\n }\n }\n }\n": typeof types.UserSearchDocument,
"\n query ServerInfoBlobSizeLimit {\n serverInfo {\n configuration {\n blobSizeLimitBytes\n }\n }\n }\n": typeof types.ServerInfoBlobSizeLimitDocument,
"\n query ServerInfoAllScopes {\n serverInfo {\n scopes {\n name\n description\n }\n }\n }\n": typeof types.ServerInfoAllScopesDocument,
"\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": typeof types.ProjectModelsSelectorValuesDocument,
"\n query MainServerInfoData {\n serverInfo {\n adminContact\n canonicalUrl\n company\n description\n guestModeEnabled\n inviteOnly\n name\n termsOfService\n version\n automateUrl\n }\n }\n": typeof types.MainServerInfoDataDocument,
"\n mutation DashboardRequestToJoinWorkspace($input: WorkspaceRequestToJoinInput!) {\n workspaceMutations {\n requestToJoin(input: $input)\n }\n }\n": typeof types.DashboardRequestToJoinWorkspaceDocument,
"\n query DashboardProjectsPageQuery {\n activeUser {\n id\n projects(limit: 3) {\n items {\n ...DashboardProjectCard_Project\n }\n }\n ...ProjectsDashboardHeaderProjects_User\n }\n }\n": typeof types.DashboardProjectsPageQueryDocument,
"\n query DashboardProjectsPageWorkspaceQuery {\n activeUser {\n id\n ...ProjectsDashboardHeaderWorkspaces_User\n }\n }\n": typeof types.DashboardProjectsPageWorkspaceQueryDocument,
"\n mutation DeleteAccessToken($token: String!) {\n apiTokenRevoke(token: $token)\n }\n": typeof types.DeleteAccessTokenDocument,
"\n mutation CreateAccessToken($token: ApiTokenCreateInput!) {\n apiTokenCreate(token: $token)\n }\n": typeof types.CreateAccessTokenDocument,
"\n mutation DeleteApplication($appId: String!) {\n appDelete(appId: $appId)\n }\n": typeof types.DeleteApplicationDocument,
"\n mutation CreateApplication($app: AppCreateInput!) {\n appCreate(app: $app)\n }\n": typeof types.CreateApplicationDocument,
"\n mutation EditApplication($app: AppUpdateInput!) {\n appUpdate(app: $app)\n }\n": typeof types.EditApplicationDocument,
"\n mutation RevokeAppAccess($appId: String!) {\n appRevokeAccess(appId: $appId)\n }\n": typeof types.RevokeAppAccessDocument,
"\n query DeveloperSettingsAccessTokens {\n activeUser {\n id\n apiTokens {\n id\n name\n lastUsed\n lastChars\n createdAt\n scopes\n }\n }\n }\n": typeof types.DeveloperSettingsAccessTokensDocument,
"\n query DeveloperSettingsApplications {\n activeUser {\n createdApps {\n id\n secret\n name\n description\n redirectUrl\n scopes {\n name\n description\n }\n }\n id\n }\n }\n": typeof types.DeveloperSettingsApplicationsDocument,
"\n query DeveloperSettingsAuthorizedApps {\n activeUser {\n id\n authorizedApps {\n id\n description\n name\n author {\n id\n name\n avatar\n }\n }\n }\n }\n": typeof types.DeveloperSettingsAuthorizedAppsDocument,
"\n query SearchProjects(\n $search: String\n $onlyWithRoles: [String!] = null\n $workspaceId: ID\n ) {\n activeUser {\n projects(\n limit: 10\n filter: {\n search: $search\n onlyWithRoles: $onlyWithRoles\n workspaceId: $workspaceId\n }\n ) {\n totalCount\n items {\n ...FormSelectProjects_Project\n }\n }\n }\n }\n": typeof types.SearchProjectsDocument,
"\n query SearchProjectModels($search: String, $projectId: String!) {\n project(id: $projectId) {\n id\n models(limit: 10, filter: { search: $search }) {\n totalCount\n items {\n ...FormSelectModels_Model\n }\n }\n }\n }\n": typeof types.SearchProjectModelsDocument,
"\n query ActiveUserGendoLimits {\n activeUser {\n id\n gendoAICredits {\n used\n limit\n resetDate\n }\n }\n }\n": typeof types.ActiveUserGendoLimitsDocument,
"\n mutation requestGendoAIRender($input: GendoAIRenderInput!) {\n versionMutations {\n requestGendoAIRender(input: $input)\n }\n }\n": typeof types.RequestGendoAiRenderDocument,
"\n query GendoAIRender(\n $gendoAiRenderId: String!\n $versionId: String!\n $projectId: String!\n ) {\n project(id: $projectId) {\n id\n version(id: $versionId) {\n id\n gendoAIRender(id: $gendoAiRenderId) {\n id\n projectId\n modelId\n versionId\n createdAt\n updatedAt\n gendoGenerationId\n status\n prompt\n camera\n responseImage\n user {\n name\n avatar\n id\n }\n }\n }\n }\n }\n": typeof types.GendoAiRenderDocument,
"\n query GendoAIRenders($versionId: String!, $projectId: String!) {\n project(id: $projectId) {\n id\n version(id: $versionId) {\n id\n gendoAIRenders {\n totalCount\n items {\n id\n createdAt\n updatedAt\n status\n gendoGenerationId\n prompt\n camera\n }\n }\n }\n }\n }\n": typeof types.GendoAiRendersDocument,
"\n subscription ProjectVersionGendoAIRenderCreated($id: String!, $versionId: String!) {\n projectVersionGendoAIRenderCreated(id: $id, versionId: $versionId) {\n id\n createdAt\n updatedAt\n status\n gendoGenerationId\n prompt\n camera\n }\n }\n": typeof types.ProjectVersionGendoAiRenderCreatedDocument,
"\n subscription ProjectVersionGendoAIRenderUpdated($id: String!, $versionId: String!) {\n projectVersionGendoAIRenderUpdated(id: $id, versionId: $versionId) {\n id\n projectId\n modelId\n versionId\n createdAt\n updatedAt\n gendoGenerationId\n status\n prompt\n camera\n responseImage\n }\n }\n": typeof types.ProjectVersionGendoAiRenderUpdatedDocument,
"\n query InviteUserSearch($input: UsersRetrievalInput!) {\n users(input: $input) {\n items {\n id\n name\n avatar\n }\n }\n }\n": typeof types.InviteUserSearchDocument,
"\n mutation CreateNewRegion($input: CreateServerRegionInput!) {\n serverInfoMutations {\n multiRegion {\n create(input: $input) {\n id\n ...SettingsServerRegionsAddEditDialog_ServerRegionItem\n ...SettingsServerRegionsTable_ServerRegionItem\n }\n }\n }\n }\n": typeof types.CreateNewRegionDocument,
"\n mutation UpdateRegion($input: UpdateServerRegionInput!) {\n serverInfoMutations {\n multiRegion {\n update(input: $input) {\n id\n ...SettingsServerRegionsAddEditDialog_ServerRegionItem\n ...SettingsServerRegionsTable_ServerRegionItem\n }\n }\n }\n }\n": typeof types.UpdateRegionDocument,
"\n query PagesOnboardingDiscoverableWorkspaces_ActiveUser {\n activeUser {\n id\n ...PagesOnboarding_DiscoverableWorkspaces\n }\n }\n": typeof types.PagesOnboardingDiscoverableWorkspaces_ActiveUserDocument,
"\n fragment ProjectPageTeamInternals_Project on Project {\n id\n role\n invitedTeam {\n id\n title\n role\n inviteId\n user {\n role\n ...LimitedUserAvatar\n }\n }\n team {\n role\n user {\n id\n role\n ...LimitedUserAvatar\n }\n }\n }\n": typeof types.ProjectPageTeamInternals_ProjectFragmentDoc,
"\n fragment ProjectPageTeamInternals_Workspace on Workspace {\n id\n team {\n items {\n id\n role\n user {\n id\n }\n }\n }\n }\n": typeof types.ProjectPageTeamInternals_WorkspaceFragmentDoc,
"\n fragment ProjectDashboardItemNoModels on Project {\n id\n name\n createdAt\n updatedAt\n role\n team {\n id\n user {\n id\n name\n avatar\n }\n }\n ...ProjectPageModelsCardProject\n }\n": typeof types.ProjectDashboardItemNoModelsFragmentDoc,
"\n fragment ProjectDashboardItem on Project {\n id\n ...ProjectDashboardItemNoModels\n models(limit: 4) {\n totalCount\n items {\n ...ProjectPageLatestItemsModelItem\n }\n }\n workspace {\n id\n slug\n name\n logo\n readOnly\n }\n pendingImportedModels(limit: 4) {\n ...PendingFileUpload\n }\n }\n": typeof types.ProjectDashboardItemFragmentDoc,
"\n fragment PendingFileUpload on FileUpload {\n id\n projectId\n modelName\n convertedStatus\n convertedMessage\n uploadDate\n convertedLastUpdate\n fileType\n fileName\n }\n": typeof types.PendingFileUploadFragmentDoc,
"\n fragment ProjectPageLatestItemsModelItem on Model {\n id\n name\n displayName\n versionCount: versions(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n pendingImportedVersions(limit: 1) {\n ...PendingFileUpload\n }\n previewUrl\n createdAt\n updatedAt\n ...ProjectPageModelsCardRenameDialog\n ...ProjectPageModelsCardDeleteDialog\n ...ProjectPageModelsActions\n automationsStatus {\n ...AutomateRunsTriggerStatus_TriggeredAutomationsStatus\n }\n }\n": typeof types.ProjectPageLatestItemsModelItemFragmentDoc,
"\n fragment ProjectUpdatableMetadata on Project {\n id\n name\n description\n visibility\n allowPublicComments\n }\n": typeof types.ProjectUpdatableMetadataFragmentDoc,
"\n fragment ProjectPageLatestItemsModels on Project {\n id\n role\n visibility\n workspace {\n id\n readOnly\n }\n modelCount: models(limit: 0) {\n totalCount\n }\n ...ProjectPageModelsStructureItem_Project\n }\n": typeof types.ProjectPageLatestItemsModelsFragmentDoc,
"\n fragment ProjectPageLatestItemsComments on Project {\n id\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n }\n": typeof types.ProjectPageLatestItemsCommentsFragmentDoc,
"\n fragment ProjectPageLatestItemsCommentItem on Comment {\n id\n author {\n ...FormUsersSelectItem\n }\n screenshot\n rawText\n createdAt\n updatedAt\n archived\n repliesCount: replies(limit: 0) {\n totalCount\n }\n replyAuthors(limit: 4) {\n totalCount\n items {\n ...FormUsersSelectItem\n }\n }\n }\n": typeof types.ProjectPageLatestItemsCommentItemFragmentDoc,
"\n mutation CreateModel($input: CreateModelInput!) {\n modelMutations {\n create(input: $input) {\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n": typeof types.CreateModelDocument,
"\n mutation CreateProject($input: ProjectCreateInput) {\n projectMutations {\n create(input: $input) {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n": typeof types.CreateProjectDocument,
"\n mutation CreateWorkspaceProject($input: WorkspaceProjectCreateInput!) {\n workspaceMutations {\n projects {\n create(input: $input) {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n }\n": typeof types.CreateWorkspaceProjectDocument,
"\n mutation UpdateModel($input: UpdateModelInput!) {\n modelMutations {\n update(input: $input) {\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n": typeof types.UpdateModelDocument,
"\n mutation DeleteModel($input: DeleteModelInput!) {\n modelMutations {\n delete(input: $input)\n }\n }\n": typeof types.DeleteModelDocument,
"\n mutation UpdateProjectRole($input: ProjectUpdateRoleInput!) {\n projectMutations {\n updateRole(input: $input) {\n id\n team {\n id\n role\n user {\n ...LimitedUserAvatar\n }\n }\n }\n }\n }\n": typeof types.UpdateProjectRoleDocument,
"\n mutation UpdateWorkspaceProjectRole($input: ProjectUpdateRoleInput!) {\n workspaceMutations {\n projects {\n updateRole(input: $input) {\n id\n team {\n id\n role\n }\n }\n }\n }\n }\n": typeof types.UpdateWorkspaceProjectRoleDocument,
"\n mutation InviteProjectUser($projectId: ID!, $input: [ProjectInviteCreateInput!]!) {\n projectMutations {\n invites {\n batchCreate(projectId: $projectId, input: $input) {\n ...ProjectPageTeamDialog\n }\n }\n }\n }\n": typeof types.InviteProjectUserDocument,
"\n mutation InviteWorkspaceProjectUser(\n $projectId: ID!\n $inputs: [WorkspaceProjectInviteCreateInput!]!\n ) {\n projectMutations {\n invites {\n createForWorkspace(projectId: $projectId, inputs: $inputs) {\n ...ProjectPageTeamDialog\n }\n }\n }\n }\n": typeof types.InviteWorkspaceProjectUserDocument,
"\n mutation CancelProjectInvite($projectId: ID!, $inviteId: String!) {\n projectMutations {\n invites {\n cancel(projectId: $projectId, inviteId: $inviteId) {\n ...ProjectPageTeamDialog\n }\n }\n }\n }\n": typeof types.CancelProjectInviteDocument,
"\n mutation UpdateProjectMetadata($update: ProjectUpdateInput!) {\n projectMutations {\n update(update: $update) {\n id\n ...ProjectUpdatableMetadata\n }\n }\n }\n": typeof types.UpdateProjectMetadataDocument,
"\n mutation DeleteProject($id: String!) {\n projectMutations {\n delete(id: $id)\n }\n }\n": typeof types.DeleteProjectDocument,
"\n mutation UseProjectInvite($input: ProjectInviteUseInput!) {\n projectMutations {\n invites {\n use(input: $input)\n }\n }\n }\n": typeof types.UseProjectInviteDocument,
"\n mutation LeaveProject($projectId: String!) {\n projectMutations {\n leave(id: $projectId)\n }\n }\n": typeof types.LeaveProjectDocument,
"\n mutation DeleteVersions($input: DeleteVersionsInput!) {\n versionMutations {\n delete(input: $input)\n }\n }\n": typeof types.DeleteVersionsDocument,
"\n mutation MoveVersions($input: MoveVersionsInput!) {\n versionMutations {\n moveToModel(input: $input) {\n id\n }\n }\n }\n": typeof types.MoveVersionsDocument,
"\n mutation UpdateVersion($input: UpdateVersionInput!) {\n versionMutations {\n update(input: $input) {\n id\n message\n }\n }\n }\n": typeof types.UpdateVersionDocument,
"\n mutation deleteWebhook($webhook: WebhookDeleteInput!) {\n webhookDelete(webhook: $webhook)\n }\n": typeof types.DeleteWebhookDocument,
"\n mutation createWebhook($webhook: WebhookCreateInput!) {\n webhookCreate(webhook: $webhook)\n }\n": typeof types.CreateWebhookDocument,
"\n mutation updateWebhook($webhook: WebhookUpdateInput!) {\n webhookUpdate(webhook: $webhook)\n }\n": typeof types.UpdateWebhookDocument,
"\n mutation CreateAutomation($projectId: ID!, $input: ProjectAutomationCreateInput!) {\n projectMutations {\n automationMutations(projectId: $projectId) {\n create(input: $input) {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n }\n }\n }\n": typeof types.CreateAutomationDocument,
"\n mutation UpdateAutomation($projectId: ID!, $input: ProjectAutomationUpdateInput!) {\n projectMutations {\n automationMutations(projectId: $projectId) {\n update(input: $input) {\n id\n name\n enabled\n }\n }\n }\n }\n": typeof types.UpdateAutomationDocument,
"\n mutation CreateAutomationRevision(\n $projectId: ID!\n $input: ProjectAutomationRevisionCreateInput!\n ) {\n projectMutations {\n automationMutations(projectId: $projectId) {\n createRevision(input: $input) {\n id\n }\n }\n }\n }\n": typeof types.CreateAutomationRevisionDocument,
"\n mutation TriggerAutomation($projectId: ID!, $automationId: ID!) {\n projectMutations {\n automationMutations(projectId: $projectId) {\n trigger(automationId: $automationId)\n }\n }\n }\n": typeof types.TriggerAutomationDocument,
"\n mutation CreateTestAutomation(\n $projectId: ID!\n $input: ProjectTestAutomationCreateInput!\n ) {\n projectMutations {\n automationMutations(projectId: $projectId) {\n createTestAutomation(input: $input) {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n }\n }\n }\n": typeof types.CreateTestAutomationDocument,
"\n mutation MoveProjectToWorkspace($workspaceId: String!, $projectId: String!) {\n workspaceMutations {\n projects {\n moveToWorkspace(workspaceId: $workspaceId, projectId: $projectId) {\n id\n workspace {\n id\n projects {\n items {\n id\n }\n }\n ...ProjectsMoveToWorkspaceDialog_Workspace\n ...MoveProjectsDialog_Workspace\n }\n }\n }\n }\n }\n": typeof types.MoveProjectToWorkspaceDocument,
"\n query ProjectAccessCheck($id: String!) {\n project(id: $id) {\n id\n visibility\n workspace {\n id\n slug\n }\n }\n }\n": typeof types.ProjectAccessCheckDocument,
"\n query ProjectRoleCheck($id: String!) {\n project(id: $id) {\n id\n role\n }\n }\n": typeof types.ProjectRoleCheckDocument,
"\n query ProjectsDashboardQuery($filter: UserProjectsFilter, $cursor: String) {\n activeUser {\n id\n projects(filter: $filter, limit: 6, cursor: $cursor) {\n ...ProjectsDashboard_UserProjectCollection\n cursor\n totalCount\n items {\n ...ProjectDashboardItem\n }\n }\n ...ProjectsHiddenProjectWarning_User\n ...ProjectsDashboardHeaderProjects_User\n }\n }\n": typeof types.ProjectsDashboardQueryDocument,
"\n query ProjectsDashboardWorkspaceQuery {\n activeUser {\n id\n ...ProjectsDashboardHeaderWorkspaces_User\n }\n }\n": typeof types.ProjectsDashboardWorkspaceQueryDocument,
"\n query ProjectPageQuery($id: String!, $token: String) {\n project(id: $id) {\n ...ProjectPageProject\n }\n projectInvite(projectId: $id, token: $token) {\n ...ProjectsInviteBanner\n }\n }\n": typeof types.ProjectPageQueryDocument,
"\n query ProjectLatestModels($projectId: String!, $filter: ProjectModelsFilter) {\n project(id: $projectId) {\n id\n models(cursor: null, limit: 16, filter: $filter) {\n totalCount\n cursor\n items {\n ...ProjectPageLatestItemsModelItem\n }\n }\n pendingImportedModels {\n ...PendingFileUpload\n }\n }\n }\n": typeof types.ProjectLatestModelsDocument,
"\n query ProjectLatestModelsPagination(\n $projectId: String!\n $filter: ProjectModelsFilter\n $cursor: String = null\n ) {\n project(id: $projectId) {\n id\n models(cursor: $cursor, limit: 16, filter: $filter) {\n totalCount\n cursor\n items {\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n }\n": typeof types.ProjectLatestModelsPaginationDocument,
"\n query ProjectModelsTreeTopLevel(\n $projectId: String!\n $filter: ProjectModelsTreeFilter\n ) {\n project(id: $projectId) {\n id\n modelsTree(cursor: null, limit: 8, filter: $filter) {\n totalCount\n cursor\n items {\n ...SingleLevelModelTreeItem\n }\n }\n pendingImportedModels {\n ...PendingFileUpload\n }\n }\n }\n": typeof types.ProjectModelsTreeTopLevelDocument,
"\n query ProjectModelsTreeTopLevelPagination(\n $projectId: String!\n $filter: ProjectModelsTreeFilter\n $cursor: String = null\n ) {\n project(id: $projectId) {\n id\n modelsTree(cursor: $cursor, limit: 8, filter: $filter) {\n totalCount\n cursor\n items {\n ...SingleLevelModelTreeItem\n }\n }\n }\n }\n": typeof types.ProjectModelsTreeTopLevelPaginationDocument,
"\n query ProjectModelChildrenTree($projectId: String!, $parentName: String!) {\n project(id: $projectId) {\n id\n modelChildrenTree(fullName: $parentName) {\n ...SingleLevelModelTreeItem\n }\n }\n }\n": typeof types.ProjectModelChildrenTreeDocument,
"\n query ProjectLatestCommentThreads(\n $projectId: String!\n $cursor: String = null\n $filter: ProjectCommentsFilter = null\n ) {\n project(id: $projectId) {\n id\n commentThreads(cursor: $cursor, limit: 8, filter: $filter) {\n totalCount\n cursor\n items {\n ...ProjectPageLatestItemsCommentItem\n }\n }\n }\n }\n": typeof types.ProjectLatestCommentThreadsDocument,
"\n query ProjectInvite($projectId: String!, $token: String) {\n projectInvite(projectId: $projectId, token: $token) {\n ...ProjectsInviteBanner\n }\n }\n": typeof types.ProjectInviteDocument,
"\n query ProjectModelCheck($projectId: String!, $modelId: String!) {\n project(id: $projectId) {\n visibility\n model(id: $modelId) {\n id\n }\n }\n }\n": typeof types.ProjectModelCheckDocument,
"\n query ProjectModelPage(\n $projectId: String!\n $modelId: String!\n $versionsCursor: String\n ) {\n project(id: $projectId) {\n id\n ...ProjectModelPageHeaderProject\n ...ProjectModelPageVersionsProject\n }\n }\n": typeof types.ProjectModelPageDocument,
"\n query ProjectModelVersions(\n $projectId: String!\n $modelId: String!\n $versionsCursor: String\n ) {\n project(id: $projectId) {\n id\n ...ProjectModelPageVersionsPagination\n }\n }\n": typeof types.ProjectModelVersionsDocument,
"\n query ProjectModelsPage($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectModelsPageHeader_Project\n ...ProjectModelsPageResults_Project\n }\n }\n": typeof types.ProjectModelsPageDocument,
"\n query ProjectDiscussionsPage($projectId: String!) {\n project(id: $projectId) {\n id\n ...ProjectDiscussionsPageHeader_Project\n ...ProjectDiscussionsPageResults_Project\n }\n }\n": typeof types.ProjectDiscussionsPageDocument,
"\n query ProjectAutomationsTab($projectId: String!) {\n project(id: $projectId) {\n id\n role\n models(limit: 1) {\n items {\n id\n }\n }\n automations(filter: null, cursor: null, limit: 5) {\n totalCount\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n cursor\n }\n workspace {\n id\n automateFunctions(limit: 0) {\n totalCount\n }\n ...AutomateFunctionCreateDialog_Workspace\n }\n ...FormSelectProjects_Project\n }\n ...AutomateFunctionsPageHeader_Query\n }\n": typeof types.ProjectAutomationsTabDocument,
"\n query ProjectAutomationsTabAutomationsPagination(\n $projectId: String!\n $search: String = null\n $cursor: String = null\n ) {\n project(id: $projectId) {\n id\n automations(filter: $search, cursor: $cursor, limit: 5) {\n totalCount\n cursor\n items {\n id\n ...ProjectPageAutomationsRow_Automation\n }\n }\n }\n }\n": typeof types.ProjectAutomationsTabAutomationsPaginationDocument,
"\n query ProjectAutomationPage($projectId: String!, $automationId: String!) {\n project(id: $projectId) {\n id\n ...ProjectPageAutomationPage_Project\n automation(id: $automationId) {\n id\n ...ProjectPageAutomationPage_Automation\n }\n }\n }\n": typeof types.ProjectAutomationPageDocument,
"\n query ProjectAutomationPagePaginatedRuns(\n $projectId: String!\n $automationId: String!\n $cursor: String = null\n ) {\n project(id: $projectId) {\n id\n automation(id: $automationId) {\n id\n runs(cursor: $cursor, limit: 10) {\n totalCount\n cursor\n items {\n id\n ...AutomationRunDetails\n }\n }\n }\n }\n }\n": typeof types.ProjectAutomationPagePaginatedRunsDocument,
"\n query ProjectAutomationAccessCheck($projectId: String!) {\n project(id: $projectId) {\n id\n automations(limit: 0) {\n totalCount\n }\n }\n }\n": typeof types.ProjectAutomationAccessCheckDocument,
"\n query ProjectWebhooks($projectId: String!) {\n project(id: $projectId) {\n id\n name\n webhooks {\n items {\n streamId\n triggers\n enabled\n url\n id\n description\n history(limit: 5) {\n items {\n status\n statusInfo\n }\n }\n }\n totalCount\n }\n }\n }\n": typeof types.ProjectWebhooksDocument,
"\n query ProjectBlobInfo($blobId: String!, $projectId: String!) {\n project(id: $projectId) {\n id\n blob(id: $blobId) {\n id\n fileName\n fileType\n fileSize\n createdAt\n }\n }\n }\n": typeof types.ProjectBlobInfoDocument,
"\n query ProjectWorkspaceSelect {\n activeUser {\n id\n ...ProjectsAddDialog_User\n }\n }\n": typeof types.ProjectWorkspaceSelectDocument,
"\n subscription OnProjectUpdated($id: String!) {\n projectUpdated(id: $id) {\n id\n type\n project {\n ...ProjectPageProject\n ...ProjectDashboardItemNoModels\n }\n }\n }\n": typeof types.OnProjectUpdatedDocument,
"\n subscription OnProjectModelsUpdate($id: String!) {\n projectModelsUpdated(id: $id) {\n id\n type\n model {\n id\n versions(limit: 1) {\n items {\n id\n referencedObject\n }\n }\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n": typeof types.OnProjectModelsUpdateDocument,
"\n subscription OnProjectVersionsUpdate($id: String!) {\n projectVersionsUpdated(id: $id) {\n id\n modelId\n type\n version {\n id\n ...ViewerModelVersionCardItem\n ...ProjectModelPageVersionsCardVersion\n model {\n id\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n }\n": typeof types.OnProjectVersionsUpdateDocument,
"\n subscription OnProjectVersionsPreviewGenerated($id: String!) {\n projectVersionsPreviewGenerated(id: $id) {\n projectId\n objectId\n versionId\n }\n }\n": typeof types.OnProjectVersionsPreviewGeneratedDocument,
"\n subscription OnProjectPendingModelsUpdated($id: String!) {\n projectPendingModelsUpdated(id: $id) {\n id\n type\n model {\n ...PendingFileUpload\n model {\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n }\n": typeof types.OnProjectPendingModelsUpdatedDocument,
"\n subscription OnProjectPendingVersionsUpdated($id: String!) {\n projectPendingVersionsUpdated(id: $id) {\n id\n type\n version {\n ...PendingFileUpload\n model {\n ...ProjectPageLatestItemsModelItem\n }\n }\n }\n }\n": typeof types.OnProjectPendingVersionsUpdatedDocument,
"\n subscription OnProjectTriggeredAutomationsStatusUpdated($id: String!) {\n projectTriggeredAutomationsStatusUpdated(projectId: $id) {\n type\n version {\n id\n automationsStatus {\n automationRuns {\n ...AutomateViewerPanel_AutomateRun\n }\n ...TriggeredAutomationsStatusSummary\n ...AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus\n }\n }\n model {\n id\n }\n run {\n id\n automationId\n ...AutomationRunDetails\n }\n }\n }\n": typeof types.OnProjectTriggeredAutomationsStatusUpdatedDocument,
"\n subscription OnProjectAutomationsUpdated($id: String!) {\n projectAutomationsUpdated(projectId: $id) {\n type\n automationId\n automation {\n id\n ...ProjectPageAutomationPage_Automation\n ...ProjectPageAutomationsRow_Automation\n }\n }\n }\n": typeof types.OnProjectAutomationsUpdatedDocument,
"\n mutation ServerInfoUpdate($info: ServerInfoUpdateInput!) {\n serverInfoUpdate(info: $info)\n }\n": typeof types.ServerInfoUpdateDocument,
"\n mutation AdminPanelDeleteUser($userConfirmation: UserDeleteInput!) {\n adminDeleteUser(userConfirmation: $userConfirmation)\n }\n": typeof types.AdminPanelDeleteUserDocument,
"\n mutation AdminPanelDeleteProject($ids: [String!]!) {\n projectMutations {\n batchDelete(ids: $ids)\n }\n }\n": typeof types.AdminPanelDeleteProjectDocument,
"\n mutation AdminPanelResendInvite($inviteId: String!) {\n inviteResend(inviteId: $inviteId)\n }\n": typeof types.AdminPanelResendInviteDocument,
"\n mutation AdminPanelDeleteInvite($inviteId: String!) {\n inviteDelete(inviteId: $inviteId)\n }\n": typeof types.AdminPanelDeleteInviteDocument,
"\n mutation AdminChangeUseRole($userRoleInput: UserRoleInput!) {\n userRoleChange(userRoleInput: $userRoleInput)\n }\n": typeof types.AdminChangeUseRoleDocument,
"\n query ServerManagementDataPage {\n admin {\n userList {\n totalCount\n }\n projectList {\n totalCount\n }\n inviteList {\n totalCount\n }\n }\n serverInfo {\n name\n version\n }\n }\n": typeof types.ServerManagementDataPageDocument,
"\n query ServerSettingsDialogData {\n serverInfo {\n name\n description\n adminContact\n company\n termsOfService\n inviteOnly\n guestModeEnabled\n }\n }\n": typeof types.ServerSettingsDialogDataDocument,
"\n query AdminPanelUsersList($limit: Int!, $cursor: String, $query: String) {\n admin {\n userList(limit: $limit, cursor: $cursor, query: $query) {\n totalCount\n cursor\n items {\n id\n email\n avatar\n name\n role\n verified\n company\n }\n }\n }\n }\n": typeof types.AdminPanelUsersListDocument,
"\n query AdminPanelProjectsList(\n $query: String\n $orderBy: String\n $limit: Int!\n $visibility: String\n $cursor: String\n ) {\n admin {\n projectList(\n query: $query\n orderBy: $orderBy\n limit: $limit\n visibility: $visibility\n cursor: $cursor\n ) {\n cursor\n ...SettingsServerProjects_ProjectCollection\n }\n }\n }\n": typeof types.AdminPanelProjectsListDocument,
"\n query AdminPanelInvitesList($limit: Int!, $cursor: String, $query: String) {\n admin {\n inviteList(limit: $limit, cursor: $cursor, query: $query) {\n cursor\n items {\n email\n id\n invitedBy {\n id\n name\n }\n }\n totalCount\n }\n }\n }\n": typeof types.AdminPanelInvitesListDocument,
"\n query UsersCount {\n admin {\n userList {\n totalCount\n }\n }\n }\n": typeof types.UsersCountDocument,
"\n query InvitesCount {\n admin {\n inviteList {\n totalCount\n }\n }\n }\n": typeof types.InvitesCountDocument,
"\n mutation InviteServerUser($input: [ServerInviteCreateInput!]!) {\n serverInviteBatchCreate(input: $input)\n }\n": typeof types.InviteServerUserDocument,
"\n fragment AddDomainWorkspace on Workspace {\n slug\n }\n ": typeof types.AddDomainWorkspaceFragmentDoc,
"\n fragment SettingsMenu_Workspace on Workspace {\n id\n sso {\n provider {\n id\n }\n session {\n validUntil\n }\n }\n }\n": typeof types.SettingsMenu_WorkspaceFragmentDoc,
"\n mutation SettingsUpdateWorkspace($input: WorkspaceUpdateInput!) {\n workspaceMutations {\n update(input: $input) {\n ...SettingsWorkspacesGeneral_Workspace\n }\n }\n }\n": typeof types.SettingsUpdateWorkspaceDocument,
"\n mutation SettingsCreateUserEmail($input: CreateUserEmailInput!) {\n activeUserMutations {\n emailMutations {\n create(input: $input) {\n id\n emails {\n ...EmailFields\n }\n }\n }\n }\n }\n": typeof types.SettingsCreateUserEmailDocument,
"\n mutation SettingsDeleteUserEmail($input: DeleteUserEmailInput!) {\n activeUserMutations {\n emailMutations {\n delete(input: $input) {\n id\n emails {\n ...EmailFields\n }\n }\n }\n }\n }\n": typeof types.SettingsDeleteUserEmailDocument,
"\n mutation SettingsSetPrimaryUserEmail($input: SetPrimaryUserEmailInput!) {\n activeUserMutations {\n emailMutations {\n setPrimary(input: $input) {\n id\n emails {\n ...EmailFields\n }\n }\n }\n }\n }\n": typeof types.SettingsSetPrimaryUserEmailDocument,
"\n mutation SettingsNewEmailVerification($input: EmailVerificationRequestInput!) {\n activeUserMutations {\n emailMutations {\n requestNewEmailVerification(input: $input)\n }\n }\n }\n": typeof types.SettingsNewEmailVerificationDocument,
"\n mutation SettingsUpdateWorkspaceSecurity($input: WorkspaceUpdateInput!) {\n workspaceMutations {\n update(input: $input) {\n id\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n }\n }\n": typeof types.SettingsUpdateWorkspaceSecurityDocument,
"\n mutation SettingsDeleteWorkspace($workspaceId: String!) {\n workspaceMutations {\n delete(workspaceId: $workspaceId)\n }\n }\n": typeof types.SettingsDeleteWorkspaceDocument,
"\n mutation SettingsResendWorkspaceInvite($input: WorkspaceInviteResendInput!) {\n workspaceMutations {\n invites {\n resend(input: $input)\n }\n }\n }\n": typeof types.SettingsResendWorkspaceInviteDocument,
"\n mutation SettingsCancelWorkspaceInvite($workspaceId: String!, $inviteId: String!) {\n workspaceMutations {\n invites {\n cancel(workspaceId: $workspaceId, inviteId: $inviteId) {\n id\n }\n }\n }\n }\n": typeof types.SettingsCancelWorkspaceInviteDocument,
"\n mutation AddWorkspaceDomain($input: AddDomainToWorkspaceInput!) {\n workspaceMutations {\n addDomain(input: $input) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n }\n }\n": typeof types.AddWorkspaceDomainDocument,
"\n mutation DeleteWorkspaceDomain($input: WorkspaceDomainDeleteInput!) {\n workspaceMutations {\n deleteDomain(input: $input) {\n ...SettingsWorkspacesSecurityDomainRemoveDialog_Workspace\n }\n }\n }\n": typeof types.DeleteWorkspaceDomainDocument,
"\n mutation SettingsLeaveWorkspace($leaveId: ID!) {\n workspaceMutations {\n leave(id: $leaveId)\n }\n }\n": typeof types.SettingsLeaveWorkspaceDocument,
"\n mutation SettingsBillingCancelCheckoutSession($input: CancelCheckoutSessionInput!) {\n workspaceMutations {\n billing {\n cancelCheckoutSession(input: $input)\n }\n }\n }\n": typeof types.SettingsBillingCancelCheckoutSessionDocument,
"\n query SettingsSidebar {\n activeUser {\n ...SettingsDialog_User\n }\n }\n": typeof types.SettingsSidebarDocument,
"\n query SettingsSidebarAutomateFunctions {\n activeUser {\n ...Sidebar_User\n }\n }\n": typeof types.SettingsSidebarAutomateFunctionsDocument,
"\n query SettingsWorkspaceGeneral($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesGeneral_Workspace\n }\n }\n": typeof types.SettingsWorkspaceGeneralDocument,
"\n query SettingsWorkspaceBilling($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesBilling_Workspace\n }\n }\n": typeof types.SettingsWorkspaceBillingDocument,
"\n query SettingsWorkspaceBillingCustomerPortal($workspaceId: String!) {\n workspace(id: $workspaceId) {\n customerPortalUrl\n }\n }\n": typeof types.SettingsWorkspaceBillingCustomerPortalDocument,
"\n query SettingsWorkspaceRegions($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesRegions_Workspace\n }\n serverInfo {\n ...SettingsWorkspacesRegions_ServerInfo\n }\n }\n": typeof types.SettingsWorkspaceRegionsDocument,
"\n query SettingsWorkspacesMembers($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersDocument,
"\n query SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersTableDocument,
"\n query SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsDocument,
"\n query SettingsWorkspacesMembersInvites($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesDocument,
"\n query SettingsWorkspacesMembersRequests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersRequestsDocument,
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersSearchDocument,
"\n query SettingsWorkspacesInvitesSearch(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesInvitesSearchDocument,
"\n query SettingsWorkspacesProjects(\n $slug: String!\n $limit: Int!\n $cursor: String\n $filter: WorkspaceProjectsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n slug\n readOnly\n projects(limit: $limit, cursor: $cursor, filter: $filter) {\n cursor\n ...SettingsWorkspacesProjects_ProjectCollection\n }\n }\n }\n": typeof types.SettingsWorkspacesProjectsDocument,
"\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n activeUser {\n ...SettingsWorkspacesSecurity_User\n }\n }\n": typeof types.SettingsWorkspaceSecurityDocument,
"\n fragment AppAuthorAvatar on AppAuthor {\n id\n name\n avatar\n }\n": typeof types.AppAuthorAvatarFragmentDoc,
"\n fragment LimitedUserAvatar on LimitedUser {\n id\n name\n avatar\n }\n": typeof types.LimitedUserAvatarFragmentDoc,
"\n fragment ActiveUserAvatar on User {\n id\n name\n avatar\n }\n": typeof types.ActiveUserAvatarFragmentDoc,
"\n subscription OnUserProjectsUpdate {\n userProjectsUpdated {\n type\n id\n project {\n ...ProjectDashboardItem\n }\n }\n }\n ": typeof types.OnUserProjectsUpdateDocument,
"\n mutation UpdateUser($input: UserUpdateInput!) {\n activeUserMutations {\n update(user: $input) {\n id\n name\n bio\n company\n avatar\n }\n }\n }\n": typeof types.UpdateUserDocument,
"\n mutation UpdateNotificationPreferences($input: JSONObject!) {\n userNotificationPreferencesUpdate(preferences: $input)\n }\n": typeof types.UpdateNotificationPreferencesDocument,
"\n mutation DeleteAccount($input: UserDeleteInput!) {\n userDelete(userConfirmation: $input)\n }\n": typeof types.DeleteAccountDocument,
"\n mutation verifyEmail($input: VerifyUserEmailInput!) {\n activeUserMutations {\n emailMutations {\n verify(input: $input)\n }\n }\n }\n": typeof types.VerifyEmailDocument,
"\n fragment EmailFields on UserEmail {\n id\n email\n verified\n primary\n userId\n }\n": typeof types.EmailFieldsFragmentDoc,
"\n query UserEmails {\n activeUser {\n id\n emails {\n ...EmailFields\n }\n hasPendingVerification\n }\n }\n": typeof types.UserEmailsDocument,
"\n fragment ViewerCommentBubblesData on Comment {\n id\n viewedAt\n viewerState\n }\n": typeof types.ViewerCommentBubblesDataFragmentDoc,
"\n fragment ViewerCommentThread on Comment {\n ...ViewerCommentsListItem\n ...ViewerCommentBubblesData\n ...ViewerCommentsReplyItem\n }\n": typeof types.ViewerCommentThreadFragmentDoc,
"\n fragment ViewerCommentsReplyItem on Comment {\n id\n archived\n rawText\n text {\n doc\n }\n author {\n ...LimitedUserAvatar\n }\n createdAt\n ...ThreadCommentAttachment\n }\n": typeof types.ViewerCommentsReplyItemFragmentDoc,
"\n mutation BroadcastViewerUserActivity(\n $projectId: String!\n $resourceIdString: String!\n $message: ViewerUserActivityMessageInput!\n ) {\n broadcastViewerUserActivity(\n projectId: $projectId\n resourceIdString: $resourceIdString\n message: $message\n )\n }\n": typeof types.BroadcastViewerUserActivityDocument,
"\n mutation MarkCommentViewed($input: MarkCommentViewedInput!) {\n commentMutations {\n markViewed(input: $input)\n }\n }\n": typeof types.MarkCommentViewedDocument,
"\n mutation CreateCommentThread($input: CreateCommentInput!) {\n commentMutations {\n create(input: $input) {\n ...ViewerCommentThread\n }\n }\n }\n": typeof types.CreateCommentThreadDocument,
"\n mutation CreateCommentReply($input: CreateCommentReplyInput!) {\n commentMutations {\n reply(input: $input) {\n ...ViewerCommentsReplyItem\n }\n }\n }\n": typeof types.CreateCommentReplyDocument,
"\n mutation ArchiveComment($input: ArchiveCommentInput!) {\n commentMutations {\n archive(input: $input)\n }\n }\n": typeof types.ArchiveCommentDocument,
"\n query ProjectViewerResources($projectId: String!, $resourceUrlString: String!) {\n project(id: $projectId) {\n id\n viewerResources(resourceIdString: $resourceUrlString) {\n identifier\n items {\n modelId\n versionId\n objectId\n }\n }\n }\n }\n": typeof types.ProjectViewerResourcesDocument,
"\n query ViewerLoadedResources(\n $projectId: String!\n $modelIds: [String!]!\n $versionIds: [String!]\n ) {\n project(id: $projectId) {\n id\n role\n allowPublicComments\n models(filter: { ids: $modelIds }) {\n totalCount\n items {\n id\n name\n updatedAt\n loadedVersion: versions(\n filter: { priorityIds: $versionIds, priorityIdsOnly: true }\n ) {\n items {\n ...ViewerModelVersionCardItem\n automationsStatus {\n id\n automationRuns {\n ...AutomateViewerPanel_AutomateRun\n }\n }\n }\n }\n versions(limit: 5) {\n totalCount\n cursor\n items {\n ...ViewerModelVersionCardItem\n }\n }\n }\n }\n ...ProjectPageLatestItemsModels\n ...ModelPageProject\n ...HeaderNavShare_Project\n }\n }\n": typeof types.ViewerLoadedResourcesDocument,
"\n query ViewerModelVersions(\n $projectId: String!\n $modelId: String!\n $versionsCursor: String\n ) {\n project(id: $projectId) {\n id\n role\n model(id: $modelId) {\n id\n versions(cursor: $versionsCursor, limit: 5) {\n totalCount\n cursor\n items {\n ...ViewerModelVersionCardItem\n }\n }\n }\n }\n }\n": typeof types.ViewerModelVersionsDocument,
"\n query ViewerDiffVersions(\n $projectId: String!\n $modelId: String!\n $versionAId: String!\n $versionBId: String!\n ) {\n project(id: $projectId) {\n id\n model(id: $modelId) {\n id\n versionA: version(id: $versionAId) {\n ...ViewerModelVersionCardItem\n }\n versionB: version(id: $versionBId) {\n ...ViewerModelVersionCardItem\n }\n }\n }\n }\n": typeof types.ViewerDiffVersionsDocument,
"\n query ViewerLoadedThreads(\n $projectId: String!\n $filter: ProjectCommentsFilter!\n $cursor: String\n $limit: Int\n ) {\n project(id: $projectId) {\n id\n commentThreads(filter: $filter, cursor: $cursor, limit: $limit) {\n totalCount\n totalArchivedCount\n items {\n ...ViewerCommentThread\n ...LinkableComment\n }\n }\n }\n }\n": typeof types.ViewerLoadedThreadsDocument,
"\n query ViewerRawProjectObject($projectId: String!, $objectId: String!) {\n project(id: $projectId) {\n id\n object(id: $objectId) {\n id\n data\n }\n }\n }\n": typeof types.ViewerRawProjectObjectDocument,
"\n subscription OnViewerUserActivityBroadcasted(\n $target: ViewerUpdateTrackingTarget!\n $sessionId: String!\n ) {\n viewerUserActivityBroadcasted(target: $target, sessionId: $sessionId) {\n userName\n userId\n user {\n ...LimitedUserAvatar\n }\n state\n status\n sessionId\n }\n }\n": typeof types.OnViewerUserActivityBroadcastedDocument,
"\n subscription OnViewerCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n id\n type\n comment {\n id\n parent {\n id\n }\n ...ViewerCommentThread\n }\n }\n }\n": typeof types.OnViewerCommentsUpdatedDocument,
"\n fragment LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\n }\n }\n": typeof types.LinkableCommentFragmentDoc,
"\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n": typeof types.UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragmentDoc,
"\n subscription OnWorkspaceProjectsUpdate($slug: String!) {\n workspaceProjectsUpdated(workspaceId: null, workspaceSlug: $slug) {\n projectId\n workspaceId\n type\n project {\n ...ProjectDashboardItem\n }\n }\n }\n ": typeof types.OnWorkspaceProjectsUpdateDocument,
"\n fragment WorkspaceHasCustomDataResidency_Workspace on Workspace {\n id\n defaultRegion {\n id\n name\n }\n }\n": typeof types.WorkspaceHasCustomDataResidency_WorkspaceFragmentDoc,
"\n query CheckProjectWorkspaceDataResidency($projectId: String!) {\n project(id: $projectId) {\n id\n workspace {\n ...WorkspaceHasCustomDataResidency_Workspace\n }\n }\n }\n": typeof types.CheckProjectWorkspaceDataResidencyDocument,
"\n fragment WorkspaceSsoStatus_Workspace on Workspace {\n id\n sso {\n provider {\n id\n name\n clientId\n issuerUrl\n }\n session {\n validUntil\n }\n }\n }\n ": typeof types.WorkspaceSsoStatus_WorkspaceFragmentDoc,
"\n fragment WorkspaceSsoStatus_User on User {\n expiredSsoSessions {\n id\n slug\n }\n }\n ": typeof types.WorkspaceSsoStatus_UserFragmentDoc,
"\n fragment WorkspaceBase_Workspace on Workspace {\n id\n name\n slug\n role\n description\n logo\n plan {\n status\n createdAt\n }\n }\n": typeof types.WorkspaceBase_WorkspaceFragmentDoc,
"\n fragment WorkspaceDashboardAbout_Workspace on Workspace {\n id\n name\n description\n }\n": typeof types.WorkspaceDashboardAbout_WorkspaceFragmentDoc,
"\n fragment WorkspaceInvitedTeam_Workspace on Workspace {\n id\n invitedTeam(filter: $invitesFilter) {\n id\n role\n email\n }\n }\n": typeof types.WorkspaceInvitedTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n items {\n status\n id\n }\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n": typeof types.WorkspaceTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n }\n }\n": typeof types.WorkspaceSecurity_WorkspaceFragmentDoc,
"\n mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {\n workspaceMutations {\n updateRole(input: $input) {\n team {\n items {\n id\n role\n }\n }\n }\n }\n }\n": typeof types.UpdateRoleDocument,
"\n mutation InviteToWorkspace(\n $workspaceId: String!\n $input: [WorkspaceInviteCreateInput!]!\n ) {\n workspaceMutations {\n invites {\n batchCreate(workspaceId: $workspaceId, input: $input) {\n id\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n }\n }\n }\n": typeof types.InviteToWorkspaceDocument,
"\n mutation CreateWorkspace($input: WorkspaceCreateInput!) {\n workspaceMutations {\n create(input: $input) {\n id\n ...SettingsDialog_Workspace\n }\n }\n }\n": typeof types.CreateWorkspaceDocument,
"\n mutation ProcessWorkspaceInvite($input: WorkspaceInviteUseInput!) {\n workspaceMutations {\n invites {\n use(input: $input)\n }\n }\n }\n": typeof types.ProcessWorkspaceInviteDocument,
"\n mutation SetDefaultWorkspaceRegion($workspaceId: String!, $regionKey: String!) {\n workspaceMutations {\n setDefaultRegion(workspaceId: $workspaceId, regionKey: $regionKey) {\n id\n defaultRegion {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n }\n": typeof types.SetDefaultWorkspaceRegionDocument,
"\n mutation DeleteWorkspaceSsoProvider($workspaceId: String!) {\n workspaceMutations {\n deleteSsoProvider(workspaceId: $workspaceId)\n }\n }\n": typeof types.DeleteWorkspaceSsoProviderDocument,
"\n mutation SetWorkspaceCreationState($input: WorkspaceCreationStateInput!) {\n workspaceMutations {\n updateCreationState(input: $input)\n }\n }\n": typeof types.SetWorkspaceCreationStateDocument,
"\n mutation WorkspaceUpdateDomainProtectionMutation($input: WorkspaceUpdateInput!) {\n workspaceMutations {\n update(input: $input) {\n id\n domainBasedMembershipProtectionEnabled\n }\n }\n }\n": typeof types.WorkspaceUpdateDomainProtectionMutationDocument,
"\n mutation WorkspaceUpdateDiscoverabilityMutation($input: WorkspaceUpdateInput!) {\n workspaceMutations {\n update(input: $input) {\n id\n discoverabilityEnabled\n }\n }\n }\n": typeof types.WorkspaceUpdateDiscoverabilityMutationDocument,
"\n mutation ApproveWorkspaceJoinRequest($input: ApproveWorkspaceJoinRequestInput!) {\n workspaceJoinRequestMutations {\n approve(input: $input)\n }\n }\n": typeof types.ApproveWorkspaceJoinRequestDocument,
"\n mutation DenyWorkspaceJoinRequest($input: DenyWorkspaceJoinRequestInput!) {\n workspaceJoinRequestMutations {\n deny(input: $input)\n }\n }\n": typeof types.DenyWorkspaceJoinRequestDocument,
"\n query WorkspaceAccessCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n }\n }\n": typeof types.WorkspaceAccessCheckDocument,
"\n query WorkspacePageQuery(\n $workspaceSlug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $token: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n ...WorkspaceProjectList_Workspace\n }\n workspaceInvite(\n workspaceId: $workspaceSlug\n token: $token\n options: { useSlug: true }\n ) {\n id\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n": typeof types.WorkspacePageQueryDocument,
"\n query WorkspaceProjectsQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n }\n": typeof types.WorkspaceProjectsQueryDocument,
"\n query WorkspaceFunctionsQuery($workspaceSlug: String!) {\n ...AutomateFunctionsPageHeader_Query\n workspaceBySlug(slug: $workspaceSlug) {\n id\n name\n automateFunctions {\n items {\n id\n ...AutomationsFunctionsCard_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n }\n }\n }\n }\n": typeof types.WorkspaceFunctionsQueryDocument,
"\n query WorkspaceInvite(\n $workspaceId: String\n $token: String\n $options: WorkspaceInviteLookupOptions\n ) {\n workspaceInvite(workspaceId: $workspaceId, token: $token, options: $options) {\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n": typeof types.WorkspaceInviteDocument,
"\n query MoveProjectsDialog {\n activeUser {\n ...MoveProjectsDialog_User\n }\n }\n": typeof types.MoveProjectsDialogDocument,
"\n query ValidateWorkspaceSlug($slug: String!) {\n validateWorkspaceSlug(slug: $slug)\n }\n": typeof types.ValidateWorkspaceSlugDocument,
"\n query WorkspaceSsoByEmail($email: String!) {\n workspaceSsoByEmail(email: $email) {\n ...AuthSsoLogin_Workspace\n }\n }\n": typeof types.WorkspaceSsoByEmailDocument,
"\n query WorkspaceSsoCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceSsoStatus_Workspace\n }\n activeUser {\n ...WorkspaceSsoStatus_User\n }\n }\n": typeof types.WorkspaceSsoCheckDocument,
"\n query WorkspaceWizard($workspaceId: String!) {\n workspace(id: $workspaceId) {\n id\n ...WorkspaceWizard_Workspace\n }\n }\n": typeof types.WorkspaceWizardDocument,
"\n query WorkspaceWizardRegion {\n serverInfo {\n ...WorkspaceWizardStepRegion_ServerInfo\n }\n }\n": typeof types.WorkspaceWizardRegionDocument,
"\n subscription onWorkspaceUpdated(\n $workspaceId: String\n $workspaceSlug: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceUpdated(workspaceId: $workspaceId, workspaceSlug: $workspaceSlug) {\n id\n workspace {\n id\n ...WorkspaceProjectList_Workspace\n }\n }\n }\n": typeof types.OnWorkspaceUpdatedDocument,
"\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\n }\n }\n }\n": typeof types.LegacyBranchRedirectMetadataDocument,
"\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\n id\n }\n }\n }\n }\n": typeof types.LegacyViewerCommitRedirectMetadataDocument,
"\n query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n project(id: $streamId) {\n id\n versions(limit: 1) {\n totalCount\n items {\n id\n model {\n id\n }\n }\n }\n }\n }\n": typeof types.LegacyViewerStreamRedirectMetadataDocument,
"\n query AutoAcceptableWorkspaceInvite(\n $token: String!\n $workspaceId: String!\n $options: WorkspaceInviteLookupOptions\n ) {\n workspaceInvite(token: $token, workspaceId: $workspaceId, options: $options) {\n id\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n }\n": typeof types.AutoAcceptableWorkspaceInviteDocument,
"\n query ResolveCommentLink($commentId: String!, $projectId: String!) {\n project(id: $projectId) {\n comment(id: $commentId) {\n id\n ...LinkableComment\n }\n }\n }\n": typeof types.ResolveCommentLinkDocument,
"\n fragment AutomateFunctionPage_AutomateFunction on AutomateFunction {\n id\n name\n description\n logo\n supportedSourceApps\n tags\n ...AutomateFunctionPageHeader_Function\n ...AutomateFunctionPageInfo_AutomateFunction\n ...AutomateAutomationCreateDialog_AutomateFunction\n creator {\n id\n }\n }\n": typeof types.AutomateFunctionPage_AutomateFunctionFragmentDoc,
"\n query AutomateFunctionPage($functionId: ID!) {\n automateFunction(id: $functionId) {\n ...AutomateFunctionPage_AutomateFunction\n }\n activeUser {\n workspaces {\n items {\n ...AutomateFunctionCreateDialog_Workspace\n ...AutomateFunctionEditDialog_Workspace\n }\n }\n }\n }\n": typeof types.AutomateFunctionPageDocument,
"\n query AutomateFunctionPageWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n id\n ...AutomateFunctionPageHeader_Workspace\n }\n }\n": typeof types.AutomateFunctionPageWorkspaceDocument,
"\n fragment PagesOnboarding_DiscoverableWorkspaces on User {\n discoverableWorkspaces {\n id\n name\n logo\n description\n slug\n }\n }\n": typeof types.PagesOnboarding_DiscoverableWorkspacesFragmentDoc,
"\n fragment ProjectPageProject on Project {\n id\n createdAt\n modelCount: models(limit: 0) {\n totalCount\n }\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n workspace {\n id\n }\n ...ProjectPageTeamInternals_Project\n ...ProjectPageProjectHeader\n ...ProjectPageTeamDialog\n ...ProjectsMoveToWorkspaceDialog_Project\n }\n": typeof types.ProjectPageProjectFragmentDoc,
"\n fragment ProjectPageAutomationPage_Automation on Automation {\n id\n ...ProjectPageAutomationHeader_Automation\n ...ProjectPageAutomationFunctions_Automation\n ...ProjectPageAutomationRuns_Automation\n }\n": typeof types.ProjectPageAutomationPage_AutomationFragmentDoc,
"\n fragment ProjectPageAutomationPage_Project on Project {\n id\n workspaceId\n ...ProjectPageAutomationHeader_Project\n }\n": typeof types.ProjectPageAutomationPage_ProjectFragmentDoc,
"\n fragment ProjectPageSettingsTab_Project on Project {\n id\n role\n }\n": typeof types.ProjectPageSettingsTab_ProjectFragmentDoc,
"\n fragment SettingsServerProjects_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...SettingsSharedProjects_Project\n }\n }\n": typeof types.SettingsServerProjects_ProjectCollectionFragmentDoc,
"\n query SettingsServerRegions {\n serverInfo {\n multiRegion {\n regions {\n id\n ...SettingsServerRegionsTable_ServerRegionItem\n }\n availableKeys\n }\n }\n }\n": typeof types.SettingsServerRegionsDocument,
"\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n ...BillingAlert_Workspace\n id\n role\n plan {\n name\n status\n createdAt\n paymentMethod\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n team {\n items {\n id\n role\n }\n }\n }\n": typeof types.SettingsWorkspacesBilling_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesGeneral_Workspace on Workspace {\n ...SettingsWorkspacesGeneralEditAvatar_Workspace\n ...SettingsWorkspaceGeneralDeleteDialog_Workspace\n ...SettingsWorkspacesGeneralEditSlugDialog_Workspace\n id\n name\n slug\n description\n logo\n role\n defaultProjectRole\n plan {\n status\n name\n }\n }\n": typeof types.SettingsWorkspacesGeneral_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n role\n }\n }\n invitedTeam {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests {\n items {\n id\n status\n }\n totalCount\n }\n }\n": typeof types.SettingsWorkspacesMembers_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesProjects_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...SettingsSharedProjects_Project\n }\n }\n": typeof types.SettingsWorkspacesProjects_ProjectCollectionFragmentDoc,
"\n fragment SettingsWorkspacesRegions_Workspace on Workspace {\n id\n role\n defaultRegion {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n hasAccessToMultiRegion: hasAccessToFeature(\n featureName: workspaceDataRegionSpecificity\n )\n hasProjects: projects(limit: 0) {\n totalCount\n }\n }\n": typeof types.SettingsWorkspacesRegions_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesRegions_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": typeof types.SettingsWorkspacesRegions_ServerInfoFragmentDoc,
"\n fragment SettingsWorkspacesSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n ...SettingsWorkspacesSecuritySsoWrapper_Workspace\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n }\n\n fragment SettingsWorkspacesSecurity_User on User {\n id\n emails {\n id\n email\n verified\n }\n }\n": typeof types.SettingsWorkspacesSecurity_WorkspaceFragmentDoc,
};
const documents: Documents = {
"\n fragment AuthLoginWithEmailBlock_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n email\n user {\n id\n }\n }\n": types.AuthLoginWithEmailBlock_PendingWorkspaceCollaboratorFragmentDoc,
"\n query AuthRegisterPanelWorkspaceInvite($token: String) {\n workspaceInvite(token: $token) {\n id\n ...AuthWorkspaceInviteHeader_PendingWorkspaceCollaborator\n }\n }\n": types.AuthRegisterPanelWorkspaceInviteDocument,
"\n fragment ServerTermsOfServicePrivacyPolicyFragment on ServerInfo {\n termsOfService\n }\n": types.ServerTermsOfServicePrivacyPolicyFragmentFragmentDoc,
@@ -119,13 +506,13 @@ const documents = {
"\n fragment SettingsWorkspacesGeneralEditAvatar_Workspace on Workspace {\n id\n logo\n name\n }\n": types.SettingsWorkspacesGeneralEditAvatar_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesGeneralEditSlugDialog_Workspace on Workspace {\n id\n name\n slug\n }\n": types.SettingsWorkspacesGeneralEditSlugDialog_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersChangeRoleDialog_Workspace on Workspace {\n id\n plan {\n status\n name\n }\n subscription {\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n }\n": types.SettingsWorkspacesMembersChangeRoleDialog_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": types.SettingsWorkspacesMembersGuestsTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": types.SettingsWorkspacesMembersGuestsTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersGuestsTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n inviteId\n role\n title\n updatedAt\n user {\n id\n ...LimitedUserAvatar\n }\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersRequestsTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n workspaceDomainPolicyCompliant\n }\n }\n": types.SettingsWorkspacesMembersMembersTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersMembersTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersRequestsTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n workspaceDomainPolicyCompliant\n }\n }\n": types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n": types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesRegionsSelect_ServerRegionItem on ServerRegionItem {\n id\n key\n name\n description\n }\n": types.SettingsWorkspacesRegionsSelect_ServerRegionItemFragmentDoc,
"\n fragment SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain on WorkspaceDomain {\n id\n domain\n }\n": types.SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragmentDoc,
@@ -145,14 +532,13 @@ const documents = {
"\n fragment WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspace on LimitedWorkspace {\n id\n name\n slug\n description\n logo\n }\n": types.WorkspaceInviteDiscoverableWorkspaceBanner_LimitedWorkspaceFragmentDoc,
"\n fragment WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest on WorkspaceJoinRequest {\n id\n user {\n id\n name\n }\n workspace {\n id\n }\n }\n": types.WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequestFragmentDoc,
"\n fragment WorkspaceSidebarAbout_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\n }\n": types.WorkspaceSidebarAbout_WorkspaceFragmentDoc,
"\n fragment WorkspaceSidebarMembers_Workspace on Workspace {\n ...WorkspaceTeam_Workspace\n }\n": types.WorkspaceSidebarMembers_WorkspaceFragmentDoc,
"\n fragment WorkspaceSidebarSecurity_Workspace on Workspace {\n ...WorkspaceSecurity_Workspace\n }\n": types.WorkspaceSidebarSecurity_WorkspaceFragmentDoc,
"\n fragment WorkspaceSidebar_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\n ...WorkspaceTeam_Workspace\n ...WorkspaceSecurity_Workspace\n slug\n plan {\n status\n }\n }\n": types.WorkspaceSidebar_WorkspaceFragmentDoc,
"\n fragment WorkspaceWizard_Workspace on Workspace {\n creationState {\n completed\n state\n }\n name\n slug\n }\n": types.WorkspaceWizard_WorkspaceFragmentDoc,
"\n fragment WorkspaceWizardStepRegion_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": types.WorkspaceWizardStepRegion_ServerInfoFragmentDoc,
"\n query ActiveUserMainMetadata {\n activeUser {\n id\n email\n emails {\n id\n verified\n }\n company\n bio\n name\n role\n avatar\n isOnboardingFinished\n createdAt\n verified\n notificationPreferences\n versions(limit: 0) {\n totalCount\n }\n }\n }\n": types.ActiveUserMainMetadataDocument,
"\n mutation CreateOnboardingProject {\n projectMutations {\n createForOnboarding {\n ...ProjectPageProject\n ...ProjectDashboardItem\n }\n }\n }\n ": types.CreateOnboardingProjectDocument,
"\n mutation FinishOnboarding {\n activeUserMutations {\n finishOnboarding\n }\n }\n": types.FinishOnboardingDocument,
"\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n": types.FinishOnboardingDocument,
"\n mutation RequestVerificationByEmail($email: String!) {\n requestVerificationByEmail(email: $email)\n }\n": types.RequestVerificationByEmailDocument,
"\n query AuthLoginPanel {\n serverInfo {\n authStrategies {\n id\n }\n ...AuthStategiesServerInfoFragment\n }\n }\n": types.AuthLoginPanelDocument,
"\n query AuthRegisterPanel($token: String) {\n serverInfo {\n inviteOnly\n authStrategies {\n id\n }\n ...AuthStategiesServerInfoFragment\n ...ServerTermsOfServicePrivacyPolicyFragment\n }\n serverInviteByToken(token: $token) {\n id\n email\n }\n }\n": types.AuthRegisterPanelDocument,
@@ -308,9 +694,12 @@ const documents = {
"\n query SettingsWorkspaceBilling($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesBilling_Workspace\n }\n }\n": types.SettingsWorkspaceBillingDocument,
"\n query SettingsWorkspaceBillingCustomerPortal($workspaceId: String!) {\n workspace(id: $workspaceId) {\n customerPortalUrl\n }\n }\n": types.SettingsWorkspaceBillingCustomerPortalDocument,
"\n query SettingsWorkspaceRegions($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesRegions_Workspace\n }\n serverInfo {\n ...SettingsWorkspacesRegions_ServerInfo\n }\n }\n": types.SettingsWorkspaceRegionsDocument,
"\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersDocument,
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersSearchDocument,
"\n query SettingsWorkspacesJoinRequestsSearch(\n $slug: String!\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": types.SettingsWorkspacesJoinRequestsSearchDocument,
"\n query SettingsWorkspacesMembers($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n }\n }\n": types.SettingsWorkspacesMembersDocument,
"\n query SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersTableDocument,
"\n query SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersGuestsDocument,
"\n query SettingsWorkspacesMembersInvites($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersInvitesDocument,
"\n query SettingsWorkspacesMembersRequests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersRequestsDocument,
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersSearchDocument,
"\n query SettingsWorkspacesInvitesSearch(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": types.SettingsWorkspacesInvitesSearchDocument,
"\n query SettingsWorkspacesProjects(\n $slug: String!\n $limit: Int!\n $cursor: String\n $filter: WorkspaceProjectsFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n slug\n readOnly\n projects(limit: $limit, cursor: $cursor, filter: $filter) {\n cursor\n ...SettingsWorkspacesProjects_ProjectCollection\n }\n }\n }\n": types.SettingsWorkspacesProjectsDocument,
"\n query SettingsWorkspaceSecurity($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesSecurity_Workspace\n }\n activeUser {\n ...SettingsWorkspacesSecurity_User\n }\n }\n": types.SettingsWorkspaceSecurityDocument,
@@ -350,7 +739,7 @@ const documents = {
"\n fragment WorkspaceBase_Workspace on Workspace {\n id\n name\n slug\n role\n description\n logo\n plan {\n status\n createdAt\n }\n }\n": types.WorkspaceBase_WorkspaceFragmentDoc,
"\n fragment WorkspaceDashboardAbout_Workspace on Workspace {\n id\n name\n description\n }\n": types.WorkspaceDashboardAbout_WorkspaceFragmentDoc,
"\n fragment WorkspaceInvitedTeam_Workspace on Workspace {\n id\n invitedTeam(filter: $invitesFilter) {\n id\n role\n email\n }\n }\n": types.WorkspaceInvitedTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n": types.WorkspaceTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n items {\n status\n id\n }\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n": types.WorkspaceTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n }\n }\n": types.WorkspaceSecurity_WorkspaceFragmentDoc,
"\n mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {\n workspaceMutations {\n updateRole(input: $input) {\n team {\n items {\n id\n role\n }\n }\n }\n }\n }\n": types.UpdateRoleDocument,
"\n mutation InviteToWorkspace(\n $workspaceId: String!\n $input: [WorkspaceInviteCreateInput!]!\n ) {\n workspaceMutations {\n invites {\n batchCreate(workspaceId: $workspaceId, input: $input) {\n id\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n }\n }\n }\n": types.InviteToWorkspaceDocument,
@@ -392,7 +781,7 @@ const documents = {
"\n query SettingsServerRegions {\n serverInfo {\n multiRegion {\n regions {\n id\n ...SettingsServerRegionsTable_ServerRegionItem\n }\n availableKeys\n }\n }\n }\n": types.SettingsServerRegionsDocument,
"\n fragment SettingsWorkspacesBilling_Workspace on Workspace {\n ...BillingAlert_Workspace\n id\n role\n plan {\n name\n status\n createdAt\n paymentMethod\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n guest\n plan\n }\n }\n team {\n items {\n id\n role\n }\n }\n }\n": types.SettingsWorkspacesBilling_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesGeneral_Workspace on Workspace {\n ...SettingsWorkspacesGeneralEditAvatar_Workspace\n ...SettingsWorkspaceGeneralDeleteDialog_Workspace\n ...SettingsWorkspacesGeneralEditSlugDialog_Workspace\n id\n name\n slug\n description\n logo\n role\n defaultProjectRole\n plan {\n status\n name\n }\n }\n": types.SettingsWorkspacesGeneral_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n }\n }\n": types.SettingsWorkspacesMembers_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n role\n }\n }\n invitedTeam {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests {\n items {\n id\n status\n }\n totalCount\n }\n }\n": types.SettingsWorkspacesMembers_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesProjects_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...SettingsSharedProjects_Project\n }\n }\n": types.SettingsWorkspacesProjects_ProjectCollectionFragmentDoc,
"\n fragment SettingsWorkspacesRegions_Workspace on Workspace {\n id\n role\n defaultRegion {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n hasAccessToMultiRegion: hasAccessToFeature(\n featureName: workspaceDataRegionSpecificity\n )\n hasProjects: projects(limit: 0) {\n totalCount\n }\n }\n": types.SettingsWorkspacesRegions_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesRegions_ServerInfo on ServerInfo {\n multiRegion {\n regions {\n id\n ...SettingsWorkspacesRegionsSelect_ServerRegionItem\n }\n }\n }\n": types.SettingsWorkspacesRegions_ServerInfoFragmentDoc,
@@ -836,7 +1225,7 @@ export function graphql(source: "\n fragment SettingsWorkspacesMembersChangeRol
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"];
export function graphql(source: "\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -848,19 +1237,19 @@ export function graphql(source: "\n fragment SettingsWorkspacesMembersInvitesTa
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n"];
export function graphql(source: "\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\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 fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n"];
export function graphql(source: "\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\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 fragment SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n workspaceDomainPolicyCompliant\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n workspaceDomainPolicyCompliant\n }\n }\n"];
export function graphql(source: "\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n workspaceDomainPolicyCompliant\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n workspaceDomainPolicyCompliant\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 fragment SettingsWorkspacesMembersMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n"];
export function graphql(source: "\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -937,10 +1326,6 @@ export function graphql(source: "\n fragment WorkspaceJoinRequestApproveDialog_
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment WorkspaceSidebarAbout_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceSidebarAbout_Workspace on Workspace {\n ...WorkspaceDashboardAbout_Workspace\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 fragment WorkspaceSidebarMembers_Workspace on Workspace {\n ...WorkspaceTeam_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceSidebarMembers_Workspace on Workspace {\n ...WorkspaceTeam_Workspace\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -968,7 +1353,7 @@ export function graphql(source: "\n mutation CreateOnboardingProject {\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 FinishOnboarding {\n activeUserMutations {\n finishOnboarding\n }\n }\n"): (typeof documents)["\n mutation FinishOnboarding {\n activeUserMutations {\n finishOnboarding\n }\n }\n"];
export function graphql(source: "\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n"): (typeof documents)["\n mutation FinishOnboarding($input: OnboardingCompletionInput) {\n activeUserMutations {\n finishOnboarding(input: $input)\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -1592,15 +1977,27 @@ export function graphql(source: "\n query SettingsWorkspaceRegions($slug: Strin
/**
* 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 SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembers(\n $slug: String!\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n ...SettingsWorkspacesMembersMembersTable_Workspace\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"];
export function graphql(source: "\n query SettingsWorkspacesMembers($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembers($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\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 SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n"];
export function graphql(source: "\n query SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_Workspace\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 SettingsWorkspacesJoinRequestsSearch(\n $slug: String!\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesJoinRequestsSearch(\n $slug: String!\n $joinRequestsFilter: AdminWorkspaceJoinRequestFilter\n ) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"];
export function graphql(source: "\n query SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\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 SettingsWorkspacesMembersInvites($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersInvites($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\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 SettingsWorkspacesMembersRequests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersRequests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersRequestsTable_Workspace\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 SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -1760,7 +2157,7 @@ export function graphql(source: "\n fragment WorkspaceInvitedTeam_Workspace on
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n"];
export function graphql(source: "\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n items {\n status\n id\n }\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n items {\n status\n id\n }\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -1928,7 +2325,7 @@ export function graphql(source: "\n fragment SettingsWorkspacesGeneral_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 fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {\n totalCount\n }\n }\n"];
export function graphql(source: "\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n role\n }\n }\n invitedTeam {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests {\n items {\n id\n status\n }\n totalCount\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembers_Workspace on Workspace {\n id\n role\n team {\n items {\n id\n role\n }\n }\n invitedTeam {\n user {\n id\n }\n }\n adminWorkspacesJoinRequests {\n items {\n id\n status\n }\n totalCount\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
File diff suppressed because one or more lines are too long
@@ -47,6 +47,18 @@ export const settingsWorkspaceRoutes = {
name: 'settings-workspaces-slug-members',
route: (slug: string) => `/settings/workspaces/${slug}/members`
},
membersGuests: {
name: 'settings-workspaces-slug-members-guests',
route: (slug: string) => `/settings/workspaces/${slug}/members/guests`
},
membersInvites: {
name: 'settings-workspaces-slug-members-invites',
route: (slug: string) => `/settings/workspaces/${slug}/members/invites`
},
membersRequests: {
name: 'settings-workspaces-slug-members-requests',
route: (slug: string) => `/settings/workspaces/${slug}/members/requests`
},
projects: {
name: 'settings-workspaces-slug-projects',
route: (slug: string) => `/settings/workspaces/${slug}/projects`
@@ -382,6 +382,7 @@ function createLink(params: {
// only log as error if at least one error has a status code of 5xx or has no status code
const shouldLogAsWarn = gqlErrors.every(
(e) =>
e.extensions &&
'statusCode' in e.extensions &&
typeof e.extensions.statusCode === 'number' &&
e.extensions.statusCode < 500
@@ -1,3 +1,5 @@
import { BaseError } from '@speckle/ui-components'
export {
BaseError,
LogicError,
@@ -5,3 +7,7 @@ export {
ComposableInvokedOutOfScopeError,
UnsupportedEnvironmentError
} from '@speckle/ui-components'
export class ResourceLoadError extends BaseError {
static defaultMessage = 'External resource failed to load'
}
@@ -13,6 +13,7 @@ import {
noop
} from 'lodash-es'
import type { Logger, Level } from 'pino'
import { ResourceLoadError } from '~/lib/core/errors/base'
/**
* Add pino-pretty like formatting
@@ -103,6 +104,12 @@ export const formatAppError = (err: SimpleError): SimpleError => {
finalStatusCode = 429
}
if (finalMessage.match(/\/_nuxt\/builds\/meta.*?404/i)) {
finalMessage =
'Speckle is currently upgrading to a newer version. Please reload the page in a few seconds.'
finalStatusCode = 500
}
finalMessage = upperFirst(finalMessage)
return {
@@ -152,12 +159,30 @@ export function enableCustomLoggerHandling(params: {
return (...args: unknown[]) => {
const log = logMethod.bind(target)
// Format passed in data, if needed
args = args
.map((arg) => {
// Convert error events to error type
if (arg instanceof Event && arg.type === 'error') {
return new ResourceLoadError()
}
return arg
})
.filter((arg) => {
// Filter out falsy values
return !!arg && !(['null', 'undefined'] as unknown[]).includes(arg)
})
// If nothing valid to log, skip entirely
if (args.length === 0) return
const level = prop as Level
const firstError = args.find((arg): arg is Error => arg instanceof Error)
const firstString = args.find(isString)
const otherData: unknown[] = args.filter(
(o) => !(o instanceof Error) && o !== firstString
(o) => o !== firstString && o !== firstError
)
const errorMessage = firstError?.message ?? firstString ?? `Unknown error`
@@ -1,6 +1,5 @@
import type { MaybeRef } from '@vueuse/core'
import type { MaybeNullOrUndefined, Nullable } from '@speckle/shared'
import { useAuthCookie } from '~~/lib/auth/composables/auth'
import { onProjectVersionsPreviewGeneratedSubscription } from '~~/lib/projects/graphql/subscriptions'
import { useSubscription } from '@vue/apollo-composable'
import { useLock } from '~~/lib/common/composables/singleton'
@@ -26,11 +25,7 @@ export function usePreviewImageBlob(
}>
) {
const { enabled = ref(true) } = options || {}
const authToken = useAuthCookie()
const logger = useLogger()
const {
public: { enableDirectPreviews }
} = useRuntimeConfig()
const url = ref(PreviewPlaceholder as Nullable<string>)
const hasDoneFirstLoad = ref(false)
@@ -49,21 +44,15 @@ export function usePreviewImageBlob(
hasDoneFirstLoad: computed(() => hasDoneFirstLoad.value)
}
if (enableDirectPreviews) {
const directPreviewUrl = unref(previewUrl)
// const directPanoramicUrl = basePanoramaUrl.value
useHead({
link: [
...(directPreviewUrl?.length
? [{ rel: 'preload', as: <const>'image', href: directPreviewUrl }]
: [])
// ...(directPanoramicUrl?.length
// ? [{ rel: 'prefetch', as: <const>'image', href: directPanoramicUrl }]
// : [])
]
})
}
// Preload the image
const directPreviewUrl = unref(previewUrl)
useHead({
link: [
...(directPreviewUrl?.length
? [{ rel: 'preload', as: <const>'image', href: directPreviewUrl }]
: [])
]
})
if (import.meta.server) return ret
@@ -133,23 +122,9 @@ export function usePreviewImageBlob(
return
}
let blobUrl: string
if (enableDirectPreviews || import.meta.server) {
const blobUrlConfig = new URL(basePreviewUrl)
blobUrlConfig.searchParams.set('v', cacheBust.value.toString())
blobUrl = blobUrlConfig.toString()
} else {
const res = await fetch(basePreviewUrl, {
headers: authToken.value ? { Authorization: `Bearer ${authToken.value}` } : {}
})
if (res.headers.has('X-Preview-Error')) {
throw new Error('Failed getting preview')
}
const blob = await res.blob()
blobUrl = URL.createObjectURL(blob)
}
const blobUrlConfig = new URL(basePreviewUrl)
blobUrlConfig.searchParams.set('v', cacheBust.value.toString())
const blobUrl = blobUrlConfig.toString()
// Load img in browser first, before we set the url
if (import.meta.client) {
@@ -181,30 +156,9 @@ export function usePreviewImageBlob(
return
}
let blobUrl: string
if (enableDirectPreviews || import.meta.server) {
const blobUrlConfig = new URL(basePanoramaUrl.value)
blobUrlConfig.searchParams.set('v', cacheBust.value.toString())
blobUrl = blobUrlConfig.toString()
} else {
const res = await fetch(basePanoramaUrl.value, {
headers: authToken.value ? { Authorization: `Bearer ${authToken.value}` } : {}
})
const errCode = res.headers.get('X-Preview-Error-Code')
if (errCode?.length) {
if (errCode === 'ANGLE_NOT_FOUND') {
throw new AngleNotFoundError()
}
}
if (res.headers.has('X-Preview-Error')) {
throw new Error('Failed getting preview')
}
const blob = await res.blob()
blobUrl = URL.createObjectURL(blob)
}
const blobUrlConfig = new URL(basePanoramaUrl.value)
blobUrlConfig.searchParams.set('v', cacheBust.value.toString())
const blobUrl = blobUrlConfig.toString()
// Load img in browser first, before we set the url
if (import.meta.client) {
@@ -85,10 +85,12 @@ export function useProjectVersionUpdateTracking(
const event = res.data.projectVersionsUpdated
const version = event.version
if (
[
ProjectVersionsUpdatedMessageType.Created,
ProjectVersionsUpdatedMessageType.Updated
].includes(event.type) &&
(
[
ProjectVersionsUpdatedMessageType.Created,
ProjectVersionsUpdatedMessageType.Updated
] as string[]
).includes(event.type) &&
version
) {
// Added new model w/ versions OR updated model that now has versions (it might not have had them previously)
@@ -54,16 +54,40 @@ export const settingsWorkspaceRegionsQuery = graphql(`
`)
export const settingsWorkspacesMembersQuery = graphql(`
query SettingsWorkspacesMembers(
$slug: String!
$invitesFilter: PendingWorkspaceCollaboratorsFilter
$joinRequestsFilter: AdminWorkspaceJoinRequestFilter
) {
query SettingsWorkspacesMembers($slug: String!) {
workspaceBySlug(slug: $slug) {
...SettingsWorkspacesMembers_Workspace
...SettingsWorkspacesMembersMembersTable_Workspace
}
}
`)
export const settingsWorkspacesMembersTableQuery = graphql(`
query SettingsWorkspacesMembersTable($slug: String!) {
workspaceBySlug(slug: $slug) {
...SettingsWorkspacesMembersTable_Workspace
}
}
`)
export const settingsWorkspacesMembersGuestsQuery = graphql(`
query SettingsWorkspacesMembersGuests($slug: String!) {
workspaceBySlug(slug: $slug) {
...SettingsWorkspacesMembersGuestsTable_Workspace
}
}
`)
export const settingsWorkspacesMembersInvitesQuery = graphql(`
query SettingsWorkspacesMembersInvites($slug: String!) {
workspaceBySlug(slug: $slug) {
...SettingsWorkspacesMembersInvitesTable_Workspace
}
}
`)
export const settingsWorkspacesMembersRequestsQuery = graphql(`
query SettingsWorkspacesMembersRequests($slug: String!) {
workspaceBySlug(slug: $slug) {
...SettingsWorkspacesMembersRequestsTable_Workspace
}
}
@@ -76,25 +100,13 @@ export const settingsWorkspacesMembersSearchQuery = graphql(`
team(filter: $filter) {
items {
id
...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator
...SettingsWorkspacesMembersTable_WorkspaceCollaborator
}
}
}
}
`)
export const settingsWorkspacesJoinRequestsSearchQuery = graphql(`
query SettingsWorkspacesJoinRequestsSearch(
$slug: String!
$joinRequestsFilter: AdminWorkspaceJoinRequestFilter
) {
workspaceBySlug(slug: $slug) {
id
...SettingsWorkspacesMembersRequestsTable_Workspace
}
}
`)
export const settingsWorkspacesInvitesSearchQuery = graphql(`
query SettingsWorkspacesInvitesSearch(
$slug: String!
@@ -1,6 +1,4 @@
import type { AvailableRoles } from '@speckle/shared'
import { isObjectLike, has } from 'lodash'
import type { WorkspacePlans } from '~/lib/common/generated/gql/graphql'
type BaseSettingsMenuItem = {
title: string
@@ -17,22 +15,3 @@ export type WorkspaceSettingsMenuItem = BaseSettingsMenuItem & {
name: string
route: (slug: string) => string
}
export type WorkspacePricingPlans = {
workspacePricingPlans: {
workspacePlanInformation: {
[key: string]: {
name: WorkspacePlans
}
}
}
}
export function isWorkspacePricingPlans(
pricingPlans: unknown
): pricingPlans is WorkspacePricingPlans {
return (
isObjectLike(pricingPlans) &&
has(pricingPlans, 'workspacePricingPlans.workspacePlanInformation')
)
}
@@ -87,17 +87,23 @@ export function useUserEmails() {
return false
}
const deleteUserEmail = async (email: UserEmail, cancel = false) => {
const deleteUserEmail = async (options: {
email: UserEmail
hideToast?: boolean
}) => {
const { email, hideToast } = options
const result = await deleteMutation({
input: { id: email.id }
}).catch(convertThrowIntoFetchResult)
if (result?.data) {
triggerNotification({
type: ToastNotificationType.Success,
title: `${cancel ? 'Cancelled adding email' : 'Deleted email'}`,
description: email.email
})
if (!hideToast) {
triggerNotification({
type: ToastNotificationType.Success,
title: 'Deleted email',
description: email.email
})
}
mixpanel.track('Email Deleted')
// If we're on the verify email page and there are no more unverified emails, redirect home
@@ -3,9 +3,10 @@ import {
approveWorkspaceJoinRequestMutation,
denyWorkspaceJoinRequestMutation
} from '~/lib/workspaces/graphql/mutations'
import type {
ApproveWorkspaceJoinRequestInput,
DenyWorkspaceJoinRequestInput
import {
type ApproveWorkspaceJoinRequestInput,
type DenyWorkspaceJoinRequestInput,
WorkspaceJoinRequestStatus
} from '~~/lib/common/generated/gql/graphql'
import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast'
import {
@@ -30,22 +31,11 @@ export const useWorkspaceJoinRequest = () => {
{ input },
{
update: (cache) => {
cache.evict({
id: getCacheId('WorkspaceJoinRequest', requestId)
})
modifyObjectField(
cache,
getCacheId('Workspace', input.workspaceId),
'adminWorkspacesJoinRequests',
({ helpers: { createUpdatedValue } }) => {
return createUpdatedValue(({ update }) => {
update('totalCount', (totalCount) => totalCount - 1)
})
},
{
autoEvictFiltered: true
}
getCacheId('WorkspaceJoinRequest', requestId),
'status',
() => WorkspaceJoinRequestStatus.Approved
)
}
}
@@ -76,22 +66,11 @@ export const useWorkspaceJoinRequest = () => {
{ input },
{
update: (cache) => {
cache.evict({
id: getCacheId('WorkspaceJoinRequest', requestId)
})
modifyObjectField(
cache,
getCacheId('Workspace', input.workspaceId),
'adminWorkspacesJoinRequests',
({ helpers: { createUpdatedValue } }) => {
return createUpdatedValue(({ update }) => {
update('totalCount', (totalCount) => totalCount - 1)
})
},
{
autoEvictFiltered: true
}
getCacheId('WorkspaceJoinRequest', requestId),
'status',
() => WorkspaceJoinRequestStatus.Denied
)
}
}
@@ -52,6 +52,10 @@ export const workspaceTeamFragment = graphql(`
}
adminWorkspacesJoinRequests {
totalCount
items {
status
id
}
}
...WorkspaceInvitedTeam_Workspace
}
File diff suppressed because one or more lines are too long
@@ -73,7 +73,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
const apollo = useApolloClientFromNuxt()
const resourceBuilder = () => SpeckleViewer.ViewerRoute.resourceBuilder()
if (['/streams', '/commits'].includes(path)) {
if (['/streams', '/commits', '/streams/', '/commits/'].includes(path)) {
return navigateTo(homeRoute)
}
+4 -1
View File
@@ -70,11 +70,14 @@ export default defineNuxtConfig({
datadogSite: '',
datadogService: '',
datadogEnv: '',
enableDirectPreviews: true,
ghostApiKey: ''
}
},
experimental: {
emitRouteChunkError: 'automatic-immediate'
},
alias: {
// Rewriting all lodash calls to lodash-es for proper tree-shaking & chunk splitting
// lodash: 'lodash-es'
+4 -4
View File
@@ -89,11 +89,11 @@
"@babel/preset-typescript": "^7.18.6",
"@datadog/datadog-ci": "^2.37.0",
"@eslint/config-inspector": "^0.4.10",
"@graphql-codegen/cli": "^5.0.3",
"@graphql-codegen/client-preset": "^4.5.0",
"@graphql-codegen/cli": "^5.0.5",
"@graphql-codegen/client-preset": "^4.6.4",
"@graphql-codegen/plugin-helpers": "^5.1.0",
"@graphql-codegen/typescript": "^4.1.1",
"@graphql-codegen/visitor-plugin-common": "5.5.0",
"@graphql-codegen/typescript": "^4.1.5",
"@graphql-codegen/visitor-plugin-common": "^5.7.1",
"@nuxt/devtools": "^1.7.0",
"@nuxt/eslint": "^1.1.0",
"@nuxt/image": "^1.8.1",
@@ -183,7 +183,13 @@ const actionsItems = computed<LayoutMenuItem[][]>(() => {
})
useHead({
title: projectName
title: projectName,
meta: [
{
name: 'robots',
content: 'noindex, nofollow'
}
]
})
const onInviteAccepted = async (params: { accepted: boolean }) => {
@@ -217,13 +217,14 @@ import {
type PaidWorkspacePlans
} from '~/lib/common/generated/gql/graphql'
import { useBillingActions } from '~/lib/billing/composables/actions'
import { pricingPlansConfig } from '~/lib/billing/helpers/constants'
import type { PaidWorkspacePlansOld } from '@speckle/shared'
import { Roles } from '@speckle/shared'
import { InformationCircleIcon } from '@heroicons/vue/24/outline'
import { isPaidPlan } from '@/lib/billing/helpers/types'
import { useMixpanel } from '~/lib/core/composables/mp'
import { guideBillingUrl } from '~/lib/common/helpers/route'
import { adminUpdateWorkspacePlanMutation } from '~/lib/billing/graphql/mutations'
import { WorkspaceOldPaidPlanPrices } from '~/lib/billing/helpers/constants'
graphql(`
fragment SettingsWorkspacesBilling_Workspace on Workspace {
@@ -280,15 +281,15 @@ const { billingPortalRedirect, redirectToCheckout } = useBillingActions()
const mixpanel = useMixpanel()
const { mutate: mutateWorkspacePlan } = useMutation(adminUpdateWorkspacePlanMutation)
const seatPrices = ref({
[WorkspacePlans.Starter]: pricingPlansConfig.plans[WorkspacePlans.Starter].cost,
[WorkspacePlans.Plus]: pricingPlansConfig.plans[WorkspacePlans.Plus].cost,
[WorkspacePlans.Business]: pricingPlansConfig.plans[WorkspacePlans.Business].cost
})
const selectedPlanName = ref<WorkspacePlans>()
const selectedPlanName = ref<PaidWorkspacePlansOld>()
const selectedPlanCycle = ref<BillingInterval>()
const isUpgradeDialogOpen = ref(false)
const seatPrices = computed(() => ({
[WorkspacePlans.Starter]: WorkspaceOldPaidPlanPrices[WorkspacePlans.Starter],
[WorkspacePlans.Plus]: WorkspaceOldPaidPlanPrices[WorkspacePlans.Plus],
[WorkspacePlans.Business]: WorkspaceOldPaidPlanPrices[WorkspacePlans.Business]
}))
const workspace = computed(() => workspaceResult.value?.workspaceBySlug)
const currentPlan = computed(() => workspace.value?.plan)
const subscription = computed(() => workspace.value?.subscription)
@@ -422,7 +423,10 @@ const showStatusBadge = computed(() => {
)
})
const onPlanSelected = (plan: { name: WorkspacePlans; cycle: BillingInterval }) => {
const onPlanSelected = (plan: {
name: PaidWorkspacePlansOld
cycle: BillingInterval
}) => {
const { name, cycle } = plan
if (!isPaidPlan(name) || !workspace.value?.id) return
@@ -244,11 +244,13 @@ const canDeleteWorkspace = computed(
!needsSsoLogin.value &&
(!isBillingIntegrationEnabled ||
!(
[
WorkspacePlanStatuses.Valid,
WorkspacePlanStatuses.PaymentFailed,
WorkspacePlanStatuses.CancelationScheduled
].includes(
(
[
WorkspacePlanStatuses.Valid,
WorkspacePlanStatuses.PaymentFailed,
WorkspacePlanStatuses.CancelationScheduled
] as string[]
).includes(
workspaceResult.value?.workspaceBySlug?.plan?.status as WorkspacePlanStatuses
) && isPaidPlan(workspaceResult.value?.workspaceBySlug?.plan?.name)
))
@@ -8,28 +8,7 @@
class="mb-6"
/>
<LayoutTabsHorizontal v-model:active-item="activeTab" :items="tabItems">
<template #default="{ activeItem }">
<SettingsWorkspacesMembersTable
v-if="activeItem.id === 'members'"
:workspace="workspace"
:workspace-slug="slug"
/>
<SettingsWorkspacesMembersGuestsTable
v-if="activeItem.id === 'guests'"
:workspace="workspace"
:workspace-slug="slug"
/>
<SettingsWorkspacesMembersInvitesTable
v-if="activeItem.id === 'invites'"
:workspace="workspace"
:workspace-slug="slug"
/>
<SettingsWorkspacesMembersJoinRequestsTable
v-if="activeItem.id === 'joinRequests'"
:workspace="workspace"
:workspace-slug="slug"
/>
</template>
<NuxtPage />
</LayoutTabsHorizontal>
</div>
</section>
@@ -42,7 +21,8 @@ import { graphql } from '~/lib/common/generated/gql'
import { settingsWorkspacesMembersQuery } from '~/lib/settings/graphql/queries'
import type { LayoutPageTabItem } from '~~/lib/layout/helpers/components'
import { useOnWorkspaceUpdated } from '~/lib/workspaces/composables/management'
import { WorkspaceJoinRequestStatus } from '~/lib/common/generated/gql/graphql'
import { WorkspaceJoinRequestStatus } from '~~/lib/common/generated/gql/graphql'
import { settingsWorkspaceRoutes } from '~/lib/common/helpers/route'
graphql(`
fragment SettingsWorkspacesMembers_Workspace on Workspace {
@@ -51,14 +31,19 @@ graphql(`
team {
items {
id
role
}
}
invitedTeam(filter: $invitesFilter) {
invitedTeam {
user {
id
}
}
adminWorkspacesJoinRequests(filter: $joinRequestsFilter) {
adminWorkspacesJoinRequests {
items {
id
status
}
totalCount
}
}
@@ -75,11 +60,9 @@ useHead({
const route = useRoute()
const slug = computed(() => (route.params.slug as string) || '')
const router = useRouter()
const { result } = useQuery(settingsWorkspacesMembersQuery, () => ({
slug: slug.value,
joinRequestsFilter: {
status: WorkspaceJoinRequestStatus.Pending
}
slug: slug.value
}))
const workspace = computed(() => result.value?.workspaceBySlug)
@@ -96,7 +79,10 @@ const guestCount = computed(
)
const invitedCount = computed(() => workspace.value?.invitedTeam?.length)
const joinRequestCount = computed(
() => workspace.value?.adminWorkspacesJoinRequests?.totalCount
() =>
workspace.value?.adminWorkspacesJoinRequests?.items.filter(
(item) => item.status === WorkspaceJoinRequestStatus.Pending
).length
)
const tabItems = computed<LayoutPageTabItem[]>(() => [
{ title: 'Members', id: 'members', count: memberCount.value },
@@ -117,7 +103,32 @@ const tabItems = computed<LayoutPageTabItem[]>(() => [
}
])
const activeTab = ref(tabItems.value[0])
const activeTab = computed({
get: () => {
const path = route.path
if (path.includes('/members/guests')) return tabItems.value[1]
if (path.includes('/members/invites')) return tabItems.value[2]
if (path.includes('/members/requests')) return tabItems.value[3]
if (path.includes('/members')) return tabItems.value[0]
return tabItems.value[0]
},
set: (val: LayoutPageTabItem) => {
switch (val.id) {
case 'members':
router.push(settingsWorkspaceRoutes.members.route(slug.value))
break
case 'guests':
router.push(settingsWorkspaceRoutes.membersGuests.route(slug.value))
break
case 'invites':
router.push(settingsWorkspaceRoutes.membersInvites.route(slug.value))
break
case 'joinRequests':
router.push(settingsWorkspaceRoutes.membersRequests.route(slug.value))
break
}
}
})
useOnWorkspaceUpdated({ workspaceSlug: slug })
</script>
@@ -0,0 +1,17 @@
<template>
<SettingsWorkspacesMembersGuestsTable :workspace="workspace" :workspace-slug="slug" />
</template>
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable'
import { settingsWorkspacesMembersGuestsQuery } from '~/lib/settings/graphql/queries'
const route = useRoute()
const slug = computed(() => (route.params.slug as string) || '')
const { result } = useQuery(settingsWorkspacesMembersGuestsQuery, () => ({
slug: slug.value
}))
const workspace = computed(() => result.value?.workspaceBySlug)
</script>
@@ -0,0 +1,15 @@
<template>
<SettingsWorkspacesMembersTable :workspace="workspace" :workspace-slug="slug" />
</template>
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable'
import { settingsWorkspacesMembersTableQuery } from '~/lib/settings/graphql/queries'
const route = useRoute()
const slug = computed(() => (route.params.slug as string) || '')
const { result } = useQuery(settingsWorkspacesMembersTableQuery, () => ({
slug: slug.value
}))
const workspace = computed(() => result.value?.workspaceBySlug)
</script>
@@ -0,0 +1,20 @@
<template>
<SettingsWorkspacesMembersInvitesTable
:workspace="workspace"
:workspace-slug="slug"
/>
</template>
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable'
import { settingsWorkspacesMembersInvitesQuery } from '~/lib/settings/graphql/queries'
const route = useRoute()
const slug = computed(() => (route.params.slug as string) || '')
const { result } = useQuery(settingsWorkspacesMembersInvitesQuery, () => ({
slug: slug.value
}))
const workspace = computed(() => result.value?.workspaceBySlug)
</script>
@@ -0,0 +1,20 @@
<template>
<SettingsWorkspacesMembersJoinRequestsTable
:workspace="workspace"
:workspace-slug="slug"
/>
</template>
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable'
import { settingsWorkspacesMembersRequestsQuery } from '~/lib/settings/graphql/queries'
const route = useRoute()
const slug = computed(() => (route.params.slug as string) || '')
const { result } = useQuery(settingsWorkspacesMembersRequestsQuery, () => ({
slug: slug.value
}))
const workspace = computed(() => result.value?.workspaceBySlug)
</script>
+1 -1
View File
@@ -73,7 +73,7 @@
<SettingsUserEmailDeleteDialog
v-model:open="showDeleteDialog"
:email="currentEmail"
cancel
is-adding
/>
</div>
</HeaderWithEmptyPage>
+8 -1
View File
@@ -1,3 +1,4 @@
import { collectLongTrace } from '@speckle/shared'
import { omit } from 'lodash-es'
import type { SetRequired } from 'type-fest'
import { useReadUserId } from '~/lib/auth/composables/activeUser'
@@ -170,6 +171,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
}) => {
if (!args.length) return
const stack = collectLongTrace()
const isError = ['error', 'fatal'].includes(level)
const isImportant = !!otherData?.important
if (!isError && !isImportant) return
@@ -189,6 +191,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
properties: {
mainSeqErrorMessage: errorMessage, // weird name to avoid collision with otherData
extraData: nonObjectOtherData,
stack,
firstError,
firstString,
...otherData,
...collectCoreInfo()
},
@@ -202,6 +207,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
properties: {
extraData: nonObjectOtherData,
firstError,
stack,
...otherData,
...collectCoreInfo()
}
@@ -318,7 +324,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
if (!import.meta.server) {
nuxtApp.hook('app:mounted', () => {
logger.info('App mounted in the client', {
important: true
important: true,
speckleServerVersion
})
})
}
+1 -3
View File
@@ -22,9 +22,7 @@
"imports": {
"#lodash": {
"require": "lodash",
"import": "lodash-es",
"node": "lodash",
"default": "lodash-es"
"import": "lodash-es"
}
},
"homepage": "https://speckle.systems",
+6 -8
View File
@@ -42,7 +42,7 @@
"@types/lodash-es": "^4.17.12",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"@typescript-eslint/parser": "^7.12.0",
"@vitest/coverage-v8": "^1.6.0",
"@vitest/coverage-v8": "^3.0.7",
"eslint": "^9.4.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.3.2",
@@ -54,21 +54,19 @@
"exports": {
".": {
"import": {
"default": "./dist/objectsender.js",
"types": "./dist/index.d.ts"
"types": "./dist/index.d.ts",
"default": "./dist/objectsender.js"
},
"require": {
"default": "./dist/objectsender.cjs",
"types": "./dist/index.d.ts"
"types": "./dist/index.d.ts",
"default": "./dist/objectsender.cjs"
}
}
},
"imports": {
"#lodash": {
"require": "lodash",
"import": "lodash-es",
"node": "lodash",
"default": "lodash-es"
"import": "lodash-es"
}
}
}
+1 -3
View File
@@ -1,3 +1 @@
{
"vitest.disableWorkspaceWarning": true
}
{}
+14 -135
View File
@@ -31,8 +31,7 @@ import { expressMiddleware } from '@apollo/server/express4'
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default'
import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting'
import { ApolloServerPluginUsageReportingDisabled } from '@apollo/server/plugin/disabled'
import type { ConnectionContext, ExecutionParams } from 'subscriptions-transport-ws'
import type { ConnectionContext } from 'subscriptions-transport-ws'
import { SubscriptionServer } from 'subscriptions-transport-ws'
import { execute, subscribe } from 'graphql'
@@ -67,15 +66,12 @@ import {
requestBodyParsingMiddlewareFactory,
setContentSecurityPolicyHeaderMiddleware
} from '@/modules/shared/middleware'
import { GraphQLError } from 'graphql'
import { redactSensitiveVariables } from '@/logging/loggingHelper'
import { buildMocksConfig } from '@/modules/mocks'
import { defaultErrorHandler } from '@/modules/core/rest/defaultErrorHandler'
import { migrateDbToLatest } from '@/db/migrations'
import { statusCodePlugin } from '@/modules/core/graph/plugins/statusCode'
import { BadRequestError, BaseError, ForbiddenError } from '@/modules/shared/errors'
import { BadRequestError, ForbiddenError } from '@/modules/shared/errors'
import { loggingPluginFactory } from '@/modules/core/graph/plugins/logging'
import { shouldLogAsInfoLevel } from '@/logging/graphqlError'
import { getUserFactory } from '@/modules/core/repositories/users'
import { initFactory as healthchecksInitFactory } from '@/healthchecks'
import type { ReadinessHandler } from '@/healthchecks/types'
@@ -88,62 +84,16 @@ import {
initiateRequestContextMiddleware
} from '@/logging/requestContext'
import { randomUUID } from 'crypto'
import { onOperationHandlerFactory } from '@/logging/apolloSubscriptions'
import { initApolloSubscriptionMonitoring } from './logging/apolloSubscriptionMonitoring'
const GRAPHQL_PATH = '/graphql'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type SubscriptionResponse = { errors?: GraphQLError[]; data?: any }
/**
* In mocked Ws connections, request will be undefined
*/
type PossiblyMockedConnectionContext = SetOptional<ConnectionContext, 'request'>
function logSubscriptionOperation(params: {
ctx: GraphQLContext
execParams: ExecutionParams
error?: Error
response?: SubscriptionResponse
}) {
const { error, response, ctx, execParams } = params
const userId = ctx.userId
if (!error && !response) return
const reqCtx = getRequestContext()
const logger = ctx.log.child({
graphql_query: execParams.query.toString(),
graphql_variables: redactSensitiveVariables(execParams.variables),
graphql_operation_name: execParams.operationName,
graphql_operation_type: 'subscription',
userId,
...(reqCtx
? {
req: { id: reqCtx.requestId },
dbMetrics: reqCtx.dbMetrics
}
: {})
})
const errMsg = 'GQL subscription event {graphql_operation_name} errored'
const errors = response?.errors || (error ? [error] : [])
if (errors.length) {
for (const error of errors) {
let errorLogger = logger
if (error instanceof BaseError) {
errorLogger = errorLogger.child({ ...error.info() })
}
if (shouldLogAsInfoLevel(error)) {
errorLogger.info({ err: error }, errMsg)
} else {
errorLogger.error({ err: error }, errMsg)
}
}
} else if (response?.data) {
logger.info('GQL subscription event {graphql_operation_name} emitted')
}
}
const isWsServer = (server: http.Server | MockWsServer): server is MockWsServer => {
return 'on' in server && 'clients' in server
}
@@ -163,35 +113,12 @@ export function buildApolloSubscriptionServer(
const wsServer = mockServer ? (mockServer as unknown as ws.Server) : undefined
const schema = ModulesSetup.graphSchema()
// Init metrics
prometheusClient.register.removeSingleMetric('speckle_server_apollo_connect')
const metricConnectCounter = new prometheusClient.Counter({
name: 'speckle_server_apollo_connect',
help: 'Number of connects'
})
prometheusClient.register.removeSingleMetric('speckle_server_apollo_clients')
const metricConnectedClients = new prometheusClient.Gauge({
name: 'speckle_server_apollo_clients',
help: 'Number of currently connected clients'
})
prometheusClient.register.removeSingleMetric(
'speckle_server_apollo_graphql_total_subscription_operations'
)
const metricSubscriptionTotalOperations = new prometheusClient.Counter({
name: 'speckle_server_apollo_graphql_total_subscription_operations',
help: 'Number of total subscription operations served by this instance',
labelNames: ['subscriptionType'] as const
})
prometheusClient.register.removeSingleMetric(
'speckle_server_apollo_graphql_total_subscription_responses'
)
const metricSubscriptionTotalResponses = new prometheusClient.Counter({
name: 'speckle_server_apollo_graphql_total_subscription_responses',
help: 'Number of total subscription responses served by this instance',
labelNames: ['subscriptionType', 'status'] as const
})
const {
metricConnectCounter,
metricConnectedClients,
metricSubscriptionTotalOperations,
metricSubscriptionTotalResponses
} = initApolloSubscriptionMonitoring()
const getHeaders = (params: {
connContext?: PossiblyMockedConnectionContext
@@ -302,58 +229,10 @@ export function buildApolloSubscriptionServer(
)
metricConnectedClients.dec()
},
onOperation: (...params: [() => void, ExecutionParams]) => {
// kinda hacky, but we're using this as an "subscription event emitted"
// callback to clear subscription connection dataloaders to avoid stale cache
const baseParams = params[1]
metricSubscriptionTotalOperations.inc({
subscriptionType: baseParams.operationName // FIXME: operationName can be empty
})
const ctx = baseParams.context as GraphQLContext
const reqCtx = getRequestContext()
if (reqCtx) {
// Reset db metrics for each event
reqCtx.dbMetrics.totalCount = 0
reqCtx.dbMetrics.totalDuration = 0
}
const logger = ctx.log || subscriptionLogger
logger.info(
{
graphql_operation_name: baseParams.operationName,
userId: baseParams.context.userId,
graphql_query: baseParams.query.toString(),
graphql_variables: redactSensitiveVariables(baseParams.variables),
graphql_operation_type: 'subscription',
...(reqCtx ? { req: { id: reqCtx.requestId } } : {})
},
'Subscription event fired for {graphql_operation_name}'
)
baseParams.formatResponse = (val: SubscriptionResponse) => {
ctx.loaders.clearAll()
logSubscriptionOperation({ ctx, execParams: baseParams, response: val })
metricSubscriptionTotalResponses.inc({
subscriptionType: baseParams.operationName,
status: 'success'
})
return val
}
baseParams.formatError = (e: Error) => {
ctx.loaders.clearAll()
logSubscriptionOperation({ ctx, execParams: baseParams, error: e })
metricSubscriptionTotalResponses.inc({
subscriptionType: baseParams.operationName,
status: 'error'
})
return e
}
return baseParams
},
onOperation: onOperationHandlerFactory({
metricSubscriptionTotalOperations,
metricSubscriptionTotalResponses
}),
keepAlive: 30000 //milliseconds. Loadbalancers may close the connection after inactivity. e.g. nginx default is 60000ms.
},
wsServer || {
@@ -173,11 +173,17 @@ type UserSearchResultCollection {
items: [LimitedUser!]!
}
input OnboardingCompletionInput {
role: String
plans: [String!]
source: String
}
type ActiveUserMutations {
"""
Mark onboarding as complete
"""
finishOnboarding: Boolean!
finishOnboarding(input: OnboardingCompletionInput): Boolean!
"""
Edit a user's profile
@@ -1,7 +1,3 @@
extend type Query {
workspacePricingPlans: JSONObject!
}
extend type WorkspaceMutations {
billing: WorkspaceBillingMutations! @hasScope(scope: "workspace:billing")
}
@@ -66,6 +62,8 @@ enum WorkspacePlans {
starterInvoiced
plusInvoiced
businessInvoiced
team
pro
}
enum WorkspacePlanStatuses {
@@ -23,3 +23,32 @@ type WorkspaceJoinRequestMutations {
@hasScope(scope: "workspace:update")
@hasWorkspaceRole(role: ADMIN)
}
type LimitedWorkspaceJoinRequest {
id: String!
workspace: LimitedWorkspace!
user: LimitedUser!
status: WorkspaceJoinRequestStatus!
createdAt: DateTime!
}
type LimitedWorkspaceJoinRequestCollection {
totalCount: Int!
cursor: String
items: [LimitedWorkspaceJoinRequest!]!
}
input WorkspaceJoinRequestFilter {
status: WorkspaceJoinRequestStatus
}
extend type User {
workspaceJoinRequests(
filter: WorkspaceJoinRequestFilter
cursor: String
limit: Int! = 25
): LimitedWorkspaceJoinRequestCollection
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "workspace:read")
@isOwner
}
@@ -367,6 +367,20 @@ type LimitedWorkspace {
Optional base64 encoded workspace logo image
"""
logo: String
"""
Workspace members visible to people with verified email domain
"""
team(cursor: String, limit: Int! = 25): DiscoverableWorkspaceCollaboratorCollection
}
type DiscoverableWorkspaceCollaboratorCollection {
totalCount: Int!
cursor: String
items: [DiscoverableWorkspaceCollaborator!]!
}
type DiscoverableWorkspaceCollaborator {
avatar: String
}
type WorkspaceDomain {
+5
View File
@@ -8,6 +8,7 @@ generates:
- 'typescript'
- 'typescript-resolvers'
config:
enumsAsConst: true
contextType: '@/modules/shared/helpers/typeHelper#GraphQLContext'
mappers:
Stream: '@/modules/core/helpers/graphTypes#StreamGraphQLReturn'
@@ -66,6 +67,7 @@ generates:
PendingWorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#PendingWorkspaceCollaboratorGraphQLReturn'
WorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceCollaboratorGraphQLReturn'
WorkspaceJoinRequest: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestGraphQLReturn'
LimitedWorkspaceJoinRequest: '@/modules/workspacesCore/helpers/graphTypes#LimitedWorkspaceJoinRequestGraphQLReturn'
Webhook: '@/modules/webhooks/helpers/graphTypes#WebhookGraphQLReturn'
SmartTextEditorValue: '@/modules/core/services/richTextEditorService#SmartTextEditorValueGraphQLReturn'
BlobMetadata: '@/modules/blobstorage/domain/types#BlobStorageItem'
@@ -88,6 +90,7 @@ generates:
documents:
- 'modules/cross-server-sync/**/*.{js,ts}'
config:
enumsAsConst: true
scalars:
JSONObject: Record<string, unknown>
DateTime: string
@@ -100,10 +103,12 @@ generates:
- 'test/graphql/*.{js,ts}'
- 'modules/**/tests/helpers/graphql.ts'
config:
enumsAsConst: true
scalars:
JSONObject: Record<string, unknown>
DateTime: string
config:
enumsAsConst: true
scalars:
JSONObject: Record<string, unknown>
DateTime: Date
@@ -0,0 +1,59 @@
import prometheusClient from 'prom-client'
let apolloSubscriptionMonitoringIsInitialized = false
let metricConnectCounter: prometheusClient.Counter<string>
let metricConnectedClients: prometheusClient.Gauge<string>
let metricSubscriptionTotalOperations: prometheusClient.Counter<'subscriptionType'>
let metricSubscriptionTotalResponses: prometheusClient.Counter<
'subscriptionType' | 'status'
>
export const initApolloSubscriptionMonitoring = () => {
if (apolloSubscriptionMonitoringIsInitialized)
return {
metricConnectCounter,
metricConnectedClients,
metricSubscriptionTotalOperations,
metricSubscriptionTotalResponses
}
// Init metrics
prometheusClient.register.removeSingleMetric('speckle_server_apollo_connect')
metricConnectCounter = new prometheusClient.Counter({
name: 'speckle_server_apollo_connect',
help: 'Number of connects'
})
prometheusClient.register.removeSingleMetric('speckle_server_apollo_clients')
metricConnectedClients = new prometheusClient.Gauge({
name: 'speckle_server_apollo_clients',
help: 'Number of currently connected clients'
})
prometheusClient.register.removeSingleMetric(
'speckle_server_apollo_graphql_total_subscription_operations'
)
metricSubscriptionTotalOperations = new prometheusClient.Counter({
name: 'speckle_server_apollo_graphql_total_subscription_operations',
help: 'Number of total subscription operations served by this instance',
labelNames: ['subscriptionType'] as const
})
prometheusClient.register.removeSingleMetric(
'speckle_server_apollo_graphql_total_subscription_responses'
)
metricSubscriptionTotalResponses = new prometheusClient.Counter({
name: 'speckle_server_apollo_graphql_total_subscription_responses',
help: 'Number of total subscription responses served by this instance',
labelNames: ['subscriptionType', 'status'] as const
})
apolloSubscriptionMonitoringIsInitialized = true
return {
metricConnectCounter,
metricConnectedClients,
metricSubscriptionTotalOperations,
metricSubscriptionTotalResponses
}
}
@@ -0,0 +1,119 @@
/* eslint-disable camelcase */
import type { GraphQLContext } from '@/modules/shared/helpers/typeHelper'
import type { ExecutionParams } from 'subscriptions-transport-ws'
import { shouldLogAsInfoLevel, shouldLogAsWarnLevel } from '@/logging/graphqlError'
import { BaseError } from '@/modules/shared/errors'
import { GraphQLError } from 'graphql'
import { redactSensitiveVariables } from '@/logging/loggingHelper'
import type { Counter } from 'prom-client'
import { getRequestContext } from '@/logging/requestContext'
import { subscriptionLogger } from '@/logging/logging'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type SubscriptionResponse = { errors?: GraphQLError[]; data?: any }
export const onOperationHandlerFactory = (deps: {
metricSubscriptionTotalOperations: Counter<'subscriptionType' | 'status'>
metricSubscriptionTotalResponses: Counter<'subscriptionType' | 'status'>
}) => {
const { metricSubscriptionTotalOperations, metricSubscriptionTotalResponses } = deps
return (...params: [() => void, ExecutionParams]) => {
// kinda hacky, but we're using this as an "subscription event emitted"
// callback to clear subscription connection dataloaders to avoid stale cache
const baseParams = params[1]
metricSubscriptionTotalOperations.inc({
subscriptionType: baseParams.operationName // FIXME: operationName can be empty
})
const ctx = baseParams.context as GraphQLContext
const reqCtx = getRequestContext()
if (reqCtx) {
// Reset db metrics for each event
reqCtx.dbMetrics.totalCount = 0
reqCtx.dbMetrics.totalDuration = 0
}
const logger = ctx.log || subscriptionLogger
logger.info(
{
graphql_operation_name: baseParams.operationName,
userId: baseParams.context.userId,
graphql_query: baseParams.query.toString(),
graphql_variables: redactSensitiveVariables(baseParams.variables),
graphql_operation_type: 'subscription',
...(reqCtx ? { req: { id: reqCtx.requestId } } : {})
},
'Subscription event fired for {graphql_operation_name}'
)
baseParams.formatResponse = (val: SubscriptionResponse) => {
ctx.loaders.clearAll()
logSubscriptionOperation({ ctx, execParams: baseParams, response: val })
metricSubscriptionTotalResponses.inc({
subscriptionType: baseParams.operationName,
status: 'success'
})
return val
}
baseParams.formatError = (e: Error) => {
ctx.loaders.clearAll()
logSubscriptionOperation({ ctx, execParams: baseParams, error: e })
metricSubscriptionTotalResponses.inc({
subscriptionType: baseParams.operationName,
status: 'error'
})
return e
}
return baseParams
}
}
export function logSubscriptionOperation(params: {
ctx: GraphQLContext
execParams: ExecutionParams
error?: Error
response?: SubscriptionResponse
}) {
const { error, response, ctx, execParams } = params
const userId = ctx.userId
if (!error && !response) return
const reqCtx = getRequestContext()
const logger = ctx.log.child({
graphql_query: execParams.query.toString(),
graphql_variables: redactSensitiveVariables(execParams.variables),
graphql_operation_name: execParams.operationName,
graphql_operation_type: 'subscription',
userId,
...(reqCtx
? {
req: { id: reqCtx.requestId },
dbMetrics: reqCtx.dbMetrics
}
: {})
})
const errMsg = 'GQL subscription event {graphql_operation_name} errored'
const errors = response?.errors || (error ? [error] : [])
if (errors.length) {
for (const error of errors) {
let errorLogger = logger
if (error instanceof BaseError) {
errorLogger = errorLogger.child({ ...error.info() })
}
if (shouldLogAsInfoLevel(error)) {
errorLogger.info({ err: error }, errMsg)
} else if (shouldLogAsWarnLevel(error)) {
errorLogger.warn({ err: error }, errMsg)
} else {
errorLogger.error({ err: error }, errMsg)
}
}
} else if (response?.data) {
logger.info('GQL subscription event {graphql_operation_name} emitted')
}
}
+23
View File
@@ -6,6 +6,7 @@ import { GraphQLError } from 'graphql'
export const shouldLogAsInfoLevel = (err: unknown): boolean => {
if (err instanceof GraphQLError) {
if (isUserGraphqlError(err)) return true
if (err.message === 'Connection is closed.') return true
if (!!err.cause && shouldLogAsInfoLevel(err.cause)) return true
if (!!err.originalError && shouldLogAsInfoLevel(err.originalError)) return true
}
@@ -20,3 +21,25 @@ export const shouldLogAsInfoLevel = (err: unknown): boolean => {
return err instanceof ApolloError
}
export const shouldLogAsWarnLevel = (err: unknown): boolean => {
if (!(err instanceof GraphQLError)) return false
if (err.message.startsWith('Cannot return null for non-nullable field')) return true
if (
/Variable\s"(\$[^\s]+)"\sof non-null type\s"([^\s]+)"\smust not be null\./.test(
err.message
)
)
return true
if (
/Cannot query field\s"([^\s]+)"\son type\s"([^\s]+)"\. Did you mean\s"([^\s]+)"\?/.test(
err.message
)
)
return true
if (!!err.cause && shouldLogAsWarnLevel(err.cause)) return true
if (!!err.originalError && shouldLogAsWarnLevel(err.originalError)) return true
return false
}
@@ -72,6 +72,13 @@ export const finalizeAuthMiddlewareFactory =
throw new ForbiddenError('Cannot finalize auth - No user attached to session')
}
if (res.headersSent) {
req.log.info(
'Headers already sent, probably by Passport if prior steps fail; skipping auth finalization'
)
return
}
const ac = await deps.createAuthorizationCode({
appId: 'spklwebapp',
userId: req.user.id,
@@ -4,6 +4,7 @@ import { md5 } from '@/modules/shared/helpers/cryptoHelper'
import { getMailchimpConfig } from '@/modules/shared/helpers/envHelper'
import { UserRecord } from '@/modules/core/helpers/types'
import { MisconfiguredEnvironmentError } from '@/modules/shared/errors'
import { OnboardingCompletionInput } from '@/modules/core/graph/generated/graphql'
let mailchimpInitialized = false
@@ -63,4 +64,55 @@ async function triggerMailchimpCustomerJourney(
})
}
export { addToMailchimpAudience, triggerMailchimpCustomerJourney }
async function updateMailchimpMemberTags(
user: UserRecord,
listId: string,
onboardingData: OnboardingCompletionInput
) {
initializeMailchimp()
const subscriberHash = md5(user.email.toLowerCase())
// Check if user is already in audience (meaning they consented to marketing emails)
try {
await mailchimp.lists.getListMember(listId, subscriberHash)
} catch {
throw new Error(
`User ${user.email} not found in Mailchimp audience. They should have been added during registration.`
)
}
const tags: { name: string; status: 'active' | 'inactive' }[] = []
if (onboardingData.role) {
tags.push({
name: `Role: ${onboardingData.role}`,
status: 'active'
})
}
if (onboardingData.plans?.length) {
onboardingData.plans.forEach((plan) => {
tags.push({
name: `Use case: ${plan}`,
status: 'active'
})
})
}
if (onboardingData.source) {
tags.push({
name: `Source: ${onboardingData.source}`,
status: 'active'
})
}
await mailchimp.lists.updateListMemberTags(listId, subscriberHash, {
tags
})
}
export {
addToMailchimpAudience,
triggerMailchimpCustomerJourney,
updateMailchimpMemberTags
}
@@ -1,11 +1,10 @@
import passport, { Strategy, AuthenticateOptions } from 'passport'
import { logger } from '@/logging/logging'
import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
import {
UnverifiedEmailSSOLoginError,
UserInputError
} from '@/modules/core/errors/userinput'
import type { Handler } from 'express'
import type { Request, Response, NextFunction, RequestHandler } from 'express'
import { Optional } from '@speckle/shared'
import { get, isArray, isObjectLike, isString } from 'lodash'
import { PassportAuthenticateHandlerBuilder } from '@/modules/auth/domain/operations'
@@ -27,6 +26,48 @@ const resolveInfoMessage = (
return null
}
export const passportAuthenticationCallbackFactory =
(context: {
strategy: Strategy | string
req: Request
res: Response
next: NextFunction
}) =>
(
err: unknown,
user: Optional<Express.User>,
info: Optional<string | Record<string, unknown> | Array<string | undefined>>
) => {
const { strategy, req, res, next } = context
if (err && !(err instanceof UserInputError))
req.log.error({ err, strategy }, 'Authentication error for strategy "{strategy}"')
if (!user) {
const infoMsg = resolveInfoMessage(info)
const errMsg = err instanceof UserInputError ? err.message : null
const finalMessage =
infoMsg ||
errMsg ||
(err
? 'An issue occurred during authentication, contact server admins'
: 'Failed to authenticate, contact server admins')
let errPath = `/error?message=${finalMessage}`
if (err instanceof UnverifiedEmailSSOLoginError) {
const email = err.info()?.email || ''
errPath = `/error-email-verify?email=${email}`
}
res.redirect(new URL(errPath, getFrontendOrigin()).toString())
return
}
if (err && !(err instanceof UserInputError)) return next(err)
req.user = user
next()
}
/**
* Wrapper for passport.authenticate that handles success & failure scenarios correctly
* (passport.authenticate() by default doesn't, so don't use it)
@@ -36,42 +77,12 @@ export const passportAuthenticateHandlerBuilderFactory =
(
strategy: Strategy | string,
options: Optional<AuthenticateOptions> = undefined
): Handler => {
): RequestHandler => {
return (req, res, next) => {
passport.authenticate(
strategy,
options || {},
// Not sure why types aren't automatically picked up
(
err: unknown,
user: Optional<Express.User>,
info: Optional<string | Record<string, unknown> | Array<string | undefined>>
) => {
if (err && !(err instanceof UserInputError)) logger.error(err)
if (!user) {
const infoMsg = resolveInfoMessage(info)
const errMsg = err instanceof UserInputError ? err.message : null
const finalMessage =
infoMsg ||
errMsg ||
(err
? 'An issue occurred during authentication, contact server admins'
: 'Failed to authenticate, contact server admins')
let errPath = `/error?message=${finalMessage}`
if (err instanceof UnverifiedEmailSSOLoginError) {
const email = err.info()?.email || ''
errPath = `/error-email-verify?email=${email}`
}
res.redirect(new URL(errPath, getFrontendOrigin()).toString())
}
req.user = user
next()
}
passportAuthenticationCallbackFactory({ strategy, req, res, next })
)(req, res, next)
}
}
@@ -199,14 +199,15 @@ const azureAdStrategyBuilderFactory =
case UnverifiedEmailSSOLoginError:
case InviteNotFoundError:
logger.info(
{ e },
{ err: e },
'User input error during Entra ID authentication callback.'
)
break
default:
logger.error(e, 'Error during Entra ID authentication callback.')
}
return next()
//skip remaining route handlers and go to error handler
return next(e)
}
},
finalizeAuthMiddleware
@@ -163,12 +163,13 @@ const githubStrategyBuilderFactory =
case UserInputError:
case InviteNotFoundError:
case UnverifiedEmailSSOLoginError:
logger.info(err)
return done(null, false, { message: e.message })
logger.info({ err: e }, 'Auth error for GitHub strategy')
// note; passportjs suggests that err should be null for user input errors.
// However, we are relying on the error being passed to `passportAuthenticationCallbackFactory` and handling it there
return done(e, false, { message: e.message })
default:
logger.error(err)
// Only when the server is operating abnormally should err be set, to indicate an internal error.
return done(err, false, { message: e.message })
logger.error({ err: e }, 'Auth error for GitHub strategy')
return done(e, false, { message: e.message })
}
}
}
@@ -146,11 +146,12 @@ const googleStrategyBuilderFactory =
case UnverifiedEmailSSOLoginError:
case UserInputError:
case InviteNotFoundError:
logger.info({ err: e })
return done(null, false, { message: e.message })
logger.info({ err: e }, 'Auth error for Google strategy')
// note; passportjs suggests that err should be null for user input errors.
// However, we are relying on the error being passed to `passportAuthenticationCallbackFactory` and handling it there
return done(e, false, { message: e.message })
default:
logger.error({ err: e })
// Only when the server is operating abnormally should err be set, to indicate an internal error.
logger.error({ err: e }, 'Auth error for Google strategy')
return done(e, false, { message: e.message })
}
}
@@ -5,8 +5,11 @@ import {
} from '@/modules/core/services/ratelimiter'
import { getIpFromRequest } from '@/modules/shared/utils/ip'
import { InviteNotFoundError } from '@/modules/serverinvites/errors'
import { UserInputError, PasswordTooShortError } from '@/modules/core/errors/userinput'
import {
UserInputError,
PasswordTooShortError,
BlockedEmailDomainError
} from '@/modules/core/errors/userinput'
import { ServerInviteResourceType } from '@/modules/serverinvites/domain/constants'
import { getResourceTypeRole } from '@/modules/serverinvites/helpers/core'
import { AuthStrategyMetadata, AuthStrategyBuilder } from '@/modules/auth/helpers/types'
@@ -117,7 +120,7 @@ const localStrategyBuilderFactory =
invite = await deps.validateServerInvite(user.email, req.session.token)
}
// 3. at this point we know, that we have one of these cases:
// 3.. at this point we know, that we have one of these cases:
// * the server is invite only and the user has a valid invite
// * the server public and the user has a valid invite
// * the server public and the user doesn't have an invite
@@ -155,6 +158,7 @@ const localStrategyBuilderFactory =
case PasswordTooShortError:
case UserInputError:
case InviteNotFoundError:
case BlockedEmailDomainError:
req.log.info({ err }, 'Error while registering.')
return res.status(400).send({ err: e.message })
default:
@@ -161,11 +161,12 @@ const oidcStrategyBuilderFactory =
case UnverifiedEmailSSOLoginError:
case UserInputError:
case InviteNotFoundError:
logger.info({ err: e })
return done(null, undefined)
logger.info({ err: e }, 'Auth error for OIDC strategy')
// note; passportjs suggests that err should be null for user input errors.
// However, we are relying on the error being passed to `passportAuthenticationCallbackFactory` and handling it there
return done(e, undefined)
default:
logger.error({ err: e })
// Only when the server is operating abnormally should err be set, to indicate an internal error.
logger.error({ err: e }, 'Auth error for OIDC strategy')
return done(e, undefined)
}
}
@@ -130,7 +130,7 @@ describe('GraphQL @apps-api', () => {
;({ sendRequest } = await initializeTestServer(ctx))
testUser = {
name: 'Dimitrie Stefanescu',
email: 'didimitrie@gmail.com',
email: 'didimitrie@example.org',
password: 'wtfwtfwtf'
}
@@ -158,7 +158,7 @@ const validateToken = validateTokenFactory({
describe('Services @apps-services', () => {
const actor = {
name: 'Dimitrie Stefanescu',
email: 'didimitrie@gmail.com',
email: 'didimitrie@example.org',
password: 'wtfwtfwtf'
}
@@ -495,7 +495,7 @@ describe('Services @apps-services', () => {
})
const secondUser = {
name: 'Dimitrie Stefanescu',
email: 'didimitrie.wow@gmail.com',
email: 'didimitrie.wow@example.org',
password: 'wtfwtfwtf'
}
@@ -1,77 +1,62 @@
const crs = require('crypto-random-string')
const chai = require('chai')
const request = require('supertest')
const { TIME } = require('@speckle/shared')
const { RATE_LIMITERS, createConsumer } = require('@/modules/core/services/ratelimiter')
const { beforeEachContext, initializeTestServer } = require('@/test/hooks')
const { createStreamInviteDirectly } = require('@/test/speckle-helpers/inviteHelper')
const { RateLimiterMemory } = require('rate-limiter-flexible')
const {
import crs from 'crypto-random-string'
import chai from 'chai'
import request from 'supertest'
import httpMocks from 'node-mocks-http'
import { TIME } from '@speckle/shared'
import { RATE_LIMITERS, createConsumer } from '@/modules/core/services/ratelimiter'
import { beforeEachContext, initializeTestServer } from '@/test/hooks'
import { createStreamInviteDirectly } from '@/test/speckle-helpers/inviteHelper'
import { RateLimiterMemory } from 'rate-limiter-flexible'
import {
findInviteFactory,
findUserByTargetFactory,
insertInviteAndDeleteOldFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} = require('@/modules/serverinvites/repositories/serverInvites')
const { db } = require('@/db/knex')
const {
} from '@/modules/serverinvites/repositories/serverInvites'
import { db } from '@/db/knex'
import {
legacyCreateStreamFactory,
createStreamReturnRecordFactory
} = require('@/modules/core/services/streams/management')
const {
inviteUsersToProjectFactory
} = require('@/modules/serverinvites/services/projectInviteManagement')
const {
createAndSendInviteFactory
} = require('@/modules/serverinvites/services/creation')
const {
collectAndValidateCoreTargetsFactory
} = require('@/modules/serverinvites/services/coreResourceCollection')
const {
} from '@/modules/core/services/streams/management'
import { inviteUsersToProjectFactory } from '@/modules/serverinvites/services/projectInviteManagement'
import { createAndSendInviteFactory } from '@/modules/serverinvites/services/creation'
import { collectAndValidateCoreTargetsFactory } from '@/modules/serverinvites/services/coreResourceCollection'
import {
getStreamFactory,
createStreamFactory
} = require('@/modules/core/repositories/streams')
const {
buildCoreInviteEmailContentsFactory
} = require('@/modules/serverinvites/services/coreEmailContents')
const { getEventBus } = require('@/modules/shared/services/eventBus')
const { createBranchFactory } = require('@/modules/core/repositories/branches')
const {
} from '@/modules/core/repositories/streams'
import { buildCoreInviteEmailContentsFactory } from '@/modules/serverinvites/services/coreEmailContents'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { createBranchFactory } from '@/modules/core/repositories/branches'
import {
getUsersFactory,
getUserFactory,
storeUserFactory,
countAdminUsersFactory,
storeUserAclFactory,
legacyGetUserByEmailFactory
} = require('@/modules/core/repositories/users')
const {
} from '@/modules/core/repositories/users'
import {
findEmailFactory,
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory
} = require('@/modules/core/repositories/userEmails')
const {
requestNewEmailVerificationFactory
} = require('@/modules/emails/services/verification/request')
const {
deleteOldAndInsertNewVerificationFactory
} = require('@/modules/emails/repositories')
const { renderEmail } = require('@/modules/emails/services/emailRendering')
const { sendEmail } = require('@/modules/emails/services/sending')
const { createUserFactory } = require('@/modules/core/services/users/management')
const {
validateAndCreateUserEmailFactory
} = require('@/modules/core/services/userEmails')
const {
finalizeInvitedServerRegistrationFactory
} = require('@/modules/serverinvites/services/processing')
const {
} from '@/modules/core/repositories/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { createUserFactory } from '@/modules/core/services/users/management'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { finalizeInvitedServerRegistrationFactory } from '@/modules/serverinvites/services/processing'
import {
getServerInfoFactory,
updateServerInfoFactory
} = require('@/modules/core/repositories/server')
const {
temporarilyEnableRateLimiter
} = require('@/modules/core/tests/ratelimiter.spec')
} from '@/modules/core/repositories/server'
import { temporarilyEnableRateLimiter } from '@/modules/core/tests/ratelimiter.spec'
import { passportAuthenticationCallbackFactory } from '@/modules/auth/services/passportService'
import { testLogger } from '@/logging/logging'
import { Application } from 'express'
const getServerInfo = getServerInfoFactory({ db })
const getUser = getUserFactory({ db })
@@ -139,14 +124,20 @@ const updateServerInfo = updateServerInfoFactory({ db })
const expect = chai.expect
let app
let sendRequest
let app: Application
let sendRequest: Awaited<ReturnType<typeof initializeTestServer>>['sendRequest']
describe('Auth @auth', () => {
describe('Local authN & authZ (token endpoints)', () => {
const registeredUserEmail = 'registered@speckle.systems'
const me = {
const me: {
name: string
company: string
email: string
password: string
id?: string
} = {
name: 'dimitrie stefanescu',
company: 'speckle',
email: registeredUserEmail,
@@ -154,7 +145,11 @@ describe('Auth @auth', () => {
id: undefined
}
const myPrivateStream = {
const myPrivateStream: {
name: string
isPublic: boolean
id?: string
} = {
name: 'My Private Stream 1',
isPublic: false,
id: undefined
@@ -169,7 +164,7 @@ describe('Auth @auth', () => {
await createUser(me).then((id) => (me.id = id))
// Create a test stream for testing stream invites
await createStream({ ...myPrivateStream, ownerId: me.id }).then(
await createStream({ ...myPrivateStream, ownerId: me.id! }).then(
(id) => (myPrivateStream.id = id)
)
})
@@ -219,7 +214,7 @@ describe('Auth @auth', () => {
: {
email: targetEmail
},
inviterUser.id
inviterUser!.id
)
// No invite
@@ -483,7 +478,7 @@ describe('Auth @auth', () => {
.expect(401)
})
let frontendCredentials
let frontendCredentials: { token: string; refreshToken: string }
it('Should get an access code (redirected response)', async () => {
const appId = 'spklwebapp'
@@ -565,7 +560,7 @@ describe('Auth @auth', () => {
})
it('Should rate-limit user creation', async () => {
const newUser = async (id, ip, expectCode) => {
const newUser = async (id: string, ip: string, expectCode: number) => {
await request(app)
.post(`/auth/local/register?challenge=test`)
.set('CF-Connecting-IP', ip)
@@ -609,4 +604,142 @@ describe('Auth @auth', () => {
RATE_LIMITERS.USER_CREATE = oldRateLimiter
})
})
describe('passportAuthenticationCallbackFactory', () => {
it('Should handle a successful passport authentication (a user exists)', async () => {
const req = httpMocks.createRequest({})
const res = httpMocks.createResponse()
let errorCalledCounter = 0
let nextCalledCounter = 0
const next = (err: unknown) => {
if (err) {
errorCalledCounter++
}
nextCalledCounter++
}
const SUT = passportAuthenticationCallbackFactory({
strategy: 'wotStrategy',
req,
res,
next
})
SUT(null, { id: '123', email: 'weLoveAuth@example.org' }, undefined)
expect(req).to.have.property('user')
expect(req.user?.id).to.equal('123')
expect(
errorCalledCounter,
'error request handler "next(err)" should not have been called'
).to.equal(0)
expect(
nextCalledCounter,
'next request handler should have been called'
).to.equal(1)
})
it('Should handle case where there is an error but no user', async () => {
const req = httpMocks.createRequest()
req.log = testLogger
const res = httpMocks.createResponse()
let errorCalledCounter = 0
let nextCalledCounter = 0
const next = (err: unknown) => {
if (err) {
errorCalledCounter++
}
nextCalledCounter++
}
const SUT = passportAuthenticationCallbackFactory({
strategy: 'wotStrategy',
req,
res,
next
})
SUT(new Error('I brrrrroke'), undefined, undefined)
expect(
res._getRedirectUrl().includes('/error'),
`Redirect url was '${res._getRedirectUrl()}'`
).to.be.true
expect(req).not.to.have.property('user')
expect(
errorCalledCounter,
'error request handler "next(err)" should not have been called'
).to.equal(0)
expect(
nextCalledCounter,
'next request handler should not have been called'
).to.equal(0)
})
it('Should handle case where there is an error and a user', async () => {
const req = httpMocks.createRequest()
req.log = testLogger
const res = httpMocks.createResponse()
let errorCalledCounter = 0
let nextCalledCounter = 0
const next = (err: unknown) => {
if (err) {
errorCalledCounter++
}
nextCalledCounter++
}
const SUT = passportAuthenticationCallbackFactory({
strategy: 'wotStrategy',
req,
res,
next
})
SUT(
new Error('I brrrrrooooken'),
{ id: '1234', email: 'allFizzy@example.org' },
undefined
)
// Should not have set the user if there was an error
expect(req).to.not.have.property('user')
expect(
errorCalledCounter,
'error request handler "next(err)" should have been called'
).to.equal(1)
expect(
nextCalledCounter,
'next request handler should have been called'
).to.equal(1)
})
it('Should handle the case where there is no user and no error', async () => {
const req = httpMocks.createRequest()
req.log = testLogger
const res = httpMocks.createResponse()
let errorCalledCounter = 0
let nextCalledCounter = 0
const next = (err: unknown) => {
if (err) {
errorCalledCounter++
}
nextCalledCounter++
}
const SUT = passportAuthenticationCallbackFactory({
strategy: 'wotStrategy',
req,
res,
next
})
SUT(null, undefined, undefined)
expect(
res._getRedirectUrl().includes('/error'),
`Redirect url was '${res._getRedirectUrl()}'`
).to.be.true
expect(req).not.to.have.property('user')
expect(
errorCalledCounter,
'error request handler "next(err)" should not have been called'
).to.equal(0)
expect(
nextCalledCounter,
'next request handler should not have been called'
).to.equal(0)
})
})
})
@@ -233,7 +233,7 @@ export type LocalAuthRestApiHelpers = ReturnType<typeof localAuthRestApi>
export const generateRegistrationParams = (): RegisterParams => ({
challenge: faker.string.uuid(),
user: {
email: (random(0, 1000) + faker.internet.email()).toLowerCase(),
email: `${random(0, 1000)}@example.org`.toLowerCase(),
password: faker.internet.password(),
name: faker.person.fullName()
}
@@ -8,6 +8,7 @@ import {
import { AllScopes } from '@/modules/core/helpers/mainConstants'
import { updateServerInfoFactory } from '@/modules/core/repositories/server'
import { findInviteFactory } from '@/modules/serverinvites/repositories/serverInvites'
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
import { expectToThrow, itEach } from '@/test/assertionHelper'
import { BasicTestUser, createTestUsers } from '@/test/authHelper'
import {
@@ -33,6 +34,8 @@ import {
import { Roles } from '@speckle/shared'
import { expect } from 'chai'
const { FF_NO_PERSONAL_EMAILS_ENABLED } = getFeatureFlags()
const updateServerInfo = updateServerInfoFactory({ db })
describe('Server registration', () => {
@@ -96,6 +99,18 @@ describe('Server registration', () => {
expect(user.emails.every((e) => !e.verified)).to.be.true
})
FF_NO_PERSONAL_EMAILS_ENABLED
? it('rejects registration with blocked email domain', async () => {
const params = generateRegistrationParams()
params.user.email = 'test@gmail.com'
const error = await expectToThrow(() => restApi.register(params))
expect(error.message).to.contain(
'Please use your work email instead of a personal email address'
)
})
: null
it('fails without challenge', async () => {
const params = generateRegistrationParams()
params.challenge = ''
@@ -0,0 +1,2 @@
export const getObjectKey = (projectId: string, blobId: string): string =>
`assets/${projectId}/${blobId}`
@@ -20,7 +20,7 @@ import {
import { MaybeNullOrUndefined, Nullable } from '@speckle/shared'
import { Knex } from 'knex'
const BlobStorage = buildTableHelper('blob_storage', [
export const BlobStorage = buildTableHelper('blob_storage', [
'id',
'streamId',
'userId',
@@ -7,6 +7,7 @@ import {
UpsertBlob
} from '@/modules/blobstorage/domain/operations'
import { BlobStorageItem } from '@/modules/blobstorage/domain/types'
import { getObjectKey } from '@/modules/blobstorage/helpers/blobs'
import { BadRequestError } from '@/modules/shared/errors'
import { getFileSizeLimitMB } from '@/modules/shared/helpers/envHelper'
import { MaybeAsync } from '@speckle/shared'
@@ -31,7 +32,7 @@ export const uploadFileStreamFactory =
if (!userId || userId.length !== 10)
throw new BadRequestError('The user id has to be of length 10')
const objectKey = `assets/${streamId}/${blobId}`
const objectKey = getObjectKey(streamId, blobId)
const dbFile = {
id: blobId,
streamId,
@@ -40,8 +41,9 @@ export const uploadFileStreamFactory =
fileName,
fileType
}
// need to insert the upload data before starting otherwise the upload finished
// even might fire faster, than the db insert, causing missing asset data in the db
// event might fire faster, than the db insert, causing missing asset data in the db
await deps.upsertBlob(dbFile)
const { fileHash } = await deps.storeFileStream({ objectKey, fileStream })
@@ -3,11 +3,8 @@ import { cliLogger } from '@/logging/logging'
import { getWorkspaceBySlugOrIdFactory } from '@/modules/workspaces/repositories/workspaces'
import { db } from '@/db/knex'
import { upsertPaidWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing'
import {
PaidWorkspacePlans,
PaidWorkspacePlanStatuses
} from '@/modules/gatekeeperCore/domain/billing'
import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace'
import { PaidWorkspacePlans, PaidWorkspacePlanStatuses } from '@speckle/shared'
const command: CommandModule<
unknown,
@@ -319,3 +319,8 @@ export type GetPaginatedProjectComments = (
items: CommentRecord[]
cursor: string | null
}>
export type GetStreamCommentCount = (
streamId: string,
options?: Partial<{ threadsOnly: boolean; includeArchived: boolean }>
) => Promise<number>
@@ -6,12 +6,14 @@ export type ResourceIdentifier = {
resourceType: ResourceType
}
export enum ResourceType {
Comment = 'comment',
Commit = 'commit',
Object = 'object',
Stream = 'stream'
}
export const ResourceType = {
Comment: 'comment',
Commit: 'commit',
Object: 'object',
Stream: 'stream'
} as const
export type ResourceType = (typeof ResourceType)[keyof typeof ResourceType]
export type ExtendedComment = CommentRecord & {
/**
@@ -1,4 +1,3 @@
import { ProjectVisibility } from '@/modules/core/domain/projects/operations'
import { Project } from '@/modules/core/domain/streams/types'
import {
ProjectCreateInput,
@@ -7,7 +6,6 @@ import {
StreamUpdateInput
} from '@/modules/core/graph/generated/graphql'
import { StreamRoles } from '@speckle/shared'
import { OverrideProperties } from 'type-fest'
export const projectEventsNamespace = 'projects' as const
@@ -24,23 +22,13 @@ export type ProjectEventsPayloads = {
[ProjectEvents.Created]: {
project: Project
ownerId: string
input:
| StreamCreateInput
| OverrideProperties<
ProjectCreateInput,
{ visibility: ProjectCreateInput['visibility'] | ProjectVisibility }
>
input: StreamCreateInput | ProjectCreateInput
}
[ProjectEvents.Updated]: {
updaterId: string
oldProject: Project
newProject: Project
update:
| OverrideProperties<
ProjectUpdateInput,
{ visibility: ProjectUpdateInput['visibility'] | ProjectVisibility }
>
| StreamUpdateInput
update: ProjectUpdateInput | StreamUpdateInput
}
[ProjectEvents.Deleted]: {
deleterId: string
@@ -25,3 +25,9 @@ export class UnverifiedEmailSSOLoginError extends UserInputError<UnverifiedEmail
'Email already in use by a user with unverified email. Verify the email on the existing user to be able to log in with this method.'
static code = 'UNVERIFIED_EMAIL_SSO_LOGIN_ERROR'
}
export class BlockedEmailDomainError extends UserInputError {
static defaultMessage =
'Please use your work email instead of a personal email address'
static code = 'BLOCKED_EMAIL_DOMAIN_ERROR'
}
@@ -5,7 +5,7 @@ import { CommentReplyAuthorCollectionGraphQLReturn, CommentGraphQLReturn } from
import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/helpers/graphTypes';
import { FileUploadGraphQLReturn } from '@/modules/fileuploads/helpers/types';
import { AutomateFunctionGraphQLReturn, AutomateFunctionReleaseGraphQLReturn, AutomationGraphQLReturn, AutomationRevisionGraphQLReturn, AutomationRevisionFunctionGraphQLReturn, AutomateRunGraphQLReturn, AutomationRunTriggerGraphQLReturn, AutomationRevisionTriggerDefinitionGraphQLReturn, AutomateFunctionRunGraphQLReturn, TriggeredAutomationsStatusGraphQLReturn, ProjectAutomationMutationsGraphQLReturn, ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn, ProjectAutomationsUpdatedMessageGraphQLReturn, UserAutomateInfoGraphQLReturn } from '@/modules/automate/helpers/graphTypes';
import { WorkspaceGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceJoinRequestMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, WorkspaceJoinRequestGraphQLReturn, ProjectRoleGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes';
import { WorkspaceGraphQLReturn, WorkspaceSsoGraphQLReturn, WorkspaceMutationsGraphQLReturn, WorkspaceJoinRequestMutationsGraphQLReturn, WorkspaceInviteMutationsGraphQLReturn, WorkspaceProjectMutationsGraphQLReturn, PendingWorkspaceCollaboratorGraphQLReturn, WorkspaceCollaboratorGraphQLReturn, WorkspaceJoinRequestGraphQLReturn, LimitedWorkspaceJoinRequestGraphQLReturn, ProjectRoleGraphQLReturn } from '@/modules/workspacesCore/helpers/graphTypes';
import { WorkspaceBillingMutationsGraphQLReturn } from '@/modules/gatekeeper/helpers/graphTypes';
import { WebhookGraphQLReturn } from '@/modules/webhooks/helpers/graphTypes';
import { SmartTextEditorValueGraphQLReturn } from '@/modules/core/services/richTextEditorService';
@@ -49,6 +49,11 @@ export type ActiveUserMutations = {
};
export type ActiveUserMutationsFinishOnboardingArgs = {
input?: InputMaybe<OnboardingCompletionInput>;
};
export type ActiveUserMutationsUpdateArgs = {
user: UserUpdateInput;
};
@@ -348,12 +353,13 @@ export type AutomateFunctionTemplate = {
url: Scalars['String']['output'];
};
export enum AutomateFunctionTemplateLanguage {
DotNet = 'DOT_NET',
Python = 'PYTHON',
Typescript = 'TYPESCRIPT'
}
export const AutomateFunctionTemplateLanguage = {
DotNet: 'DOT_NET',
Python: 'PYTHON',
Typescript: 'TYPESCRIPT'
} as const;
export type AutomateFunctionTemplateLanguage = typeof AutomateFunctionTemplateLanguage[keyof typeof AutomateFunctionTemplateLanguage];
export type AutomateFunctionToken = {
__typename?: 'AutomateFunctionToken';
functionId: Scalars['String']['output'];
@@ -407,21 +413,23 @@ export type AutomateRunCollection = {
totalCount: Scalars['Int']['output'];
};
export enum AutomateRunStatus {
Canceled = 'CANCELED',
Exception = 'EXCEPTION',
Failed = 'FAILED',
Initializing = 'INITIALIZING',
Pending = 'PENDING',
Running = 'RUNNING',
Succeeded = 'SUCCEEDED',
Timeout = 'TIMEOUT'
}
export const AutomateRunStatus = {
Canceled: 'CANCELED',
Exception: 'EXCEPTION',
Failed: 'FAILED',
Initializing: 'INITIALIZING',
Pending: 'PENDING',
Running: 'RUNNING',
Succeeded: 'SUCCEEDED',
Timeout: 'TIMEOUT'
} as const;
export enum AutomateRunTriggerType {
VersionCreated = 'VERSION_CREATED'
}
export type AutomateRunStatus = typeof AutomateRunStatus[keyof typeof AutomateRunStatus];
export const AutomateRunTriggerType = {
VersionCreated: 'VERSION_CREATED'
} as const;
export type AutomateRunTriggerType = typeof AutomateRunTriggerType[keyof typeof AutomateRunTriggerType];
export type Automation = {
__typename?: 'Automation';
createdAt: Scalars['DateTime']['output'];
@@ -482,11 +490,12 @@ export type BasicGitRepositoryMetadata = {
url: Scalars['String']['output'];
};
export enum BillingInterval {
Monthly = 'monthly',
Yearly = 'yearly'
}
export const BillingInterval = {
Monthly: 'monthly',
Yearly: 'yearly'
} as const;
export type BillingInterval = typeof BillingInterval[keyof typeof BillingInterval];
export type BlobMetadata = {
__typename?: 'BlobMetadata';
createdAt: Scalars['DateTime']['output'];
@@ -950,16 +959,29 @@ export type DenyWorkspaceJoinRequestInput = {
workspaceId: Scalars['String']['input'];
};
export enum DiscoverableStreamsSortType {
CreatedDate = 'CREATED_DATE',
FavoritesCount = 'FAVORITES_COUNT'
}
export const DiscoverableStreamsSortType = {
CreatedDate: 'CREATED_DATE',
FavoritesCount: 'FAVORITES_COUNT'
} as const;
export type DiscoverableStreamsSortType = typeof DiscoverableStreamsSortType[keyof typeof DiscoverableStreamsSortType];
export type DiscoverableStreamsSortingInput = {
direction: SortDirection;
type: DiscoverableStreamsSortType;
};
export type DiscoverableWorkspaceCollaborator = {
__typename?: 'DiscoverableWorkspaceCollaborator';
avatar?: Maybe<Scalars['String']['output']>;
};
export type DiscoverableWorkspaceCollaboratorCollection = {
__typename?: 'DiscoverableWorkspaceCollaboratorCollection';
cursor?: Maybe<Scalars['String']['output']>;
items: Array<DiscoverableWorkspaceCollaborator>;
totalCount: Scalars['Int']['output'];
};
export type EditCommentInput = {
commentId: Scalars['String']['input'];
content: CommentContentInput;
@@ -1173,6 +1195,31 @@ export type LimitedWorkspace = {
name: Scalars['String']['output'];
/** Unique workspace short id. Used for navigation. */
slug: Scalars['String']['output'];
/** Workspace members visible to people with verified email domain */
team?: Maybe<DiscoverableWorkspaceCollaboratorCollection>;
};
/** Workspace metadata visible to non-workspace members. */
export type LimitedWorkspaceTeamArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
limit?: Scalars['Int']['input'];
};
export type LimitedWorkspaceJoinRequest = {
__typename?: 'LimitedWorkspaceJoinRequest';
createdAt: Scalars['DateTime']['output'];
id: Scalars['String']['output'];
status: WorkspaceJoinRequestStatus;
user: LimitedUser;
workspace: LimitedWorkspace;
};
export type LimitedWorkspaceJoinRequestCollection = {
__typename?: 'LimitedWorkspaceJoinRequestCollection';
cursor?: Maybe<Scalars['String']['output']>;
items: Array<LimitedWorkspaceJoinRequest>;
totalCount: Scalars['Int']['output'];
};
export type MarkCommentViewedInput = {
@@ -1833,12 +1880,19 @@ export type ObjectCreateInput = {
streamId: Scalars['String']['input'];
};
export enum PaidWorkspacePlans {
Business = 'business',
Plus = 'plus',
Starter = 'starter'
}
export type OnboardingCompletionInput = {
plans?: InputMaybe<Array<Scalars['String']['input']>>;
role?: InputMaybe<Scalars['String']['input']>;
source?: InputMaybe<Scalars['String']['input']>;
};
export const PaidWorkspacePlans = {
Business: 'business',
Plus: 'plus',
Starter: 'starter'
} as const;
export type PaidWorkspacePlans = typeof PaidWorkspacePlans[keyof typeof PaidWorkspacePlans];
export type PasswordStrengthCheckFeedback = {
__typename?: 'PasswordStrengthCheckFeedback';
suggestions: Array<Scalars['String']['output']>;
@@ -2166,12 +2220,13 @@ export type ProjectAutomationsUpdatedMessage = {
type: ProjectAutomationsUpdatedMessageType;
};
export enum ProjectAutomationsUpdatedMessageType {
Created = 'CREATED',
CreatedRevision = 'CREATED_REVISION',
Updated = 'UPDATED'
}
export const ProjectAutomationsUpdatedMessageType = {
Created: 'CREATED',
CreatedRevision: 'CREATED_REVISION',
Updated: 'UPDATED'
} as const;
export type ProjectAutomationsUpdatedMessageType = typeof ProjectAutomationsUpdatedMessageType[keyof typeof ProjectAutomationsUpdatedMessageType];
export type ProjectCollaborator = {
__typename?: 'ProjectCollaborator';
id: Scalars['ID']['output'];
@@ -2218,12 +2273,13 @@ export type ProjectCommentsUpdatedMessage = {
type: ProjectCommentsUpdatedMessageType;
};
export enum ProjectCommentsUpdatedMessageType {
Archived = 'ARCHIVED',
Created = 'CREATED',
Updated = 'UPDATED'
}
export const ProjectCommentsUpdatedMessageType = {
Archived: 'ARCHIVED',
Created: 'CREATED',
Updated: 'UPDATED'
} as const;
export type ProjectCommentsUpdatedMessageType = typeof ProjectCommentsUpdatedMessageType[keyof typeof ProjectCommentsUpdatedMessageType];
/** Any values left null will be ignored */
export type ProjectCreateInput = {
description?: InputMaybe<Scalars['String']['input']>;
@@ -2239,11 +2295,12 @@ export type ProjectFileImportUpdatedMessage = {
upload: FileUpload;
};
export enum ProjectFileImportUpdatedMessageType {
Created = 'CREATED',
Updated = 'UPDATED'
}
export const ProjectFileImportUpdatedMessageType = {
Created: 'CREATED',
Updated: 'UPDATED'
} as const;
export type ProjectFileImportUpdatedMessageType = typeof ProjectFileImportUpdatedMessageType[keyof typeof ProjectFileImportUpdatedMessageType];
export type ProjectInviteCreateInput = {
/** Either this or userId must be filled */
email?: InputMaybe<Scalars['String']['input']>;
@@ -2340,12 +2397,13 @@ export type ProjectModelsUpdatedMessage = {
type: ProjectModelsUpdatedMessageType;
};
export enum ProjectModelsUpdatedMessageType {
Created = 'CREATED',
Deleted = 'DELETED',
Updated = 'UPDATED'
}
export const ProjectModelsUpdatedMessageType = {
Created: 'CREATED',
Deleted: 'DELETED',
Updated: 'UPDATED'
} as const;
export type ProjectModelsUpdatedMessageType = typeof ProjectModelsUpdatedMessageType[keyof typeof ProjectModelsUpdatedMessageType];
export type ProjectMutations = {
__typename?: 'ProjectMutations';
/** Access request related mutations */
@@ -2415,11 +2473,12 @@ export type ProjectPendingModelsUpdatedMessage = {
type: ProjectPendingModelsUpdatedMessageType;
};
export enum ProjectPendingModelsUpdatedMessageType {
Created = 'CREATED',
Updated = 'UPDATED'
}
export const ProjectPendingModelsUpdatedMessageType = {
Created: 'CREATED',
Updated: 'UPDATED'
} as const;
export type ProjectPendingModelsUpdatedMessageType = typeof ProjectPendingModelsUpdatedMessageType[keyof typeof ProjectPendingModelsUpdatedMessageType];
export type ProjectPendingVersionsUpdatedMessage = {
__typename?: 'ProjectPendingVersionsUpdatedMessage';
/** Upload ID */
@@ -2428,11 +2487,12 @@ export type ProjectPendingVersionsUpdatedMessage = {
version: FileUpload;
};
export enum ProjectPendingVersionsUpdatedMessageType {
Created = 'CREATED',
Updated = 'UPDATED'
}
export const ProjectPendingVersionsUpdatedMessageType = {
Created: 'CREATED',
Updated: 'UPDATED'
} as const;
export type ProjectPendingVersionsUpdatedMessageType = typeof ProjectPendingVersionsUpdatedMessageType[keyof typeof ProjectPendingVersionsUpdatedMessageType];
export type ProjectRole = {
__typename?: 'ProjectRole';
project: Project;
@@ -2454,11 +2514,12 @@ export type ProjectTriggeredAutomationsStatusUpdatedMessage = {
version: Version;
};
export enum ProjectTriggeredAutomationsStatusUpdatedMessageType {
RunCreated = 'RUN_CREATED',
RunUpdated = 'RUN_UPDATED'
}
export const ProjectTriggeredAutomationsStatusUpdatedMessageType = {
RunCreated: 'RUN_CREATED',
RunUpdated: 'RUN_UPDATED'
} as const;
export type ProjectTriggeredAutomationsStatusUpdatedMessageType = typeof ProjectTriggeredAutomationsStatusUpdatedMessageType[keyof typeof ProjectTriggeredAutomationsStatusUpdatedMessageType];
/** Any values left null will be ignored, so only set the properties that you want updated */
export type ProjectUpdateInput = {
allowPublicComments?: InputMaybe<Scalars['Boolean']['input']>;
@@ -2485,11 +2546,12 @@ export type ProjectUpdatedMessage = {
type: ProjectUpdatedMessageType;
};
export enum ProjectUpdatedMessageType {
Deleted = 'DELETED',
Updated = 'UPDATED'
}
export const ProjectUpdatedMessageType = {
Deleted: 'DELETED',
Updated: 'UPDATED'
} as const;
export type ProjectUpdatedMessageType = typeof ProjectUpdatedMessageType[keyof typeof ProjectUpdatedMessageType];
export type ProjectVersionsPreviewGeneratedMessage = {
__typename?: 'ProjectVersionsPreviewGeneratedMessage';
objectId: Scalars['String']['output'];
@@ -2508,18 +2570,20 @@ export type ProjectVersionsUpdatedMessage = {
version?: Maybe<Version>;
};
export enum ProjectVersionsUpdatedMessageType {
Created = 'CREATED',
Deleted = 'DELETED',
Updated = 'UPDATED'
}
export const ProjectVersionsUpdatedMessageType = {
Created: 'CREATED',
Deleted: 'DELETED',
Updated: 'UPDATED'
} as const;
export enum ProjectVisibility {
Private = 'PRIVATE',
Public = 'PUBLIC',
Unlisted = 'UNLISTED'
}
export type ProjectVersionsUpdatedMessageType = typeof ProjectVersionsUpdatedMessageType[keyof typeof ProjectVersionsUpdatedMessageType];
export const ProjectVisibility = {
Private: 'PRIVATE',
Public: 'PUBLIC',
Unlisted: 'UNLISTED'
} as const;
export type ProjectVisibility = typeof ProjectVisibility[keyof typeof ProjectVisibility];
export type Query = {
__typename?: 'Query';
/** Stare into the void. */
@@ -2643,7 +2707,6 @@ export type Query = {
* Either token or workspaceId must be specified, or both
*/
workspaceInvite?: Maybe<PendingWorkspaceCollaborator>;
workspacePricingPlans: Scalars['JSONObject']['output'];
/** Find workspaces a given user email can use SSO to sign with */
workspaceSsoByEmail: Array<LimitedWorkspace>;
};
@@ -2830,13 +2893,14 @@ export type ResourceIdentifierInput = {
resourceType: ResourceType;
};
export enum ResourceType {
Comment = 'comment',
Commit = 'commit',
Object = 'object',
Stream = 'stream'
}
export const ResourceType = {
Comment: 'comment',
Commit: 'commit',
Object: 'object',
Stream: 'stream'
} as const;
export type ResourceType = typeof ResourceType[keyof typeof ResourceType];
export type Role = {
__typename?: 'Role';
description: Scalars['String']['output'];
@@ -3000,13 +3064,14 @@ export type ServerRegionMutationsUpdateArgs = {
input: UpdateServerRegionInput;
};
export enum ServerRole {
ServerAdmin = 'SERVER_ADMIN',
ServerArchivedUser = 'SERVER_ARCHIVED_USER',
ServerGuest = 'SERVER_GUEST',
ServerUser = 'SERVER_USER'
}
export const ServerRole = {
ServerAdmin: 'SERVER_ADMIN',
ServerArchivedUser: 'SERVER_ARCHIVED_USER',
ServerGuest: 'SERVER_GUEST',
ServerUser: 'SERVER_USER'
} as const;
export type ServerRole = typeof ServerRole[keyof typeof ServerRole];
export type ServerRoleItem = {
__typename?: 'ServerRoleItem';
id: Scalars['String']['output'];
@@ -3045,11 +3110,12 @@ export type ServerWorkspacesInfo = {
workspacesEnabled: Scalars['Boolean']['output'];
};
export enum SessionPaymentStatus {
Paid = 'paid',
Unpaid = 'unpaid'
}
export const SessionPaymentStatus = {
Paid: 'paid',
Unpaid: 'unpaid'
} as const;
export type SessionPaymentStatus = typeof SessionPaymentStatus[keyof typeof SessionPaymentStatus];
export type SetPrimaryUserEmailInput = {
id: Scalars['ID']['input'];
};
@@ -3069,11 +3135,12 @@ export type SmartTextEditorValue = {
version: Scalars['String']['output'];
};
export enum SortDirection {
Asc = 'ASC',
Desc = 'DESC'
}
export const SortDirection = {
Asc: 'ASC',
Desc: 'DESC'
} as const;
export type SortDirection = typeof SortDirection[keyof typeof SortDirection];
export type Stream = {
__typename?: 'Stream';
/**
@@ -3268,12 +3335,13 @@ export type StreamRevokePermissionInput = {
userId: Scalars['String']['input'];
};
export enum StreamRole {
StreamContributor = 'STREAM_CONTRIBUTOR',
StreamOwner = 'STREAM_OWNER',
StreamReviewer = 'STREAM_REVIEWER'
}
export const StreamRole = {
StreamContributor: 'STREAM_CONTRIBUTOR',
StreamOwner: 'STREAM_OWNER',
StreamReviewer: 'STREAM_REVIEWER'
} as const;
export type StreamRole = typeof StreamRole[keyof typeof StreamRole];
export type StreamUpdateInput = {
allowPublicComments?: InputMaybe<Scalars['Boolean']['input']>;
description?: InputMaybe<Scalars['String']['input']>;
@@ -3589,11 +3657,12 @@ export type TokenResourceIdentifierInput = {
type: TokenResourceIdentifierType;
};
export enum TokenResourceIdentifierType {
Project = 'project',
Workspace = 'workspace'
}
export const TokenResourceIdentifierType = {
Project: 'project',
Workspace: 'workspace'
} as const;
export type TokenResourceIdentifierType = typeof TokenResourceIdentifierType[keyof typeof TokenResourceIdentifierType];
export type TriggeredAutomationsStatus = {
__typename?: 'TriggeredAutomationsStatus';
automationRuns: Array<AutomateRun>;
@@ -3729,6 +3798,7 @@ export type User = {
versions: CountOnlyCollection;
/** Get all invitations to workspaces that the active user has */
workspaceInvites: Array<PendingWorkspaceCollaborator>;
workspaceJoinRequests?: Maybe<LimitedWorkspaceJoinRequestCollection>;
/** Get the workspaces for the user */
workspaces: WorkspaceCollection;
};
@@ -3830,6 +3900,17 @@ export type UserVersionsArgs = {
};
/**
* Full user type, should only be used in the context of admin operations or
* when a user is reading/writing info about himself
*/
export type UserWorkspaceJoinRequestsArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<WorkspaceJoinRequestFilter>;
limit?: Scalars['Int']['input'];
};
/**
* Full user type, should only be used in the context of admin operations or
* when a user is reading/writing info about himself
@@ -3926,11 +4007,12 @@ export type UserProjectsUpdatedMessage = {
type: UserProjectsUpdatedMessageType;
};
export enum UserProjectsUpdatedMessageType {
Added = 'ADDED',
Removed = 'REMOVED'
}
export const UserProjectsUpdatedMessageType = {
Added: 'ADDED',
Removed: 'REMOVED'
} as const;
export type UserProjectsUpdatedMessageType = typeof UserProjectsUpdatedMessageType[keyof typeof UserProjectsUpdatedMessageType];
export type UserRoleInput = {
id: Scalars['String']['input'];
role: Scalars['String']['input'];
@@ -4120,11 +4202,12 @@ export type ViewerUserActivityMessageInput = {
userName: Scalars['String']['input'];
};
export enum ViewerUserActivityStatus {
Disconnected = 'DISCONNECTED',
Viewing = 'VIEWING'
}
export const ViewerUserActivityStatus = {
Disconnected: 'DISCONNECTED',
Viewing: 'VIEWING'
} as const;
export type ViewerUserActivityStatus = typeof ViewerUserActivityStatus[keyof typeof ViewerUserActivityStatus];
export type Webhook = {
__typename?: 'Webhook';
description?: Maybe<Scalars['String']['output']>;
@@ -4352,12 +4435,13 @@ export type WorkspaceDomainDeleteInput = {
workspaceId: Scalars['ID']['input'];
};
export enum WorkspaceFeatureName {
DomainBasedSecurityPolicies = 'domainBasedSecurityPolicies',
OidcSso = 'oidcSso',
WorkspaceDataRegionSpecificity = 'workspaceDataRegionSpecificity'
}
export const WorkspaceFeatureName = {
DomainBasedSecurityPolicies: 'domainBasedSecurityPolicies',
OidcSso: 'oidcSso',
WorkspaceDataRegionSpecificity: 'workspaceDataRegionSpecificity'
} as const;
export type WorkspaceFeatureName = typeof WorkspaceFeatureName[keyof typeof WorkspaceFeatureName];
export type WorkspaceInviteCreateInput = {
/** Either this or userId must be filled */
email?: InputMaybe<Scalars['String']['input']>;
@@ -4442,6 +4526,10 @@ export type WorkspaceJoinRequestCollection = {
totalCount: Scalars['Int']['output'];
};
export type WorkspaceJoinRequestFilter = {
status?: InputMaybe<WorkspaceJoinRequestStatus>;
};
export type WorkspaceJoinRequestMutations = {
__typename?: 'WorkspaceJoinRequestMutations';
approve: Scalars['Boolean']['output'];
@@ -4458,12 +4546,13 @@ export type WorkspaceJoinRequestMutationsDenyArgs = {
input: DenyWorkspaceJoinRequestInput;
};
export enum WorkspaceJoinRequestStatus {
Approved = 'approved',
Denied = 'denied',
Pending = 'pending'
}
export const WorkspaceJoinRequestStatus = {
Approved: 'approved',
Denied: 'denied',
Pending: 'pending'
} as const;
export type WorkspaceJoinRequestStatus = typeof WorkspaceJoinRequestStatus[keyof typeof WorkspaceJoinRequestStatus];
export type WorkspaceMutations = {
__typename?: 'WorkspaceMutations';
addDomain: Workspace;
@@ -4552,12 +4641,13 @@ export type WorkspaceMutationsUpdateRoleArgs = {
input: WorkspaceRoleUpdateInput;
};
export enum WorkspacePaymentMethod {
Billing = 'billing',
Invoice = 'invoice',
Unpaid = 'unpaid'
}
export const WorkspacePaymentMethod = {
Billing: 'billing',
Invoice: 'invoice',
Unpaid: 'unpaid'
} as const;
export type WorkspacePaymentMethod = typeof WorkspacePaymentMethod[keyof typeof WorkspacePaymentMethod];
export type WorkspacePlan = {
__typename?: 'WorkspacePlan';
createdAt: Scalars['DateTime']['output'];
@@ -4566,27 +4656,31 @@ export type WorkspacePlan = {
status: WorkspacePlanStatuses;
};
export enum WorkspacePlanStatuses {
CancelationScheduled = 'cancelationScheduled',
Canceled = 'canceled',
Expired = 'expired',
PaymentFailed = 'paymentFailed',
Trial = 'trial',
Valid = 'valid'
}
export const WorkspacePlanStatuses = {
CancelationScheduled: 'cancelationScheduled',
Canceled: 'canceled',
Expired: 'expired',
PaymentFailed: 'paymentFailed',
Trial: 'trial',
Valid: 'valid'
} as const;
export enum WorkspacePlans {
Academia = 'academia',
Business = 'business',
BusinessInvoiced = 'businessInvoiced',
Free = 'free',
Plus = 'plus',
PlusInvoiced = 'plusInvoiced',
Starter = 'starter',
StarterInvoiced = 'starterInvoiced',
Unlimited = 'unlimited'
}
export type WorkspacePlanStatuses = typeof WorkspacePlanStatuses[keyof typeof WorkspacePlanStatuses];
export const WorkspacePlans = {
Academia: 'academia',
Business: 'business',
BusinessInvoiced: 'businessInvoiced',
Free: 'free',
Plus: 'plus',
PlusInvoiced: 'plusInvoiced',
Pro: 'pro',
Starter: 'starter',
StarterInvoiced: 'starterInvoiced',
Team: 'team',
Unlimited: 'unlimited'
} as const;
export type WorkspacePlans = typeof WorkspacePlans[keyof typeof WorkspacePlans];
export type WorkspaceProjectCreateInput = {
description?: InputMaybe<Scalars['String']['input']>;
name?: InputMaybe<Scalars['String']['input']>;
@@ -4658,21 +4752,23 @@ export type WorkspaceProjectsUpdatedMessage = {
workspaceId: Scalars['String']['output'];
};
export enum WorkspaceProjectsUpdatedMessageType {
Added = 'ADDED',
Removed = 'REMOVED'
}
export const WorkspaceProjectsUpdatedMessageType = {
Added: 'ADDED',
Removed: 'REMOVED'
} as const;
export type WorkspaceProjectsUpdatedMessageType = typeof WorkspaceProjectsUpdatedMessageType[keyof typeof WorkspaceProjectsUpdatedMessageType];
export type WorkspaceRequestToJoinInput = {
workspaceId: Scalars['ID']['input'];
};
export enum WorkspaceRole {
Admin = 'ADMIN',
Guest = 'GUEST',
Member = 'MEMBER'
}
export const WorkspaceRole = {
Admin: 'ADMIN',
Guest: 'GUEST',
Member: 'MEMBER'
} as const;
export type WorkspaceRole = typeof WorkspaceRole[keyof typeof WorkspaceRole];
export type WorkspaceRoleDeleteInput = {
userId: Scalars['String']['input'];
workspaceId: Scalars['String']['input'];
@@ -4915,6 +5011,8 @@ export type ResolversTypes = {
DenyWorkspaceJoinRequestInput: DenyWorkspaceJoinRequestInput;
DiscoverableStreamsSortType: DiscoverableStreamsSortType;
DiscoverableStreamsSortingInput: DiscoverableStreamsSortingInput;
DiscoverableWorkspaceCollaborator: ResolverTypeWrapper<DiscoverableWorkspaceCollaborator>;
DiscoverableWorkspaceCollaboratorCollection: ResolverTypeWrapper<DiscoverableWorkspaceCollaboratorCollection>;
EditCommentInput: EditCommentInput;
EmailVerificationRequestInput: EmailVerificationRequestInput;
FileUpload: ResolverTypeWrapper<FileUploadGraphQLReturn>;
@@ -4929,6 +5027,8 @@ export type ResolversTypes = {
LegacyCommentViewerData: ResolverTypeWrapper<LegacyCommentViewerData>;
LimitedUser: ResolverTypeWrapper<LimitedUserGraphQLReturn>;
LimitedWorkspace: ResolverTypeWrapper<LimitedWorkspace>;
LimitedWorkspaceJoinRequest: ResolverTypeWrapper<LimitedWorkspaceJoinRequestGraphQLReturn>;
LimitedWorkspaceJoinRequestCollection: ResolverTypeWrapper<Omit<LimitedWorkspaceJoinRequestCollection, 'items'> & { items: Array<ResolversTypes['LimitedWorkspaceJoinRequest']> }>;
MarkCommentViewedInput: MarkCommentViewedInput;
MarkReceivedVersionInput: MarkReceivedVersionInput;
Model: ResolverTypeWrapper<ModelGraphQLReturn>;
@@ -4942,6 +5042,7 @@ export type ResolversTypes = {
Object: ResolverTypeWrapper<ObjectGraphQLReturn>;
ObjectCollection: ResolverTypeWrapper<Omit<ObjectCollection, 'objects'> & { objects: Array<ResolversTypes['Object']> }>;
ObjectCreateInput: ObjectCreateInput;
OnboardingCompletionInput: OnboardingCompletionInput;
PaidWorkspacePlans: PaidWorkspacePlans;
PasswordStrengthCheckFeedback: ResolverTypeWrapper<PasswordStrengthCheckFeedback>;
PasswordStrengthCheckResults: ResolverTypeWrapper<PasswordStrengthCheckResults>;
@@ -5097,6 +5198,7 @@ export type ResolversTypes = {
WorkspaceInviteUseInput: WorkspaceInviteUseInput;
WorkspaceJoinRequest: ResolverTypeWrapper<WorkspaceJoinRequestGraphQLReturn>;
WorkspaceJoinRequestCollection: ResolverTypeWrapper<Omit<WorkspaceJoinRequestCollection, 'items'> & { items: Array<ResolversTypes['WorkspaceJoinRequest']> }>;
WorkspaceJoinRequestFilter: WorkspaceJoinRequestFilter;
WorkspaceJoinRequestMutations: ResolverTypeWrapper<WorkspaceJoinRequestMutationsGraphQLReturn>;
WorkspaceJoinRequestStatus: WorkspaceJoinRequestStatus;
WorkspaceMutations: ResolverTypeWrapper<WorkspaceMutationsGraphQLReturn>;
@@ -5217,6 +5319,8 @@ export type ResolversParentTypes = {
DeleteVersionsInput: DeleteVersionsInput;
DenyWorkspaceJoinRequestInput: DenyWorkspaceJoinRequestInput;
DiscoverableStreamsSortingInput: DiscoverableStreamsSortingInput;
DiscoverableWorkspaceCollaborator: DiscoverableWorkspaceCollaborator;
DiscoverableWorkspaceCollaboratorCollection: DiscoverableWorkspaceCollaboratorCollection;
EditCommentInput: EditCommentInput;
EmailVerificationRequestInput: EmailVerificationRequestInput;
FileUpload: FileUploadGraphQLReturn;
@@ -5231,6 +5335,8 @@ export type ResolversParentTypes = {
LegacyCommentViewerData: LegacyCommentViewerData;
LimitedUser: LimitedUserGraphQLReturn;
LimitedWorkspace: LimitedWorkspace;
LimitedWorkspaceJoinRequest: LimitedWorkspaceJoinRequestGraphQLReturn;
LimitedWorkspaceJoinRequestCollection: Omit<LimitedWorkspaceJoinRequestCollection, 'items'> & { items: Array<ResolversParentTypes['LimitedWorkspaceJoinRequest']> };
MarkCommentViewedInput: MarkCommentViewedInput;
MarkReceivedVersionInput: MarkReceivedVersionInput;
Model: ModelGraphQLReturn;
@@ -5244,6 +5350,7 @@ export type ResolversParentTypes = {
Object: ObjectGraphQLReturn;
ObjectCollection: Omit<ObjectCollection, 'objects'> & { objects: Array<ResolversParentTypes['Object']> };
ObjectCreateInput: ObjectCreateInput;
OnboardingCompletionInput: OnboardingCompletionInput;
PasswordStrengthCheckFeedback: PasswordStrengthCheckFeedback;
PasswordStrengthCheckResults: PasswordStrengthCheckResults;
PendingStreamCollaborator: PendingStreamCollaboratorGraphQLReturn;
@@ -5379,6 +5486,7 @@ export type ResolversParentTypes = {
WorkspaceInviteUseInput: WorkspaceInviteUseInput;
WorkspaceJoinRequest: WorkspaceJoinRequestGraphQLReturn;
WorkspaceJoinRequestCollection: Omit<WorkspaceJoinRequestCollection, 'items'> & { items: Array<ResolversParentTypes['WorkspaceJoinRequest']> };
WorkspaceJoinRequestFilter: WorkspaceJoinRequestFilter;
WorkspaceJoinRequestMutations: WorkspaceJoinRequestMutationsGraphQLReturn;
WorkspaceMutations: WorkspaceMutationsGraphQLReturn;
WorkspacePlan: WorkspacePlan;
@@ -5436,7 +5544,7 @@ export type IsOwnerDirectiveResolver<Result, Parent, ContextType = GraphQLContex
export type ActiveUserMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ActiveUserMutations'] = ResolversParentTypes['ActiveUserMutations']> = {
emailMutations?: Resolver<ResolversTypes['UserEmailMutations'], ParentType, ContextType>;
finishOnboarding?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
finishOnboarding?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, Partial<ActiveUserMutationsFinishOnboardingArgs>>;
update?: Resolver<ResolversTypes['User'], ParentType, ContextType, RequireFields<ActiveUserMutationsUpdateArgs, 'user'>>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -5844,6 +5952,18 @@ export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig<ResolversT
name: 'DateTime';
}
export type DiscoverableWorkspaceCollaboratorResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['DiscoverableWorkspaceCollaborator'] = ResolversParentTypes['DiscoverableWorkspaceCollaborator']> = {
avatar?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type DiscoverableWorkspaceCollaboratorCollectionResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['DiscoverableWorkspaceCollaboratorCollection'] = ResolversParentTypes['DiscoverableWorkspaceCollaboratorCollection']> = {
cursor?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
items?: Resolver<Array<ResolversTypes['DiscoverableWorkspaceCollaborator']>, ParentType, ContextType>;
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type FileUploadResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['FileUpload'] = ResolversParentTypes['FileUpload']> = {
branchName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
convertedCommitId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
@@ -5925,6 +6045,23 @@ export type LimitedWorkspaceResolvers<ContextType = GraphQLContext, ParentType e
logo?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
slug?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
team?: Resolver<Maybe<ResolversTypes['DiscoverableWorkspaceCollaboratorCollection']>, ParentType, ContextType, RequireFields<LimitedWorkspaceTeamArgs, 'limit'>>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type LimitedWorkspaceJoinRequestResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['LimitedWorkspaceJoinRequest'] = ResolversParentTypes['LimitedWorkspaceJoinRequest']> = {
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
id?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
status?: Resolver<ResolversTypes['WorkspaceJoinRequestStatus'], ParentType, ContextType>;
user?: Resolver<ResolversTypes['LimitedUser'], ParentType, ContextType>;
workspace?: Resolver<ResolversTypes['LimitedWorkspace'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type LimitedWorkspaceJoinRequestCollectionResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['LimitedWorkspaceJoinRequestCollection'] = ResolversParentTypes['LimitedWorkspaceJoinRequestCollection']> = {
cursor?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
items?: Resolver<Array<ResolversTypes['LimitedWorkspaceJoinRequest']>, ParentType, ContextType>;
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -6330,7 +6467,6 @@ export type QueryResolvers<ContextType = GraphQLContext, ParentType extends Reso
workspace?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<QueryWorkspaceArgs, 'id'>>;
workspaceBySlug?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType, RequireFields<QueryWorkspaceBySlugArgs, 'slug'>>;
workspaceInvite?: Resolver<Maybe<ResolversTypes['PendingWorkspaceCollaborator']>, ParentType, ContextType, Partial<QueryWorkspaceInviteArgs>>;
workspacePricingPlans?: Resolver<ResolversTypes['JSONObject'], ParentType, ContextType>;
workspaceSsoByEmail?: Resolver<Array<ResolversTypes['LimitedWorkspace']>, ParentType, ContextType, RequireFields<QueryWorkspaceSsoByEmailArgs, 'email'>>;
};
@@ -6653,6 +6789,7 @@ export type UserResolvers<ContextType = GraphQLContext, ParentType extends Resol
verified?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
versions?: Resolver<ResolversTypes['CountOnlyCollection'], ParentType, ContextType, RequireFields<UserVersionsArgs, 'authoredOnly' | 'limit'>>;
workspaceInvites?: Resolver<Array<ResolversTypes['PendingWorkspaceCollaborator']>, ParentType, ContextType>;
workspaceJoinRequests?: Resolver<Maybe<ResolversTypes['LimitedWorkspaceJoinRequestCollection']>, ParentType, ContextType, RequireFields<UserWorkspaceJoinRequestsArgs, 'limit'>>;
workspaces?: Resolver<ResolversTypes['WorkspaceCollection'], ParentType, ContextType, RequireFields<UserWorkspacesArgs, 'cursor' | 'limit'>>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -7058,6 +7195,8 @@ export type Resolvers<ContextType = GraphQLContext> = {
CommitCollection?: CommitCollectionResolvers<ContextType>;
CountOnlyCollection?: CountOnlyCollectionResolvers<ContextType>;
DateTime?: GraphQLScalarType;
DiscoverableWorkspaceCollaborator?: DiscoverableWorkspaceCollaboratorResolvers<ContextType>;
DiscoverableWorkspaceCollaboratorCollection?: DiscoverableWorkspaceCollaboratorCollectionResolvers<ContextType>;
FileUpload?: FileUploadResolvers<ContextType>;
GendoAIRender?: GendoAiRenderResolvers<ContextType>;
GendoAIRenderCollection?: GendoAiRenderCollectionResolvers<ContextType>;
@@ -7065,6 +7204,8 @@ export type Resolvers<ContextType = GraphQLContext> = {
LegacyCommentViewerData?: LegacyCommentViewerDataResolvers<ContextType>;
LimitedUser?: LimitedUserResolvers<ContextType>;
LimitedWorkspace?: LimitedWorkspaceResolvers<ContextType>;
LimitedWorkspaceJoinRequest?: LimitedWorkspaceJoinRequestResolvers<ContextType>;
LimitedWorkspaceJoinRequestCollection?: LimitedWorkspaceJoinRequestCollectionResolvers<ContextType>;
Model?: ModelResolvers<ContextType>;
ModelCollection?: ModelCollectionResolvers<ContextType>;
ModelMutations?: ModelMutationsResolvers<ContextType>;
@@ -40,6 +40,11 @@ import { getAdminUsersListCollectionFactory } from '@/modules/core/services/user
import { Resolvers } from '@/modules/core/graph/generated/graphql'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { getEventBus } from '@/modules/shared/services/eventBus'
import {
getMailchimpStatus,
getMailchimpOnboardingIds
} from '@/modules/shared/helpers/envHelper'
import { updateMailchimpMemberTags } from '@/modules/auth/services/mailchimp'
const getUser = legacyGetUserFactory({ db })
const getUserByEmail = legacyGetUserByEmailFactory({ db })
@@ -248,8 +253,30 @@ export = {
activeUserMutations: () => ({})
},
ActiveUserMutations: {
async finishOnboarding(_parent, _args, ctx) {
return await markOnboardingComplete(ctx.userId || '')
async finishOnboarding(_parent, args, ctx) {
const userId = ctx.userId
if (!userId) return false
const success = await markOnboardingComplete(userId)
// If onboarding was marked complete successfully and we have onboarding data
if (success && args.input && getMailchimpStatus()) {
try {
const user = await getUser(userId)
const { listId } = getMailchimpOnboardingIds()
await updateMailchimpMemberTags(user, listId, {
role: args.input?.role || undefined,
plans: args.input?.plans || undefined,
source: args.input?.source || undefined
})
} catch (error) {
// Log but don't fail the request
ctx.log.warn({ err: error }, 'Failed to update Mailchimp tags')
}
}
return success
},
async update(_parent, args, context) {
const newUser = await updateUserAndNotify(context.userId!, args.user)
@@ -21,11 +21,16 @@ import {
UserUpdateError,
UserValidationError
} from '@/modules/core/errors/user'
import { PasswordTooShortError, UserInputError } from '@/modules/core/errors/userinput'
import {
BlockedEmailDomainError,
PasswordTooShortError,
UserInputError
} from '@/modules/core/errors/userinput'
import { UserUpdateInput } from '@/modules/core/graph/generated/graphql'
import type { UserRecord } from '@/modules/core/helpers/userHelper'
import { sanitizeImageUrl } from '@/modules/shared/helpers/sanitization'
import {
blockedDomains,
isNullOrUndefined,
NullableKeysToOptional,
Roles,
@@ -48,6 +53,9 @@ import { DeleteAllUserInvites } from '@/modules/serverinvites/domain/operations'
import { GetServerInfo } from '@/modules/core/domain/server/operations'
import { EventBusEmit } from '@/modules/shared/services/eventBus'
import { UserEvents } from '@/modules/core/domain/users/events'
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
const { FF_NO_PERSONAL_EMAILS_ENABLED } = getFeatureFlags()
export const MINIMUM_PASSWORD_LENGTH = 8
@@ -163,6 +171,15 @@ export const createUserFactory =
if (!finalUser.email?.length) throw new UserInputError('E-mail address is required')
// Temporary experiment: require work emails for all new users
const isBlockedDomain = blockedDomains.includes(
finalUser.email.split('@')[1]?.toLowerCase()
)
const requireWorkDomain =
!user?.signUpContext?.isInvite && FF_NO_PERSONAL_EMAILS_ENABLED
if (requireWorkDomain && isBlockedDomain) throw new BlockedEmailDomainError()
let expectedRole = null
if (finalUser.role) {
const isValidRole = Object.values(Roles.Server).includes(finalUser.role)

Some files were not shown because too many files have changed in this diff Show More