feat(fe2 & server): saved views foundation (list & view) + bits n bobs (#5163)
* init db migration * WIP store view * create service call * WIP insertion * insert sort of works * moving code arounmd * creation tests * avoid duplicate entries * fixes from main * basic group retrieval works * group filtering works * WIP view listing * filter by acl * fixes + WIP single group retrieval * wip pivot * more pivot query fixes * tests fixed after pivot * views list tests * fixing test command * business plan only checks * more tests for coverage * .dts import fix * cli fix * anutha one * auth policy tests for business plan access * WIP saved views panel base * BE listing adjustments * WIP group rendering * group render done * WIP post create cache updates * listing fine? * my vs theirs * auto open * minor fixes * click load omg * nicely loading views * type fix * less spammy loading * another type fix: * more lint fix * test fix * codecov disable * moar coverage * fix sidebar flashin * more test coverage * more test cvoverage * minor adfjustments * adj * saved view wipe fixes * CSR viewer * more improvements * extra feature flag checks * lint fix * feature flags fix * more test fixes
This commit is contained in:
committed by
GitHub
parent
23e9cd31b9
commit
a6287fc06d
@@ -6,7 +6,7 @@ orbs:
|
||||
aliases:
|
||||
- &docker-node-image
|
||||
docker:
|
||||
- image: cimg/node:22.6.0
|
||||
- image: cimg/node:22.17.1
|
||||
|
||||
- &yarn
|
||||
run:
|
||||
|
||||
+1
-11
@@ -5,17 +5,7 @@ codecov:
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 70% #overall project/ repo coverage
|
||||
server:
|
||||
target: 70%
|
||||
flags:
|
||||
- server
|
||||
shared:
|
||||
target: 70%
|
||||
flags:
|
||||
- shared
|
||||
project: off
|
||||
patch:
|
||||
default:
|
||||
target: 90% #overall project/ repo coverage
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": "^22.6.0"
|
||||
"node": "^22.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn workspaces foreach --parallel --topological --verbose --worktree run build",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"url": "git+https://github.com/specklesystems/speckle-server.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.6.0"
|
||||
"node": "^22.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build:tsc:watch": "tsc -p ./tsconfig.build.json --watch",
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Speckle</title>
|
||||
<script>
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`
|
||||
const parts = value.split(`; ${name}=`)
|
||||
if (parts.length === 2) return parts.pop().split(';').shift()
|
||||
}
|
||||
const theme = getCookie('theme')
|
||||
|
||||
// Add 'dark' class to root html node, if dark theme
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark')
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
:root {
|
||||
--speckle-bg: #fafafa;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--speckle-bg: #101012;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--speckle-bg);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -98,6 +98,7 @@ import {
|
||||
import { useWorkspacePlanPrices } from '~/lib/billing/composables/prices'
|
||||
import { formatPrice, formatName } from '~/lib/billing/helpers/plan'
|
||||
import type { SetupContext } from 'vue'
|
||||
import { useFeatureFlags } from '~/lib/common/composables/env'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'onUpgradeClick'): void
|
||||
@@ -120,9 +121,15 @@ const isYearlyIntervalSelected = defineModel<boolean>('isYearlyIntervalSelected'
|
||||
|
||||
const slots: SetupContext['slots'] = useSlots()
|
||||
const { prices } = useWorkspacePlanPrices()
|
||||
const featureFlags = useFeatureFlags()
|
||||
|
||||
const planLimits = computed(
|
||||
() => WorkspacePlanConfigs({ featureFlags })[props.plan].limits
|
||||
)
|
||||
const planFeatures = computed(
|
||||
() => WorkspacePlanConfigs({ featureFlags })[props.plan].features
|
||||
)
|
||||
|
||||
const planLimits = computed(() => WorkspacePlanConfigs[props.plan].limits)
|
||||
const planFeatures = computed(() => WorkspacePlanConfigs[props.plan].features)
|
||||
const commonFeatures = shallowRef([
|
||||
{
|
||||
displayName: 'Unlimited members and guests',
|
||||
|
||||
@@ -68,6 +68,7 @@ import {
|
||||
BillingInterval,
|
||||
WorkspacePlanStatuses
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { useFeatureFlags } from '~/lib/common/composables/env'
|
||||
|
||||
graphql(`
|
||||
fragment WorkspaceBillingPage_Workspace on Workspace {
|
||||
@@ -91,6 +92,8 @@ graphql(`
|
||||
const route = useRoute()
|
||||
const slug = computed(() => (route.params.slug as string) || '')
|
||||
const isBillingIntegrationEnabled = useIsBillingIntegrationEnabled()
|
||||
const featureFlags = useFeatureFlags()
|
||||
|
||||
const { isFreePlan } = useWorkspacePlan(slug.value)
|
||||
const { result: workspaceResult } = useQuery(
|
||||
settingsWorkspaceBillingQuery,
|
||||
@@ -110,11 +113,12 @@ const showBillingAlert = computed(
|
||||
workspace.value?.plan?.status === WorkspacePlanStatuses.CancelationScheduled
|
||||
)
|
||||
const reachedPlanLimit = computed(() =>
|
||||
workspaceReachedPlanLimit(
|
||||
workspace.value?.plan?.name,
|
||||
workspace.value?.plan?.usage?.projectCount,
|
||||
workspace.value?.plan?.usage?.modelCount
|
||||
)
|
||||
workspaceReachedPlanLimit({
|
||||
plan: workspace.value?.plan?.name,
|
||||
projectCount: workspace.value?.plan?.usage?.projectCount,
|
||||
modelCount: workspace.value?.plan?.usage?.modelCount,
|
||||
featureFlags
|
||||
})
|
||||
)
|
||||
const showPricingInfo = computed(() => {
|
||||
if (!workspace.value?.plan?.name) return false
|
||||
|
||||
+3
-1
@@ -36,6 +36,7 @@ import type { BillingInterval } from '~/lib/common/generated/gql/graphql'
|
||||
import { useWorkspacePlan } from '~/lib/workspaces/composables/plan'
|
||||
import { useWorkspaceUsage } from '~/lib/workspaces/composables/usage'
|
||||
import { useMixpanel } from '~/lib/core/composables/mp'
|
||||
import { useFeatureFlags } from '~/lib/common/composables/env'
|
||||
|
||||
type AddonIncludedSelect = 'yes' | 'no'
|
||||
|
||||
@@ -59,6 +60,7 @@ const { hasUnlimitedAddon, plan, subscription, statusIsCanceled, seats } =
|
||||
useWorkspacePlan(props.slug)
|
||||
const mixpanel = useMixpanel()
|
||||
const { projectCount, modelCount } = useWorkspaceUsage(props.slug)
|
||||
const featureFlags = useFeatureFlags()
|
||||
|
||||
const showAddonSelect = ref<boolean>(true)
|
||||
const isLoading = ref<boolean>(false)
|
||||
@@ -78,7 +80,7 @@ const title = computed(() => {
|
||||
})
|
||||
|
||||
const usageExceedsNewPlanLimit = computed(() => {
|
||||
const limits = WorkspacePlanConfigs[props.plan].limits
|
||||
const limits = WorkspacePlanConfigs({ featureFlags })[props.plan].limits
|
||||
const modelLimit = limits.modelCount
|
||||
const projectLimit = limits.projectCount
|
||||
|
||||
|
||||
@@ -32,6 +32,16 @@
|
||||
<ChatBubbleLeftRightIcon class="h-4 w-4 md:h-5 md:w-5" />
|
||||
</ViewerControlsButtonToggle>
|
||||
|
||||
<!-- Saved views -->
|
||||
<ViewerControlsButtonToggle
|
||||
v-if="isSavedViewsEnabled"
|
||||
v-tippy="getShortcutDisplayText(shortcuts.ToggleSavedViews)"
|
||||
:active="activePanel === 'savedViews'"
|
||||
@click="toggleActivePanel('savedViews')"
|
||||
>
|
||||
<Camera class="h-4 w-4 md:h-5 md:w-5" />
|
||||
</ViewerControlsButtonToggle>
|
||||
|
||||
<!-- Automation runs -->
|
||||
<ViewerControlsButtonToggle
|
||||
v-if="allAutomationRuns.length !== 0"
|
||||
@@ -192,6 +202,12 @@
|
||||
<div><ViewerMeasurementsOptions @close="toggleMeasurements" /></div>
|
||||
</KeepAlive>
|
||||
</div>
|
||||
<div v-if="activePanel === 'savedViews'">
|
||||
<ViewerSavedViewsPanel
|
||||
v-if="isSavedViewsEnabled"
|
||||
@close="activePanel = 'none'"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="activePanel === 'models'">
|
||||
<KeepAlive>
|
||||
<div>
|
||||
@@ -287,6 +303,8 @@ import {
|
||||
} from '@vueuse/core'
|
||||
import { useFunctionRunsStatusSummary } from '~/lib/automate/composables/runStatus'
|
||||
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
|
||||
import { Camera } from 'lucide-vue-next'
|
||||
import { useAreSavedViewsEnabled } from '~/lib/viewer/composables/savedViews/general'
|
||||
|
||||
type ActivePanel =
|
||||
| 'none'
|
||||
@@ -297,6 +315,7 @@ type ActivePanel =
|
||||
| 'measurements'
|
||||
| 'gendo'
|
||||
| 'mobileOverflow'
|
||||
| 'savedViews'
|
||||
|
||||
type ActiveControl =
|
||||
| 'none'
|
||||
@@ -308,6 +327,7 @@ type ActiveControl =
|
||||
| 'explode'
|
||||
| 'settings'
|
||||
|
||||
const isSavedViewsEnabled = useAreSavedViewsEnabled()
|
||||
const isGendoEnabled = useIsGendoModuleEnabled()
|
||||
const { width: windowWidth } = useWindowSize()
|
||||
|
||||
@@ -427,6 +447,7 @@ registerShortcuts({
|
||||
ToggleMeasurements: () => toggleMeasurements(),
|
||||
ToggleProjection: () => trackAndtoggleProjection(),
|
||||
ToggleSectionBox: () => toggleSectionBox(),
|
||||
ToggleSavedViews: () => isSavedViewsEnabled && toggleActivePanel('savedViews'),
|
||||
ZoomExtentsOrSelection: () => trackAndzoomExtentsOrSelection()
|
||||
})
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div
|
||||
class="flex items-center h-full w-full pr-8 text-body-xs text-foreground font-medium"
|
||||
>
|
||||
<span class="truncate">
|
||||
<span class="truncate grow">
|
||||
<slot name="title"></slot>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<ViewerLayoutPanel @close="$emit('close')">
|
||||
<template #title>
|
||||
<div class="flex justify-between items-center">
|
||||
<div>Views</div>
|
||||
<div class="flex">
|
||||
<FormButton size="sm" color="subtle" :icon-left="Search" hide-text />
|
||||
<div v-tippy="canCreateViewOrGroup?.errorMessage">
|
||||
<FormButton
|
||||
size="sm"
|
||||
color="subtle"
|
||||
:icon-left="FolderPlus"
|
||||
hide-text
|
||||
name="addGroup"
|
||||
:disabled="!canCreateViewOrGroup?.authorized || isLoading"
|
||||
/>
|
||||
</div>
|
||||
<div v-tippy="canCreateViewOrGroup?.errorMessage">
|
||||
<FormButton
|
||||
size="sm"
|
||||
color="subtle"
|
||||
:icon-left="Plus"
|
||||
hide-text
|
||||
name="addView"
|
||||
:disabled="!canCreateViewOrGroup?.authorized || isLoading"
|
||||
@click="onAddView"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<FormSelectBase
|
||||
v-model="selectedViewsType"
|
||||
mount-menu-on-body
|
||||
label="Views Type"
|
||||
name="viewsType"
|
||||
button-style="simple"
|
||||
:menu-max-width="150"
|
||||
menu-open-direction="right"
|
||||
:allow-unset="false"
|
||||
:items="viewsTypeItems"
|
||||
>
|
||||
<template #nothing-selected>Views Type</template>
|
||||
<template #option="{ item }">
|
||||
<span>{{ viewsTypeLabels[item] }}</span>
|
||||
</template>
|
||||
<template #something-selected="{ value }">
|
||||
<span v-if="!isArray(value)" class="flex items-center gap-2">
|
||||
{{ viewsTypeLabels[value] }}
|
||||
</span>
|
||||
</template>
|
||||
</FormSelectBase>
|
||||
</template>
|
||||
<div class="text-body-sm">
|
||||
<ViewerSavedViewsPanelConnectorViews
|
||||
v-if="selectedViewsType === ViewsType.Connector"
|
||||
/>
|
||||
<ViewerSavedViewsPanelViews
|
||||
v-else
|
||||
v-model:selected-group-id="selectedGroupId"
|
||||
:views-type="selectedViewsType"
|
||||
/>
|
||||
</div>
|
||||
</ViewerLayoutPanel>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useMutationLoading } from '@vue/apollo-composable'
|
||||
import { isArray } from 'lodash-es'
|
||||
import { Search, FolderPlus, Plus } from 'lucide-vue-next'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { useCreateSavedView } from '~/lib/viewer/composables/savedViews/management'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
import { ViewsType, viewsTypeLabels } from '~/lib/viewer/helpers/savedViews'
|
||||
|
||||
graphql(`
|
||||
fragment ViewerSavedViewsPanel_Project on Project {
|
||||
id
|
||||
permissions {
|
||||
canCreateSavedView {
|
||||
...FullPermissionCheckResult
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const {
|
||||
resources: {
|
||||
response: { project }
|
||||
}
|
||||
} = useInjectedViewerState()
|
||||
const createSavedView = useCreateSavedView()
|
||||
const isLoading = useMutationLoading()
|
||||
|
||||
const selectedViewsType = ref<ViewsType>(ViewsType.All)
|
||||
const selectedGroupId = ref<string | null>(null)
|
||||
|
||||
const viewsTypeItems = computed((): ViewsType[] => [
|
||||
ViewsType.All,
|
||||
ViewsType.My,
|
||||
ViewsType.Connector
|
||||
])
|
||||
const canCreateViewOrGroup = computed(
|
||||
() => project.value?.permissions.canCreateSavedView
|
||||
)
|
||||
|
||||
const onAddView = async () => {
|
||||
if (isLoading.value) return
|
||||
const view = await createSavedView({})
|
||||
if (view) {
|
||||
// Auto-open the group that the view created to
|
||||
selectedGroupId.value = view.group.id
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>TODO: Connector Views</div>
|
||||
</template>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!-- eslint-disable vuejs-accessibility/click-events-have-key-events -->
|
||||
<!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
|
||||
<template>
|
||||
<div class="flex gap-2 p-2 w-full hover:bg-foundation-2 rounded" :view-id="view.id">
|
||||
<img
|
||||
v-keyboard-clickable
|
||||
:src="view.screenshot"
|
||||
alt="View screenshot"
|
||||
class="w-20 h-14 object-cover rounded border border-outline-3 bg-foundation-page cursor-pointer"
|
||||
@click="apply"
|
||||
/>
|
||||
<div class="flex flex-col gap-1 min-w-0">
|
||||
<div class="text-body-2xs font-medium text-foreground truncate grow-0">
|
||||
{{ view.name }}
|
||||
</div>
|
||||
<div class="text-body-2xs text-foreground-3 truncate">
|
||||
{{ view.author?.name }}
|
||||
</div>
|
||||
<div
|
||||
v-tippy="formattedFullDate(view.updatedAt)"
|
||||
class="text-body-2xs text-foreground-3 truncate"
|
||||
>
|
||||
{{ formattedRelativeDate(view.updatedAt) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { ViewerSavedViewsPanelView_SavedViewFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { useEventBus } from '~/lib/core/composables/eventBus'
|
||||
import { ViewerEventBusKeys } from '~/lib/viewer/helpers/eventBus'
|
||||
|
||||
graphql(`
|
||||
fragment ViewerSavedViewsPanelView_SavedView on SavedView {
|
||||
id
|
||||
name
|
||||
description
|
||||
screenshot
|
||||
author {
|
||||
id
|
||||
name
|
||||
}
|
||||
updatedAt
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
view: ViewerSavedViewsPanelView_SavedViewFragment
|
||||
}>()
|
||||
|
||||
const eventBus = useEventBus()
|
||||
|
||||
const apply = async () => {
|
||||
// Force update, even if the view id is already set
|
||||
// (in case this is a frustration click w/ the state not applying)
|
||||
eventBus.emit(ViewerEventBusKeys.UpdateSavedView, {
|
||||
viewId: props.view.id
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div v-if="isVeryFirstLoading" class="flex justify-center">
|
||||
<CommonLoadingIcon class="m-16" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<ViewerSavedViewsPanelViewsEmptyState v-if="!hasGroups" :type="emptyStateType" />
|
||||
<div v-else class="p-2">
|
||||
<ViewerSavedViewsPanelViewsGroup
|
||||
v-for="group in groups"
|
||||
:key="group.id"
|
||||
:group="group"
|
||||
:is-selected="group.id === selectedGroupId"
|
||||
:only-authored="viewsType === ViewsType.My"
|
||||
/>
|
||||
<InfiniteLoading
|
||||
v-if="groups.length"
|
||||
:settings="{ identifier }"
|
||||
hide-when-complete
|
||||
@infinite="onInfiniteLoad"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { omit } from 'lodash-es'
|
||||
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
import { ViewsType } from '~/lib/viewer/helpers/savedViews'
|
||||
|
||||
graphql(`
|
||||
fragment ViewerSavedViewsPanelViews_Project on Project {
|
||||
id
|
||||
savedViewGroups(input: $savedViewGroupsInput) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
...ViewerSavedViewsPanelViewsGroup_SavedViewGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const paginableGroupsQuery = graphql(`
|
||||
query ViewerSavedViewsPanelViews_Groups(
|
||||
$projectId: String!
|
||||
$savedViewGroupsInput: SavedViewGroupsInput!
|
||||
) {
|
||||
project(id: $projectId) {
|
||||
id
|
||||
...ViewerSavedViewsPanelViews_Project
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
defineProps<{
|
||||
viewsType: ViewsType
|
||||
}>()
|
||||
|
||||
const selectedGroupId = defineModel<string | null>('selectedGroupId', {
|
||||
required: true
|
||||
})
|
||||
|
||||
const {
|
||||
projectId,
|
||||
resources: {
|
||||
request: { resourceIdString }
|
||||
}
|
||||
} = useInjectedViewerState()
|
||||
|
||||
const search = ref('')
|
||||
|
||||
const {
|
||||
identifier,
|
||||
onInfiniteLoad,
|
||||
query: { result },
|
||||
isVeryFirstLoading
|
||||
} = usePaginatedQuery({
|
||||
query: paginableGroupsQuery,
|
||||
baseVariables: computed(() => ({
|
||||
projectId: projectId.value,
|
||||
savedViewGroupsInput: {
|
||||
limit: 1,
|
||||
resourceIdString: resourceIdString.value,
|
||||
cursor: null as null | string,
|
||||
search: search.value?.trim() || null
|
||||
}
|
||||
})),
|
||||
resolveKey: (vars) => ({
|
||||
projectId: vars.projectId,
|
||||
savedViewGroupsInput: omit(vars.savedViewGroupsInput, ['cursor'])
|
||||
}),
|
||||
resolveCurrentResult: (res) => res?.project.savedViewGroups,
|
||||
resolveNextPageVariables: (baseVars, cursor) => ({
|
||||
...baseVars,
|
||||
savedViewGroupsInput: {
|
||||
...baseVars.savedViewGroupsInput,
|
||||
cursor
|
||||
}
|
||||
}),
|
||||
resolveCursorFromVariables: (vars) => vars.savedViewGroupsInput.cursor
|
||||
})
|
||||
|
||||
const hasGroups = computed(
|
||||
() => (result.value?.project.savedViewGroups.items.length || 0) > 0
|
||||
)
|
||||
const isSearch = computed(() => search.value?.trim().length > 0)
|
||||
const emptyStateType = computed(() => (isSearch.value ? 'search' : 'base'))
|
||||
|
||||
const groups = computed(() => {
|
||||
return result.value?.project.savedViewGroups.items || []
|
||||
})
|
||||
|
||||
watch(
|
||||
groups,
|
||||
(newGroups) => {
|
||||
if (newGroups.length && !selectedGroupId.value) {
|
||||
selectedGroupId.value = newGroups[0].id
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-8 items-center my-16">
|
||||
<ViewerSavedViewsPanelViewsEmptyStateImage />
|
||||
<div class="text-foreground-2">{{ message }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
type?: 'base' | 'search'
|
||||
}>(),
|
||||
{
|
||||
type: 'base'
|
||||
}
|
||||
)
|
||||
|
||||
const message = computed(() => {
|
||||
if (props.type === 'search') {
|
||||
return 'No saved scenes match your search criteria'
|
||||
}
|
||||
return 'There are no saved scenes yet'
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<LayoutDisclosure v-model:open="open" :title="group.title" lazy-load>
|
||||
<div>
|
||||
<div v-if="isVeryFirstLoading" class="flex justify-center">
|
||||
<CommonLoadingIcon class="m-4" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
v-if="views.length"
|
||||
class="flex flex-col gap-3 max-h-64 overflow-y-auto simple-scrollbar"
|
||||
>
|
||||
<ViewerSavedViewsPanelView
|
||||
v-for="view in views"
|
||||
:key="view.id"
|
||||
:view="view"
|
||||
></ViewerSavedViewsPanelView>
|
||||
</div>
|
||||
<InfiniteLoading
|
||||
v-if="views.length"
|
||||
:settings="{ identifier }"
|
||||
hide-when-complete
|
||||
@infinite="onInfiniteLoad"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutDisclosure>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { omit } from 'lodash-es'
|
||||
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
|
||||
graphql(`
|
||||
fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {
|
||||
id
|
||||
title
|
||||
}
|
||||
`)
|
||||
|
||||
graphql(`
|
||||
fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated on SavedViewGroup {
|
||||
id
|
||||
views(input: $savedViewsInput) {
|
||||
cursor
|
||||
totalCount
|
||||
items {
|
||||
id
|
||||
...ViewerSavedViewsPanelView_SavedView
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const viewsQuery = graphql(`
|
||||
query ViewerSavedViewsPanelViewsGroup_Views(
|
||||
$projectId: String!
|
||||
$groupId: ID!
|
||||
$savedViewsInput: SavedViewGroupViewsInput!
|
||||
) {
|
||||
project(id: $projectId) {
|
||||
id
|
||||
savedViewGroup(id: $groupId) {
|
||||
id
|
||||
...ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
group: ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragment
|
||||
search?: string
|
||||
onlyAuthored?: boolean
|
||||
isSelected?: boolean
|
||||
}>()
|
||||
|
||||
const { projectId } = useInjectedViewerState()
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
const {
|
||||
identifier,
|
||||
onInfiniteLoad,
|
||||
query: { result },
|
||||
isVeryFirstLoading
|
||||
} = usePaginatedQuery({
|
||||
query: viewsQuery,
|
||||
options: {
|
||||
enabled: open
|
||||
},
|
||||
baseVariables: computed(() => ({
|
||||
projectId: projectId.value,
|
||||
groupId: props.group.id,
|
||||
savedViewsInput: {
|
||||
limit: 10,
|
||||
cursor: null as null | string,
|
||||
search: props.search?.trim() || null,
|
||||
onlyAuthored: props.onlyAuthored
|
||||
}
|
||||
})),
|
||||
resolveKey: (vars) => ({
|
||||
projectId: vars.projectId,
|
||||
groupId: vars.groupId,
|
||||
savedViewsInput: omit(vars.savedViewsInput, ['cursor'])
|
||||
}),
|
||||
resolveCurrentResult: (res) => res?.project.savedViewGroup.views,
|
||||
resolveNextPageVariables: (baseVars, cursor) => ({
|
||||
...baseVars,
|
||||
savedViewsInput: {
|
||||
...baseVars.savedViewsInput,
|
||||
cursor
|
||||
}
|
||||
}),
|
||||
resolveCursorFromVariables: (vars) => vars.savedViewsInput.cursor
|
||||
})
|
||||
|
||||
const views = computed(() => result.value?.project.savedViewGroup.views.items || [])
|
||||
|
||||
watch(
|
||||
() => props.isSelected,
|
||||
(isSelected) => {
|
||||
if (isSelected) {
|
||||
open.value = true
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<svg
|
||||
v-if="isLightTheme"
|
||||
width="194"
|
||||
height="141"
|
||||
viewBox="0 0 194 141"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="0.781312"
|
||||
y="0.236562"
|
||||
width="136.25"
|
||||
height="102.5"
|
||||
rx="5.5"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 -0.105141 58.9668)"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<rect
|
||||
x="0.781312"
|
||||
y="0.236562"
|
||||
width="136.25"
|
||||
height="102.5"
|
||||
rx="5.5"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 -0.105141 58.9668)"
|
||||
stroke="#C4C4C4"
|
||||
/>
|
||||
<g clip-path="url(#clip0_460_265265)">
|
||||
<path
|
||||
d="M70.4361 42.9116L88.1415 37.0098C90.1203 36.3504 92.7625 37.1508 94.0431 38.7972C95.6629 40.8795 99.005 41.891 101.508 41.0567L114.418 36.7534C117.415 35.7543 121.418 36.9668 123.358 39.4608L145.637 68.1056C147.577 70.5996 146.719 73.4311 143.722 74.4303L83.9661 94.349C80.9687 95.3479 76.9666 94.1362 75.0268 91.6424L52.7469 62.9967C50.8075 60.5028 51.6647 57.6712 54.6618 56.672L67.5719 52.3687C70.0746 51.5344 70.7911 49.1701 69.1717 47.0877C67.891 45.4412 68.4572 43.5712 70.4361 42.9116Z"
|
||||
fill="white"
|
||||
stroke="#C4C4C4"
|
||||
/>
|
||||
<circle
|
||||
cx="19.8295"
|
||||
cy="19.8295"
|
||||
r="19.3295"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 67.3618 55.0841)"
|
||||
fill="#FAFAFA"
|
||||
stroke="#C4C4C4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<circle
|
||||
cx="13.6085"
|
||||
cy="13.6085"
|
||||
r="13.1085"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 77.0829 58.0275)"
|
||||
fill="#F5F5F5"
|
||||
stroke="#C4C4C4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M91.8971 58.3619C88.8257 60.0022 87.9419 63.2331 89.7792 66.2916"
|
||||
stroke="#C4C4C4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="0.781312"
|
||||
y="0.236562"
|
||||
width="136.25"
|
||||
height="102.5"
|
||||
rx="5.5"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 -0.105141 43.9669)"
|
||||
stroke="#C4C4C4"
|
||||
stroke-dasharray="3 4"
|
||||
/>
|
||||
<defs>
|
||||
<clipPath id="clip0_460_265265">
|
||||
<rect
|
||||
width="137.25"
|
||||
height="103.5"
|
||||
rx="6"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 0 43.67)"
|
||||
fill="white"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
width="194"
|
||||
height="141"
|
||||
viewBox="0 0 194 141"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="0.781312"
|
||||
y="0.236562"
|
||||
width="136.25"
|
||||
height="102.5"
|
||||
rx="5.5"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 -0.105141 58.9668)"
|
||||
fill="#191A22"
|
||||
/>
|
||||
<rect
|
||||
x="0.781312"
|
||||
y="0.236562"
|
||||
width="136.25"
|
||||
height="102.5"
|
||||
rx="5.5"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 -0.105141 58.9668)"
|
||||
stroke="#434559"
|
||||
/>
|
||||
<g clip-path="url(#clip0_864_35861)">
|
||||
<path
|
||||
d="M70.4361 42.9116L88.1415 37.0098C90.1203 36.3504 92.7625 37.1508 94.0431 38.7972C95.6629 40.8795 99.005 41.891 101.508 41.0567L114.418 36.7534C117.415 35.7543 121.418 36.9668 123.358 39.4608L145.637 68.1056C147.577 70.5996 146.719 73.4311 143.722 74.4303L83.9661 94.349C80.9687 95.3479 76.9666 94.1362 75.0268 91.6424L52.7469 62.9967C50.8075 60.5028 51.6647 57.6712 54.6618 56.672L67.5719 52.3687C70.0746 51.5344 70.7911 49.1701 69.1717 47.0877C67.891 45.4412 68.4572 43.5712 70.4361 42.9116Z"
|
||||
fill="#15161C"
|
||||
stroke="#434559"
|
||||
/>
|
||||
<circle
|
||||
cx="19.8295"
|
||||
cy="19.8295"
|
||||
r="19.3295"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 67.3618 55.0841)"
|
||||
fill="#101012"
|
||||
stroke="#434559"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<circle
|
||||
cx="13.6085"
|
||||
cy="13.6085"
|
||||
r="13.1085"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 77.0829 58.0275)"
|
||||
fill="#191A22"
|
||||
stroke="#434559"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M91.8971 58.3619C88.8257 60.0022 87.9419 63.2331 89.7792 66.2916"
|
||||
stroke="#434559"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="0.781312"
|
||||
y="0.236562"
|
||||
width="136.25"
|
||||
height="102.5"
|
||||
rx="5.5"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 -0.105141 43.9669)"
|
||||
stroke="#434559"
|
||||
stroke-dasharray="3 4"
|
||||
/>
|
||||
<defs>
|
||||
<clipPath id="clip0_864_35861">
|
||||
<rect
|
||||
width="137.25"
|
||||
height="103.5"
|
||||
rx="6"
|
||||
transform="matrix(0.948683 -0.316228 0.613941 0.789352 0 43.67)"
|
||||
fill="white"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useTheme } from '~/lib/core/composables/theme'
|
||||
|
||||
const { isLightTheme } = useTheme()
|
||||
</script>
|
||||
@@ -68,7 +68,6 @@ import {
|
||||
ArrowTopRightOnSquareIcon
|
||||
} from '@heroicons/vue/24/solid'
|
||||
import { FunnelIcon as FunnelIconOutline } from '@heroicons/vue/24/outline'
|
||||
|
||||
import { onKeyStroke } from '@vueuse/core'
|
||||
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
|
||||
import { getTargetObjectIds } from '~~/lib/object-sidebar/helpers'
|
||||
@@ -205,36 +204,30 @@ onKeyStroke('Escape', () => {
|
||||
})
|
||||
|
||||
watch(
|
||||
() => objects.value.length,
|
||||
(newLength) => {
|
||||
// Dont open sidebar if a comment is open
|
||||
if (newLength !== 0 && !focusedThreadId.value) {
|
||||
sidebarOpen.value = true
|
||||
} else if (newLength === 0) {
|
||||
[
|
||||
() => objects.value.length,
|
||||
() => focusedThreadId.value,
|
||||
() => threads.openThread.newThreadEditor.value,
|
||||
() => isSmallerOrEqualSm.value
|
||||
],
|
||||
([objLen, threadId, isNewThreadEditorOpen, isSmSm]) => {
|
||||
// Close sidebar if a thread is focused
|
||||
if (threadId) {
|
||||
sidebarOpen.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Close sidebar when a new thread is being added and screen is smaller than md breakpoint
|
||||
watch(
|
||||
() => threads.openThread.newThreadEditor.value,
|
||||
(isNewThreadEditorOpen) => {
|
||||
if (isNewThreadEditorOpen && isSmallerOrEqualSm.value) {
|
||||
// Close sidebar if new thread editor is open and screen is small
|
||||
if (isNewThreadEditorOpen && isSmSm) {
|
||||
sidebarOpen.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => focusedThreadId.value,
|
||||
(newThreadId) => {
|
||||
if (newThreadId) {
|
||||
// If a thread is focused, close the sidebar
|
||||
sidebarOpen.value = false
|
||||
} else if (objects.value.length > 0) {
|
||||
// If no thread is focused and we have objects selected, open the sidebar
|
||||
// Open sidebar if objects are selected and no thread is focused
|
||||
if (objLen !== 0 && !threadId) {
|
||||
sidebarOpen.value = true
|
||||
} else if (objLen === 0) {
|
||||
sidebarOpen.value = false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import { settingsWorkspaceRoutes } from '~/lib/common/helpers/route'
|
||||
import { formatName } from '~/lib/billing/helpers/plan'
|
||||
import { useMixpanel } from '~/lib/core/composables/mp'
|
||||
import { useFeatureFlags } from '~/lib/common/composables/env'
|
||||
|
||||
const props = defineProps<{
|
||||
workspaceSlug: string
|
||||
@@ -39,10 +40,11 @@ const dialogOpen = defineModel<boolean>('open', {
|
||||
})
|
||||
|
||||
const mixpanel = useMixpanel()
|
||||
const featureFlags = useFeatureFlags()
|
||||
|
||||
const planConfig = computed(() => {
|
||||
if (!props.plan) return null
|
||||
return WorkspacePlanConfigs[props.plan]
|
||||
return WorkspacePlanConfigs({ featureFlags })[props.plan]
|
||||
})
|
||||
|
||||
const explorePlansButton: LayoutDialogButton = {
|
||||
|
||||
@@ -6,5 +6,6 @@ export const permissionCheckResultFragment = graphql(`
|
||||
code
|
||||
message
|
||||
payload
|
||||
errorMessage
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export const useFeatureFlags = () => {
|
||||
const config = useRuntimeConfig()
|
||||
return config.public
|
||||
}
|
||||
@@ -165,6 +165,13 @@ type Documents = {
|
||||
"\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 ViewerResourcesPersonalLimitAlert_Project on Project {\n id\n ...WorkspaceMoveProject_Project\n }\n": typeof types.ViewerResourcesPersonalLimitAlert_ProjectFragmentDoc,
|
||||
"\n fragment ViewerResourcesWorkspaceLimitAlert_Workspace on Workspace {\n id\n slug\n }\n": typeof types.ViewerResourcesWorkspaceLimitAlert_WorkspaceFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanel_Project on Project {\n id\n permissions {\n canCreateSavedView {\n ...FullPermissionCheckResult\n }\n }\n }\n": typeof types.ViewerSavedViewsPanel_ProjectFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n author {\n id\n name\n }\n updatedAt\n }\n": typeof types.ViewerSavedViewsPanelView_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViews_Project on Project {\n id\n savedViewGroups(input: $savedViewGroupsInput) {\n totalCount\n cursor\n items {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n": typeof types.ViewerSavedViewsPanelViews_ProjectFragmentDoc,
|
||||
"\n query ViewerSavedViewsPanelViews_Groups(\n $projectId: String!\n $savedViewGroupsInput: SavedViewGroupsInput!\n ) {\n project(id: $projectId) {\n id\n ...ViewerSavedViewsPanelViews_Project\n }\n }\n": typeof types.ViewerSavedViewsPanelViews_GroupsDocument,
|
||||
"\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n title\n }\n": typeof types.ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated on SavedViewGroup {\n id\n views(input: $savedViewsInput) {\n cursor\n totalCount\n items {\n id\n ...ViewerSavedViewsPanelView_SavedView\n }\n }\n }\n": typeof types.ViewerSavedViewsPanelViewsGroup_SavedViewGroup_PaginatedFragmentDoc,
|
||||
"\n query ViewerSavedViewsPanelViewsGroup_Views(\n $projectId: String!\n $groupId: ID!\n $savedViewsInput: SavedViewGroupViewsInput!\n ) {\n project(id: $projectId) {\n id\n savedViewGroup(id: $groupId) {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated\n }\n }\n }\n": typeof types.ViewerSavedViewsPanelViewsGroup_ViewsDocument,
|
||||
"\n fragment WorkspaceAddProjectMenu_Workspace on Workspace {\n id\n name\n slug\n role\n plan {\n name\n }\n permissions {\n canCreateProject {\n ...FullPermissionCheckResult\n }\n canMoveProjectToWorkspace {\n ...FullPermissionCheckResult\n }\n }\n ...ProjectsAdd_Workspace\n ...WorkspaceMoveProject_Workspace\n ...UseCanCreateWorkspaceProject_Workspace\n ...UseCanMoveProjectIntoWorkspace_Workspace\n }\n": typeof types.WorkspaceAddProjectMenu_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceDashboard_Workspace on Workspace {\n ...WorkspaceSidebarMembers_Workspace\n ...WorkspaceDashboardHeader_Workspace\n ...WorkspaceDashboardProjectList_Workspace\n ...BillingActions_Workspace\n id\n name\n role\n creationState {\n completed\n state\n }\n }\n": typeof types.WorkspaceDashboard_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceDashboardHeader_Workspace on Workspace {\n ...WorkspaceSidebarMembers_Workspace\n ...WorkspaceAddProjectMenu_Workspace\n ...BillingAlert_Workspace\n id\n role\n }\n": typeof types.WorkspaceDashboardHeader_WorkspaceFragmentDoc,
|
||||
@@ -190,7 +197,7 @@ type Documents = {
|
||||
"\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 email\n verified\n primary\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 ...ProjectsAdd_User\n }\n }\n": typeof types.ActiveUserMainMetadataDocument,
|
||||
"\n query ActiveUserProjectsToMove($filter: UserProjectsFilter) {\n activeUser {\n id\n projects(filter: $filter) {\n totalCount\n }\n }\n }\n": typeof types.ActiveUserProjectsToMoveDocument,
|
||||
"\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n }\n": typeof types.FullPermissionCheckResultFragmentDoc,
|
||||
"\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n errorMessage\n }\n": typeof types.FullPermissionCheckResultFragmentDoc,
|
||||
"\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,
|
||||
@@ -394,6 +401,8 @@ type Documents = {
|
||||
"\n fragment ViewerCommentBubblesData on Comment {\n id\n viewedAt\n viewerState\n }\n": typeof types.ViewerCommentBubblesDataFragmentDoc,
|
||||
"\n fragment UseCheckViewerCommentingAccess_Project on Project {\n id\n permissions {\n canCreateComment {\n ...FullPermissionCheckResult\n }\n }\n }\n": typeof types.UseCheckViewerCommentingAccess_ProjectFragmentDoc,
|
||||
"\n fragment UseLoadLatestVersion_Project on Project {\n id\n workspace {\n slug\n }\n }\n": typeof types.UseLoadLatestVersion_ProjectFragmentDoc,
|
||||
"\n mutation CreateSavedView($input: CreateSavedViewInput!) {\n projectMutations {\n savedViewMutations {\n createView(input: $input) {\n id\n ...ViewerSavedViewsPanelView_SavedView\n group {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n }\n }\n": typeof types.CreateSavedViewDocument,
|
||||
"\n fragment UseViewerSavedViewSetup_SavedView on SavedView {\n id\n viewerState\n }\n": typeof types.UseViewerSavedViewSetup_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerCommentThread on Comment {\n ...ViewerCommentsListItem\n ...ViewerCommentBubblesData\n ...ViewerCommentsReplyItem\n ...ViewerCommentThreadData\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,
|
||||
@@ -401,8 +410,9 @@ type Documents = {
|
||||
"\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 ...UseCheckViewerCommentingAccess_Project\n ...UseViewerUserActivityBroadcasting_Project\n ...ViewerGendoPanel_Project\n ...ViewerResourcesLimitAlert_Project\n }\n }\n": typeof types.ViewerLoadedResourcesDocument,
|
||||
"\n query ProjectViewerResources(\n $projectId: String!\n $resourceUrlString: String!\n $savedViewId: String\n ) {\n project(id: $projectId) {\n id\n viewerResources(resourceIdString: $resourceUrlString, savedViewId: $savedViewId) {\n identifier\n items {\n modelId\n versionId\n objectId\n }\n }\n }\n }\n": typeof types.ProjectViewerResourcesDocument,
|
||||
"\n query ViewerActiveSavedView($projectId: String!, $savedViewId: ID!) {\n project(id: $projectId) {\n id\n savedView(id: $savedViewId) {\n id\n ...UseViewerSavedViewSetup_SavedView\n }\n }\n }\n": typeof types.ViewerActiveSavedViewDocument,
|
||||
"\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 ...UseCheckViewerCommentingAccess_Project\n ...UseViewerUserActivityBroadcasting_Project\n ...ViewerGendoPanel_Project\n ...ViewerResourcesLimitAlert_Project\n ...ViewerSavedViewsPanel_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,
|
||||
@@ -641,6 +651,13 @@ const documents: Documents = {
|
||||
"\n fragment ViewerModelVersionCardItem on Version {\n id\n message\n referencedObject\n sourceApplication\n createdAt\n previewUrl\n authorUser {\n ...LimitedUserAvatar\n }\n }\n": types.ViewerModelVersionCardItemFragmentDoc,
|
||||
"\n fragment ViewerResourcesPersonalLimitAlert_Project on Project {\n id\n ...WorkspaceMoveProject_Project\n }\n": types.ViewerResourcesPersonalLimitAlert_ProjectFragmentDoc,
|
||||
"\n fragment ViewerResourcesWorkspaceLimitAlert_Workspace on Workspace {\n id\n slug\n }\n": types.ViewerResourcesWorkspaceLimitAlert_WorkspaceFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanel_Project on Project {\n id\n permissions {\n canCreateSavedView {\n ...FullPermissionCheckResult\n }\n }\n }\n": types.ViewerSavedViewsPanel_ProjectFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n author {\n id\n name\n }\n updatedAt\n }\n": types.ViewerSavedViewsPanelView_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViews_Project on Project {\n id\n savedViewGroups(input: $savedViewGroupsInput) {\n totalCount\n cursor\n items {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n": types.ViewerSavedViewsPanelViews_ProjectFragmentDoc,
|
||||
"\n query ViewerSavedViewsPanelViews_Groups(\n $projectId: String!\n $savedViewGroupsInput: SavedViewGroupsInput!\n ) {\n project(id: $projectId) {\n id\n ...ViewerSavedViewsPanelViews_Project\n }\n }\n": types.ViewerSavedViewsPanelViews_GroupsDocument,
|
||||
"\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n title\n }\n": types.ViewerSavedViewsPanelViewsGroup_SavedViewGroupFragmentDoc,
|
||||
"\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated on SavedViewGroup {\n id\n views(input: $savedViewsInput) {\n cursor\n totalCount\n items {\n id\n ...ViewerSavedViewsPanelView_SavedView\n }\n }\n }\n": types.ViewerSavedViewsPanelViewsGroup_SavedViewGroup_PaginatedFragmentDoc,
|
||||
"\n query ViewerSavedViewsPanelViewsGroup_Views(\n $projectId: String!\n $groupId: ID!\n $savedViewsInput: SavedViewGroupViewsInput!\n ) {\n project(id: $projectId) {\n id\n savedViewGroup(id: $groupId) {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated\n }\n }\n }\n": types.ViewerSavedViewsPanelViewsGroup_ViewsDocument,
|
||||
"\n fragment WorkspaceAddProjectMenu_Workspace on Workspace {\n id\n name\n slug\n role\n plan {\n name\n }\n permissions {\n canCreateProject {\n ...FullPermissionCheckResult\n }\n canMoveProjectToWorkspace {\n ...FullPermissionCheckResult\n }\n }\n ...ProjectsAdd_Workspace\n ...WorkspaceMoveProject_Workspace\n ...UseCanCreateWorkspaceProject_Workspace\n ...UseCanMoveProjectIntoWorkspace_Workspace\n }\n": types.WorkspaceAddProjectMenu_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceDashboard_Workspace on Workspace {\n ...WorkspaceSidebarMembers_Workspace\n ...WorkspaceDashboardHeader_Workspace\n ...WorkspaceDashboardProjectList_Workspace\n ...BillingActions_Workspace\n id\n name\n role\n creationState {\n completed\n state\n }\n }\n": types.WorkspaceDashboard_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceDashboardHeader_Workspace on Workspace {\n ...WorkspaceSidebarMembers_Workspace\n ...WorkspaceAddProjectMenu_Workspace\n ...BillingAlert_Workspace\n id\n role\n }\n": types.WorkspaceDashboardHeader_WorkspaceFragmentDoc,
|
||||
@@ -666,7 +683,7 @@ const documents: Documents = {
|
||||
"\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 email\n verified\n primary\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 ...ProjectsAdd_User\n }\n }\n": types.ActiveUserMainMetadataDocument,
|
||||
"\n query ActiveUserProjectsToMove($filter: UserProjectsFilter) {\n activeUser {\n id\n projects(filter: $filter) {\n totalCount\n }\n }\n }\n": types.ActiveUserProjectsToMoveDocument,
|
||||
"\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n }\n": types.FullPermissionCheckResultFragmentDoc,
|
||||
"\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n errorMessage\n }\n": types.FullPermissionCheckResultFragmentDoc,
|
||||
"\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,
|
||||
@@ -870,6 +887,8 @@ const documents: Documents = {
|
||||
"\n fragment ViewerCommentBubblesData on Comment {\n id\n viewedAt\n viewerState\n }\n": types.ViewerCommentBubblesDataFragmentDoc,
|
||||
"\n fragment UseCheckViewerCommentingAccess_Project on Project {\n id\n permissions {\n canCreateComment {\n ...FullPermissionCheckResult\n }\n }\n }\n": types.UseCheckViewerCommentingAccess_ProjectFragmentDoc,
|
||||
"\n fragment UseLoadLatestVersion_Project on Project {\n id\n workspace {\n slug\n }\n }\n": types.UseLoadLatestVersion_ProjectFragmentDoc,
|
||||
"\n mutation CreateSavedView($input: CreateSavedViewInput!) {\n projectMutations {\n savedViewMutations {\n createView(input: $input) {\n id\n ...ViewerSavedViewsPanelView_SavedView\n group {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n }\n }\n": types.CreateSavedViewDocument,
|
||||
"\n fragment UseViewerSavedViewSetup_SavedView on SavedView {\n id\n viewerState\n }\n": types.UseViewerSavedViewSetup_SavedViewFragmentDoc,
|
||||
"\n fragment ViewerCommentThread on Comment {\n ...ViewerCommentsListItem\n ...ViewerCommentBubblesData\n ...ViewerCommentsReplyItem\n ...ViewerCommentThreadData\n }\n": 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": 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": types.BroadcastViewerUserActivityDocument,
|
||||
@@ -877,8 +896,9 @@ const documents: Documents = {
|
||||
"\n mutation CreateCommentThread($input: CreateCommentInput!) {\n commentMutations {\n create(input: $input) {\n ...ViewerCommentThread\n }\n }\n }\n": types.CreateCommentThreadDocument,
|
||||
"\n mutation CreateCommentReply($input: CreateCommentReplyInput!) {\n commentMutations {\n reply(input: $input) {\n ...ViewerCommentsReplyItem\n }\n }\n }\n": types.CreateCommentReplyDocument,
|
||||
"\n mutation ArchiveComment($input: ArchiveCommentInput!) {\n commentMutations {\n archive(input: $input)\n }\n }\n": 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": 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 ...UseCheckViewerCommentingAccess_Project\n ...UseViewerUserActivityBroadcasting_Project\n ...ViewerGendoPanel_Project\n ...ViewerResourcesLimitAlert_Project\n }\n }\n": types.ViewerLoadedResourcesDocument,
|
||||
"\n query ProjectViewerResources(\n $projectId: String!\n $resourceUrlString: String!\n $savedViewId: String\n ) {\n project(id: $projectId) {\n id\n viewerResources(resourceIdString: $resourceUrlString, savedViewId: $savedViewId) {\n identifier\n items {\n modelId\n versionId\n objectId\n }\n }\n }\n }\n": types.ProjectViewerResourcesDocument,
|
||||
"\n query ViewerActiveSavedView($projectId: String!, $savedViewId: ID!) {\n project(id: $projectId) {\n id\n savedView(id: $savedViewId) {\n id\n ...UseViewerSavedViewSetup_SavedView\n }\n }\n }\n": types.ViewerActiveSavedViewDocument,
|
||||
"\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 ...UseCheckViewerCommentingAccess_Project\n ...UseViewerUserActivityBroadcasting_Project\n ...ViewerGendoPanel_Project\n ...ViewerResourcesLimitAlert_Project\n ...ViewerSavedViewsPanel_Project\n }\n }\n": 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": 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": 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": types.ViewerLoadedThreadsDocument,
|
||||
@@ -1584,6 +1604,34 @@ export function graphql(source: "\n fragment ViewerResourcesPersonalLimitAlert_
|
||||
* 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 ViewerResourcesWorkspaceLimitAlert_Workspace on Workspace {\n id\n slug\n }\n"): (typeof documents)["\n fragment ViewerResourcesWorkspaceLimitAlert_Workspace on Workspace {\n id\n slug\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 ViewerSavedViewsPanel_Project on Project {\n id\n permissions {\n canCreateSavedView {\n ...FullPermissionCheckResult\n }\n }\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanel_Project on Project {\n id\n permissions {\n canCreateSavedView {\n ...FullPermissionCheckResult\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 ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n author {\n id\n name\n }\n updatedAt\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelView_SavedView on SavedView {\n id\n name\n description\n screenshot\n author {\n id\n name\n }\n updatedAt\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 ViewerSavedViewsPanelViews_Project on Project {\n id\n savedViewGroups(input: $savedViewGroupsInput) {\n totalCount\n cursor\n items {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelViews_Project on Project {\n id\n savedViewGroups(input: $savedViewGroupsInput) {\n totalCount\n cursor\n items {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query ViewerSavedViewsPanelViews_Groups(\n $projectId: String!\n $savedViewGroupsInput: SavedViewGroupsInput!\n ) {\n project(id: $projectId) {\n id\n ...ViewerSavedViewsPanelViews_Project\n }\n }\n"): (typeof documents)["\n query ViewerSavedViewsPanelViews_Groups(\n $projectId: String!\n $savedViewGroupsInput: SavedViewGroupsInput!\n ) {\n project(id: $projectId) {\n id\n ...ViewerSavedViewsPanelViews_Project\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 ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n title\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n title\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 ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated on SavedViewGroup {\n id\n views(input: $savedViewsInput) {\n cursor\n totalCount\n items {\n id\n ...ViewerSavedViewsPanelView_SavedView\n }\n }\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated on SavedViewGroup {\n id\n views(input: $savedViewsInput) {\n cursor\n totalCount\n items {\n id\n ...ViewerSavedViewsPanelView_SavedView\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query ViewerSavedViewsPanelViewsGroup_Views(\n $projectId: String!\n $groupId: ID!\n $savedViewsInput: SavedViewGroupViewsInput!\n ) {\n project(id: $projectId) {\n id\n savedViewGroup(id: $groupId) {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated\n }\n }\n }\n"): (typeof documents)["\n query ViewerSavedViewsPanelViewsGroup_Views(\n $projectId: String!\n $groupId: ID!\n $savedViewsInput: SavedViewGroupViewsInput!\n ) {\n project(id: $projectId) {\n id\n savedViewGroup(id: $groupId) {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup_Paginated\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1687,7 +1735,7 @@ export function graphql(source: "\n query ActiveUserProjectsToMove($filter: Use
|
||||
/**
|
||||
* 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 FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n }\n"): (typeof documents)["\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n }\n"];
|
||||
export function graphql(source: "\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n errorMessage\n }\n"): (typeof documents)["\n fragment FullPermissionCheckResult on PermissionCheckResult {\n authorized\n code\n message\n payload\n errorMessage\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -2500,6 +2548,14 @@ export function graphql(source: "\n fragment UseCheckViewerCommentingAccess_Pro
|
||||
* 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 UseLoadLatestVersion_Project on Project {\n id\n workspace {\n slug\n }\n }\n"): (typeof documents)["\n fragment UseLoadLatestVersion_Project on Project {\n id\n workspace {\n slug\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation CreateSavedView($input: CreateSavedViewInput!) {\n projectMutations {\n savedViewMutations {\n createView(input: $input) {\n id\n ...ViewerSavedViewsPanelView_SavedView\n group {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n }\n }\n"): (typeof documents)["\n mutation CreateSavedView($input: CreateSavedViewInput!) {\n projectMutations {\n savedViewMutations {\n createView(input: $input) {\n id\n ...ViewerSavedViewsPanelView_SavedView\n group {\n id\n ...ViewerSavedViewsPanelViewsGroup_SavedViewGroup\n }\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment UseViewerSavedViewSetup_SavedView on SavedView {\n id\n viewerState\n }\n"): (typeof documents)["\n fragment UseViewerSavedViewSetup_SavedView on SavedView {\n id\n viewerState\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -2531,11 +2587,15 @@ export function graphql(source: "\n mutation ArchiveComment($input: ArchiveComm
|
||||
/**
|
||||
* 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 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 documents)["\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"];
|
||||
export function graphql(source: "\n query ProjectViewerResources(\n $projectId: String!\n $resourceUrlString: String!\n $savedViewId: String\n ) {\n project(id: $projectId) {\n id\n viewerResources(resourceIdString: $resourceUrlString, savedViewId: $savedViewId) {\n identifier\n items {\n modelId\n versionId\n objectId\n }\n }\n }\n }\n"): (typeof documents)["\n query ProjectViewerResources(\n $projectId: String!\n $resourceUrlString: String!\n $savedViewId: String\n ) {\n project(id: $projectId) {\n id\n viewerResources(resourceIdString: $resourceUrlString, savedViewId: $savedViewId) {\n identifier\n items {\n modelId\n versionId\n objectId\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query 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 ...UseCheckViewerCommentingAccess_Project\n ...UseViewerUserActivityBroadcasting_Project\n ...ViewerGendoPanel_Project\n ...ViewerResourcesLimitAlert_Project\n }\n }\n"): (typeof documents)["\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 ...UseCheckViewerCommentingAccess_Project\n ...UseViewerUserActivityBroadcasting_Project\n ...ViewerGendoPanel_Project\n ...ViewerResourcesLimitAlert_Project\n }\n }\n"];
|
||||
export function graphql(source: "\n query ViewerActiveSavedView($projectId: String!, $savedViewId: ID!) {\n project(id: $projectId) {\n id\n savedView(id: $savedViewId) {\n id\n ...UseViewerSavedViewSetup_SavedView\n }\n }\n }\n"): (typeof documents)["\n query ViewerActiveSavedView($projectId: String!, $savedViewId: ID!) {\n project(id: $projectId) {\n id\n savedView(id: $savedViewId) {\n id\n ...UseViewerSavedViewSetup_SavedView\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query 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 ...UseCheckViewerCommentingAccess_Project\n ...UseViewerUserActivityBroadcasting_Project\n ...ViewerGendoPanel_Project\n ...ViewerResourcesLimitAlert_Project\n ...ViewerSavedViewsPanel_Project\n }\n }\n"): (typeof documents)["\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 ...UseCheckViewerCommentingAccess_Project\n ...UseViewerUserActivityBroadcasting_Project\n ...ViewerGendoPanel_Project\n ...ViewerResourcesLimitAlert_Project\n ...ViewerSavedViewsPanel_Project\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
@@ -380,8 +380,7 @@ export function modifyObjectFields<
|
||||
debug: boolean
|
||||
}>
|
||||
) {
|
||||
const { fieldNameWhitelist, debug = !!(import.meta.dev && import.meta.client) } =
|
||||
options || {}
|
||||
const { fieldNameWhitelist, debug = false } = options || {}
|
||||
|
||||
const logger = useLogger()
|
||||
const invocationId = nanoid()
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { RelativeURL, type MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
|
||||
export const checkIfIsInPlaceNavigation = (
|
||||
to?: MaybeNullOrUndefined<RouteLocationNormalized>,
|
||||
from?: MaybeNullOrUndefined<RouteLocationNormalized>
|
||||
): boolean => {
|
||||
if (!to || !from) return false
|
||||
|
||||
// if only hash state or querystring changed, its not a full on navigation to a new page
|
||||
const toUrl = new RelativeURL(to.fullPath)
|
||||
const fromUrl = new RelativeURL(from.fullPath)
|
||||
|
||||
return toUrl.pathOnly === fromUrl.pathOnly
|
||||
}
|
||||
@@ -1,13 +1,48 @@
|
||||
export enum EventBusKeys {
|
||||
import type {
|
||||
ViewerEventBusKeyPayloadMap,
|
||||
ViewerEventBusKeys
|
||||
} from '~/lib/viewer/helpers/eventBus'
|
||||
|
||||
export enum CoreEventBusKeys {
|
||||
TestKey = 'test_event_bus'
|
||||
}
|
||||
|
||||
export type EventBusKeys = CoreEventBusKeys | ViewerEventBusKeys
|
||||
|
||||
// Add mappings between event keys and expected payloads here
|
||||
export type EventBusKeyPayloadMap = {
|
||||
[EventBusKeys.TestKey]: { foo: string; bar: string }
|
||||
} & { [k in EventBusKeys]: unknown } & Record<string, unknown>
|
||||
[CoreEventBusKeys.TestKey]: { foo: string; bar: string }
|
||||
} & ViewerEventBusKeyPayloadMap & { [k in EventBusKeys]: unknown } & Record<
|
||||
string,
|
||||
unknown
|
||||
>
|
||||
|
||||
export function useEventBus() {
|
||||
const nuxt = useNuxtApp()
|
||||
return nuxt.$eventBus
|
||||
const $eventBus = nuxt.$eventBus
|
||||
const handles = shallowRef<Array<() => void>>([])
|
||||
|
||||
const on = <T extends EventBusKeys>(
|
||||
key: T,
|
||||
handler: (event: EventBusKeyPayloadMap[T]) => void
|
||||
) => {
|
||||
$eventBus.on(key, handler)
|
||||
const offHandle = () => $eventBus.off(key, handler)
|
||||
handles.value = [...handles.value, offHandle]
|
||||
return offHandle
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
handles.value.forEach((quit) => quit())
|
||||
handles.value = []
|
||||
})
|
||||
|
||||
return {
|
||||
/**
|
||||
* Event subscribe w/ automatic cleanup on unmount.
|
||||
* Returns a function to manually unsubscribe if needed.
|
||||
*/
|
||||
on,
|
||||
emit: $eventBus.emit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +220,21 @@ function createCache(): InMemoryCache {
|
||||
},
|
||||
permissions: {
|
||||
merge: mergeAsObjectsFunction
|
||||
},
|
||||
savedViewGroups: {
|
||||
keyArgs: ['input', ['limit', 'search', 'onlyAuthored', 'resourceIdString']],
|
||||
merge: buildAbstractCollectionMergeFunction('SavedViewGroupCollection')
|
||||
}
|
||||
}
|
||||
},
|
||||
SavedViewGroup: {
|
||||
fields: {
|
||||
views: {
|
||||
keyArgs: [
|
||||
'input',
|
||||
['limit', 'search', 'sortBy', 'sortDirection', 'onlyAuthored']
|
||||
],
|
||||
merge: buildAbstractCollectionMergeFunction('SavedViewCollection')
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -300,6 +315,9 @@ function createCache(): InMemoryCache {
|
||||
ServerInfo: {
|
||||
merge: true
|
||||
},
|
||||
ServerConfiguration: {
|
||||
merge: true
|
||||
},
|
||||
CommentThreadActivityMessage: {
|
||||
merge: true
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
useSelectionEvents,
|
||||
useViewerCameraControlEndTracker
|
||||
} from '~~/lib/viewer/composables/viewer'
|
||||
import { SpeckleViewer, xor, TIME_MS } from '@speckle/shared'
|
||||
import { xor, TIME_MS } from '@speckle/shared'
|
||||
import type { Nullable, Optional } from '@speckle/shared'
|
||||
import { Vector3 } from 'three'
|
||||
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
|
||||
@@ -35,8 +35,13 @@ import {
|
||||
useApplySerializedState,
|
||||
useStateSerialization
|
||||
} from '~~/lib/viewer/composables/serialization'
|
||||
import type { Merge } from 'type-fest'
|
||||
import type { Merge, OverrideProperties } from 'type-fest'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import {
|
||||
isSerializedViewerState,
|
||||
type SerializedViewerState
|
||||
} from '@speckle/shared/viewer/state'
|
||||
import { omit } from 'lodash-es'
|
||||
|
||||
/**
|
||||
* How often we send out an "activity" message even if user hasn't made any clicks (just to keep him active)
|
||||
@@ -55,11 +60,16 @@ const USER_STALE_AFTER_PERIOD = 20 * OWN_ACTIVITY_UPDATE_INTERVAL
|
||||
*/
|
||||
const USER_REMOVABLE_AFTER_PERIOD = USER_STALE_AFTER_PERIOD * 2
|
||||
|
||||
type ViewerActivityMetadata = OverrideProperties<
|
||||
Required<Omit<ViewerUserActivityMessageInput, 'status'>>,
|
||||
{ state: SerializedViewerState }
|
||||
>
|
||||
|
||||
function useCollectMainMetadata() {
|
||||
const { sessionId } = useInjectedViewerState()
|
||||
const { activeUser } = useActiveUser()
|
||||
const { serialize } = useStateSerialization()
|
||||
return (): Omit<ViewerUserActivityMessageInput, 'status' | 'selection'> => ({
|
||||
return (): ViewerActivityMetadata => ({
|
||||
userId: activeUser.value?.id || null,
|
||||
userName: activeUser.value?.name || 'Anonymous Viewer',
|
||||
state: serialize(),
|
||||
@@ -78,6 +88,63 @@ graphql(`
|
||||
}
|
||||
`)
|
||||
|
||||
const useViewerRealtimeActivityState = () =>
|
||||
useState('viewer_realtime_activity_state', () => ({
|
||||
activity: undefined as Optional<ViewerActivityMetadata>,
|
||||
status: ViewerUserActivityStatus.Viewing as ViewerUserActivityStatus
|
||||
}))
|
||||
|
||||
export const useViewerRealtimeActivityTracker = () => {
|
||||
const state = useViewerRealtimeActivityState()
|
||||
const getMainMetadata = useCollectMainMetadata()
|
||||
|
||||
const activity = computed({
|
||||
get: () => state.value.activity || getMainMetadata(),
|
||||
set: (value) => {
|
||||
state.value.activity = value
|
||||
}
|
||||
})
|
||||
|
||||
const status = computed({
|
||||
get: () => state.value.status,
|
||||
set: (value) => {
|
||||
state.value.status = value
|
||||
}
|
||||
})
|
||||
|
||||
const serializedState = computed(() => activity.value.state)
|
||||
|
||||
// Ids for easy equality comparisons
|
||||
const serializedStateId = computed(() => JSON.stringify(serializedState.value))
|
||||
const activityId = computed(() => {
|
||||
const stateId = serializedStateId.value
|
||||
const otherActivity: Omit<ViewerActivityMetadata, 'state'> = omit(activity.value, [
|
||||
'state'
|
||||
])
|
||||
const otherActivityId = JSON.stringify(otherActivity)
|
||||
return `${stateId}-${otherActivityId}-${status.value}`
|
||||
})
|
||||
|
||||
const update = (params?: {
|
||||
newActivity?: ViewerActivityMetadata
|
||||
status?: ViewerUserActivityStatus
|
||||
}) => {
|
||||
activity.value = params?.newActivity || getMainMetadata()
|
||||
|
||||
if (params?.status) {
|
||||
status.value = params.status
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
// Reset activity state on unmount
|
||||
state.value.activity = undefined
|
||||
state.value.status = ViewerUserActivityStatus.Viewing
|
||||
})
|
||||
|
||||
return { activity, serializedState, status, update, serializedStateId, activityId }
|
||||
}
|
||||
|
||||
export function useViewerUserActivityBroadcasting(
|
||||
options?: Partial<{
|
||||
state: InjectableViewerState
|
||||
@@ -90,7 +157,7 @@ export function useViewerUserActivityBroadcasting(
|
||||
response: { project }
|
||||
}
|
||||
} = options?.state || useInjectedViewerState()
|
||||
const getMainMetadata = useCollectMainMetadata()
|
||||
const { update, activity, status, activityId } = useViewerRealtimeActivityTracker()
|
||||
const apollo = useApolloClient().client
|
||||
const { isEnabled: isEmbedEnabled } = useEmbed()
|
||||
|
||||
@@ -98,22 +165,25 @@ export function useViewerUserActivityBroadcasting(
|
||||
() => project.value?.permissions.canBroadcastActivity.authorized
|
||||
)
|
||||
|
||||
const isSameMessage = (
|
||||
previousSerializedMessage: Optional<string>,
|
||||
newMessage: ViewerUserActivityMessageInput
|
||||
const isSameActivity = (
|
||||
previousActivityId: Optional<string>,
|
||||
newActivityId: string
|
||||
) => {
|
||||
if (xor(previousSerializedMessage, newMessage)) return false
|
||||
if (!previousSerializedMessage && !newMessage) return false
|
||||
return previousSerializedMessage === JSON.stringify(newMessage)
|
||||
if (xor(previousActivityId, newActivityId)) return false
|
||||
if (!previousActivityId && !newActivityId) return false
|
||||
return previousActivityId === newActivityId
|
||||
}
|
||||
|
||||
const invokeMutation = async (message: ViewerUserActivityMessageInput) => {
|
||||
const invokeMutation = async () => {
|
||||
const result = await apollo
|
||||
.mutate({
|
||||
mutation: broadcastViewerUserActivityMutation,
|
||||
variables: {
|
||||
resourceIdString: resourceIdString.value,
|
||||
message,
|
||||
message: {
|
||||
...activity.value,
|
||||
status: status.value
|
||||
},
|
||||
projectId: projectId.value
|
||||
}
|
||||
})
|
||||
@@ -122,43 +192,43 @@ export function useViewerUserActivityBroadcasting(
|
||||
return result.data?.broadcastViewerUserActivity || false
|
||||
}
|
||||
|
||||
let serializedPreviousMessage: Optional<string> = undefined
|
||||
const invokeObservabilityEvent = async (message: ViewerUserActivityMessageInput) => {
|
||||
let previousActivityId: Optional<string> = undefined
|
||||
const invokeObservabilityEvent = async () => {
|
||||
const dd = window.DD_RUM
|
||||
if (!dd || !('addAction' in dd)) return
|
||||
|
||||
if (isSameMessage(serializedPreviousMessage, message)) return
|
||||
const message = {
|
||||
...activity.value,
|
||||
status: status.value
|
||||
}
|
||||
|
||||
serializedPreviousMessage = JSON.stringify(message)
|
||||
if (isSameActivity(previousActivityId, activityId.value)) return
|
||||
|
||||
previousActivityId = activityId.value
|
||||
dd.addAction('Viewer User Activity', { message })
|
||||
}
|
||||
|
||||
const invoke = async (message: ViewerUserActivityMessageInput) => {
|
||||
const invoke = async () => {
|
||||
if (!canBroadcast.value || isEmbedEnabled.value) return false
|
||||
return await Promise.all([
|
||||
invokeMutation(message),
|
||||
invokeObservabilityEvent(message)
|
||||
])
|
||||
|
||||
return await Promise.all([invokeMutation(), invokeObservabilityEvent()])
|
||||
}
|
||||
|
||||
return {
|
||||
emitDisconnected: async () =>
|
||||
await invoke({
|
||||
...getMainMetadata(),
|
||||
status: ViewerUserActivityStatus.Disconnected
|
||||
}),
|
||||
emitDisconnected: async () => {
|
||||
update({ status: ViewerUserActivityStatus.Disconnected })
|
||||
await invoke()
|
||||
},
|
||||
emitViewing: async () => {
|
||||
await invoke({
|
||||
...getMainMetadata(),
|
||||
status: ViewerUserActivityStatus.Viewing
|
||||
})
|
||||
update({ status: ViewerUserActivityStatus.Viewing })
|
||||
await invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type UserActivityModel = Merge<
|
||||
OnViewerUserActivityBroadcastedSubscription['viewerUserActivityBroadcasted'],
|
||||
{ state: SpeckleViewer.ViewerState.SerializedViewerState }
|
||||
{ state: SerializedViewerState }
|
||||
> & {
|
||||
isStale: boolean
|
||||
isOccluded: boolean
|
||||
@@ -239,9 +309,7 @@ export function useViewerUserActivityTracking(params: {
|
||||
return
|
||||
}
|
||||
|
||||
const state = SpeckleViewer.ViewerState.isSerializedViewerState(event.state)
|
||||
? event.state
|
||||
: null
|
||||
const state = isSerializedViewerState(event.state) ? event.state : null
|
||||
if (!state) return
|
||||
|
||||
const userData: UserActivityModel = {
|
||||
@@ -429,7 +497,7 @@ function useViewerSpotlightTracking() {
|
||||
type UserTypingInfo = {
|
||||
userId: string
|
||||
userName: string
|
||||
thread: SpeckleViewer.ViewerState.SerializedViewerState['ui']['threads']['openThread']
|
||||
thread: SerializedViewerState['ui']['threads']['openThread']
|
||||
lastSeen: Dayjs
|
||||
}
|
||||
|
||||
@@ -471,9 +539,7 @@ export function useViewerThreadTypingTracking(threadId: MaybeRef<string>) {
|
||||
usersTyping.value.splice(existingItemIdx, 1)
|
||||
}
|
||||
|
||||
const state = SpeckleViewer.ViewerState.isSerializedViewerState(event.state)
|
||||
? event.state
|
||||
: null
|
||||
const state = isSerializedViewerState(event.state) ? event.state : null
|
||||
if (!state) return
|
||||
const typingPayload = state.ui.threads.openThread
|
||||
if (typingPayload.threadId !== unref(threadId)) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const useAreSavedViewsEnabled = () => {
|
||||
const {
|
||||
public: { FF_SAVED_VIEWS_ENABLED }
|
||||
} = useRuntimeConfig()
|
||||
|
||||
return FF_SAVED_VIEWS_ENABLED
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { useMutation } from '@vue/apollo-composable'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type { CreateSavedViewInput } from '~/lib/common/generated/gql/graphql'
|
||||
import { useStateSerialization } from '~/lib/viewer/composables/serialization'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
|
||||
const createSavedViewMutation = graphql(`
|
||||
mutation CreateSavedView($input: CreateSavedViewInput!) {
|
||||
projectMutations {
|
||||
savedViewMutations {
|
||||
createView(input: $input) {
|
||||
id
|
||||
...ViewerSavedViewsPanelView_SavedView
|
||||
group {
|
||||
id
|
||||
...ViewerSavedViewsPanelViewsGroup_SavedViewGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const useCreateSavedView = () => {
|
||||
const { mutate } = useMutation(createSavedViewMutation)
|
||||
const { userId } = useActiveUser()
|
||||
const {
|
||||
projectId,
|
||||
viewer: { instance: viewerInstance }
|
||||
} = useInjectedViewerState()
|
||||
const { serialize, buildConcreteResourceIdString } = useStateSerialization()
|
||||
const { triggerNotification } = useGlobalToast()
|
||||
|
||||
return async (
|
||||
input: Omit<
|
||||
CreateSavedViewInput,
|
||||
'projectId' | 'resourceIdString' | 'viewerState' | 'screenshot'
|
||||
>
|
||||
) => {
|
||||
if (!userId.value) return
|
||||
const screenshot = await viewerInstance.screenshot()
|
||||
|
||||
const result = await mutate(
|
||||
{
|
||||
input: {
|
||||
...input,
|
||||
projectId: projectId.value,
|
||||
resourceIdString: buildConcreteResourceIdString(),
|
||||
viewerState: serialize({ concreteResourceIdString: true }),
|
||||
screenshot
|
||||
}
|
||||
},
|
||||
{
|
||||
update: (cache, { data }) => {
|
||||
const res = data?.projectMutations.savedViewMutations.createView
|
||||
if (!res) return
|
||||
|
||||
const viewId = res.id
|
||||
const groupId = res.group.id
|
||||
|
||||
// Project.savedViewGroups + 1, if it is a new group
|
||||
modifyObjectField(
|
||||
cache,
|
||||
getCacheId('Project', projectId.value),
|
||||
'savedViewGroups',
|
||||
({ helpers: { createUpdatedValue, ref, readField }, value }) => {
|
||||
const isNewGroup = !value?.items?.some(
|
||||
(group) => readField(group, 'id') === groupId
|
||||
)
|
||||
if (!isNewGroup) return
|
||||
|
||||
return createUpdatedValue(({ update }) => {
|
||||
update('totalCount', (count) => count + 1)
|
||||
update('items', (items) => [...items, ref('SavedViewGroup', groupId)])
|
||||
})
|
||||
},
|
||||
{ autoEvictFiltered: true }
|
||||
)
|
||||
|
||||
// SavedViewGroup.views + 1
|
||||
modifyObjectField(
|
||||
cache,
|
||||
getCacheId('SavedViewGroup', groupId),
|
||||
'views',
|
||||
({ helpers: { createUpdatedValue, ref } }) => {
|
||||
return createUpdatedValue(({ update }) => {
|
||||
update('totalCount', (count) => count + 1)
|
||||
update('items', (items) => [ref('SavedView', viewId), ...items])
|
||||
})
|
||||
},
|
||||
{ autoEvictFiltered: true }
|
||||
)
|
||||
}
|
||||
}
|
||||
).catch(convertThrowIntoFetchResult)
|
||||
|
||||
const res = result?.data?.projectMutations.savedViewMutations.createView
|
||||
if (res?.id) {
|
||||
triggerNotification({
|
||||
title: 'Saved View Created',
|
||||
type: ToastNotificationType.Success
|
||||
})
|
||||
} else {
|
||||
const err = getFirstGqlErrorMessage(result?.errors)
|
||||
triggerNotification({
|
||||
title: "Couldn't create saved view",
|
||||
description: err,
|
||||
type: ToastNotificationType.Danger
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { CameraController, ViewMode, VisualDiffMode } from '@speckle/viewer'
|
||||
import type { NumericPropertyInfo } from '@speckle/viewer'
|
||||
import type { PartialDeep } from 'type-fest'
|
||||
import type { SectionBoxData } from '@speckle/shared/viewer/state'
|
||||
import { useViewerRealtimeActivityTracker } from '~/lib/viewer/composables/activity'
|
||||
|
||||
type SerializedViewerState = SpeckleViewer.ViewerState.SerializedViewerState
|
||||
|
||||
@@ -125,7 +126,7 @@ export function useStateSerialization() {
|
||||
return ret
|
||||
}
|
||||
|
||||
return { serialize }
|
||||
return { serialize, buildConcreteResourceIdString }
|
||||
}
|
||||
|
||||
export enum StateApplyMode {
|
||||
@@ -133,7 +134,8 @@ export enum StateApplyMode {
|
||||
ThreadOpen,
|
||||
ThreadFullContextOpen,
|
||||
Reset,
|
||||
FederatedContext
|
||||
FederatedContext,
|
||||
SavedView
|
||||
}
|
||||
|
||||
export function useApplySerializedState() {
|
||||
@@ -167,10 +169,12 @@ export function useApplySerializedState() {
|
||||
const { diffModelVersions, deserializeDiffCommand, endDiff } = useDiffUtilities()
|
||||
const { setSelectionFromObjectIds } = useSelectionUtilities()
|
||||
const logger = useLogger()
|
||||
const { update } = useViewerRealtimeActivityTracker()
|
||||
|
||||
return async (state: PartialDeep<SerializedViewerState>, mode: StateApplyMode) => {
|
||||
if (mode === StateApplyMode.Reset) {
|
||||
resetState()
|
||||
update() // Trigger activity update
|
||||
return
|
||||
}
|
||||
|
||||
@@ -251,15 +255,6 @@ export function useApplySerializedState() {
|
||||
})
|
||||
}
|
||||
|
||||
const selectedObjectIds = Object.keys(filters.selectedObjectApplicationIds ?? {})
|
||||
if (mode === StateApplyMode.Spotlight) {
|
||||
highlightedObjectIds.value = selectedObjectIds
|
||||
} else {
|
||||
if (selectedObjectIds.length) {
|
||||
setSelectionFromObjectIds(selectedObjectIds)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle resource string updates
|
||||
if (
|
||||
[StateApplyMode.Spotlight, StateApplyMode.ThreadFullContextOpen].includes(mode)
|
||||
@@ -288,12 +283,21 @@ export function useApplySerializedState() {
|
||||
}
|
||||
}
|
||||
|
||||
if ([StateApplyMode.Spotlight].includes(mode)) {
|
||||
if ([StateApplyMode.Spotlight, StateApplyMode.SavedView].includes(mode)) {
|
||||
await urlHashState.focusedThreadId.update(
|
||||
state.ui?.threads?.openThread?.threadId || null
|
||||
)
|
||||
}
|
||||
|
||||
const selectedObjectIds = Object.keys(filters.selectedObjectApplicationIds ?? {})
|
||||
if (mode === StateApplyMode.Spotlight) {
|
||||
highlightedObjectIds.value = selectedObjectIds
|
||||
} else {
|
||||
if (selectedObjectIds.length || mode === StateApplyMode.SavedView) {
|
||||
setSelectionFromObjectIds(selectedObjectIds)
|
||||
}
|
||||
}
|
||||
|
||||
const command = state.ui?.diff?.command
|
||||
? deserializeDiffCommand(state.ui.diff.command)
|
||||
: null
|
||||
@@ -324,5 +328,8 @@ export function useApplySerializedState() {
|
||||
...lightConfig.value,
|
||||
...(state.ui?.lightConfig || {})
|
||||
}
|
||||
|
||||
// Trigger activity update
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,11 @@ import { inject, ref, provide } from 'vue'
|
||||
import type { ComputedRef, WritableComputedRef, Raw, Ref, ShallowRef } from 'vue'
|
||||
import { useScopedState } from '~~/lib/common/composables/scopedState'
|
||||
import type { MaybeNullOrUndefined, Nullable, Optional } from '@speckle/shared'
|
||||
import { SpeckleViewer, isNonNullable } from '@speckle/shared'
|
||||
import { isNonNullable } from '@speckle/shared'
|
||||
import { useApolloClient, useLazyQuery, useQuery } from '@vue/apollo-composable'
|
||||
import {
|
||||
projectViewerResourcesQuery,
|
||||
viewerActiveSavedViewQuery,
|
||||
viewerLoadedResourcesQuery,
|
||||
viewerLoadedThreadsQuery,
|
||||
viewerModelVersionsQuery
|
||||
@@ -38,7 +39,8 @@ import type {
|
||||
ViewerResourceItem,
|
||||
ViewerLoadedThreadsQueryVariables,
|
||||
ProjectCommentsFilter,
|
||||
ViewerModelVersionCardItemFragment
|
||||
ViewerModelVersionCardItemFragment,
|
||||
UseViewerSavedViewSetup_SavedViewFragment
|
||||
} from '~~/lib/common/generated/gql/graphql'
|
||||
import type { SetNonNullable, Get } from 'type-fest'
|
||||
import {
|
||||
@@ -66,6 +68,18 @@ import { useSynchronizedCookie } from '~~/lib/common/composables/reactiveCookie'
|
||||
import { buildManualPromise } from '@speckle/ui-components'
|
||||
import { PassReader } from '../extensions/PassReader'
|
||||
import type { SectionBoxData } from '@speckle/shared/viewer/state'
|
||||
import {
|
||||
createGetParamFromResources,
|
||||
isAllModelsResource,
|
||||
isModelFolderResource,
|
||||
isModelResource,
|
||||
isObjectResource,
|
||||
parseUrlParameters,
|
||||
resourceBuilder,
|
||||
ViewerModelResource,
|
||||
type ViewerResource
|
||||
} from '@speckle/shared/viewer/route'
|
||||
import { useAreSavedViewsEnabled } from '~/lib/viewer/composables/savedViews/general'
|
||||
|
||||
export type LoadedModel = NonNullable<
|
||||
Get<ViewerLoadedResourcesQuery, 'project.models.items[0]'>
|
||||
@@ -75,6 +89,8 @@ export type LoadedThreadsMetadata = NonNullable<
|
||||
Get<ViewerLoadedThreadsQuery, 'project.commentThreads'>
|
||||
>
|
||||
|
||||
export type LoadedSavedView = UseViewerSavedViewSetup_SavedViewFragment
|
||||
|
||||
export type LoadedCommentThread = NonNullable<Get<LoadedThreadsMetadata, 'items[0]'>>
|
||||
|
||||
export type InjectableViewerState = Readonly<{
|
||||
@@ -82,6 +98,11 @@ export type InjectableViewerState = Readonly<{
|
||||
* The project which we're opening in the viewer (all loaded models should belong to it)
|
||||
*/
|
||||
projectId: AsyncWritableComputedRef<string>
|
||||
/**
|
||||
* Core source of truth for the view id (other is in hash state). This allows you to
|
||||
* set a view to load, without it showing up in the URL.
|
||||
*/
|
||||
savedViewId: Ref<Nullable<string>>
|
||||
/**
|
||||
* User viewer session ID. The same user will have different IDs in different tabs if multiple are open.
|
||||
* This is used to ignore user activity messages from the same tab.
|
||||
@@ -136,7 +157,7 @@ export type InjectableViewerState = Readonly<{
|
||||
* All currently requested identifiers. You
|
||||
* can write to this to change which resources should be loaded.
|
||||
*/
|
||||
items: AsyncWritableComputedRef<SpeckleViewer.ViewerRoute.ViewerResource[]>
|
||||
items: AsyncWritableComputedRef<ViewerResource[]>
|
||||
/**
|
||||
* All currently requested identifiers in a comma-delimited string, the way it's
|
||||
* represented in the URL. Is writable also.
|
||||
@@ -161,6 +182,10 @@ export type InjectableViewerState = Readonly<{
|
||||
* are resolved from multiple GQL requests and update whenever resources.request updates.
|
||||
*/
|
||||
response: {
|
||||
/**
|
||||
* Resource id string w/ saved view applied, if any
|
||||
*/
|
||||
resolvedResourceIdString: ComputedRef<string>
|
||||
/**
|
||||
* Metadata about loaded items
|
||||
*/
|
||||
@@ -218,6 +243,10 @@ export type InjectableViewerState = Readonly<{
|
||||
*/
|
||||
loadMoreVersions: (modelId: string) => Promise<void>
|
||||
resourcesLoading: ComputedRef<boolean>
|
||||
/**
|
||||
* Loaded saved view, if any
|
||||
*/
|
||||
savedView: ComputedRef<Optional<LoadedSavedView>>
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -290,6 +319,7 @@ export type InjectableViewerState = Readonly<{
|
||||
urlHashState: {
|
||||
focusedThreadId: AsyncWritableComputedRef<Nullable<string>>
|
||||
diff: AsyncWritableComputedRef<Nullable<DiffStateCommand>>
|
||||
savedViewId: AsyncWritableComputedRef<Nullable<string>>
|
||||
}
|
||||
}>
|
||||
|
||||
@@ -302,7 +332,7 @@ type CachedViewerState = Pick<
|
||||
|
||||
type InitialSetupState = Pick<
|
||||
InjectableViewerState,
|
||||
'projectId' | 'viewer' | 'sessionId' | 'urlHashState'
|
||||
'projectId' | 'viewer' | 'sessionId' | 'urlHashState' | 'savedViewId'
|
||||
>
|
||||
|
||||
type InitialStateWithRequest = InitialSetupState & {
|
||||
@@ -410,6 +440,7 @@ function setupInitialState(params: UseSetupViewerParams): InitialSetupState {
|
||||
|
||||
return {
|
||||
projectId: params.projectId,
|
||||
savedViewId: ref<string | null>(null),
|
||||
sessionId,
|
||||
viewer: import.meta.server
|
||||
? ({
|
||||
@@ -450,10 +481,9 @@ function setupResourceRequest(state: InitialSetupState): InitialStateWithRequest
|
||||
const getParam = computed(() => route.params.modelId as string)
|
||||
|
||||
const resources = writableAsyncComputed({
|
||||
get: () => SpeckleViewer.ViewerRoute.parseUrlParameters(getParam.value),
|
||||
get: () => parseUrlParameters(getParam.value),
|
||||
set: async (newResources) => {
|
||||
const modelId =
|
||||
SpeckleViewer.ViewerRoute.createGetParamFromResources(newResources)
|
||||
const modelId = createGetParamFromResources(newResources)
|
||||
await router.push({
|
||||
params: { modelId },
|
||||
query: route.query,
|
||||
@@ -465,11 +495,15 @@ function setupResourceRequest(state: InitialSetupState): InitialStateWithRequest
|
||||
})
|
||||
|
||||
// we could use getParam, but `createGetParamFromResources` does sorting and de-duplication AFAIK
|
||||
// + we can skip duplicate updates
|
||||
const resourceIdString = writableAsyncComputed({
|
||||
get: () => SpeckleViewer.ViewerRoute.createGetParamFromResources(resources.value),
|
||||
get: () => createGetParamFromResources(resources.value),
|
||||
set: async (newVal) => {
|
||||
const newResources = SpeckleViewer.ViewerRoute.parseUrlParameters(newVal)
|
||||
await resources.update(newResources)
|
||||
const newResources = resourceBuilder().addResources(parseUrlParameters(newVal))
|
||||
const currentResources = resourceBuilder().addResources(resources.value)
|
||||
if (newResources.toString() === currentResources.toString()) return
|
||||
|
||||
await resources.update(newResources.toResources())
|
||||
},
|
||||
initialState: '',
|
||||
asyncRead: false
|
||||
@@ -488,23 +522,19 @@ function setupResourceRequest(state: InitialSetupState): InitialStateWithRequest
|
||||
const resourceArr = resources.value.slice()
|
||||
|
||||
const resourceIdx = resourceArr.findIndex(
|
||||
(r) => SpeckleViewer.ViewerRoute.isModelResource(r) && r.modelId === modelId
|
||||
(r) => isModelResource(r) && r.modelId === modelId
|
||||
)
|
||||
|
||||
if (resourceIdx !== -1) {
|
||||
// Replace
|
||||
const newResources = resources.value.slice()
|
||||
newResources.splice(
|
||||
resourceIdx,
|
||||
1,
|
||||
new SpeckleViewer.ViewerRoute.ViewerModelResource(modelId, versionId)
|
||||
)
|
||||
newResources.splice(resourceIdx, 1, new ViewerModelResource(modelId, versionId))
|
||||
|
||||
await resources.update(newResources)
|
||||
} else {
|
||||
// Add new one and allow de-duplication to do its thing
|
||||
await resources.update([
|
||||
new SpeckleViewer.ViewerRoute.ViewerModelResource(modelId, versionId),
|
||||
new ViewerModelResource(modelId, versionId),
|
||||
...resources.value
|
||||
])
|
||||
}
|
||||
@@ -540,11 +570,15 @@ function setupResponseResourceItems(
|
||||
state: InitialStateWithRequest
|
||||
): Pick<
|
||||
InjectableViewerState['resources']['response'],
|
||||
'resourceItems' | 'resourceItemsQueryVariables' | 'resourceItemsLoaded'
|
||||
| 'resourceItems'
|
||||
| 'resourceItemsQueryVariables'
|
||||
| 'resourceItemsLoaded'
|
||||
| 'resolvedResourceIdString'
|
||||
> {
|
||||
const globalError = useError()
|
||||
const {
|
||||
projectId,
|
||||
savedViewId,
|
||||
resources: {
|
||||
request: { resourceIdString }
|
||||
}
|
||||
@@ -560,7 +594,8 @@ function setupResponseResourceItems(
|
||||
projectViewerResourcesQuery,
|
||||
() => ({
|
||||
projectId: projectId.value,
|
||||
resourceUrlString: resourceIdString.value
|
||||
resourceUrlString: resourceIdString.value,
|
||||
savedViewId: savedViewId.value
|
||||
}),
|
||||
{ keepPreviousResult: true }
|
||||
)
|
||||
@@ -595,20 +630,20 @@ function setupResponseResourceItems(
|
||||
const objectItems: ViewerResourceItem[] = []
|
||||
const allModelItems: ViewerResourceItem[] = []
|
||||
for (const group of resolvedResourceGroups.value) {
|
||||
const [resource] = SpeckleViewer.ViewerRoute.parseUrlParameters(group.identifier)
|
||||
const [resource] = parseUrlParameters(group.identifier)
|
||||
|
||||
for (const item of group.items) {
|
||||
if (SpeckleViewer.ViewerRoute.isModelResource(resource)) {
|
||||
if (isModelResource(resource)) {
|
||||
if (resource.versionId) {
|
||||
versionItems.push(item)
|
||||
} else {
|
||||
modelItems.push(item)
|
||||
}
|
||||
} else if (SpeckleViewer.ViewerRoute.isAllModelsResource(resource)) {
|
||||
} else if (isAllModelsResource(resource)) {
|
||||
allModelItems.push(item)
|
||||
} else if (SpeckleViewer.ViewerRoute.isModelFolderResource(resource)) {
|
||||
} else if (isModelFolderResource(resource)) {
|
||||
folderItems.push(item)
|
||||
} else if (SpeckleViewer.ViewerRoute.isObjectResource(resource)) {
|
||||
} else if (isObjectResource(resource)) {
|
||||
objectItems.push(item)
|
||||
}
|
||||
}
|
||||
@@ -645,10 +680,18 @@ function setupResponseResourceItems(
|
||||
|
||||
const resourceItemsLoaded = computed(() => initLoadDone.value)
|
||||
|
||||
const resolvedResourceIdString = computed(() =>
|
||||
resourceBuilder()
|
||||
// Combined group identifiers should result in the final resource id string
|
||||
.addFromString(resolvedResourceGroups.value.map((group) => group.identifier))
|
||||
.toString()
|
||||
)
|
||||
|
||||
return {
|
||||
resourceItems,
|
||||
resourceItemsQueryVariables: computed(() => resourceItemsQueryVariables.value),
|
||||
resourceItemsLoaded
|
||||
resourceItemsLoaded,
|
||||
resolvedResourceIdString
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,14 +700,19 @@ function setupResponseResourceData(
|
||||
resourceItemsData: ReturnType<typeof setupResponseResourceItems>
|
||||
): Omit<
|
||||
InjectableViewerState['resources']['response'],
|
||||
'resourceItems' | 'resourceItemsQueryVariables' | 'resourceItemsLoaded'
|
||||
| 'resourceItems'
|
||||
| 'resourceItemsQueryVariables'
|
||||
| 'resourceItemsLoaded'
|
||||
| 'resolvedResourceIdString'
|
||||
> {
|
||||
const apollo = useApolloClient().client
|
||||
const globalError = useError()
|
||||
const { triggerNotification } = useGlobalToast()
|
||||
const logger = useLogger()
|
||||
const savedViewsEnabled = useAreSavedViewsEnabled()
|
||||
|
||||
const {
|
||||
savedViewId,
|
||||
projectId,
|
||||
resources: {
|
||||
request: { resourceIdString, threadFilters }
|
||||
@@ -867,6 +915,36 @@ function setupResponseResourceData(
|
||||
logger.error(err)
|
||||
})
|
||||
|
||||
// SAVED VIEW
|
||||
const { result: viewerActiveSavedViewResult, onError: onViewerActiveSavedViewError } =
|
||||
useQuery(
|
||||
viewerActiveSavedViewQuery,
|
||||
() => ({
|
||||
projectId: projectId.value,
|
||||
savedViewId: savedViewId.value!
|
||||
}),
|
||||
{
|
||||
enabled: computed(() => !!savedViewId.value && savedViewsEnabled)
|
||||
}
|
||||
)
|
||||
|
||||
onViewerActiveSavedViewError((err) => {
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Saved view loading failed',
|
||||
description: `${err.message}`
|
||||
})
|
||||
logger.error(err)
|
||||
})
|
||||
|
||||
// Shows only the one matching the savedViewId. If the query is still loading/stale, it will return undefined
|
||||
const savedView = computed(() =>
|
||||
savedViewId.value &&
|
||||
viewerActiveSavedViewResult.value?.project?.savedView.id === savedViewId.value
|
||||
? viewerActiveSavedViewResult.value?.project?.savedView
|
||||
: undefined
|
||||
)
|
||||
|
||||
onServerPrefetch(async () => {
|
||||
await Promise.all([serverResourcesLoadedPromise.promise])
|
||||
})
|
||||
@@ -882,7 +960,8 @@ function setupResponseResourceData(
|
||||
threadsQueryVariables: computed(() => threadsQueryVariables.value),
|
||||
loadMoreVersions,
|
||||
resourcesLoaded: computed(() => initLoadDone.value),
|
||||
resourcesLoading: computed(() => viewerLoadedResourcesLoading.value)
|
||||
resourcesLoading: computed(() => viewerLoadedResourcesLoading.value),
|
||||
savedView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1050,9 +1129,7 @@ export function useSetupViewer(params: UseSetupViewerParams): InjectableViewerSt
|
||||
return rawState
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPOSABLES FOR RETRIEVING (PARTS OF) INJECTABLE STATE
|
||||
*/
|
||||
// COMPOSABLES FOR RETRIEVING (PARTS OF) INJECTABLE STATE
|
||||
|
||||
export function useInjectedViewerState(): InjectableViewerState {
|
||||
// we're forcing TS to ignore the scenario where this data can't be found and returns undefined
|
||||
|
||||
@@ -52,7 +52,20 @@ import {
|
||||
import { setupDebugMode } from '~~/lib/viewer/composables/setup/dev'
|
||||
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
|
||||
import { useMixpanel } from '~~/lib/core/composables/mp'
|
||||
import type { SectionBoxData } from '@speckle/shared/viewer/state'
|
||||
import {
|
||||
isSerializedViewerState,
|
||||
type SectionBoxData,
|
||||
type SerializedViewerState
|
||||
} from '@speckle/shared/viewer/state'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import {
|
||||
StateApplyMode,
|
||||
useApplySerializedState
|
||||
} from '~/lib/viewer/composables/serialization'
|
||||
import { useViewerRealtimeActivityTracker } from '~/lib/viewer/composables/activity'
|
||||
import { resourceBuilder } from '@speckle/shared/viewer/route'
|
||||
import { useEventBus } from '~/lib/core/composables/eventBus'
|
||||
import { ViewerEventBusKeys } from '~/lib/viewer/helpers/eventBus'
|
||||
|
||||
function useViewerLoadCompleteEventHandler() {
|
||||
const state = useInjectedViewerState()
|
||||
@@ -435,8 +448,8 @@ function useViewerCameraIntegration() {
|
||||
useViewerCameraTracker(
|
||||
() => {
|
||||
loadCameraDataFromViewer()
|
||||
}
|
||||
// { debounceWait: 100 }
|
||||
},
|
||||
{ throttleWait: 100 }
|
||||
)
|
||||
|
||||
useOnViewerLoadComplete(({ isInitial }) => {
|
||||
@@ -900,9 +913,115 @@ function useDisableZoomOnEmbed() {
|
||||
)
|
||||
}
|
||||
|
||||
graphql(`
|
||||
fragment UseViewerSavedViewSetup_SavedView on SavedView {
|
||||
id
|
||||
viewerState
|
||||
}
|
||||
`)
|
||||
|
||||
const useViewerSavedViewSetup = () => {
|
||||
const {
|
||||
savedViewId,
|
||||
resources: {
|
||||
request: { resourceIdString },
|
||||
response: { savedView, resolvedResourceIdString }
|
||||
},
|
||||
urlHashState: { savedViewId: urlHashSavedViewId }
|
||||
} = useInjectedViewerState()
|
||||
const applyState = useApplySerializedState()
|
||||
const { serializedStateId } = useViewerRealtimeActivityTracker()
|
||||
const { on } = useEventBus()
|
||||
|
||||
// Saved View ID will be unset, once the user does anything to the viewer that
|
||||
// changes it from the saved view
|
||||
const savedViewStateId = ref<string>()
|
||||
|
||||
const validState = (state: unknown) => (isSerializedViewerState(state) ? state : null)
|
||||
|
||||
const apply = async (state: SerializedViewerState) => {
|
||||
// Combine resolved w/ old, resolved taking precedence - we dont want to unload
|
||||
// other federated resources that are not a part of the saved view
|
||||
const combinedIdString = resourceBuilder()
|
||||
.addResources(resolvedResourceIdString.value)
|
||||
.addNew(resourceIdString.value)
|
||||
.toString()
|
||||
|
||||
await resourceIdString.update(combinedIdString)
|
||||
await applyState(state, StateApplyMode.SavedView)
|
||||
savedViewStateId.value = serializedStateId.value
|
||||
}
|
||||
|
||||
const update = (params: { viewId?: string }) => {
|
||||
// If passing in viewId and it differs, apply and wait for that to finish
|
||||
if (params.viewId && params.viewId !== savedViewId.value) {
|
||||
savedViewId.value = params.viewId
|
||||
return
|
||||
}
|
||||
|
||||
// Re-apply current state
|
||||
const state = validState(savedView.value?.viewerState)
|
||||
if (!state) return
|
||||
apply(state)
|
||||
}
|
||||
|
||||
// Allow force update
|
||||
on(ViewerEventBusKeys.UpdateSavedView, (params) => {
|
||||
update(params)
|
||||
})
|
||||
|
||||
// Apply saved view state on initial load
|
||||
useOnViewerLoadComplete(async ({ isInitial }) => {
|
||||
const state = validState(savedView.value?.viewerState)
|
||||
|
||||
if (isInitial && state) {
|
||||
await apply(state)
|
||||
}
|
||||
})
|
||||
|
||||
// Saved view changed, apply
|
||||
watch(savedView, (newVal, oldVal) => {
|
||||
if (!newVal || newVal.id === oldVal?.id) return
|
||||
|
||||
const state = validState(newVal.viewerState)
|
||||
if (!state) return
|
||||
|
||||
// If the saved view has changed, apply it
|
||||
apply(state)
|
||||
})
|
||||
|
||||
// If the URL hash saved view ID has changed, update the saved view ID
|
||||
watch(
|
||||
urlHashSavedViewId,
|
||||
async (newVal, oldVal) => {
|
||||
if (newVal === oldVal) return
|
||||
|
||||
savedViewId.value = newVal
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Did state change after applying saved view? Undo view
|
||||
watch(
|
||||
serializedStateId,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal === oldVal) return
|
||||
|
||||
// If the saved view state ID is different from the current serialized state ID, reset the saved view
|
||||
if (savedViewStateId.value && newVal !== savedViewStateId.value) {
|
||||
savedViewId.value = null
|
||||
void urlHashSavedViewId.update(null)
|
||||
savedViewStateId.value = undefined
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
}
|
||||
|
||||
export function useViewerPostSetup() {
|
||||
if (import.meta.server) return
|
||||
useViewerObjectAutoLoading()
|
||||
useViewerSavedViewSetup()
|
||||
useViewerReceiveTracking()
|
||||
useViewerSelectionEventHandler()
|
||||
useViewerLoadCompleteEventHandler()
|
||||
|
||||
@@ -6,7 +6,8 @@ import { useDiffBuilderUtilities } from '~~/lib/viewer/composables/setup/diff'
|
||||
export enum ViewerHashStateKeys {
|
||||
FocusedThreadId = 'threadId',
|
||||
Diff = 'diff',
|
||||
EmbedOptions = 'embed'
|
||||
EmbedOptions = 'embed',
|
||||
SavedViewId = 'savedViewId'
|
||||
}
|
||||
|
||||
export function setupUrlHashState(): InjectableViewerState['urlHashState'] {
|
||||
@@ -40,8 +41,21 @@ export function setupUrlHashState(): InjectableViewerState['urlHashState'] {
|
||||
asyncRead: false
|
||||
})
|
||||
|
||||
const savedViewId = writableAsyncComputed({
|
||||
get: () => hashState.value[ViewerHashStateKeys.SavedViewId] || null,
|
||||
set: async (newVal) => {
|
||||
await hashState.update({
|
||||
...hashState.value,
|
||||
[ViewerHashStateKeys.SavedViewId]: newVal
|
||||
})
|
||||
},
|
||||
initialState: null,
|
||||
asyncRead: false
|
||||
})
|
||||
|
||||
return {
|
||||
focusedThreadId,
|
||||
diff
|
||||
diff,
|
||||
savedViewId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
|
||||
export const projectViewerResourcesQuery = graphql(`
|
||||
query ProjectViewerResources($projectId: String!, $resourceUrlString: String!) {
|
||||
query ProjectViewerResources(
|
||||
$projectId: String!
|
||||
$resourceUrlString: String!
|
||||
$savedViewId: String
|
||||
) {
|
||||
project(id: $projectId) {
|
||||
id
|
||||
viewerResources(resourceIdString: $resourceUrlString) {
|
||||
viewerResources(resourceIdString: $resourceUrlString, savedViewId: $savedViewId) {
|
||||
identifier
|
||||
items {
|
||||
modelId
|
||||
@@ -16,6 +20,18 @@ export const projectViewerResourcesQuery = graphql(`
|
||||
}
|
||||
`)
|
||||
|
||||
export const viewerActiveSavedViewQuery = graphql(`
|
||||
query ViewerActiveSavedView($projectId: String!, $savedViewId: ID!) {
|
||||
project(id: $projectId) {
|
||||
id
|
||||
savedView(id: $savedViewId) {
|
||||
id
|
||||
...UseViewerSavedViewSetup_SavedView
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
/**
|
||||
* Query to load all metadata needed for loaded models (& their versions) in the viewer, for
|
||||
* all sidebar panels and everything
|
||||
@@ -65,6 +81,7 @@ export const viewerLoadedResourcesQuery = graphql(`
|
||||
...UseViewerUserActivityBroadcasting_Project
|
||||
...ViewerGendoPanel_Project
|
||||
...ViewerResourcesLimitAlert_Project
|
||||
...ViewerSavedViewsPanel_Project
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum ViewerEventBusKeys {
|
||||
UpdateSavedView = 'aaa'
|
||||
}
|
||||
|
||||
// Add mappings between event keys and expected payloads here
|
||||
export type ViewerEventBusKeyPayloadMap = {
|
||||
[ViewerEventBusKeys.UpdateSavedView]: { viewId?: string }
|
||||
} & { [k in ViewerEventBusKeys]: unknown } & Record<string, unknown>
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { StringEnumValues } from '@speckle/shared'
|
||||
|
||||
export const ViewsType = {
|
||||
All: 'all-views',
|
||||
My: 'my-views',
|
||||
Connector: 'connector-views'
|
||||
} as const
|
||||
export type ViewsType = StringEnumValues<typeof ViewsType>
|
||||
|
||||
export const viewsTypeLabels: Record<ViewsType, string> = {
|
||||
[ViewsType.All]: 'All Views',
|
||||
[ViewsType.My]: 'My Views',
|
||||
[ViewsType.Connector]: 'From connectors'
|
||||
}
|
||||
@@ -22,6 +22,13 @@ export const PanelShortcuts = {
|
||||
modifiers: [ModifierKeys.Shift],
|
||||
key: 'D',
|
||||
action: 'ToggleDiscussions'
|
||||
},
|
||||
ToggleSavedViews: {
|
||||
name: 'Saved Views',
|
||||
description: 'Toggle saved views panel',
|
||||
modifiers: [ModifierKeys.Shift],
|
||||
key: 'V',
|
||||
action: 'ToggleSavedViews'
|
||||
}
|
||||
} as const
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useQuery } from '@vue/apollo-composable'
|
||||
import { workspaceLimitsQuery } from '~/lib/workspaces/graphql/queries'
|
||||
import { WorkspacePlanConfigs, type MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import type { WorkspacePlanLimits_WorkspaceFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { useFeatureFlags } from '~/lib/common/composables/env'
|
||||
|
||||
graphql(`
|
||||
fragment WorkspacePlanLimits_Workspace on Workspace {
|
||||
@@ -19,6 +20,8 @@ export const useWorkspaceLimits = (params: {
|
||||
workspace?: MaybeRef<MaybeNullOrUndefined<WorkspacePlanLimits_WorkspaceFragment>>
|
||||
}) => {
|
||||
const { slug } = params
|
||||
|
||||
const featureFlags = useFeatureFlags()
|
||||
const { result } = useQuery(
|
||||
workspaceLimitsQuery,
|
||||
() => ({
|
||||
@@ -44,7 +47,7 @@ export const useWorkspaceLimits = (params: {
|
||||
commentHistory: null
|
||||
}
|
||||
|
||||
const planConfig = WorkspacePlanConfigs[planName]
|
||||
const planConfig = WorkspacePlanConfigs({ featureFlags })[planName]
|
||||
return planConfig?.limits
|
||||
})
|
||||
|
||||
|
||||
@@ -18,25 +18,30 @@ import { activeUserWorkspaceExistenceCheckQuery } from '~/lib/auth/graphql/queri
|
||||
import { useApolloClientFromNuxt } from '~~/lib/common/composables/graphql'
|
||||
import { convertThrowIntoFetchResult } from '~~/lib/common/helpers/graphql'
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
const isAuthPage = to.path.startsWith('/authn/')
|
||||
const isSSOPath = to.path.includes('/sso/')
|
||||
if (isAuthPage || isSSOPath) return
|
||||
|
||||
const client = useApolloClientFromNuxt()
|
||||
|
||||
// Fetch required data
|
||||
const { data: serverInfoData } = await client
|
||||
.query({
|
||||
query: mainServerInfoDataQuery
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
const isInPlaceNavigation = checkIfIsInPlaceNavigation(to, from)
|
||||
|
||||
const { data: userData } = await client
|
||||
.query({
|
||||
query: activeUserQuery
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
// Fetch required data
|
||||
const [{ data: serverInfoData }, { data: userData }] = await Promise.all([
|
||||
client
|
||||
.query({
|
||||
query: mainServerInfoDataQuery,
|
||||
fetchPolicy: isInPlaceNavigation ? 'cache-first' : undefined
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult),
|
||||
client
|
||||
.query({
|
||||
query: activeUserQuery,
|
||||
fetchPolicy: isInPlaceNavigation ? 'cache-first' : undefined
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
])
|
||||
|
||||
// If user is not logged in, skip all checks
|
||||
if (!userData?.activeUser?.id) return
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useSetActiveWorkspace } from '~/lib/user/composables/activeWorkspace'
|
||||
/**
|
||||
* Used in project page to validate that project ID refers to a valid project and redirects to 404 if not
|
||||
*/
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
const projectId = to.params.id as string
|
||||
|
||||
// Check if embed token is present in URL
|
||||
@@ -28,6 +28,8 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
const { isLoggedIn } = useActiveUser()
|
||||
const isWorkspacesEnabled = useIsWorkspacesEnabled()
|
||||
|
||||
const isInPlaceNavigation = checkIfIsInPlaceNavigation(to, from)
|
||||
|
||||
const { data, errors } = await client
|
||||
.query({
|
||||
query: projectAccessCheckQuery,
|
||||
@@ -35,7 +37,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
context: {
|
||||
skipLoggingErrors: true
|
||||
},
|
||||
fetchPolicy: 'network-only'
|
||||
fetchPolicy: isInPlaceNavigation ? 'cache-first' : 'network-only'
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
@@ -79,7 +81,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoggedIn.value && isWorkspacesEnabled.value) {
|
||||
if (isLoggedIn.value && isWorkspacesEnabled.value && !isInPlaceNavigation) {
|
||||
await setActiveWorkspace({ id: data?.project.workspaceId })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -220,6 +220,10 @@ export default defineNuxtConfig({
|
||||
to: '/workspaces/actions/create',
|
||||
statusCode: 301
|
||||
}
|
||||
},
|
||||
// CSR only viewer, we cant preload much because of the url hash state which is CSR only
|
||||
'/projects/:id/models/:modelId': {
|
||||
ssr: false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"js-cookie": "^3.0.1",
|
||||
"jsdom": "^22.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-vue-next": "^0.535.0",
|
||||
"marked": "^5.1.0",
|
||||
"marked-plaintext": "^0.0.2",
|
||||
"mitt": "^3.0.0",
|
||||
|
||||
@@ -14,6 +14,10 @@ export default defineNuxtPlugin(() => {
|
||||
key: T,
|
||||
handler: (event: EventBusKeyPayloadMap[T]) => void
|
||||
) => emitter.on(key, handler),
|
||||
off: <T extends EventBusKeys>(
|
||||
key: T,
|
||||
handler?: (event: EventBusKeyPayloadMap[T]) => void
|
||||
) => emitter.off(key, handler),
|
||||
emit: <T extends EventBusKeys>(key: T, payload: EventBusKeyPayloadMap[T]) =>
|
||||
emitter.emit(key, payload)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ROOT_QUERY,
|
||||
ROOT_SUBSCRIPTION
|
||||
} from '~/lib/common/helpers/graphql'
|
||||
import { checkIfIsInPlaceNavigation } from '~/lib/common/helpers/navigation'
|
||||
|
||||
/**
|
||||
* Debugging helper to ensure variables are available in debugging scope
|
||||
@@ -24,7 +25,6 @@ export const getRouteDefinition = (route?: RouteLocationNormalized) => {
|
||||
const matchedPath = route ? route.matched[route.matched.length - 1]?.path : undefined
|
||||
return matchedPath || '/404'
|
||||
}
|
||||
|
||||
export {
|
||||
ToastNotificationType,
|
||||
wrapRefWithTracking,
|
||||
@@ -33,6 +33,7 @@ export {
|
||||
getFirstGqlErrorMessage,
|
||||
modifyObjectField,
|
||||
getCacheId,
|
||||
checkIfIsInPlaceNavigation,
|
||||
ROOT_QUERY,
|
||||
ROOT_MUTATION,
|
||||
ROOT_SUBSCRIPTION
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^22.6.0"
|
||||
"node": "^22.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build:tsc:watch": "tsc -p ./tsconfig.build.json --watch",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^22.6.0"
|
||||
"node": "^22.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build:frontend": "yarn workspace @speckle/preview-frontend build",
|
||||
|
||||
+7
-2
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"mochaExplorer.env": { "NODE_ENV": "test" },
|
||||
"mochaExplorer.nodeArgv": ["--import", "tsx"],
|
||||
"mochaExplorer.env": { "NODE_ENV": "test", "TSX": "true" },
|
||||
"mochaExplorer.nodeArgv": [
|
||||
"--experimental-strip-types",
|
||||
"--experimental-transform-types",
|
||||
"--import",
|
||||
"./esmLoader.js"
|
||||
],
|
||||
"javascript.suggest.autoImports": true,
|
||||
"typescript.suggest.autoImports": true,
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
|
||||
@@ -45,4 +45,8 @@ type PermissionCheckResult {
|
||||
code: String!
|
||||
message: String!
|
||||
payload: JSONObject
|
||||
"""
|
||||
Same as message, or undefined if check is authorized
|
||||
"""
|
||||
errorMessage: String
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ extend type Project {
|
||||
"""
|
||||
viewerResources(
|
||||
resourceIdString: String!
|
||||
"""
|
||||
If a saved view ID is specified, the returned resources will be adjusted to return the view's resources instead
|
||||
"""
|
||||
savedViewId: String
|
||||
loadedVersionsOnly: Boolean = true
|
||||
): [ViewerResourceGroup!]!
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
enum SavedViewVisibility {
|
||||
public
|
||||
authorOnly
|
||||
}
|
||||
|
||||
type SavedView {
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String
|
||||
"""
|
||||
Empty ID means default/ungrouped view
|
||||
"""
|
||||
groupId: ID
|
||||
"""
|
||||
Always available because even ungrouped views show up in a fake "Ungrouped" group
|
||||
"""
|
||||
group: SavedViewGroup!
|
||||
author: LimitedUser
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
projectId: ID!
|
||||
"""
|
||||
Original resource ID string that this view is associated with.
|
||||
"""
|
||||
resourceIdString: String!
|
||||
"""
|
||||
Same as resourceIdString, but split into an array of resource IDs.
|
||||
"""
|
||||
resourceIds: [String!]!
|
||||
isHomeView: Boolean!
|
||||
visibility: SavedViewVisibility!
|
||||
"""
|
||||
Viewer state, the actual view configuration
|
||||
"""
|
||||
viewerState: JSONObject!
|
||||
"""
|
||||
Encoded screenshot of the view
|
||||
"""
|
||||
screenshot: String!
|
||||
"""
|
||||
For figuring out position in the group
|
||||
"""
|
||||
position: Float!
|
||||
}
|
||||
|
||||
type SavedViewCollection {
|
||||
cursor: String
|
||||
totalCount: Int!
|
||||
items: [SavedView!]!
|
||||
}
|
||||
|
||||
input SavedViewGroupViewsInput {
|
||||
"""
|
||||
Whether to only views authored by the current user
|
||||
"""
|
||||
onlyAuthored: Boolean
|
||||
"""
|
||||
Whether to only include views matching this search term
|
||||
"""
|
||||
search: String
|
||||
"""
|
||||
Optionally specify sort direction. Default: descending
|
||||
"""
|
||||
sortDirection: SortDirection
|
||||
"""
|
||||
Optionally specify sort by field. Default: updatedAt
|
||||
Options: updatedAt, createdAt, name
|
||||
"""
|
||||
sortBy: String
|
||||
limit: Int
|
||||
cursor: String
|
||||
}
|
||||
|
||||
type SavedViewGroup {
|
||||
"""
|
||||
This is always set even for fake/not persisted groups for Apollo caching
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
Only set if this is a real/persisted group.
|
||||
"""
|
||||
groupId: ID
|
||||
projectId: ID!
|
||||
"""
|
||||
Resources that were used to find this group
|
||||
"""
|
||||
resourceIds: [String!]!
|
||||
title: String!
|
||||
isUngroupedViewsGroup: Boolean!
|
||||
views(input: SavedViewGroupViewsInput!): SavedViewCollection!
|
||||
}
|
||||
|
||||
type SavedViewGroupCollection {
|
||||
cursor: String
|
||||
totalCount: Int!
|
||||
items: [SavedViewGroup!]!
|
||||
}
|
||||
|
||||
input SavedViewGroupsInput {
|
||||
"""
|
||||
Viewer resource ID string that identifies which resources should be loaded
|
||||
"""
|
||||
resourceIdString: String!
|
||||
"""
|
||||
Whether to only include groups w/ views authored by the current user
|
||||
"""
|
||||
onlyAuthored: Boolean
|
||||
"""
|
||||
Whether to only include groups that have views matching this search term
|
||||
"""
|
||||
search: String
|
||||
limit: Int
|
||||
cursor: String
|
||||
}
|
||||
|
||||
input GetUngroupedViewGroupInput {
|
||||
"""
|
||||
Viewer resource ID string that identifies which resources should be loaded
|
||||
"""
|
||||
resourceIdString: String!
|
||||
}
|
||||
|
||||
extend type Project {
|
||||
savedViewGroups(input: SavedViewGroupsInput!): SavedViewGroupCollection!
|
||||
savedViewGroup(id: ID!): SavedViewGroup!
|
||||
ungroupedViewGroup(input: GetUngroupedViewGroupInput!): SavedViewGroup!
|
||||
savedView(id: ID!): SavedView!
|
||||
}
|
||||
|
||||
input CreateSavedViewInput {
|
||||
projectId: ID!
|
||||
resourceIdString: String!
|
||||
"""
|
||||
Group id, if grouping necessary
|
||||
"""
|
||||
groupId: ID
|
||||
|
||||
"""
|
||||
Auto-generated name, if not specified
|
||||
"""
|
||||
name: String
|
||||
description: String
|
||||
"""
|
||||
SerializedViewerState. If omitted, comment won't render (correctly) inside the
|
||||
viewer, but will still be retrievable through the API
|
||||
"""
|
||||
viewerState: JSONObject!
|
||||
"""
|
||||
Encoded screenshot of the view
|
||||
"""
|
||||
screenshot: String!
|
||||
"""
|
||||
Optionally also set this as the home/default view for the target model
|
||||
"""
|
||||
isHomeView: Boolean
|
||||
"""
|
||||
Set visibility of the view. Default: public
|
||||
"""
|
||||
visibility: SavedViewVisibility
|
||||
}
|
||||
|
||||
input CreateSavedViewGroupInput {
|
||||
projectId: ID!
|
||||
resourceIdString: String!
|
||||
groupName: String!
|
||||
}
|
||||
|
||||
type SavedViewMutations {
|
||||
createGroup(input: CreateSavedViewGroupInput!): SavedViewGroup!
|
||||
createView(input: CreateSavedViewInput!): SavedView!
|
||||
}
|
||||
|
||||
extend type ProjectMutations {
|
||||
savedViewMutations: SavedViewMutations!
|
||||
}
|
||||
|
||||
extend type ProjectPermissionChecks {
|
||||
canCreateSavedView: PermissionCheckResult!
|
||||
}
|
||||
Vendored
+1
@@ -44,6 +44,7 @@ if ((isTestEnv() || isDevEnv()) && startDebugger) {
|
||||
}
|
||||
}
|
||||
|
||||
// Load dotenv
|
||||
dotenv.config({ path: `${packageRoot}/.env` })
|
||||
|
||||
// knex is a singleton controlled by module so can't wait til app init
|
||||
|
||||
@@ -185,7 +185,14 @@ const config: CodegenConfig = {
|
||||
RootPermissionChecks:
|
||||
'@/modules/core/helpers/graphTypes#RootPermissionChecksGraphQLReturn',
|
||||
WorkspacePermissionChecks:
|
||||
'@/modules/workspacesCore/helpers/graphTypes#WorkspacePermissionChecksGraphQLReturn'
|
||||
'@/modules/workspacesCore/helpers/graphTypes#WorkspacePermissionChecksGraphQLReturn',
|
||||
SavedViewMutations:
|
||||
'@/modules/core/helpers/graphTypes#MutationsObjectGraphQLReturn',
|
||||
SavedView: '@/modules/viewer/helpers/graphTypes#SavedViewGraphQLReturn',
|
||||
SavedViewGroup:
|
||||
'@/modules/viewer/helpers/graphTypes#SavedViewGroupGraphQLReturn',
|
||||
PermissionCheckResult:
|
||||
'@/modules/core/helpers/graphTypes#PermissionCheckResultGraphQLReturn'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,8 @@ const configs = [
|
||||
{
|
||||
files: ['**/*.spec.ts', '**/tests/**/*.{js,ts}', 'test/**/*.{js,ts}'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-expressions': 'off'
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off'
|
||||
}
|
||||
},
|
||||
prettierConfig
|
||||
|
||||
@@ -21,7 +21,7 @@ const aliases = {
|
||||
/**
|
||||
* EXTENSIONS TO EVALUATE FOR EXTENSIONLESS IMPORTS
|
||||
*/
|
||||
const extensions = ['.ts', '.js', '.mjs', '.cjs', '.json']
|
||||
const extensions = ['.ts', '.js', '.mjs', '.cjs', '.json', '.d.ts']
|
||||
|
||||
// Register the module hooks
|
||||
register('./esmLoader.js', {
|
||||
|
||||
@@ -3,7 +3,6 @@ import type {
|
||||
StreamResourceTypes,
|
||||
StreamScopeActivity
|
||||
} from '@/modules/activitystream/helpers/types'
|
||||
import type { ViewerResourceItem } from '@/modules/comments/domain/types'
|
||||
import type {
|
||||
CommentCreateInput,
|
||||
CreateCommentInput,
|
||||
@@ -11,6 +10,7 @@ import type {
|
||||
ReplyCreateInput
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import type { StreamRecord, UserRecord } from '@/modules/core/helpers/types'
|
||||
import type { ViewerResourceItem } from '@/modules/viewer/domain/types/resources'
|
||||
import z from 'zod'
|
||||
|
||||
// Activity
|
||||
|
||||
@@ -259,7 +259,8 @@ const mocks: SpeckleModuleMocksConfig = FF_AUTOMATE_MODULE_ENABLED
|
||||
canRegenerateToken: () => ({
|
||||
authorized: faker.datatype.boolean(),
|
||||
code: faker.string.alphanumeric(10),
|
||||
message: faker.lorem.words(10)
|
||||
message: faker.lorem.words(10),
|
||||
payload: null
|
||||
})
|
||||
},
|
||||
AutomateFunctionRelease: {
|
||||
|
||||
@@ -50,6 +50,7 @@ export default {
|
||||
userId: context.userId,
|
||||
projectId: parent.projectId
|
||||
})
|
||||
|
||||
return Authz.toGraphqlResult(canCreateAutomation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@/modules/core/repositories/streams'
|
||||
import {
|
||||
getBranchByIdFactory,
|
||||
getBranchesByIdsFactory,
|
||||
getBranchLatestCommitsFactory,
|
||||
getStreamBranchByNameFactory,
|
||||
getStreamBranchesByNameFactory,
|
||||
@@ -23,8 +24,6 @@ import {
|
||||
createCommentThreadAndNotifyFactory
|
||||
} from '@/modules/comments/services/management'
|
||||
import {
|
||||
getViewerResourceGroupsFactory,
|
||||
getViewerResourceItemsUngroupedFactory,
|
||||
getViewerResourcesForCommentFactory,
|
||||
getViewerResourcesForCommentsFactory,
|
||||
getViewerResourcesFromLegacyIdentifiersFactory
|
||||
@@ -53,6 +52,10 @@ import { createObjectFactory } from '@/modules/core/services/objects/management'
|
||||
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { db } from '@/db/knex'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getViewerResourceGroupsFactory,
|
||||
getViewerResourceItemsUngroupedFactory
|
||||
} from '@/modules/viewer/services/viewerResources'
|
||||
|
||||
const command: CommandModule<
|
||||
unknown,
|
||||
@@ -114,7 +117,8 @@ const command: CommandModule<
|
||||
getBranchLatestCommits,
|
||||
getStreamBranchesByName: getStreamBranchesByNameFactory({ db: projectDb }),
|
||||
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db: projectDb }),
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db: projectDb })
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db: projectDb }),
|
||||
getBranchesByIds: getBranchesByIdsFactory({ db: projectDb })
|
||||
})
|
||||
})
|
||||
const getViewerResourcesFromLegacyIdentifiers =
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import {
|
||||
createBranchFactory,
|
||||
getBranchByIdFactory,
|
||||
getBranchesByIdsFactory,
|
||||
getBranchLatestCommitsFactory,
|
||||
getStreamBranchByNameFactory,
|
||||
getStreamBranchesByNameFactory,
|
||||
@@ -35,8 +36,6 @@ import {
|
||||
insertStreamCommitsFactory
|
||||
} from '@/modules/core/repositories/commits'
|
||||
import {
|
||||
getViewerResourceGroupsFactory,
|
||||
getViewerResourceItemsUngroupedFactory,
|
||||
getViewerResourcesForCommentFactory,
|
||||
getViewerResourcesForCommentsFactory,
|
||||
getViewerResourcesFromLegacyIdentifiersFactory
|
||||
@@ -70,6 +69,10 @@ import {
|
||||
} from '@/modules/core/repositories/projects'
|
||||
import { storeModelFactory } from '@/modules/core/repositories/models'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getViewerResourceGroupsFactory,
|
||||
getViewerResourceItemsUngroupedFactory
|
||||
} from '@/modules/viewer/services/viewerResources'
|
||||
|
||||
const command: CommandModule<
|
||||
unknown,
|
||||
@@ -142,7 +145,8 @@ const command: CommandModule<
|
||||
getBranchLatestCommits: getBranchLatestCommitsFactory({ db: projectDb }),
|
||||
getStreamBranchesByName: getStreamBranchesByNameFactory({ db: projectDb }),
|
||||
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db: projectDb }),
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db: projectDb })
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db: projectDb }),
|
||||
getBranchesByIds: getBranchesByIdsFactory({ db: projectDb })
|
||||
})
|
||||
})
|
||||
const getViewerResourcesFromLegacyIdentifiers =
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
CommentCreatedActivityInput,
|
||||
ReplyCreatedActivityInput
|
||||
} from '@/modules/activitystream/domain/types'
|
||||
import type { ViewerResourceItem } from '@/modules/comments/domain/types'
|
||||
import type { ViewerResourceItem } from '@/modules/viewer/domain/types/resources'
|
||||
import type { CommentRecord } from '@/modules/comments/helpers/types'
|
||||
import type { MutationCommentArchiveArgs } from '@/modules/core/graph/generated/graphql'
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import type {
|
||||
ExtendedComment,
|
||||
ResourceIdentifier,
|
||||
ViewerResourceGroup,
|
||||
ViewerResourceItem
|
||||
ResourceIdentifier
|
||||
} from '@/modules/comments/domain/types'
|
||||
import type {
|
||||
CommentLinkRecord,
|
||||
@@ -14,8 +12,7 @@ import type {
|
||||
CreateCommentInput,
|
||||
CreateCommentReplyInput,
|
||||
EditCommentInput,
|
||||
LegacyCommentViewerData,
|
||||
ViewerUpdateTrackingTarget
|
||||
LegacyCommentViewerData
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import type { SmartTextEditorValueSchema } from '@/modules/core/services/richTextEditorService'
|
||||
import type { BatchedSelectOptions } from '@/modules/shared/helpers/dbHelper'
|
||||
@@ -26,6 +23,7 @@ import type {
|
||||
import type { MaybeNullOrUndefined, SpeckleViewer } from '@speckle/shared'
|
||||
import type { Knex } from 'knex'
|
||||
import type { Merge } from 'type-fest'
|
||||
import type { ViewerResourceItem } from '@/modules/viewer/domain/types/resources'
|
||||
|
||||
type SerializedViewerState = SpeckleViewer.ViewerState.SerializedViewerState
|
||||
|
||||
@@ -229,14 +227,6 @@ export type GetViewerResourcesFromLegacyIdentifiers = (
|
||||
resources: Array<ResourceIdentifier>
|
||||
) => Promise<ViewerResourceItem[]>
|
||||
|
||||
export type GetViewerResourceGroups = (
|
||||
target: ViewerUpdateTrackingTarget
|
||||
) => Promise<ViewerResourceGroup[]>
|
||||
|
||||
export type GetViewerResourceItemsUngrouped = (
|
||||
target: ViewerUpdateTrackingTarget
|
||||
) => Promise<ViewerResourceItem[]>
|
||||
|
||||
export type ConvertLegacyDataToState = (
|
||||
data: Partial<LegacyCommentViewerData>,
|
||||
comment: CommentRecord
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CommentLinkRecord, CommentRecord } from '@/modules/comments/helpers/types'
|
||||
import type { Nullable } from '@speckle/shared'
|
||||
|
||||
export type ResourceIdentifier = {
|
||||
resourceId: string
|
||||
@@ -27,18 +26,3 @@ export type ExtendedComment = CommentRecord & {
|
||||
*/
|
||||
viewedAt?: Date
|
||||
}
|
||||
|
||||
export type ViewerResourceItem = {
|
||||
/** Null if resource represents an object */
|
||||
modelId?: Nullable<string>
|
||||
objectId: string
|
||||
/** Null if resource represents an object */
|
||||
versionId?: Nullable<string>
|
||||
}
|
||||
|
||||
export type ViewerResourceGroup = {
|
||||
/** Resource identifier used to refer to a collection of resource items */
|
||||
identifier: string
|
||||
/** Viewer resources that the identifier refers to */
|
||||
items: Array<ViewerResourceItem>
|
||||
}
|
||||
|
||||
@@ -48,12 +48,9 @@ import {
|
||||
ProjectSubscriptions
|
||||
} from '@/modules/shared/utils/subscriptions'
|
||||
import {
|
||||
doViewerResourcesFit,
|
||||
getViewerResourcesForCommentFactory,
|
||||
getViewerResourcesFromLegacyIdentifiersFactory,
|
||||
getViewerResourcesForCommentsFactory,
|
||||
getViewerResourceItemsUngroupedFactory,
|
||||
getViewerResourceGroupsFactory
|
||||
getViewerResourcesForCommentsFactory
|
||||
} from '@/modules/core/services/commit/viewerResources'
|
||||
import {
|
||||
createCommentThreadAndNotifyFactory,
|
||||
@@ -81,6 +78,7 @@ import {
|
||||
getSpecificBranchCommitsFactory
|
||||
} from '@/modules/core/repositories/commits'
|
||||
import {
|
||||
getBranchesByIdsFactory,
|
||||
getBranchLatestCommitsFactory,
|
||||
getStreamBranchesByNameFactory
|
||||
} from '@/modules/core/repositories/branches'
|
||||
@@ -93,6 +91,11 @@ import { StreamNotFoundError } from '@/modules/core/errors/stream'
|
||||
import { throwIfAuthNotOk } from '@/modules/shared/helpers/errorHelper'
|
||||
import { withOperationLogging } from '@/observability/domain/businessLogging'
|
||||
import { isCreatedBeyondHistoryLimitCutoffFactory } from '@/modules/gatekeeperCore/utils/limits'
|
||||
import {
|
||||
doViewerResourcesFit,
|
||||
getViewerResourceGroupsFactory,
|
||||
getViewerResourceItemsUngroupedFactory
|
||||
} from '@/modules/viewer/services/viewerResources'
|
||||
|
||||
// We can use the main DB for these
|
||||
const getStream = getStreamFactory({ db })
|
||||
@@ -118,7 +121,8 @@ const buildGetViewerResourceItemsUngrouped = (deps: { db: Knex }) =>
|
||||
getBranchLatestCommits: getBranchLatestCommitsFactory(deps),
|
||||
getStreamBranchesByName: getStreamBranchesByNameFactory(deps),
|
||||
getSpecificBranchCommits: getSpecificBranchCommitsFactory(deps),
|
||||
getAllBranchCommits: getAllBranchCommitsFactory(deps)
|
||||
getAllBranchCommits: getAllBranchCommitsFactory(deps),
|
||||
getBranchesByIds: getBranchesByIdsFactory(deps)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -3,45 +3,24 @@ import type {
|
||||
GetViewerResourcesForComments
|
||||
} from '@/modules/comments/domain/operations'
|
||||
import type { LegacyCommentViewerData } from '@/modules/core/graph/generated/graphql'
|
||||
import { viewerResourcesToString } from '@/modules/core/services/commit/viewerResources'
|
||||
import type { Nullable } from '@speckle/shared'
|
||||
import { SpeckleViewer } from '@speckle/shared'
|
||||
import { has, get, intersection, isObjectLike } from 'lodash-es'
|
||||
|
||||
type SerializedViewerState = SpeckleViewer.ViewerState.SerializedViewerState
|
||||
import { viewerResourcesToString } from '@/modules/viewer/services/viewerResources'
|
||||
import type {
|
||||
VersionedSerializedViewerState,
|
||||
SerializedViewerState
|
||||
} from '@speckle/shared/viewer/state'
|
||||
import {
|
||||
formatSerializedViewerState,
|
||||
isVersionedSerializedViewerState,
|
||||
inputToVersionedState
|
||||
} from '@speckle/shared/viewer/state'
|
||||
import { intersection, isObjectLike } from 'lodash-es'
|
||||
|
||||
export type LegacyData = Partial<LegacyCommentViewerData>
|
||||
export type DataStruct = VersionedSerializedViewerState
|
||||
|
||||
export type DataStruct = {
|
||||
version: number
|
||||
state: SerializedViewerState
|
||||
}
|
||||
|
||||
export function inputToDataStruct(
|
||||
inputSerializedViewerState: unknown
|
||||
): Nullable<DataStruct> {
|
||||
const state = SpeckleViewer.ViewerState.isSerializedViewerState(
|
||||
inputSerializedViewerState
|
||||
)
|
||||
? inputSerializedViewerState
|
||||
: null
|
||||
if (!state) return null
|
||||
|
||||
return {
|
||||
version: SpeckleViewer.ViewerState.SERIALIZED_VIEWER_STATE_VERSION,
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
export function isDataStruct(data: unknown): data is DataStruct {
|
||||
if (!data) return false
|
||||
if (!has(data, 'version')) return false
|
||||
const stateRaw = get(data, 'state')
|
||||
return SpeckleViewer.ViewerState.isSerializedViewerState(stateRaw)
|
||||
}
|
||||
|
||||
export const formatSerializedViewerState =
|
||||
SpeckleViewer.ViewerState.formatSerializedViewerState
|
||||
export { formatSerializedViewerState }
|
||||
export const inputToDataStruct = inputToVersionedState
|
||||
export const isDataStruct = isVersionedSerializedViewerState
|
||||
|
||||
export function isLegacyData(data: unknown): data is LegacyData {
|
||||
if (!data) return false
|
||||
|
||||
@@ -20,7 +20,6 @@ import type {
|
||||
CreateCommentThreadAndNotify,
|
||||
EditCommentAndNotify,
|
||||
GetComment,
|
||||
GetViewerResourceItemsUngrouped,
|
||||
GetViewerResourcesForComment,
|
||||
InsertCommentLinks,
|
||||
InsertCommentPayload,
|
||||
@@ -33,6 +32,7 @@ import type {
|
||||
import type { GetStream } from '@/modules/core/domain/streams/operations'
|
||||
import type { EventBusEmit } from '@/modules/shared/services/eventBus'
|
||||
import { CommentEvents } from '@/modules/comments/domain/events'
|
||||
import type { GetViewerResourceItemsUngrouped } from '@/modules/viewer/domain/operations/resources'
|
||||
|
||||
export const createCommentThreadAndNotifyFactory =
|
||||
(deps: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
|
||||
import type { StreamGraphQLReturn, CommitGraphQLReturn, ProjectGraphQLReturn, ObjectGraphQLReturn, VersionGraphQLReturn, ServerInviteGraphQLReturnType, ModelGraphQLReturn, ModelsTreeItemGraphQLReturn, MutationsObjectGraphQLReturn, LimitedUserGraphQLReturn, UserGraphQLReturn, EmbedTokenGraphQLReturn, GraphQLEmptyReturn, StreamCollaboratorGraphQLReturn, ProjectCollaboratorGraphQLReturn, ServerInfoGraphQLReturn, BranchGraphQLReturn, UserMetaGraphQLReturn, ProjectPermissionChecksGraphQLReturn, ModelPermissionChecksGraphQLReturn, VersionPermissionChecksGraphQLReturn, RootPermissionChecksGraphQLReturn } from '@/modules/core/helpers/graphTypes';
|
||||
import type { StreamGraphQLReturn, CommitGraphQLReturn, ProjectGraphQLReturn, ObjectGraphQLReturn, VersionGraphQLReturn, ServerInviteGraphQLReturnType, ModelGraphQLReturn, ModelsTreeItemGraphQLReturn, MutationsObjectGraphQLReturn, LimitedUserGraphQLReturn, UserGraphQLReturn, EmbedTokenGraphQLReturn, GraphQLEmptyReturn, StreamCollaboratorGraphQLReturn, ProjectCollaboratorGraphQLReturn, ServerInfoGraphQLReturn, BranchGraphQLReturn, UserMetaGraphQLReturn, ProjectPermissionChecksGraphQLReturn, ModelPermissionChecksGraphQLReturn, VersionPermissionChecksGraphQLReturn, RootPermissionChecksGraphQLReturn, PermissionCheckResultGraphQLReturn } from '@/modules/core/helpers/graphTypes';
|
||||
import type { StreamAccessRequestGraphQLReturn, ProjectAccessRequestGraphQLReturn } from '@/modules/accessrequests/helpers/graphTypes';
|
||||
import type { AutomateFunctionPermissionChecksGraphQLReturn, AutomateFunctionGraphQLReturn, AutomateFunctionReleaseGraphQLReturn, AutomationGraphQLReturn, AutomationPermissionChecksGraphQLReturn, AutomationRevisionGraphQLReturn, AutomationRevisionFunctionGraphQLReturn, AutomateRunGraphQLReturn, AutomationRunTriggerGraphQLReturn, AutomationRevisionTriggerDefinitionGraphQLReturn, AutomateFunctionRunGraphQLReturn, TriggeredAutomationsStatusGraphQLReturn, ProjectAutomationMutationsGraphQLReturn, ProjectTriggeredAutomationsStatusUpdatedMessageGraphQLReturn, ProjectAutomationsUpdatedMessageGraphQLReturn, UserAutomateInfoGraphQLReturn } from '@/modules/automate/helpers/graphTypes';
|
||||
import type { CommentReplyAuthorCollectionGraphQLReturn, CommentGraphQLReturn, CommentPermissionChecksGraphQLReturn } from '@/modules/comments/helpers/graphTypes';
|
||||
@@ -15,6 +15,7 @@ import type { ActivityCollectionGraphQLReturn } from '@/modules/activitystream/h
|
||||
import type { ServerAppGraphQLReturn, ServerAppListItemGraphQLReturn } from '@/modules/auth/helpers/graphTypes';
|
||||
import type { GendoAIRenderGraphQLReturn } from '@/modules/gendo/helpers/types/graphTypes';
|
||||
import type { ServerRegionItemGraphQLReturn } from '@/modules/multiregion/helpers/graphTypes';
|
||||
import type { SavedViewGraphQLReturn, SavedViewGroupGraphQLReturn } from '@/modules/viewer/helpers/graphTypes';
|
||||
import type { GraphQLContext } from '@/modules/shared/helpers/typeHelper';
|
||||
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
export type Maybe<T> = T | null;
|
||||
@@ -971,6 +972,33 @@ export type CreateModelInput = {
|
||||
projectId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
export type CreateSavedViewGroupInput = {
|
||||
groupName: Scalars['String']['input'];
|
||||
projectId: Scalars['ID']['input'];
|
||||
resourceIdString: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type CreateSavedViewInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Group id, if grouping necessary */
|
||||
groupId?: InputMaybe<Scalars['ID']['input']>;
|
||||
/** Optionally also set this as the home/default view for the target model */
|
||||
isHomeView?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** Auto-generated name, if not specified */
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
projectId: Scalars['ID']['input'];
|
||||
resourceIdString: Scalars['String']['input'];
|
||||
/** Encoded screenshot of the view */
|
||||
screenshot: Scalars['String']['input'];
|
||||
/**
|
||||
* SerializedViewerState. If omitted, comment won't render (correctly) inside the
|
||||
* viewer, but will still be retrievable through the API
|
||||
*/
|
||||
viewerState: Scalars['JSONObject']['input'];
|
||||
/** Set visibility of the view. Default: public */
|
||||
visibility?: InputMaybe<SavedViewVisibility>;
|
||||
};
|
||||
|
||||
export type CreateServerRegionInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
key: Scalars['String']['input'];
|
||||
@@ -1220,6 +1248,11 @@ export type GetModelUploadsInput = {
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type GetUngroupedViewGroupInput = {
|
||||
/** Viewer resource ID string that identifies which resources should be loaded */
|
||||
resourceIdString: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type InvitableCollaboratorsFilter = {
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
@@ -2177,6 +2210,8 @@ export type PermissionCheckResult = {
|
||||
__typename?: 'PermissionCheckResult';
|
||||
authorized: Scalars['Boolean']['output'];
|
||||
code: Scalars['String']['output'];
|
||||
/** Same as message, or undefined if check is authorized */
|
||||
errorMessage?: Maybe<Scalars['String']['output']>;
|
||||
message: Scalars['String']['output'];
|
||||
payload?: Maybe<Scalars['JSONObject']['output']>;
|
||||
};
|
||||
@@ -2235,9 +2270,13 @@ export type Project = {
|
||||
permissions: ProjectPermissionChecks;
|
||||
/** Active user's role for this project. `null` if request is not authenticated, or the project is not explicitly shared with you. */
|
||||
role?: Maybe<Scalars['String']['output']>;
|
||||
savedView: SavedView;
|
||||
savedViewGroup: SavedViewGroup;
|
||||
savedViewGroups: SavedViewGroupCollection;
|
||||
/** Source apps used in any models of this project */
|
||||
sourceApps: Array<Scalars['String']['output']>;
|
||||
team: Array<ProjectCollaborator>;
|
||||
ungroupedViewGroup: SavedViewGroup;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
/** Retrieve a specific project version by its ID */
|
||||
version: Version;
|
||||
@@ -2350,6 +2389,26 @@ export type ProjectPendingImportedModelsArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type ProjectSavedViewArgs = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectSavedViewGroupArgs = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectSavedViewGroupsArgs = {
|
||||
input: SavedViewGroupsInput;
|
||||
};
|
||||
|
||||
|
||||
export type ProjectUngroupedViewGroupArgs = {
|
||||
input: GetUngroupedViewGroupInput;
|
||||
};
|
||||
|
||||
|
||||
export type ProjectVersionArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
@@ -2364,6 +2423,7 @@ export type ProjectVersionsArgs = {
|
||||
export type ProjectViewerResourcesArgs = {
|
||||
loadedVersionsOnly?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
resourceIdString: Scalars['String']['input'];
|
||||
savedViewId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2707,6 +2767,7 @@ export type ProjectMutations = {
|
||||
leave: Scalars['Boolean']['output'];
|
||||
revokeEmbedToken: Scalars['Boolean']['output'];
|
||||
revokeEmbedTokens: Scalars['Boolean']['output'];
|
||||
savedViewMutations: SavedViewMutations;
|
||||
/** Updates an existing project */
|
||||
update: Project;
|
||||
/** Update role for a collaborator */
|
||||
@@ -2799,6 +2860,7 @@ export type ProjectPermissionChecks = {
|
||||
canCreateComment: PermissionCheckResult;
|
||||
canCreateEmbedTokens: PermissionCheckResult;
|
||||
canCreateModel: PermissionCheckResult;
|
||||
canCreateSavedView: PermissionCheckResult;
|
||||
canDelete: PermissionCheckResult;
|
||||
canInvite: PermissionCheckResult;
|
||||
canLeave: PermissionCheckResult;
|
||||
@@ -3246,6 +3308,115 @@ export type RootPermissionChecks = {
|
||||
canCreateWorkspace: PermissionCheckResult;
|
||||
};
|
||||
|
||||
export type SavedView = {
|
||||
__typename?: 'SavedView';
|
||||
author?: Maybe<LimitedUser>;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
description?: Maybe<Scalars['String']['output']>;
|
||||
/** Always available because even ungrouped views show up in a fake "Ungrouped" group */
|
||||
group: SavedViewGroup;
|
||||
/** Empty ID means default/ungrouped view */
|
||||
groupId?: Maybe<Scalars['ID']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
isHomeView: Scalars['Boolean']['output'];
|
||||
name: Scalars['String']['output'];
|
||||
/** For figuring out position in the group */
|
||||
position: Scalars['Float']['output'];
|
||||
projectId: Scalars['ID']['output'];
|
||||
/** Original resource ID string that this view is associated with. */
|
||||
resourceIdString: Scalars['String']['output'];
|
||||
/** Same as resourceIdString, but split into an array of resource IDs. */
|
||||
resourceIds: Array<Scalars['String']['output']>;
|
||||
/** Encoded screenshot of the view */
|
||||
screenshot: Scalars['String']['output'];
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
/** Viewer state, the actual view configuration */
|
||||
viewerState: Scalars['JSONObject']['output'];
|
||||
visibility: SavedViewVisibility;
|
||||
};
|
||||
|
||||
export type SavedViewCollection = {
|
||||
__typename?: 'SavedViewCollection';
|
||||
cursor?: Maybe<Scalars['String']['output']>;
|
||||
items: Array<SavedView>;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type SavedViewGroup = {
|
||||
__typename?: 'SavedViewGroup';
|
||||
/** Only set if this is a real/persisted group. */
|
||||
groupId?: Maybe<Scalars['ID']['output']>;
|
||||
/** This is always set even for fake/not persisted groups for Apollo caching */
|
||||
id: Scalars['ID']['output'];
|
||||
isUngroupedViewsGroup: Scalars['Boolean']['output'];
|
||||
projectId: Scalars['ID']['output'];
|
||||
/** Resources that were used to find this group */
|
||||
resourceIds: Array<Scalars['String']['output']>;
|
||||
title: Scalars['String']['output'];
|
||||
views: SavedViewCollection;
|
||||
};
|
||||
|
||||
|
||||
export type SavedViewGroupViewsArgs = {
|
||||
input: SavedViewGroupViewsInput;
|
||||
};
|
||||
|
||||
export type SavedViewGroupCollection = {
|
||||
__typename?: 'SavedViewGroupCollection';
|
||||
cursor?: Maybe<Scalars['String']['output']>;
|
||||
items: Array<SavedViewGroup>;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type SavedViewGroupViewsInput = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
/** Whether to only views authored by the current user */
|
||||
onlyAuthored?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** Whether to only include views matching this search term */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
/**
|
||||
* Optionally specify sort by field. Default: updatedAt
|
||||
* Options: updatedAt, createdAt, name
|
||||
*/
|
||||
sortBy?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Optionally specify sort direction. Default: descending */
|
||||
sortDirection?: InputMaybe<SortDirection>;
|
||||
};
|
||||
|
||||
export type SavedViewGroupsInput = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
/** Whether to only include groups w/ views authored by the current user */
|
||||
onlyAuthored?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
/** Viewer resource ID string that identifies which resources should be loaded */
|
||||
resourceIdString: Scalars['String']['input'];
|
||||
/** Whether to only include groups that have views matching this search term */
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type SavedViewMutations = {
|
||||
__typename?: 'SavedViewMutations';
|
||||
createGroup: SavedViewGroup;
|
||||
createView: SavedView;
|
||||
};
|
||||
|
||||
|
||||
export type SavedViewMutationsCreateGroupArgs = {
|
||||
input: CreateSavedViewGroupInput;
|
||||
};
|
||||
|
||||
|
||||
export type SavedViewMutationsCreateViewArgs = {
|
||||
input: CreateSavedViewInput;
|
||||
};
|
||||
|
||||
export const SavedViewVisibility = {
|
||||
AuthorOnly: 'authorOnly',
|
||||
Public: 'public'
|
||||
} as const;
|
||||
|
||||
export type SavedViewVisibility = typeof SavedViewVisibility[keyof typeof SavedViewVisibility];
|
||||
/** Available scopes. */
|
||||
export type Scope = {
|
||||
__typename?: 'Scope';
|
||||
@@ -5546,6 +5717,8 @@ export type ResolversTypes = {
|
||||
CreateCommentReplyInput: CreateCommentReplyInput;
|
||||
CreateEmbedTokenReturn: ResolverTypeWrapper<Omit<CreateEmbedTokenReturn, 'tokenMetadata'> & { tokenMetadata: ResolversTypes['EmbedToken'] }>;
|
||||
CreateModelInput: CreateModelInput;
|
||||
CreateSavedViewGroupInput: CreateSavedViewGroupInput;
|
||||
CreateSavedViewInput: CreateSavedViewInput;
|
||||
CreateServerRegionInput: CreateServerRegionInput;
|
||||
CreateUserEmailInput: CreateUserEmailInput;
|
||||
CreateVersionInput: CreateVersionInput;
|
||||
@@ -5575,6 +5748,7 @@ export type ResolversTypes = {
|
||||
GenerateFileUploadUrlInput: GenerateFileUploadUrlInput;
|
||||
GenerateFileUploadUrlOutput: ResolverTypeWrapper<GenerateFileUploadUrlOutput>;
|
||||
GetModelUploadsInput: GetModelUploadsInput;
|
||||
GetUngroupedViewGroupInput: GetUngroupedViewGroupInput;
|
||||
ID: ResolverTypeWrapper<Scalars['ID']['output']>;
|
||||
Int: ResolverTypeWrapper<Scalars['Int']['output']>;
|
||||
InvitableCollaboratorsFilter: InvitableCollaboratorsFilter;
|
||||
@@ -5609,7 +5783,7 @@ export type ResolversTypes = {
|
||||
PendingStreamCollaborator: ResolverTypeWrapper<PendingStreamCollaboratorGraphQLReturn>;
|
||||
PendingWorkspaceCollaborator: ResolverTypeWrapper<PendingWorkspaceCollaboratorGraphQLReturn>;
|
||||
PendingWorkspaceCollaboratorsFilter: PendingWorkspaceCollaboratorsFilter;
|
||||
PermissionCheckResult: ResolverTypeWrapper<PermissionCheckResult>;
|
||||
PermissionCheckResult: ResolverTypeWrapper<PermissionCheckResultGraphQLReturn>;
|
||||
Price: ResolverTypeWrapper<PriceGraphQLReturn>;
|
||||
Project: ResolverTypeWrapper<ProjectGraphQLReturn>;
|
||||
ProjectAccessRequest: ResolverTypeWrapper<ProjectAccessRequestGraphQLReturn>;
|
||||
@@ -5663,6 +5837,14 @@ export type ResolversTypes = {
|
||||
ResourceType: ResourceType;
|
||||
Role: ResolverTypeWrapper<Role>;
|
||||
RootPermissionChecks: ResolverTypeWrapper<RootPermissionChecksGraphQLReturn>;
|
||||
SavedView: ResolverTypeWrapper<SavedViewGraphQLReturn>;
|
||||
SavedViewCollection: ResolverTypeWrapper<Omit<SavedViewCollection, 'items'> & { items: Array<ResolversTypes['SavedView']> }>;
|
||||
SavedViewGroup: ResolverTypeWrapper<SavedViewGroupGraphQLReturn>;
|
||||
SavedViewGroupCollection: ResolverTypeWrapper<Omit<SavedViewGroupCollection, 'items'> & { items: Array<ResolversTypes['SavedViewGroup']> }>;
|
||||
SavedViewGroupViewsInput: SavedViewGroupViewsInput;
|
||||
SavedViewGroupsInput: SavedViewGroupsInput;
|
||||
SavedViewMutations: ResolverTypeWrapper<MutationsObjectGraphQLReturn>;
|
||||
SavedViewVisibility: SavedViewVisibility;
|
||||
Scope: ResolverTypeWrapper<Scope>;
|
||||
ServerApp: ResolverTypeWrapper<ServerAppGraphQLReturn>;
|
||||
ServerAppListItem: ResolverTypeWrapper<ServerAppListItemGraphQLReturn>;
|
||||
@@ -5898,6 +6080,8 @@ export type ResolversParentTypes = {
|
||||
CreateCommentReplyInput: CreateCommentReplyInput;
|
||||
CreateEmbedTokenReturn: Omit<CreateEmbedTokenReturn, 'tokenMetadata'> & { tokenMetadata: ResolversParentTypes['EmbedToken'] };
|
||||
CreateModelInput: CreateModelInput;
|
||||
CreateSavedViewGroupInput: CreateSavedViewGroupInput;
|
||||
CreateSavedViewInput: CreateSavedViewInput;
|
||||
CreateServerRegionInput: CreateServerRegionInput;
|
||||
CreateUserEmailInput: CreateUserEmailInput;
|
||||
CreateVersionInput: CreateVersionInput;
|
||||
@@ -5925,6 +6109,7 @@ export type ResolversParentTypes = {
|
||||
GenerateFileUploadUrlInput: GenerateFileUploadUrlInput;
|
||||
GenerateFileUploadUrlOutput: GenerateFileUploadUrlOutput;
|
||||
GetModelUploadsInput: GetModelUploadsInput;
|
||||
GetUngroupedViewGroupInput: GetUngroupedViewGroupInput;
|
||||
ID: Scalars['ID']['output'];
|
||||
Int: Scalars['Int']['output'];
|
||||
InvitableCollaboratorsFilter: InvitableCollaboratorsFilter;
|
||||
@@ -5957,7 +6142,7 @@ export type ResolversParentTypes = {
|
||||
PendingStreamCollaborator: PendingStreamCollaboratorGraphQLReturn;
|
||||
PendingWorkspaceCollaborator: PendingWorkspaceCollaboratorGraphQLReturn;
|
||||
PendingWorkspaceCollaboratorsFilter: PendingWorkspaceCollaboratorsFilter;
|
||||
PermissionCheckResult: PermissionCheckResult;
|
||||
PermissionCheckResult: PermissionCheckResultGraphQLReturn;
|
||||
Price: PriceGraphQLReturn;
|
||||
Project: ProjectGraphQLReturn;
|
||||
ProjectAccessRequest: ProjectAccessRequestGraphQLReturn;
|
||||
@@ -6000,6 +6185,13 @@ export type ResolversParentTypes = {
|
||||
ResourceIdentifierInput: ResourceIdentifierInput;
|
||||
Role: Role;
|
||||
RootPermissionChecks: RootPermissionChecksGraphQLReturn;
|
||||
SavedView: SavedViewGraphQLReturn;
|
||||
SavedViewCollection: Omit<SavedViewCollection, 'items'> & { items: Array<ResolversParentTypes['SavedView']> };
|
||||
SavedViewGroup: SavedViewGroupGraphQLReturn;
|
||||
SavedViewGroupCollection: Omit<SavedViewGroupCollection, 'items'> & { items: Array<ResolversParentTypes['SavedViewGroup']> };
|
||||
SavedViewGroupViewsInput: SavedViewGroupViewsInput;
|
||||
SavedViewGroupsInput: SavedViewGroupsInput;
|
||||
SavedViewMutations: MutationsObjectGraphQLReturn;
|
||||
Scope: Scope;
|
||||
ServerApp: ServerAppGraphQLReturn;
|
||||
ServerAppListItem: ServerAppListItemGraphQLReturn;
|
||||
@@ -6959,6 +7151,7 @@ export type PendingWorkspaceCollaboratorResolvers<ContextType = GraphQLContext,
|
||||
export type PermissionCheckResultResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['PermissionCheckResult'] = ResolversParentTypes['PermissionCheckResult']> = {
|
||||
authorized?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
code?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
errorMessage?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
message?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
payload?: Resolver<Maybe<ResolversTypes['JSONObject']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
@@ -6999,8 +7192,12 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
|
||||
pendingImportedModels?: Resolver<Array<ResolversTypes['FileUpload']>, ParentType, ContextType, RequireFields<ProjectPendingImportedModelsArgs, 'limit'>>;
|
||||
permissions?: Resolver<ResolversTypes['ProjectPermissionChecks'], ParentType, ContextType>;
|
||||
role?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
savedView?: Resolver<ResolversTypes['SavedView'], ParentType, ContextType, RequireFields<ProjectSavedViewArgs, 'id'>>;
|
||||
savedViewGroup?: Resolver<ResolversTypes['SavedViewGroup'], ParentType, ContextType, RequireFields<ProjectSavedViewGroupArgs, 'id'>>;
|
||||
savedViewGroups?: Resolver<ResolversTypes['SavedViewGroupCollection'], ParentType, ContextType, RequireFields<ProjectSavedViewGroupsArgs, 'input'>>;
|
||||
sourceApps?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
team?: Resolver<Array<ResolversTypes['ProjectCollaborator']>, ParentType, ContextType>;
|
||||
ungroupedViewGroup?: Resolver<ResolversTypes['SavedViewGroup'], ParentType, ContextType, RequireFields<ProjectUngroupedViewGroupArgs, 'input'>>;
|
||||
updatedAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
|
||||
version?: Resolver<ResolversTypes['Version'], ParentType, ContextType, RequireFields<ProjectVersionArgs, 'id'>>;
|
||||
versions?: Resolver<ResolversTypes['VersionCollection'], ParentType, ContextType, RequireFields<ProjectVersionsArgs, 'limit'>>;
|
||||
@@ -7124,6 +7321,7 @@ export type ProjectMutationsResolvers<ContextType = GraphQLContext, ParentType e
|
||||
leave?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<ProjectMutationsLeaveArgs, 'id'>>;
|
||||
revokeEmbedToken?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<ProjectMutationsRevokeEmbedTokenArgs, 'projectId' | 'token'>>;
|
||||
revokeEmbedTokens?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<ProjectMutationsRevokeEmbedTokensArgs, 'projectId'>>;
|
||||
savedViewMutations?: Resolver<ResolversTypes['SavedViewMutations'], ParentType, ContextType>;
|
||||
update?: Resolver<ResolversTypes['Project'], ParentType, ContextType, RequireFields<ProjectMutationsUpdateArgs, 'update'>>;
|
||||
updateRole?: Resolver<ResolversTypes['Project'], ParentType, ContextType, RequireFields<ProjectMutationsUpdateRoleArgs, 'input'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
@@ -7149,6 +7347,7 @@ export type ProjectPermissionChecksResolvers<ContextType = GraphQLContext, Paren
|
||||
canCreateComment?: Resolver<ResolversTypes['PermissionCheckResult'], ParentType, ContextType>;
|
||||
canCreateEmbedTokens?: Resolver<ResolversTypes['PermissionCheckResult'], ParentType, ContextType>;
|
||||
canCreateModel?: Resolver<ResolversTypes['PermissionCheckResult'], ParentType, ContextType>;
|
||||
canCreateSavedView?: Resolver<ResolversTypes['PermissionCheckResult'], ParentType, ContextType>;
|
||||
canDelete?: Resolver<ResolversTypes['PermissionCheckResult'], ParentType, ContextType>;
|
||||
canInvite?: Resolver<ResolversTypes['PermissionCheckResult'], ParentType, ContextType>;
|
||||
canLeave?: Resolver<ResolversTypes['PermissionCheckResult'], ParentType, ContextType>;
|
||||
@@ -7260,6 +7459,57 @@ export type RootPermissionChecksResolvers<ContextType = GraphQLContext, ParentTy
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SavedViewResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['SavedView'] = ResolversParentTypes['SavedView']> = {
|
||||
author?: Resolver<Maybe<ResolversTypes['LimitedUser']>, ParentType, ContextType>;
|
||||
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
|
||||
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
group?: Resolver<ResolversTypes['SavedViewGroup'], ParentType, ContextType>;
|
||||
groupId?: Resolver<Maybe<ResolversTypes['ID']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
isHomeView?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
position?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
|
||||
projectId?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
resourceIdString?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
resourceIds?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
screenshot?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
updatedAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
|
||||
viewerState?: Resolver<ResolversTypes['JSONObject'], ParentType, ContextType>;
|
||||
visibility?: Resolver<ResolversTypes['SavedViewVisibility'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SavedViewCollectionResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['SavedViewCollection'] = ResolversParentTypes['SavedViewCollection']> = {
|
||||
cursor?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
items?: Resolver<Array<ResolversTypes['SavedView']>, ParentType, ContextType>;
|
||||
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SavedViewGroupResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['SavedViewGroup'] = ResolversParentTypes['SavedViewGroup']> = {
|
||||
groupId?: Resolver<Maybe<ResolversTypes['ID']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
isUngroupedViewsGroup?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
projectId?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
resourceIds?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
views?: Resolver<ResolversTypes['SavedViewCollection'], ParentType, ContextType, RequireFields<SavedViewGroupViewsArgs, 'input'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SavedViewGroupCollectionResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['SavedViewGroupCollection'] = ResolversParentTypes['SavedViewGroupCollection']> = {
|
||||
cursor?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
items?: Resolver<Array<ResolversTypes['SavedViewGroup']>, ParentType, ContextType>;
|
||||
totalCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type SavedViewMutationsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['SavedViewMutations'] = ResolversParentTypes['SavedViewMutations']> = {
|
||||
createGroup?: Resolver<ResolversTypes['SavedViewGroup'], ParentType, ContextType, RequireFields<SavedViewMutationsCreateGroupArgs, 'input'>>;
|
||||
createView?: Resolver<ResolversTypes['SavedView'], ParentType, ContextType, RequireFields<SavedViewMutationsCreateViewArgs, 'input'>>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type ScopeResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['Scope'] = ResolversParentTypes['Scope']> = {
|
||||
description?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
@@ -8141,6 +8391,11 @@ export type Resolvers<ContextType = GraphQLContext> = {
|
||||
ResourceIdentifier?: ResourceIdentifierResolvers<ContextType>;
|
||||
Role?: RoleResolvers<ContextType>;
|
||||
RootPermissionChecks?: RootPermissionChecksResolvers<ContextType>;
|
||||
SavedView?: SavedViewResolvers<ContextType>;
|
||||
SavedViewCollection?: SavedViewCollectionResolvers<ContextType>;
|
||||
SavedViewGroup?: SavedViewGroupResolvers<ContextType>;
|
||||
SavedViewGroupCollection?: SavedViewGroupCollectionResolvers<ContextType>;
|
||||
SavedViewMutations?: SavedViewMutationsResolvers<ContextType>;
|
||||
Scope?: ScopeResolvers<ContextType>;
|
||||
ServerApp?: ServerAppResolvers<ContextType>;
|
||||
ServerAppListItem?: ServerAppListItemResolvers<ContextType>;
|
||||
@@ -8506,6 +8761,60 @@ export type CrossSyncClientTestQueryVariables = Exact<{ [key: string]: never; }>
|
||||
|
||||
export type CrossSyncClientTestQuery = { __typename?: 'Query', _?: string | null };
|
||||
|
||||
export type BasicSavedViewFragment = { __typename?: 'SavedView', id: string, name: string, description?: string | null, groupId?: string | null, createdAt: Date, updatedAt: Date, resourceIdString: string, resourceIds: Array<string>, isHomeView: boolean, visibility: SavedViewVisibility, viewerState: Record<string, unknown>, screenshot: string, position: number, projectId: string, author?: { __typename?: 'LimitedUser', id: string } | null, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } };
|
||||
|
||||
export type BasicSavedViewGroupFragment = { __typename?: 'SavedViewGroup', id: string, projectId: string, resourceIds: Array<string>, title: string, isUngroupedViewsGroup: boolean, views: { __typename?: 'SavedViewCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'SavedView', id: string, name: string, description?: string | null, groupId?: string | null, createdAt: Date, updatedAt: Date, resourceIdString: string, resourceIds: Array<string>, isHomeView: boolean, visibility: SavedViewVisibility, viewerState: Record<string, unknown>, screenshot: string, position: number, projectId: string, author?: { __typename?: 'LimitedUser', id: string } | null, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } }> } };
|
||||
|
||||
export type CreateSavedViewMutationVariables = Exact<{
|
||||
input: CreateSavedViewInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateSavedViewMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', savedViewMutations: { __typename?: 'SavedViewMutations', createView: { __typename?: 'SavedView', id: string, name: string, description?: string | null, groupId?: string | null, createdAt: Date, updatedAt: Date, resourceIdString: string, resourceIds: Array<string>, isHomeView: boolean, visibility: SavedViewVisibility, viewerState: Record<string, unknown>, screenshot: string, position: number, projectId: string, author?: { __typename?: 'LimitedUser', id: string } | null, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } } } } };
|
||||
|
||||
export type CreateSavedViewGroupMutationVariables = Exact<{
|
||||
input: CreateSavedViewGroupInput;
|
||||
viewsInput?: SavedViewGroupViewsInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateSavedViewGroupMutation = { __typename?: 'Mutation', projectMutations: { __typename?: 'ProjectMutations', savedViewMutations: { __typename?: 'SavedViewMutations', createGroup: { __typename?: 'SavedViewGroup', id: string, projectId: string, resourceIds: Array<string>, title: string, isUngroupedViewsGroup: boolean, views: { __typename?: 'SavedViewCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'SavedView', id: string, name: string, description?: string | null, groupId?: string | null, createdAt: Date, updatedAt: Date, resourceIdString: string, resourceIds: Array<string>, isHomeView: boolean, visibility: SavedViewVisibility, viewerState: Record<string, unknown>, screenshot: string, position: number, projectId: string, author?: { __typename?: 'LimitedUser', id: string } | null, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } }> } } } } };
|
||||
|
||||
export type GetProjectSavedViewGroupsQueryVariables = Exact<{
|
||||
projectId: Scalars['String']['input'];
|
||||
input: SavedViewGroupsInput;
|
||||
viewsInput?: SavedViewGroupViewsInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetProjectSavedViewGroupsQuery = { __typename?: 'Query', project: { __typename?: 'Project', savedViewGroups: { __typename?: 'SavedViewGroupCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'SavedViewGroup', id: string, projectId: string, resourceIds: Array<string>, title: string, isUngroupedViewsGroup: boolean, views: { __typename?: 'SavedViewCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'SavedView', id: string, name: string, description?: string | null, groupId?: string | null, createdAt: Date, updatedAt: Date, resourceIdString: string, resourceIds: Array<string>, isHomeView: boolean, visibility: SavedViewVisibility, viewerState: Record<string, unknown>, screenshot: string, position: number, projectId: string, author?: { __typename?: 'LimitedUser', id: string } | null, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } }> } }> } } };
|
||||
|
||||
export type GetProjectSavedViewGroupQueryVariables = Exact<{
|
||||
projectId: Scalars['String']['input'];
|
||||
groupId: Scalars['ID']['input'];
|
||||
viewsInput?: SavedViewGroupViewsInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetProjectSavedViewGroupQuery = { __typename?: 'Query', project: { __typename?: 'Project', savedViewGroup: { __typename?: 'SavedViewGroup', id: string, projectId: string, resourceIds: Array<string>, title: string, isUngroupedViewsGroup: boolean, views: { __typename?: 'SavedViewCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'SavedView', id: string, name: string, description?: string | null, groupId?: string | null, createdAt: Date, updatedAt: Date, resourceIdString: string, resourceIds: Array<string>, isHomeView: boolean, visibility: SavedViewVisibility, viewerState: Record<string, unknown>, screenshot: string, position: number, projectId: string, author?: { __typename?: 'LimitedUser', id: string } | null, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } }> } } } };
|
||||
|
||||
export type GetProjectUngroupedViewGroupQueryVariables = Exact<{
|
||||
projectId: Scalars['String']['input'];
|
||||
input: GetUngroupedViewGroupInput;
|
||||
viewsInput?: SavedViewGroupViewsInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetProjectUngroupedViewGroupQuery = { __typename?: 'Query', project: { __typename?: 'Project', ungroupedViewGroup: { __typename?: 'SavedViewGroup', id: string, projectId: string, resourceIds: Array<string>, title: string, isUngroupedViewsGroup: boolean, views: { __typename?: 'SavedViewCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'SavedView', id: string, name: string, description?: string | null, groupId?: string | null, createdAt: Date, updatedAt: Date, resourceIdString: string, resourceIds: Array<string>, isHomeView: boolean, visibility: SavedViewVisibility, viewerState: Record<string, unknown>, screenshot: string, position: number, projectId: string, author?: { __typename?: 'LimitedUser', id: string } | null, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } }> } } } };
|
||||
|
||||
export type GetProjectSavedViewQueryVariables = Exact<{
|
||||
projectId: Scalars['String']['input'];
|
||||
viewId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetProjectSavedViewQuery = { __typename?: 'Query', project: { __typename?: 'Project', savedView: { __typename?: 'SavedView', id: string, name: string, description?: string | null, groupId?: string | null, createdAt: Date, updatedAt: Date, resourceIdString: string, resourceIds: Array<string>, isHomeView: boolean, visibility: SavedViewVisibility, viewerState: Record<string, unknown>, screenshot: string, position: number, projectId: string, author?: { __typename?: 'LimitedUser', id: string } | null, group: { __typename?: 'SavedViewGroup', id: string, title: string, isUngroupedViewsGroup: boolean } } } };
|
||||
|
||||
export type BasicWorkspaceFragment = { __typename?: 'Workspace', id: string, name: string, slug: string, updatedAt: Date, createdAt: Date, role?: string | null, readOnly: boolean };
|
||||
|
||||
export type BasicPendingWorkspaceCollaboratorFragment = { __typename?: 'PendingWorkspaceCollaborator', id: string, inviteId: string, title: string, role: string, token?: string | null, workspace: { __typename?: 'LimitedWorkspace', id: string, name: string }, invitedBy: { __typename?: 'LimitedUser', id: string, name: string }, user?: { __typename?: 'LimitedUser', id: string, name: string } | null };
|
||||
@@ -8710,9 +9019,9 @@ export type GetProjectInvitableCollaboratorsQueryVariables = Exact<{
|
||||
|
||||
export type GetProjectInvitableCollaboratorsQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, invitableCollaborators: { __typename?: 'WorkspaceCollaboratorCollection', totalCount: number, items: Array<{ __typename?: 'WorkspaceCollaborator', id: string, user: { __typename?: 'LimitedUser', name: string } }> } } };
|
||||
|
||||
export type FullPermissionCheckResultFragment = { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null };
|
||||
export type FullPermissionCheckResultFragment = { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null };
|
||||
|
||||
export type ProjectImplicitRoleCheckFragment = { __typename?: 'Project', id: string, role?: string | null, permissions: { __typename?: 'ProjectPermissionChecks', canRead: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canReadSettings: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canReadWebhooks: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canCreateModel: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null } } };
|
||||
export type ProjectImplicitRoleCheckFragment = { __typename?: 'Project', id: string, role?: string | null, permissions: { __typename?: 'ProjectPermissionChecks', canRead: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canReadSettings: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canReadWebhooks: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canCreateModel: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null } } };
|
||||
|
||||
export type GetUserWorkspaceAccessQueryVariables = Exact<{
|
||||
id: Scalars['String']['input'];
|
||||
@@ -8729,7 +9038,7 @@ export type GetUserWorkspaceProjectsWithAccessChecksQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetUserWorkspaceProjectsWithAccessChecksQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', role?: string | null, seatType?: WorkspaceSeatType | null, id: string, name: string, slug: string, updatedAt: Date, createdAt: Date, readOnly: boolean, projects: { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, role?: string | null, permissions: { __typename?: 'ProjectPermissionChecks', canRead: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canReadSettings: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canReadWebhooks: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canCreateModel: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null } } }> } } };
|
||||
export type GetUserWorkspaceProjectsWithAccessChecksQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', role?: string | null, seatType?: WorkspaceSeatType | null, id: string, name: string, slug: string, updatedAt: Date, createdAt: Date, readOnly: boolean, projects: { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, role?: string | null, permissions: { __typename?: 'ProjectPermissionChecks', canRead: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canReadSettings: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canReadWebhooks: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canCreateModel: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null } } }> } } };
|
||||
|
||||
export type GetUserProjectsWithAccessChecksQueryVariables = Exact<{
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
@@ -8738,7 +9047,7 @@ export type GetUserProjectsWithAccessChecksQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetUserProjectsWithAccessChecksQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, projects: { __typename?: 'UserProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, role?: string | null, permissions: { __typename?: 'ProjectPermissionChecks', canRead: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canReadSettings: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canReadWebhooks: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null }, canCreateModel: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null } } }> } } | null };
|
||||
export type GetUserProjectsWithAccessChecksQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, projects: { __typename?: 'UserProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, role?: string | null, permissions: { __typename?: 'ProjectPermissionChecks', canRead: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canReadSettings: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canReadWebhooks: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null }, canCreateModel: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string, payload?: Record<string, unknown> | null, errorMessage?: string | null } } }> } } | null };
|
||||
|
||||
export type BasicStreamAccessRequestFieldsFragment = { __typename?: 'StreamAccessRequest', id: string, requesterId: string, streamId: string, createdAt: Date, requester: { __typename?: 'LimitedUser', id: string, name: string } };
|
||||
|
||||
@@ -9565,11 +9874,13 @@ export const LimitedPersonalProjectCommentFragmentDoc = {"kind":"Document","defi
|
||||
export const LimitedPersonalProjectVersionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedPersonalProjectVersion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"commentThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedPersonalProjectComment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedPersonalProjectComment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rawText"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}},{"kind":"Field","name":{"kind":"Name","value":"type"}}]}}]}}]} as unknown as DocumentNode<LimitedPersonalProjectVersionFragment, unknown>;
|
||||
export const LimitedPersonalStreamCommitFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedPersonalStreamCommit"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Commit"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<LimitedPersonalStreamCommitFragment, unknown>;
|
||||
export const DownloadbleCommentMetadataFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"DownloadbleCommentMetadata"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}}]}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}}]}}]} as unknown as DocumentNode<DownloadbleCommentMetadataFragment, unknown>;
|
||||
export const BasicSavedViewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groupId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"isHomeView"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}}]}}]} as unknown as DocumentNode<BasicSavedViewFragment, unknown>;
|
||||
export const BasicSavedViewGroupFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}},{"kind":"Field","name":{"kind":"Name","value":"views"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedView"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groupId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"isHomeView"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}}]}}]} as unknown as DocumentNode<BasicSavedViewGroupFragment, unknown>;
|
||||
export const BasicWorkspaceFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<BasicWorkspaceFragment, unknown>;
|
||||
export const BasicPendingWorkspaceCollaboratorFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]} as unknown as DocumentNode<BasicPendingWorkspaceCollaboratorFragment, unknown>;
|
||||
export const WorkspaceProjectsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjects"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]} as unknown as DocumentNode<WorkspaceProjectsFragment, unknown>;
|
||||
export const FullPermissionCheckResultFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}}]}}]} as unknown as DocumentNode<FullPermissionCheckResultFragment, unknown>;
|
||||
export const ProjectImplicitRoleCheckFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canRead"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canCreateModel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}}]}}]} as unknown as DocumentNode<ProjectImplicitRoleCheckFragment, unknown>;
|
||||
export const FullPermissionCheckResultFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}}]}}]} as unknown as DocumentNode<FullPermissionCheckResultFragment, unknown>;
|
||||
export const ProjectImplicitRoleCheckFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canRead"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canCreateModel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}}]}}]} as unknown as DocumentNode<ProjectImplicitRoleCheckFragment, unknown>;
|
||||
export const BasicStreamAccessRequestFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<BasicStreamAccessRequestFieldsFragment, unknown>;
|
||||
export const TestAutomateFunctionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestAutomateFunction"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"repo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"owner"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"releases"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"5"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"versionTag"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"inputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"commitId"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"supportedSourceApps"}},{"kind":"Field","name":{"kind":"Name","value":"tags"}}]}}]} as unknown as DocumentNode<TestAutomateFunctionFragment, unknown>;
|
||||
export const TestAutomationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestAutomation"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Automation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"runs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"trigger"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"VersionCreatedTrigger"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"elapsed"}},{"kind":"Field","name":{"kind":"Name","value":"results"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"currentRevision"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"triggerDefinitions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"VersionCreatedTriggerDefinition"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"functions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"parameters"}},{"kind":"Field","name":{"kind":"Name","value":"release"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"versionTag"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"inputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"commitId"}}]}}]}}]}}]}}]} as unknown as DocumentNode<TestAutomationFragment, unknown>;
|
||||
@@ -9625,6 +9936,12 @@ export const CrossSyncProjectViewerResourcesDocument = {"kind":"Document","defin
|
||||
export const CrossSyncDownloadableCommitViewerThreadsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CrossSyncDownloadableCommitViewerThreads"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCommentsFilter"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"25"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalArchivedCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"DownloadbleCommentMetadata"}},{"kind":"Field","name":{"kind":"Name","value":"replies"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"DownloadbleCommentMetadata"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"DownloadbleCommentMetadata"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}}]}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}}]}}]} as unknown as DocumentNode<CrossSyncDownloadableCommitViewerThreadsQuery, CrossSyncDownloadableCommitViewerThreadsQueryVariables>;
|
||||
export const CrossSyncProjectMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CrossSyncProjectMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"versionsCursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionsCursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<CrossSyncProjectMetadataQuery, CrossSyncProjectMetadataQueryVariables>;
|
||||
export const CrossSyncClientTestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CrossSyncClientTest"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_"}}]}}]} as unknown as DocumentNode<CrossSyncClientTestQuery, CrossSyncClientTestQueryVariables>;
|
||||
export const CreateSavedViewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateSavedView"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateSavedViewInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"savedViewMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createView"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedView"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groupId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"isHomeView"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}}]}}]} as unknown as DocumentNode<CreateSavedViewMutation, CreateSavedViewMutationVariables>;
|
||||
export const CreateSavedViewGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateSavedViewGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateSavedViewGroupInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroupViewsInput"}}},"defaultValue":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"savedViewMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedViewGroup"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groupId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"isHomeView"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}},{"kind":"Field","name":{"kind":"Name","value":"views"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedView"}}]}}]}}]}}]} as unknown as DocumentNode<CreateSavedViewGroupMutation, CreateSavedViewGroupMutationVariables>;
|
||||
export const GetProjectSavedViewGroupsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectSavedViewGroups"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroupsInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroupViewsInput"}}},"defaultValue":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"savedViewGroups"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedViewGroup"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groupId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"isHomeView"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}},{"kind":"Field","name":{"kind":"Name","value":"views"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedView"}}]}}]}}]}}]} as unknown as DocumentNode<GetProjectSavedViewGroupsQuery, GetProjectSavedViewGroupsQueryVariables>;
|
||||
export const GetProjectSavedViewGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectSavedViewGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroupViewsInput"}}},"defaultValue":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"savedViewGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"groupId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedViewGroup"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groupId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"isHomeView"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}},{"kind":"Field","name":{"kind":"Name","value":"views"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedView"}}]}}]}}]}}]} as unknown as DocumentNode<GetProjectSavedViewGroupQuery, GetProjectSavedViewGroupQueryVariables>;
|
||||
export const GetProjectUngroupedViewGroupDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectUngroupedViewGroup"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GetUngroupedViewGroupInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroupViewsInput"}}},"defaultValue":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ungroupedViewGroup"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedViewGroup"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groupId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"isHomeView"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedViewGroup"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}},{"kind":"Field","name":{"kind":"Name","value":"views"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"viewsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedView"}}]}}]}}]}}]} as unknown as DocumentNode<GetProjectUngroupedViewGroupQuery, GetProjectUngroupedViewGroupQueryVariables>;
|
||||
export const GetProjectSavedViewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectSavedView"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"viewId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"savedView"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"viewId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicSavedView"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicSavedView"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SavedView"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"groupId"}},{"kind":"Field","name":{"kind":"Name","value":"group"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"isUngroupedViewsGroup"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIdString"}},{"kind":"Field","name":{"kind":"Name","value":"resourceIds"}},{"kind":"Field","name":{"kind":"Name","value":"isHomeView"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"viewerState"}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}}]}}]} as unknown as DocumentNode<GetProjectSavedViewQuery, GetProjectSavedViewQueryVariables>;
|
||||
export const CreateWorkspaceInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateWorkspaceInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceInviteCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"invites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]} as unknown as DocumentNode<CreateWorkspaceInviteMutation, CreateWorkspaceInviteMutationVariables>;
|
||||
export const BatchCreateWorkspaceInvitesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BatchCreateWorkspaceInvites"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceInviteCreateInput"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"invites"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"batchCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]} as unknown as DocumentNode<BatchCreateWorkspaceInvitesMutation, BatchCreateWorkspaceInvitesMutationVariables>;
|
||||
export const GetWorkspaceWithTeamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceWithTeam"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"invitedTeam"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicPendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"inviteId"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]} as unknown as DocumentNode<GetWorkspaceWithTeamQuery, GetWorkspaceWithTeamQueryVariables>;
|
||||
@@ -9653,8 +9970,8 @@ export const UpdateWorkspaceProjectRoleDocument = {"kind":"Document","definition
|
||||
export const UpdateWorkspaceSeatTypeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceSeatType"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceUpdateSeatTypeInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateSeatType"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"seatType"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<UpdateWorkspaceSeatTypeMutation, UpdateWorkspaceSeatTypeMutationVariables>;
|
||||
export const GetProjectInvitableCollaboratorsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectInvitableCollaborators"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"search"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"invitableCollaborators"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"search"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<GetProjectInvitableCollaboratorsQuery, GetProjectInvitableCollaboratorsQueryVariables>;
|
||||
export const GetUserWorkspaceAccessDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserWorkspaceAccess"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"seatType"}}]}}]}}]} as unknown as DocumentNode<GetUserWorkspaceAccessQuery, GetUserWorkspaceAccessQueryVariables>;
|
||||
export const GetUserWorkspaceProjectsWithAccessChecksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserWorkspaceProjectsWithAccessChecks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceProjectsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"seatType"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canRead"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canCreateModel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}}]}}]}}]} as unknown as DocumentNode<GetUserWorkspaceProjectsWithAccessChecksQuery, GetUserWorkspaceProjectsWithAccessChecksQueryVariables>;
|
||||
export const GetUserProjectsWithAccessChecksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserProjectsWithAccessChecks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UserProjectsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canRead"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canCreateModel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}}]}}]}}]} as unknown as DocumentNode<GetUserProjectsWithAccessChecksQuery, GetUserProjectsWithAccessChecksQueryVariables>;
|
||||
export const GetUserWorkspaceProjectsWithAccessChecksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserWorkspaceProjectsWithAccessChecks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceProjectsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicWorkspace"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"seatType"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicWorkspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canRead"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canCreateModel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}}]}}]}}]} as unknown as DocumentNode<GetUserWorkspaceProjectsWithAccessChecksQuery, GetUserWorkspaceProjectsWithAccessChecksQueryVariables>;
|
||||
export const GetUserProjectsWithAccessChecksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserProjectsWithAccessChecks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UserProjectsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FullPermissionCheckResult"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PermissionCheckResult"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectImplicitRoleCheck"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canRead"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canReadWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canCreateModel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FullPermissionCheckResult"}}]}}]}}]}}]} as unknown as DocumentNode<GetUserProjectsWithAccessChecksQuery, GetUserProjectsWithAccessChecksQueryVariables>;
|
||||
export const CreateStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequestCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<CreateStreamAccessRequestMutation, CreateStreamAccessRequestMutationVariables>;
|
||||
export const GetStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<GetStreamAccessRequestQuery, GetStreamAccessRequestQueryVariables>;
|
||||
export const GetFullStreamAccessRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetFullStreamAccessRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequest"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"}},{"kind":"Field","name":{"kind":"Name","value":"stream"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicStreamAccessRequestFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"StreamAccessRequest"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requester"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"requesterId"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<GetFullStreamAccessRequestQuery, GetFullStreamAccessRequestQueryVariables>;
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
} from '@/modules/core/services/branch/retrieval'
|
||||
import { getServerOrigin } from '@/modules/shared/helpers/envHelper'
|
||||
import { last } from 'lodash-es'
|
||||
|
||||
import { getViewerResourceGroupsFactory } from '@/modules/core/services/commit/viewerResources'
|
||||
import {
|
||||
getPaginatedBranchCommitsFactory,
|
||||
legacyGetPaginatedStreamCommitsFactory
|
||||
@@ -24,6 +22,7 @@ import {
|
||||
createBranchFactory,
|
||||
deleteBranchByIdFactory,
|
||||
getBranchByIdFactory,
|
||||
getBranchesByIdsFactory,
|
||||
getBranchLatestCommitsFactory,
|
||||
getModelTreeItemsFactory,
|
||||
getModelTreeItemsFilteredFactory,
|
||||
@@ -60,6 +59,13 @@ import { throwIfAuthNotOk } from '@/modules/shared/helpers/errorHelper'
|
||||
import { throwIfResourceAccessNotAllowed } from '@/modules/core/helpers/token'
|
||||
import { TokenResourceIdentifierType } from '@/modules/core/domain/tokens/types'
|
||||
import { withOperationLogging } from '@/observability/domain/businessLogging'
|
||||
import { getViewerResourceGroupsFactory } from '@/modules/viewer/services/viewerResources'
|
||||
import { NotFoundError } from '@/modules/shared/errors'
|
||||
import {
|
||||
isModelResource,
|
||||
resourceBuilder,
|
||||
ViewerModelResource
|
||||
} from '@speckle/shared/viewer/route'
|
||||
|
||||
export default {
|
||||
User: {
|
||||
@@ -164,7 +170,11 @@ export default {
|
||||
}
|
||||
)
|
||||
},
|
||||
async viewerResources(parent, { resourceIdString, loadedVersionsOnly }) {
|
||||
async viewerResources(
|
||||
parent,
|
||||
{ resourceIdString, loadedVersionsOnly, savedViewId },
|
||||
ctx
|
||||
) {
|
||||
const projectDB = await getProjectDbClient({ projectId: parent.id })
|
||||
const getStreamObjects = getStreamObjectsFactory({ db: projectDB })
|
||||
const getViewerResourceGroups = getViewerResourceGroupsFactory({
|
||||
@@ -172,8 +182,45 @@ export default {
|
||||
getBranchLatestCommits: getBranchLatestCommitsFactory({ db: projectDB }),
|
||||
getStreamBranchesByName: getStreamBranchesByNameFactory({ db: projectDB }),
|
||||
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db: projectDB }),
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db: projectDB })
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db: projectDB }),
|
||||
getBranchesByIds: getBranchesByIdsFactory({ db: projectDB })
|
||||
})
|
||||
|
||||
// Saved View: By default load already specified versions were available,
|
||||
// otherwise load latest versions
|
||||
if (savedViewId) {
|
||||
const savedView = await ctx.loaders
|
||||
.forRegion({ db: projectDB })
|
||||
.savedViews.getSavedViews.load({ viewId: savedViewId, projectId: parent.id })
|
||||
if (!savedView) {
|
||||
throw new NotFoundError(
|
||||
`Saved view with ID ${savedViewId} not found in project ${parent.id}`
|
||||
)
|
||||
}
|
||||
|
||||
const savedViewResources = resourceBuilder().addFromString(
|
||||
savedView.resourceIds
|
||||
)
|
||||
const baseResources = resourceBuilder().addFromString(resourceIdString)
|
||||
const finalSavedViewResources = savedViewResources.map((r) => {
|
||||
if (!isModelResource(r) || !r.versionId) {
|
||||
return r
|
||||
}
|
||||
|
||||
const matchingBaseResource = baseResources
|
||||
.filter(isModelResource)
|
||||
.find((r2) => {
|
||||
return r2.modelId === r.modelId
|
||||
})
|
||||
|
||||
return new ViewerModelResource(r.modelId, matchingBaseResource?.versionId)
|
||||
})
|
||||
|
||||
resourceIdString = resourceBuilder()
|
||||
.addResources(finalSavedViewResources)
|
||||
.toString()
|
||||
}
|
||||
|
||||
return await getViewerResourceGroups({
|
||||
projectId: parent.id,
|
||||
resourceIdString,
|
||||
|
||||
@@ -20,6 +20,9 @@ export default {
|
||||
User: {
|
||||
permissions: () => ({})
|
||||
},
|
||||
PermissionCheckResult: {
|
||||
errorMessage: (parent) => (parent.authorized ? undefined : parent.message)
|
||||
},
|
||||
ProjectPermissionChecks: {
|
||||
canCreateModel: async (parent, _args, ctx) => {
|
||||
const canCreateModel = await ctx.authPolicies.project.model.canCreate({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { graphSchema } from '@/modules'
|
||||
import { graphSchema } from '@/modules/index'
|
||||
|
||||
const schema = await graphSchema()
|
||||
export default schema
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BadRequestError } from '@/modules/shared/errors'
|
||||
import { isGraphQLError } from '@/modules/shared/helpers/graphqlHelper'
|
||||
import type { ApolloServerOptions, BaseContext } from '@apollo/server'
|
||||
import { ensureError } from '@speckle/shared'
|
||||
import { omit } from 'lodash-es'
|
||||
import { get, isArray, isBoolean, isNumber, isString, omit } from 'lodash-es'
|
||||
import VError from 'verror'
|
||||
import { ZodError } from 'zod'
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
@@ -13,6 +13,19 @@ import { fromZodError } from 'zod-validation-error'
|
||||
*/
|
||||
const VERROR_TRASH_PROPS = ['jse_shortmsg', 'jse_cause', 'jse_info']
|
||||
|
||||
/**
|
||||
* Add pino-pretty like formatting
|
||||
*/
|
||||
const pinoPretty = (log: object, msg: string) =>
|
||||
msg.replace(/{([^{}]+)}/g, (match: string, p1: string) => {
|
||||
const val = get(log, p1)
|
||||
if (val === undefined) return match
|
||||
|
||||
const formattedValue =
|
||||
isString(val) || isNumber(val) || isBoolean(val) ? val : JSON.stringify(val)
|
||||
return formattedValue as string
|
||||
})
|
||||
|
||||
/**
|
||||
* Builds apollo server error formatter
|
||||
*/
|
||||
@@ -64,10 +77,21 @@ export function buildErrorFormatter(params: {
|
||||
? VError.fullStack(realError)
|
||||
: ensureError(realError).stack
|
||||
} else {
|
||||
delete extensions.exception.stacktrace
|
||||
delete extensions.stacktrace
|
||||
}
|
||||
}
|
||||
|
||||
// Fix error message to work w/ pino templating
|
||||
writableFormattedError.message = pinoPretty(
|
||||
extensions,
|
||||
writableFormattedError.message
|
||||
)
|
||||
if (extensions.stacktrace && isArray(extensions.stacktrace)) {
|
||||
extensions.stacktrace = extensions.stacktrace.map((stack: string) =>
|
||||
pinoPretty(extensions, stack)
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
message: writableFormattedError.message,
|
||||
locations: writableFormattedError.locations,
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
UserRecord
|
||||
} from '@/modules/core/helpers/types'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import type { GraphqlPermissionCheckResult } from '@speckle/shared/authz'
|
||||
|
||||
/**
|
||||
* The types of objects we return in resolvers often don't have the exact type as the object in the schema.
|
||||
@@ -157,3 +158,5 @@ export type VersionPermissionChecksGraphQLReturn = {
|
||||
}
|
||||
|
||||
export type EmbedTokenGraphQLReturn = EmbedApiTokenWithMetadata
|
||||
|
||||
export type PermissionCheckResultGraphQLReturn = GraphqlPermissionCheckResult
|
||||
|
||||
@@ -45,3 +45,14 @@ export const mapDbToGqlProjectVisibility = (
|
||||
throwUncoveredError(visibility)
|
||||
}
|
||||
}
|
||||
|
||||
export const mapGqlToDbSortDirection = (direction: 'ASC' | 'DESC'): 'asc' | 'desc' => {
|
||||
switch (direction) {
|
||||
case 'ASC':
|
||||
return 'asc'
|
||||
case 'DESC':
|
||||
return 'desc'
|
||||
default:
|
||||
throwUncoveredError(direction)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,23 @@
|
||||
import type {
|
||||
GetCommentsResources,
|
||||
GetViewerResourceGroups,
|
||||
GetViewerResourceItemsUngrouped,
|
||||
GetViewerResourcesForComment,
|
||||
GetViewerResourcesForComments,
|
||||
GetViewerResourcesFromLegacyIdentifiers
|
||||
} from '@/modules/comments/domain/operations'
|
||||
import type {
|
||||
GetBranchLatestCommits,
|
||||
GetStreamBranchesByName
|
||||
} from '@/modules/core/domain/branches/operations'
|
||||
import type {
|
||||
GetAllBranchCommits,
|
||||
GetCommitsAndTheirBranchIds,
|
||||
GetSpecificBranchCommits
|
||||
} from '@/modules/core/domain/commits/operations'
|
||||
import type { GetStreamObjects } from '@/modules/core/domain/objects/operations'
|
||||
import type { GetCommitsAndTheirBranchIds } from '@/modules/core/domain/commits/operations'
|
||||
import type {
|
||||
ResourceIdentifier,
|
||||
ResourceIdentifierInput,
|
||||
ViewerResourceGroup,
|
||||
ViewerResourceItem,
|
||||
ViewerUpdateTrackingTarget
|
||||
ViewerResourceItem
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import { ResourceType } from '@/modules/core/graph/generated/graphql'
|
||||
import type { CommitRecord } from '@/modules/core/helpers/types'
|
||||
import type { Optional } from '@speckle/shared'
|
||||
import type { GetObjectResourceGroupsDeps } from '@/modules/viewer/services/viewerResources'
|
||||
import {
|
||||
getObjectResourceGroupsFactory,
|
||||
isResourceItemEqual
|
||||
} from '@/modules/viewer/services/viewerResources'
|
||||
import { SpeckleViewer } from '@speckle/shared'
|
||||
import { flatten, keyBy, reduce, uniq, uniqWith } from 'lodash-es'
|
||||
|
||||
function isResourceItemEqual(a: ViewerResourceItem, b: ViewerResourceItem) {
|
||||
if (a.modelId !== b.modelId) return false
|
||||
if (a.objectId !== b.objectId) return false
|
||||
if (a.versionId !== b.versionId) return false
|
||||
return true
|
||||
}
|
||||
import { reduce, uniqWith } from 'lodash-es'
|
||||
|
||||
function isResourceIdentifierEqual(
|
||||
a: ResourceIdentifier | ResourceIdentifierInput,
|
||||
@@ -45,328 +28,6 @@ function isResourceIdentifierEqual(
|
||||
return true
|
||||
}
|
||||
|
||||
type GetObjectResourceGroupsDeps = {
|
||||
getStreamObjects: GetStreamObjects
|
||||
}
|
||||
|
||||
const getObjectResourceGroupsFactory =
|
||||
(deps: GetObjectResourceGroupsDeps) =>
|
||||
async (
|
||||
projectId: string,
|
||||
resources: SpeckleViewer.ViewerRoute.ViewerObjectResource[]
|
||||
) => {
|
||||
const objects = keyBy(
|
||||
await deps.getStreamObjects(
|
||||
projectId,
|
||||
resources.map((r) => r.objectId)
|
||||
),
|
||||
'id'
|
||||
)
|
||||
|
||||
const results: ViewerResourceGroup[] = []
|
||||
for (const objectResource of resources) {
|
||||
if (!objects[objectResource.objectId]) continue
|
||||
|
||||
results.push({
|
||||
identifier: objectResource.toString(),
|
||||
items: [{ modelId: null, versionId: null, objectId: objectResource.objectId }]
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
type GetVersionResourceGroupsIncludingAllVersionsFactoryDeps = {
|
||||
getStreamBranchesByName: GetStreamBranchesByName
|
||||
getAllBranchCommits: GetAllBranchCommits
|
||||
}
|
||||
|
||||
const getVersionResourceGroupsIncludingAllVersionsFactory =
|
||||
(deps: GetVersionResourceGroupsIncludingAllVersionsFactoryDeps) =>
|
||||
async (
|
||||
projectId: string,
|
||||
params: {
|
||||
modelResources?: SpeckleViewer.ViewerRoute.ViewerModelResource[]
|
||||
folderResources?: SpeckleViewer.ViewerRoute.ViewerModelFolderResource[]
|
||||
}
|
||||
) => {
|
||||
// by default we pull all versions of all relevant branches, but if loadedVersionsOnly is set, we only pull
|
||||
// specifically requested versions (if version isn't set in identifier, then latest version)
|
||||
|
||||
const { modelResources = [], folderResources = [] } = params
|
||||
const results: ViewerResourceGroup[] = []
|
||||
|
||||
const foldersModels = await deps.getStreamBranchesByName(
|
||||
projectId,
|
||||
folderResources.map((r) => r.folderName),
|
||||
{ startsWithName: true }
|
||||
)
|
||||
|
||||
const allBranchIds = [
|
||||
...foldersModels.map((m) => m.id),
|
||||
...modelResources.map((m) => m.modelId)
|
||||
]
|
||||
|
||||
// get all versions of all referenced branches
|
||||
const branchCommits = await deps.getAllBranchCommits({ branchIds: allBranchIds })
|
||||
|
||||
for (const folderResource of folderResources) {
|
||||
const prefix = folderResource.folderName
|
||||
const folderModels = foldersModels.filter((m) =>
|
||||
m.name.toLowerCase().startsWith(prefix)
|
||||
)
|
||||
if (!folderModels.length) continue
|
||||
|
||||
const items: ViewerResourceItem[] = []
|
||||
for (const folderModel of folderModels) {
|
||||
const modelVersions = branchCommits[folderModel.id]
|
||||
if (!modelVersions?.length) continue
|
||||
|
||||
for (const modelVersion of modelVersions) {
|
||||
items.push({
|
||||
modelId: folderModel.id,
|
||||
versionId: modelVersion.id,
|
||||
objectId: modelVersion.referencedObject
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
results.push({
|
||||
identifier: folderResource.toString(),
|
||||
items
|
||||
})
|
||||
}
|
||||
|
||||
for (const modelResource of modelResources) {
|
||||
const modelVersions = branchCommits[modelResource.modelId] || []
|
||||
|
||||
const items: ViewerResourceItem[] = []
|
||||
for (const modelVersion of modelVersions) {
|
||||
items.push({
|
||||
modelId: modelResource.modelId,
|
||||
versionId: modelVersion.id,
|
||||
objectId: modelVersion.referencedObject
|
||||
})
|
||||
}
|
||||
|
||||
results.push({
|
||||
identifier: modelResource.toString(),
|
||||
items
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
type GetVersionResourceGroupsLoadedVersionsOnlyDeps = {
|
||||
getStreamBranchesByName: GetStreamBranchesByName
|
||||
getSpecificBranchCommits: GetSpecificBranchCommits
|
||||
getBranchLatestCommits: GetBranchLatestCommits
|
||||
}
|
||||
|
||||
const getVersionResourceGroupsLoadedVersionsOnlyFactory =
|
||||
(deps: GetVersionResourceGroupsLoadedVersionsOnlyDeps) =>
|
||||
async (
|
||||
projectId: string,
|
||||
params: {
|
||||
modelResources?: SpeckleViewer.ViewerRoute.ViewerModelResource[]
|
||||
folderResources?: SpeckleViewer.ViewerRoute.ViewerModelFolderResource[]
|
||||
}
|
||||
) => {
|
||||
// by default we pull all versions of all relevant branches, but if loadedVersionsOnly is set, we only pull
|
||||
// specifically requested versions (if version isn't set in identifier, then latest version)
|
||||
|
||||
const { modelResources = [], folderResources = [] } = params
|
||||
const results: ViewerResourceGroup[] = []
|
||||
|
||||
const foldersModels = await deps.getStreamBranchesByName(
|
||||
projectId,
|
||||
folderResources.map((r) => r.folderName),
|
||||
{ startsWithName: true }
|
||||
)
|
||||
|
||||
const specificVersionPairs = modelResources
|
||||
.filter(
|
||||
(
|
||||
r
|
||||
): r is SpeckleViewer.ViewerRoute.ViewerModelResource & { versionId: string } =>
|
||||
!!r.versionId
|
||||
)
|
||||
.map((r) => ({ branchId: r.modelId, commitId: r.versionId }))
|
||||
|
||||
const latestVersionModelIds = uniq([
|
||||
...modelResources.filter((r) => !r.versionId).map((r) => r.modelId),
|
||||
...foldersModels.map((m) => m.id)
|
||||
])
|
||||
|
||||
const [specificVersions, latestVersions] = await Promise.all([
|
||||
deps.getSpecificBranchCommits(specificVersionPairs),
|
||||
deps.getBranchLatestCommits(latestVersionModelIds)
|
||||
])
|
||||
const modelLatestVersions = keyBy(latestVersions, 'branchId')
|
||||
|
||||
for (const folderResource of folderResources) {
|
||||
const prefix = folderResource.folderName
|
||||
const folderModels = foldersModels.filter((m) =>
|
||||
m.name.toLowerCase().startsWith(prefix)
|
||||
)
|
||||
if (!folderModels.length) continue
|
||||
|
||||
const items: ViewerResourceItem[] = []
|
||||
for (const folderModel of folderModels) {
|
||||
const latestVersion = modelLatestVersions[folderModel.id]
|
||||
if (!latestVersion) continue
|
||||
|
||||
items.push({
|
||||
modelId: folderModel.id,
|
||||
versionId: latestVersion.id,
|
||||
objectId: latestVersion.referencedObject
|
||||
})
|
||||
}
|
||||
|
||||
results.push({
|
||||
identifier: folderResource.toString(),
|
||||
items
|
||||
})
|
||||
}
|
||||
|
||||
for (const modelResource of modelResources) {
|
||||
let item: Optional<CommitRecord & { branchId: string }> = undefined
|
||||
if (modelResource.versionId) {
|
||||
item = specificVersions.find(
|
||||
(v) =>
|
||||
v.branchId === modelResource.modelId && v.id === modelResource.versionId
|
||||
)
|
||||
} else {
|
||||
item = modelLatestVersions[modelResource.modelId]
|
||||
}
|
||||
|
||||
if (!item) continue
|
||||
results.push({
|
||||
identifier: modelResource.toString(),
|
||||
items: [
|
||||
{
|
||||
modelId: item.branchId,
|
||||
versionId: item.id,
|
||||
objectId: item.referencedObject
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
type GetAllModelsResourceGroupDeps = {
|
||||
getBranchLatestCommits: GetBranchLatestCommits
|
||||
}
|
||||
|
||||
const getAllModelsResourceGroupFactory =
|
||||
(deps: GetAllModelsResourceGroupDeps) =>
|
||||
async (projectId: string): Promise<ViewerResourceGroup> => {
|
||||
const allBranchCommits = await deps.getBranchLatestCommits(undefined, projectId)
|
||||
return {
|
||||
identifier: 'all',
|
||||
items: allBranchCommits.map(
|
||||
(c): ViewerResourceItem => ({
|
||||
modelId: c.branchId,
|
||||
versionId: c.id,
|
||||
objectId: c.referencedObject
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type GetVersionResourceGroupsDeps = GetAllModelsResourceGroupDeps &
|
||||
GetVersionResourceGroupsLoadedVersionsOnlyDeps &
|
||||
GetVersionResourceGroupsIncludingAllVersionsFactoryDeps
|
||||
|
||||
/**
|
||||
* Version resources can be resolved 2 ways:
|
||||
* * Default - Specific version IDs referenced in identifiers are ignored and the identifiers always
|
||||
* refer to all versions of any referenced branch/branches of folders.
|
||||
* * Loaded versions only - Identifiers only refer to specific version IDs referenced in resource
|
||||
* identifiers, or if none are specified then only the latest version is referenced (e.g. in folder
|
||||
* resources & model resources w/ an empty version ID)
|
||||
*/
|
||||
const getVersionResourceGroupsFactory =
|
||||
(deps: GetVersionResourceGroupsDeps) =>
|
||||
async (
|
||||
projectId: string,
|
||||
params: {
|
||||
modelResources?: SpeckleViewer.ViewerRoute.ViewerModelResource[]
|
||||
folderResources?: SpeckleViewer.ViewerRoute.ViewerModelFolderResource[]
|
||||
allModelsResource?: SpeckleViewer.ViewerRoute.ViewerAllModelsResource
|
||||
},
|
||||
loadedVersionsOnly?: boolean
|
||||
) => {
|
||||
const allModelsGroup = params.allModelsResource
|
||||
? await getAllModelsResourceGroupFactory(deps)(projectId)
|
||||
: null
|
||||
|
||||
const groups = loadedVersionsOnly
|
||||
? await getVersionResourceGroupsLoadedVersionsOnlyFactory(deps)(projectId, params)
|
||||
: await getVersionResourceGroupsIncludingAllVersionsFactory(deps)(
|
||||
projectId,
|
||||
params
|
||||
)
|
||||
|
||||
return [...(allModelsGroup ? [allModelsGroup] : []), ...groups]
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate requested resource identifiers and build viewer resource groups & items with
|
||||
* the metadata that the viewer needs to work with these
|
||||
*/
|
||||
export const getViewerResourceGroupsFactory =
|
||||
(
|
||||
deps: GetObjectResourceGroupsDeps & GetVersionResourceGroupsDeps
|
||||
): GetViewerResourceGroups =>
|
||||
async (target: ViewerUpdateTrackingTarget): Promise<ViewerResourceGroup[]> => {
|
||||
const { resourceIdString, projectId, loadedVersionsOnly } = target
|
||||
if (!resourceIdString?.trim().length) return []
|
||||
const resources = SpeckleViewer.ViewerRoute.parseUrlParameters(resourceIdString)
|
||||
|
||||
const allModelsResource = resources.find(
|
||||
SpeckleViewer.ViewerRoute.isAllModelsResource
|
||||
)
|
||||
const objectResources = resources.filter(SpeckleViewer.ViewerRoute.isObjectResource)
|
||||
const modelResources = resources.filter(SpeckleViewer.ViewerRoute.isModelResource)
|
||||
const folderResources = resources.filter(
|
||||
SpeckleViewer.ViewerRoute.isModelFolderResource
|
||||
)
|
||||
|
||||
const results: ViewerResourceGroup[] = flatten(
|
||||
await Promise.all([
|
||||
getObjectResourceGroupsFactory(deps)(projectId, objectResources),
|
||||
getVersionResourceGroupsFactory(deps)(
|
||||
projectId,
|
||||
{ modelResources, folderResources, allModelsResource },
|
||||
loadedVersionsOnly || false
|
||||
)
|
||||
])
|
||||
)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export const getViewerResourceItemsUngroupedFactory =
|
||||
(deps: {
|
||||
getViewerResourceGroups: GetViewerResourceGroups
|
||||
}): GetViewerResourceItemsUngrouped =>
|
||||
async (target: ViewerUpdateTrackingTarget): Promise<ViewerResourceItem[]> => {
|
||||
const { resourceIdString } = target
|
||||
if (!resourceIdString?.trim().length) return []
|
||||
|
||||
let results: ViewerResourceItem[] = []
|
||||
const groups = await deps.getViewerResourceGroups(target)
|
||||
for (const group of groups) {
|
||||
results = results.concat(group.items)
|
||||
}
|
||||
|
||||
return uniqWith(results, isResourceItemEqual)
|
||||
}
|
||||
|
||||
export const getViewerResourcesFromLegacyIdentifiersFactory =
|
||||
(
|
||||
deps: {
|
||||
@@ -450,28 +111,3 @@ export const getViewerResourcesForCommentFactory =
|
||||
async (projectId: string, commentId: string): Promise<ViewerResourceItem[]> => {
|
||||
return await getViewerResourcesForCommentsFactory(deps)(projectId, [commentId])
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any of the resource items match
|
||||
*/
|
||||
export function doViewerResourcesFit(
|
||||
requestedResources: ViewerResourceItem[],
|
||||
incomingResources: ViewerResourceItem[]
|
||||
) {
|
||||
return incomingResources.some((ir) =>
|
||||
requestedResources.some((rr) => isResourceItemEqual(ir, rr))
|
||||
)
|
||||
}
|
||||
|
||||
export function viewerResourcesToString(resources: ViewerResourceItem[]): string {
|
||||
const builder = SpeckleViewer.ViewerRoute.resourceBuilder()
|
||||
for (const resource of resources) {
|
||||
if (resource.modelId && resource.versionId) {
|
||||
builder.addModel(resource.modelId, resource.versionId)
|
||||
} else {
|
||||
builder.addObject(resource.objectId)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import {
|
||||
createBranchFactory,
|
||||
getBranchByIdFactory,
|
||||
getBranchesByIdsFactory,
|
||||
getBranchLatestCommitsFactory,
|
||||
getStreamBranchByNameFactory,
|
||||
getStreamBranchesByNameFactory,
|
||||
@@ -53,8 +54,6 @@ import { getFirstAdminFactory, getUserFactory } from '@/modules/core/repositorie
|
||||
import { createBranchAndNotifyFactory } from '@/modules/core/services/branch/management'
|
||||
import { createCommitByBranchIdFactory } from '@/modules/core/services/commit/management'
|
||||
import {
|
||||
getViewerResourceGroupsFactory,
|
||||
getViewerResourceItemsUngroupedFactory,
|
||||
getViewerResourcesForCommentFactory,
|
||||
getViewerResourcesForCommentsFactory,
|
||||
getViewerResourcesFromLegacyIdentifiersFactory
|
||||
@@ -69,6 +68,10 @@ import { ensureOnboardingProjectFactory } from '@/modules/cross-server-sync/serv
|
||||
import { downloadProjectFactory } from '@/modules/cross-server-sync/services/project'
|
||||
import type { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import {
|
||||
getViewerResourceGroupsFactory,
|
||||
getViewerResourceItemsUngroupedFactory
|
||||
} from '@/modules/viewer/services/viewerResources'
|
||||
|
||||
const crossServerSyncModule: SpeckleModule = {
|
||||
init() {
|
||||
@@ -96,7 +99,8 @@ const crossServerSyncModule: SpeckleModule = {
|
||||
getBranchLatestCommits: getBranchLatestCommitsFactory({ db }),
|
||||
getStreamBranchesByName: getStreamBranchesByNameFactory({ db }),
|
||||
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db }),
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db })
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db }),
|
||||
getBranchesByIds: getBranchesByIdsFactory({ db })
|
||||
})
|
||||
})
|
||||
const getViewerResourcesFromLegacyIdentifiers =
|
||||
|
||||
@@ -81,14 +81,15 @@ const dataLoadersDefinition = defineRequestDataloaders(
|
||||
const workspacePlans = await getWorkspacePlansByWorkspaceId({
|
||||
workspaceIds: workspaceIds.slice()
|
||||
})
|
||||
const featureFlags = getFeatureFlags()
|
||||
|
||||
return workspaceIds.map((workspaceId) => {
|
||||
const plan = workspacePlans[workspaceId]
|
||||
if (!plan) return null
|
||||
|
||||
const config = {
|
||||
...WorkspacePaidPlanConfigs,
|
||||
...WorkspaceUnpaidPlanConfigs
|
||||
...WorkspacePaidPlanConfigs({ featureFlags }),
|
||||
...WorkspaceUnpaidPlanConfigs({ featureFlags })
|
||||
}
|
||||
return config[plan.name]?.limits || null
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
CanWorkspaceAccessFeature,
|
||||
WorkspaceFeatureAccessFunction
|
||||
} from '@/modules/gatekeeper/domain/operations'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { throwUncoveredError, workspacePlanHasAccessToFeature } from '@speckle/shared'
|
||||
|
||||
export const canWorkspaceAccessFeatureFactory =
|
||||
@@ -27,7 +28,8 @@ export const canWorkspaceAccessFeatureFactory =
|
||||
|
||||
return workspacePlanHasAccessToFeature({
|
||||
plan: workspacePlan.name,
|
||||
feature: workspaceFeature
|
||||
feature: workspaceFeature,
|
||||
featureFlags: getFeatureFlags()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,8 @@ const getEnabledModuleNames = () => {
|
||||
'webhooks',
|
||||
'workspacesCore',
|
||||
'gatekeeperCore',
|
||||
'multiregion'
|
||||
'multiregion',
|
||||
'viewer'
|
||||
]
|
||||
|
||||
if (FF_AUTOMATE_MODULE_ENABLED) moduleNames.push('automate')
|
||||
|
||||
@@ -535,3 +535,6 @@ export const getPreviewServiceMaxQueueBackpressure = (): number => {
|
||||
export const emailVerificationTimeoutMinutes = (): number => {
|
||||
return getIntFromEnv('EMAIL_VERIFICATION_TIMEOUT_MINUTES', '5')
|
||||
}
|
||||
|
||||
export const areSavedViewsEnabled = (): boolean =>
|
||||
getFeatureFlags().FF_SAVED_VIEWS_ENABLED
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { ViewerUpdateTrackingTarget } from '@/modules/core/graph/generated/graphql'
|
||||
import type {
|
||||
ViewerResourceGroup,
|
||||
ViewerResourceItem
|
||||
} from '@/modules/viewer/domain/types/resources'
|
||||
|
||||
export type GetViewerResourceGroups = (
|
||||
target: ViewerUpdateTrackingTarget & { allowEmptyModels?: boolean }
|
||||
) => Promise<ViewerResourceGroup[]>
|
||||
|
||||
export type GetViewerResourceItemsUngrouped = (
|
||||
target: ViewerUpdateTrackingTarget
|
||||
) => Promise<ViewerResourceItem[]>
|
||||
@@ -0,0 +1,168 @@
|
||||
import type { Collection } from '@/modules/shared/helpers/dbHelper'
|
||||
import type {
|
||||
SavedView,
|
||||
SavedViewGroup,
|
||||
SavedViewVisibility
|
||||
} from '@/modules/viewer/domain/types/savedViews'
|
||||
import type { MaybeNullOrUndefined, NullableKeysToOptional } from '@speckle/shared'
|
||||
import type { SerializedViewerState } from '@speckle/shared/viewer/state'
|
||||
import type { Exact, SetOptional } from 'type-fest'
|
||||
|
||||
// REPO OPERATIONS:
|
||||
|
||||
export type StoreSavedView = <
|
||||
View extends Exact<
|
||||
SetOptional<NullableKeysToOptional<SavedView>, 'id' | 'createdAt' | 'updatedAt'>,
|
||||
View
|
||||
>
|
||||
>(params: {
|
||||
view: View
|
||||
}) => Promise<SavedView>
|
||||
|
||||
export type StoreSavedViewGroup = <
|
||||
Group extends Exact<
|
||||
SetOptional<
|
||||
NullableKeysToOptional<SavedViewGroup>,
|
||||
'id' | 'createdAt' | 'updatedAt'
|
||||
>,
|
||||
Group
|
||||
>
|
||||
>(params: {
|
||||
group: Group
|
||||
}) => Promise<SavedViewGroup>
|
||||
|
||||
export type GetStoredViewCount = (params: { projectId: string }) => Promise<number>
|
||||
|
||||
export type GetProjectSavedViewGroupsBaseParams = {
|
||||
/**
|
||||
* Falsy means - anonymous user (so no onlyAuthored filtering)
|
||||
*/
|
||||
userId?: MaybeNullOrUndefined<string>
|
||||
projectId: string
|
||||
resourceIdString: string
|
||||
onlyAuthored?: MaybeNullOrUndefined<boolean>
|
||||
search?: MaybeNullOrUndefined<string>
|
||||
}
|
||||
|
||||
export type GetProjectSavedViewGroupsPageParams =
|
||||
GetProjectSavedViewGroupsBaseParams & {
|
||||
limit?: MaybeNullOrUndefined<number>
|
||||
cursor?: MaybeNullOrUndefined<string>
|
||||
}
|
||||
|
||||
export type GetProjectSavedViewGroupsPageItems = (
|
||||
params: GetProjectSavedViewGroupsPageParams
|
||||
) => Promise<Omit<Collection<SavedViewGroup>, 'totalCount'>>
|
||||
|
||||
export type GetProjectSavedViewGroupsTotalCount = (
|
||||
params: GetProjectSavedViewGroupsBaseParams
|
||||
) => Promise<number>
|
||||
|
||||
export type GetGroupSavedViewsBaseParams = {
|
||||
/**
|
||||
* Falsy means - anonymous user (so no onlyAuthored filtering)
|
||||
*/
|
||||
userId?: MaybeNullOrUndefined<string>
|
||||
projectId: string
|
||||
groupResourceIdString: string
|
||||
/**
|
||||
* Null means a group w/ null id, undefined means - dont filter by group id at all
|
||||
*/
|
||||
groupId: MaybeNullOrUndefined<string>
|
||||
onlyAuthored?: MaybeNullOrUndefined<boolean>
|
||||
search?: MaybeNullOrUndefined<string>
|
||||
}
|
||||
|
||||
export type GetGroupSavedViewsPageParams = GetGroupSavedViewsBaseParams & {
|
||||
limit?: MaybeNullOrUndefined<number>
|
||||
cursor?: MaybeNullOrUndefined<string>
|
||||
sortDirection?: MaybeNullOrUndefined<'asc' | 'desc'>
|
||||
sortBy?: MaybeNullOrUndefined<'createdAt' | 'name' | 'updatedAt'>
|
||||
}
|
||||
|
||||
export type GetGroupSavedViewsTotalCount = (
|
||||
params: GetGroupSavedViewsBaseParams
|
||||
) => Promise<number>
|
||||
|
||||
export type GetGroupSavedViewsPageItems = (
|
||||
params: GetGroupSavedViewsPageParams
|
||||
) => Promise<Omit<Collection<SavedView>, 'totalCount'>>
|
||||
|
||||
export type GetSavedViewGroup = (params: {
|
||||
id: string
|
||||
/**
|
||||
* If undefined, skip project ID check
|
||||
*/
|
||||
projectId: string | undefined
|
||||
}) => Promise<SavedViewGroup | undefined>
|
||||
|
||||
export type GetUngroupedSavedViewsGroup = (params: {
|
||||
projectId: string
|
||||
resourceIdString: string
|
||||
}) => SavedViewGroup
|
||||
|
||||
export type RecalculateGroupResourceIds = (params: {
|
||||
groupId: string
|
||||
}) => Promise<SavedViewGroup | undefined>
|
||||
|
||||
/**
|
||||
* Get saved groups by IDs and their project IDs
|
||||
*/
|
||||
export type GetSavedViewGroups = (params: {
|
||||
groupIds: Array<{ groupId: string; projectId: string }>
|
||||
}) => Promise<{
|
||||
[groupId: string]: SavedViewGroup | undefined
|
||||
}>
|
||||
|
||||
export type GetSavedViews = (params: {
|
||||
viewIds: Array<{ viewId: string; projectId: string }>
|
||||
}) => Promise<{
|
||||
[viewId: string]: SavedView | undefined
|
||||
}>
|
||||
|
||||
// SERVICE OPERATIONS:
|
||||
|
||||
export type CreateSavedViewParams = {
|
||||
input: {
|
||||
projectId: string
|
||||
resourceIdString: string
|
||||
groupId?: MaybeNullOrUndefined<string>
|
||||
name?: MaybeNullOrUndefined<string>
|
||||
description?: MaybeNullOrUndefined<string>
|
||||
/**
|
||||
* SerializedViewerState that will be validated/formatted before saving.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
viewerState: unknown | SerializedViewerState
|
||||
/**
|
||||
* Base64 encoded screenshot of the view.
|
||||
*/
|
||||
screenshot: string
|
||||
isHomeView?: MaybeNullOrUndefined<boolean>
|
||||
visibility?: MaybeNullOrUndefined<SavedViewVisibility>
|
||||
}
|
||||
authorId: string
|
||||
}
|
||||
|
||||
export type CreateSavedView = (params: CreateSavedViewParams) => Promise<SavedView>
|
||||
|
||||
export type CreateSavedViewGroupParams = {
|
||||
input: {
|
||||
projectId: string
|
||||
resourceIdString: string
|
||||
groupName: string
|
||||
}
|
||||
authorId: string
|
||||
}
|
||||
|
||||
export type CreateSavedViewGroup = (
|
||||
params: CreateSavedViewGroupParams
|
||||
) => Promise<SavedViewGroup>
|
||||
|
||||
export type GetProjectSavedViewGroups = (
|
||||
params: GetProjectSavedViewGroupsPageParams
|
||||
) => Promise<Collection<SavedViewGroup>>
|
||||
|
||||
export type GetGroupSavedViews = (
|
||||
params: GetGroupSavedViewsPageParams
|
||||
) => Promise<Collection<SavedView>>
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Nullable } from '@speckle/shared'
|
||||
|
||||
export type ViewerResourceItem = {
|
||||
/** Null if resource represents an object */
|
||||
modelId?: Nullable<string>
|
||||
objectId: string
|
||||
/** Null if resource represents an object */
|
||||
versionId?: Nullable<string>
|
||||
}
|
||||
|
||||
export type ViewerResourceGroup = {
|
||||
/** Resource identifier used to refer to a collection of resource items */
|
||||
identifier: string
|
||||
/** Viewer resources that the identifier refers to */
|
||||
items: Array<ViewerResourceItem>
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { Nullable, StringEnumValues } from '@speckle/shared'
|
||||
import { StringEnum } from '@speckle/shared'
|
||||
import type { VersionedSerializedViewerState } from '@speckle/shared/viewer/state'
|
||||
|
||||
export const SavedViewVisibility = StringEnum(['public', 'authorOnly'])
|
||||
export type SavedViewVisibility = StringEnumValues<typeof SavedViewVisibility>
|
||||
|
||||
export type SavedView = {
|
||||
id: string
|
||||
name: string
|
||||
description: Nullable<string>
|
||||
projectId: string
|
||||
/**
|
||||
* Null only if the author deleted their account
|
||||
*/
|
||||
authorId: Nullable<string>
|
||||
groupId: Nullable<string>
|
||||
/**
|
||||
* Fully specific/concrete (w/ version Ids) resource ids used to create this view.
|
||||
*/
|
||||
resourceIds: string[]
|
||||
/**
|
||||
* More abstract resource ids, w/o specific versions, used to group views together. Largely
|
||||
* only exists because PGSQL can't simply truncate resourceIds in a query in realtime, and we use
|
||||
* this to find views for groups.
|
||||
*/
|
||||
groupResourceIds: string[]
|
||||
isHomeView: boolean
|
||||
visibility: SavedViewVisibility
|
||||
viewerState: VersionedSerializedViewerState
|
||||
screenshot: string
|
||||
position: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export type SavedViewGroup = {
|
||||
/**
|
||||
* Globally unique identifier. If this is the default/unsorted group, the ID will be a static string and it represents a group
|
||||
* that doesn't actually exist in the database.
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* Null only if the author deleted their account or if default/unsorted group
|
||||
*/
|
||||
authorId: Nullable<string>
|
||||
/**
|
||||
* Project that the group belongs to
|
||||
*/
|
||||
projectId: string
|
||||
/**
|
||||
* Resource (model) ids associated w/ this group. This is kept in sync w/ all of the resourceIds for the views in this group too.
|
||||
* Groups need resourceIds independent of views, because you have to be able to retrieve empty groups too.
|
||||
*/
|
||||
resourceIds: string[]
|
||||
/**
|
||||
* Null means default/unsorted group
|
||||
*/
|
||||
name: Nullable<string>
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { BaseError } from '@/modules/shared/errors'
|
||||
|
||||
export class SavedViewCreationValidationError extends BaseError {
|
||||
static code = 'SAVED_VIEW_CREATION_VALIDATION_ERROR'
|
||||
static defaultMessage = 'Saved view creation failed due to a validation error'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class SavedViewGroupCreationValidationError extends BaseError {
|
||||
static code = 'SAVED_VIEW_GROUP_CREATION_VALIDATION_ERROR'
|
||||
static defaultMessage = 'Saved view group creation failed due to a validation error'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class SavedViewInvalidResourceTargetError extends BaseError {
|
||||
static code = 'SAVED_VIEW_INVALID_RESOURCE_TARGET_ERROR'
|
||||
static defaultMessage = 'Invalid resource ids specified'
|
||||
static statusCode = 400
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { defineRequestDataloaders } from '@/modules/shared/helpers/graphqlHelper'
|
||||
import type {
|
||||
SavedView,
|
||||
SavedViewGroup
|
||||
} from '@/modules/viewer/domain/types/savedViews'
|
||||
import {
|
||||
getSavedViewGroupsFactory,
|
||||
getSavedViewsFactory
|
||||
} from '@/modules/viewer/repositories/savedViews'
|
||||
import type { Nullable } from '@speckle/shared'
|
||||
|
||||
declare module '@/modules/core/loaders' {
|
||||
interface ModularizedDataLoaders extends ReturnType<typeof dataLoadersDefinition> {}
|
||||
}
|
||||
|
||||
const dataLoadersDefinition = defineRequestDataloaders(
|
||||
({ createLoader, deps: { db } }) => {
|
||||
const getSavedViewGroups = getSavedViewGroupsFactory({ db })
|
||||
const getSavedViews = getSavedViewsFactory({ db })
|
||||
|
||||
return {
|
||||
savedViews: {
|
||||
/**
|
||||
* Get a saved view group by ID. Can also handle unpersisted ungrouped groups, just make sure
|
||||
* you use their encoded IDs.
|
||||
*/
|
||||
getSavedViewGroup: createLoader<
|
||||
{ groupId: string; projectId: string },
|
||||
Nullable<SavedViewGroup>,
|
||||
string
|
||||
>(
|
||||
async (ids) => {
|
||||
const groups = await getSavedViewGroups({ groupIds: ids.slice() })
|
||||
return ids.map(({ groupId }) => groups[groupId] || null)
|
||||
},
|
||||
{
|
||||
cacheKeyFn: ({ groupId, projectId }) => `${groupId}-${projectId}`
|
||||
}
|
||||
),
|
||||
/**
|
||||
* Get saved views by their IDs
|
||||
*/
|
||||
getSavedViews: createLoader<
|
||||
{ viewId: string; projectId: string },
|
||||
Nullable<SavedView>,
|
||||
string
|
||||
>(
|
||||
async (ids) => {
|
||||
const views = await getSavedViews({ viewIds: ids.slice() })
|
||||
return ids.map(({ viewId }) => views[viewId] || null)
|
||||
},
|
||||
{
|
||||
cacheKeyFn: ({ viewId, projectId }) => `${viewId}-${projectId}`
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default dataLoadersDefinition
|
||||
@@ -0,0 +1,292 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { TokenResourceIdentifierType } from '@/modules/core/domain/tokens/types'
|
||||
import type { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { mapGqlToDbSortDirection } from '@/modules/core/helpers/project'
|
||||
import { throwIfResourceAccessNotAllowed } from '@/modules/core/helpers/token'
|
||||
import {
|
||||
getBranchesByIdsFactory,
|
||||
getBranchLatestCommitsFactory,
|
||||
getStreamBranchesByNameFactory
|
||||
} from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getAllBranchCommitsFactory,
|
||||
getSpecificBranchCommitsFactory
|
||||
} from '@/modules/core/repositories/commits'
|
||||
import { getStreamObjectsFactory } from '@/modules/core/repositories/objects'
|
||||
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { LogicError, NotFoundError, NotImplementedError } from '@/modules/shared/errors'
|
||||
import { throwIfAuthNotOk } from '@/modules/shared/helpers/errorHelper'
|
||||
import { buildDefaultGroupId } from '@/modules/viewer/helpers/savedViews'
|
||||
import {
|
||||
getGroupSavedViewsPageItemsFactory,
|
||||
getGroupSavedViewsTotalCountFactory,
|
||||
getProjectSavedViewGroupsPageItemsFactory,
|
||||
getProjectSavedViewGroupsTotalCountFactory,
|
||||
getSavedViewGroupFactory,
|
||||
getStoredViewCountFactory,
|
||||
getUngroupedSavedViewsGroupFactory,
|
||||
recalculateGroupResourceIdsFactory,
|
||||
storeSavedViewFactory,
|
||||
storeSavedViewGroupFactory
|
||||
} from '@/modules/viewer/repositories/savedViews'
|
||||
import {
|
||||
createSavedViewFactory,
|
||||
createSavedViewGroupFactory,
|
||||
getGroupSavedViewsFactory,
|
||||
getProjectSavedViewGroupsFactory
|
||||
} from '@/modules/viewer/services/savedViewsManagement'
|
||||
import { getViewerResourceGroupsFactory } from '@/modules/viewer/services/viewerResources'
|
||||
import { Authz } from '@speckle/shared'
|
||||
import { parseResourceFromString, resourceBuilder } from '@speckle/shared/viewer/route'
|
||||
import { formatSerializedViewerState } from '@speckle/shared/viewer/state'
|
||||
import type { Knex } from 'knex'
|
||||
import { ungroupedScenesGroupTitle } from '@speckle/shared/saved-views'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
const buildGetViewerResourceGroups = (params: { projectDb: Knex }) => {
|
||||
const { projectDb } = params
|
||||
return getViewerResourceGroupsFactory({
|
||||
getStreamObjects: getStreamObjectsFactory({ db: projectDb }),
|
||||
getBranchLatestCommits: getBranchLatestCommitsFactory({ db: projectDb }),
|
||||
getStreamBranchesByName: getStreamBranchesByNameFactory({ db: projectDb }),
|
||||
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db: projectDb }),
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db: projectDb }),
|
||||
getBranchesByIds: getBranchesByIdsFactory({ db: projectDb })
|
||||
})
|
||||
}
|
||||
|
||||
const resolvers: Resolvers = {
|
||||
Project: {
|
||||
async savedViewGroups(parent, args, ctx) {
|
||||
const { input } = args
|
||||
|
||||
const getProjectSavedViewGroups = getProjectSavedViewGroupsFactory({
|
||||
getProjectSavedViewGroupsPageItems: getProjectSavedViewGroupsPageItemsFactory({
|
||||
db
|
||||
}),
|
||||
getProjectSavedViewGroupsTotalCount: getProjectSavedViewGroupsTotalCountFactory(
|
||||
{ db }
|
||||
)
|
||||
})
|
||||
|
||||
return await getProjectSavedViewGroups({
|
||||
projectId: parent.id,
|
||||
resourceIdString: input.resourceIdString,
|
||||
userId: ctx.userId,
|
||||
onlyAuthored: input.onlyAuthored,
|
||||
search: input.search,
|
||||
limit: input.limit,
|
||||
cursor: input.cursor
|
||||
})
|
||||
},
|
||||
async savedViewGroup(parent, args, ctx) {
|
||||
const projectDb = await getProjectDbClient({ projectId: parent.id })
|
||||
const group = await ctx.loaders
|
||||
.forRegion({ db: projectDb })
|
||||
.savedViews.getSavedViewGroup.load({
|
||||
groupId: args.id,
|
||||
projectId: parent.id
|
||||
})
|
||||
if (!group) {
|
||||
throw new NotFoundError(
|
||||
`Saved view group with ID ${args.id} not found in project ${parent.id}`
|
||||
)
|
||||
}
|
||||
|
||||
return group
|
||||
},
|
||||
ungroupedViewGroup: async (parent, args) => {
|
||||
const getDefaultGroup = getUngroupedSavedViewsGroupFactory()
|
||||
const group = getDefaultGroup({
|
||||
projectId: parent.id,
|
||||
resourceIdString: args.input.resourceIdString
|
||||
})
|
||||
|
||||
return group
|
||||
},
|
||||
savedView: async (parent, args, ctx) => {
|
||||
const projectDb = await getProjectDbClient({ projectId: parent.id })
|
||||
const view = await ctx.loaders
|
||||
.forRegion({ db: projectDb })
|
||||
.savedViews.getSavedViews.load({
|
||||
viewId: args.id,
|
||||
projectId: parent.id
|
||||
})
|
||||
if (!view) {
|
||||
throw new NotFoundError(
|
||||
`Saved view with ID ${args.id} not found in project ${parent.id}`
|
||||
)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
},
|
||||
SavedView: {
|
||||
async author(parent, _args, ctx) {
|
||||
return parent.authorId
|
||||
? await ctx.loaders.users.getUser.load(parent.authorId)
|
||||
: null
|
||||
},
|
||||
resourceIdString(parent) {
|
||||
const resourceIds = parent.resourceIds
|
||||
return resourceBuilder().addFromString(resourceIds.join(',')).toString()
|
||||
},
|
||||
viewerState(parent) {
|
||||
return formatSerializedViewerState(parent.viewerState.state)
|
||||
},
|
||||
group: async (parent, _args, ctx) => {
|
||||
const groupId =
|
||||
parent.groupId ||
|
||||
buildDefaultGroupId({
|
||||
resourceIds: parent.resourceIds,
|
||||
projectId: parent.projectId
|
||||
})
|
||||
const projectDb = await getProjectDbClient({ projectId: parent.projectId })
|
||||
const group = await ctx.loaders
|
||||
.forRegion({ db: projectDb })
|
||||
.savedViews.getSavedViewGroup.load({
|
||||
groupId,
|
||||
projectId: parent.projectId
|
||||
})
|
||||
if (!group) {
|
||||
throw new LogicError('Unexpectedly could not resolve a view group')
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
},
|
||||
SavedViewGroup: {
|
||||
title: (parent) => parent.name || ungroupedScenesGroupTitle,
|
||||
isUngroupedViewsGroup: (parent) => parent.name === null,
|
||||
groupId: (parent) => (parent.name ? parent.id : null),
|
||||
async views(parent, args, ctx) {
|
||||
const { input } = args
|
||||
const getGroupSavedViews = getGroupSavedViewsFactory({
|
||||
getGroupSavedViewsPageItems: getGroupSavedViewsPageItemsFactory({ db }),
|
||||
getGroupSavedViewsTotalCount: getGroupSavedViewsTotalCountFactory({ db })
|
||||
})
|
||||
|
||||
const allowedSortBy = <const>['createdAt', 'name', 'updatedAt']
|
||||
const sortBy = input.sortBy
|
||||
? allowedSortBy.find((s) => s === input.sortBy)
|
||||
: undefined
|
||||
|
||||
return await getGroupSavedViews({
|
||||
projectId: parent.projectId,
|
||||
groupResourceIdString: resourceBuilder()
|
||||
.addResources(parent.resourceIds.map(parseResourceFromString))
|
||||
.toString(),
|
||||
userId: ctx.userId,
|
||||
groupId: parent.name ? parent.id : null,
|
||||
onlyAuthored: input.onlyAuthored,
|
||||
search: input.search,
|
||||
limit: input.limit,
|
||||
cursor: input.cursor,
|
||||
sortDirection: input.sortDirection
|
||||
? mapGqlToDbSortDirection(input.sortDirection)
|
||||
: undefined,
|
||||
sortBy
|
||||
})
|
||||
}
|
||||
},
|
||||
ProjectMutations: {
|
||||
savedViewMutations: () => ({})
|
||||
},
|
||||
SavedViewMutations: {
|
||||
createView: async (_parent, args, ctx) => {
|
||||
const projectId = args.input.projectId
|
||||
throwIfResourceAccessNotAllowed({
|
||||
resourceId: projectId,
|
||||
resourceType: TokenResourceIdentifierType.Project,
|
||||
resourceAccessRules: ctx.resourceAccessRules
|
||||
})
|
||||
|
||||
const canCreate = await ctx.authPolicies.project.savedViews.canCreate({
|
||||
userId: ctx.userId,
|
||||
projectId
|
||||
})
|
||||
throwIfAuthNotOk(canCreate)
|
||||
|
||||
const projectDb = await getProjectDbClient({ projectId })
|
||||
const createSavedView = createSavedViewFactory({
|
||||
getViewerResourceGroups: buildGetViewerResourceGroups({ projectDb }),
|
||||
getStoredViewCount: getStoredViewCountFactory({ db: projectDb }),
|
||||
storeSavedView: storeSavedViewFactory({ db: projectDb }),
|
||||
getSavedViewGroup: getSavedViewGroupFactory({ db: projectDb }),
|
||||
recalculateGroupResourceIds: recalculateGroupResourceIdsFactory({
|
||||
db: projectDb
|
||||
})
|
||||
})
|
||||
return await createSavedView({ input: args.input, authorId: ctx.userId! })
|
||||
},
|
||||
createGroup: async (_parent, args, ctx) => {
|
||||
const projectId = args.input.projectId
|
||||
throwIfResourceAccessNotAllowed({
|
||||
resourceId: projectId,
|
||||
resourceType: TokenResourceIdentifierType.Project,
|
||||
resourceAccessRules: ctx.resourceAccessRules
|
||||
})
|
||||
|
||||
const canCreate = await ctx.authPolicies.project.savedViews.canCreate({
|
||||
userId: ctx.userId,
|
||||
projectId
|
||||
})
|
||||
throwIfAuthNotOk(canCreate)
|
||||
|
||||
const projectDb = await getProjectDbClient({ projectId })
|
||||
const createSavedViewGroup = createSavedViewGroupFactory({
|
||||
storeSavedViewGroup: storeSavedViewGroupFactory({ db: projectDb }),
|
||||
getViewerResourceGroups: buildGetViewerResourceGroups({ projectDb })
|
||||
})
|
||||
return await createSavedViewGroup({
|
||||
input: args.input,
|
||||
authorId: ctx.userId!
|
||||
})
|
||||
}
|
||||
},
|
||||
ProjectPermissionChecks: {
|
||||
canCreateSavedView: async (parent, _args, ctx) => {
|
||||
const projectId = parent.projectId
|
||||
const canCreate = await ctx.authPolicies.project.savedViews.canCreate({
|
||||
userId: ctx.userId,
|
||||
projectId
|
||||
})
|
||||
return Authz.toGraphqlResult(canCreate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const disabledMessage = 'Saved views are disabled on this server'
|
||||
const disabledResolvers: Resolvers = {
|
||||
Project: {
|
||||
savedViewGroups: () => {
|
||||
throw new NotImplementedError(disabledMessage)
|
||||
},
|
||||
savedViewGroup: () => {
|
||||
throw new NotImplementedError(disabledMessage)
|
||||
},
|
||||
ungroupedViewGroup: () => {
|
||||
throw new NotImplementedError(disabledMessage)
|
||||
},
|
||||
savedView: () => {
|
||||
throw new NotImplementedError(disabledMessage)
|
||||
}
|
||||
},
|
||||
ProjectMutations: {
|
||||
savedViewMutations: () => {
|
||||
throw new NotImplementedError(disabledMessage)
|
||||
}
|
||||
},
|
||||
ProjectPermissionChecks: {
|
||||
canCreateSavedView: () => {
|
||||
return {
|
||||
authorized: false,
|
||||
message: disabledMessage,
|
||||
code: 'SAVED_VIEWS_DISABLED',
|
||||
payload: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default getFeatureFlags().FF_SAVED_VIEWS_ENABLED ? resolvers : disabledResolvers
|
||||
@@ -0,0 +1,7 @@
|
||||
import type {
|
||||
SavedView,
|
||||
SavedViewGroup
|
||||
} from '@/modules/viewer/domain/types/savedViews'
|
||||
|
||||
export type SavedViewGraphQLReturn = SavedView
|
||||
export type SavedViewGroupGraphQLReturn = SavedViewGroup
|
||||
@@ -0,0 +1,71 @@
|
||||
import { base64Decode, base64Encode } from '@/modules/shared/helpers/cryptoHelper'
|
||||
import type { Nullable } from '@speckle/shared'
|
||||
import {
|
||||
isModelResource,
|
||||
isObjectResource,
|
||||
resourceBuilder
|
||||
} from '@speckle/shared/viewer/route'
|
||||
import { isObjectLike } from 'lodash-es'
|
||||
|
||||
export type DefaultGroupMetadata = {
|
||||
resourceIds: string[]
|
||||
projectId: string
|
||||
name: 'Default Group'
|
||||
}
|
||||
|
||||
export const buildDefaultGroupId = (params: {
|
||||
resourceIds: string[]
|
||||
projectId: string
|
||||
}) => {
|
||||
const payload: DefaultGroupMetadata = {
|
||||
resourceIds: formatResourceIdsForGroup(params.resourceIds),
|
||||
projectId: params.projectId,
|
||||
name: 'Default Group'
|
||||
}
|
||||
const str = JSON.stringify(payload)
|
||||
return 'default-' + base64Encode(str)
|
||||
}
|
||||
|
||||
export const decodeDefaultGroupId = (id: string): Nullable<DefaultGroupMetadata> => {
|
||||
try {
|
||||
if (!id.startsWith('default-')) return null
|
||||
const json = base64Decode(id.replace('default-', ''))
|
||||
const obj = JSON.parse(json)
|
||||
if (
|
||||
!isObjectLike(obj) ||
|
||||
!obj.resourceIds ||
|
||||
!obj.projectId ||
|
||||
obj.name !== 'Default Group'
|
||||
) {
|
||||
throw new Error('Invalid saved view group ID format')
|
||||
}
|
||||
return obj as Nullable<DefaultGroupMetadata>
|
||||
} catch {
|
||||
// Suppress - not the default group ID
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a resourceId string into a more abstract format used by groups that disregards
|
||||
* specific versions of models and objects.
|
||||
*/
|
||||
export const formatResourceIdsForGroup = (resourceIdString: string | string[]) => {
|
||||
resourceIdString = Array.isArray(resourceIdString)
|
||||
? resourceIdString.join(',')
|
||||
: resourceIdString
|
||||
|
||||
return resourceBuilder()
|
||||
.addFromString(resourceIdString)
|
||||
.forEach((r) => {
|
||||
if (isModelResource(r)) {
|
||||
// not interested in the specific version ids originally used
|
||||
r.versionId = undefined
|
||||
}
|
||||
})
|
||||
.filter((r) => {
|
||||
// filter out any resources that are not ViewerModelResource or ViewerObjectResource
|
||||
return isModelResource(r) || isObjectResource(r)
|
||||
})
|
||||
.map((r) => r.toString())
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import type { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
|
||||
import { viewerLogger } from '@/observability/logging'
|
||||
|
||||
const viewerModule: SpeckleModule = {
|
||||
init: async () => {
|
||||
if (!getFeatureFlags().FF_SAVED_VIEWS_ENABLED) return
|
||||
viewerLogger.info('🤩 Initializing viewer module...')
|
||||
}
|
||||
}
|
||||
|
||||
export default viewerModule
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { Knex } from 'knex'
|
||||
|
||||
const viewsTable = 'saved_views'
|
||||
const groupsTable = 'saved_view_groups'
|
||||
|
||||
// TODO: Validate indexing strategy based on queries
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.createTable(groupsTable, (table) => {
|
||||
table.string('id').notNullable().primary()
|
||||
table.string('name').notNullable()
|
||||
table
|
||||
.string('projectId')
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('streams')
|
||||
.onDelete('CASCADE')
|
||||
table
|
||||
.string('authorId')
|
||||
.nullable()
|
||||
.references('id')
|
||||
.inTable('users')
|
||||
.onDelete('SET NULL') // If the author is deleted, we keep the view but lose the author reference
|
||||
table.specificType('resourceIds', 'varchar(255)[]').notNullable().defaultTo('{}')
|
||||
table
|
||||
.timestamp('createdAt', { precision: 3, useTz: true })
|
||||
.defaultTo(knex.fn.now())
|
||||
.notNullable()
|
||||
table
|
||||
.timestamp('updatedAt', { precision: 3, useTz: true })
|
||||
.defaultTo(knex.fn.now())
|
||||
.notNullable()
|
||||
})
|
||||
|
||||
await knex.schema.createTable(viewsTable, (table) => {
|
||||
table.string('id').notNullable().primary()
|
||||
table.string('name').notNullable()
|
||||
table.text('description').nullable()
|
||||
table
|
||||
.string('projectId')
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('streams')
|
||||
.onDelete('CASCADE')
|
||||
table
|
||||
.string('authorId')
|
||||
.nullable()
|
||||
.references('id')
|
||||
.inTable('users')
|
||||
.onDelete('SET NULL') // If the author is deleted, we keep the view but lose the author reference
|
||||
table
|
||||
.string('groupId')
|
||||
.nullable()
|
||||
.references('id')
|
||||
.inTable(groupsTable)
|
||||
.onDelete('SET NULL') // If group deleted, ungroup
|
||||
|
||||
table.specificType('resourceIds', 'varchar(255)[]').notNullable().defaultTo('{}')
|
||||
table
|
||||
.specificType('groupResourceIds', 'varchar(255)[]')
|
||||
.notNullable()
|
||||
.defaultTo('{}')
|
||||
table.boolean('isHomeView').notNullable().defaultTo(false)
|
||||
table.string('visibility').defaultTo('public').notNullable() // public, authorOnly
|
||||
table.jsonb('viewerState').notNullable() // SerializedViewerState
|
||||
table.text('screenshot').notNullable() // Base64 encoded screenshot
|
||||
table.double('position').defaultTo(0).notNullable() // Used for manual positioning in the UI
|
||||
|
||||
table
|
||||
.timestamp('createdAt', { precision: 3, useTz: true })
|
||||
.defaultTo(knex.fn.now())
|
||||
.notNullable()
|
||||
table
|
||||
.timestamp('updatedAt', { precision: 3, useTz: true })
|
||||
.defaultTo(knex.fn.now())
|
||||
.notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(viewsTable)
|
||||
await knex.schema.dropTableIfExists(groupsTable)
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
import { buildTableHelper } from '@/modules/core/dbSchema'
|
||||
import { compositeCursorTools } from '@/modules/shared/helpers/dbHelper'
|
||||
import type {
|
||||
GetGroupSavedViewsBaseParams,
|
||||
GetGroupSavedViewsPageItems,
|
||||
GetGroupSavedViewsTotalCount,
|
||||
GetProjectSavedViewGroupsBaseParams,
|
||||
GetProjectSavedViewGroupsPageItems,
|
||||
GetProjectSavedViewGroupsTotalCount,
|
||||
GetSavedViewGroups,
|
||||
GetSavedViewGroup,
|
||||
GetStoredViewCount,
|
||||
GetUngroupedSavedViewsGroup,
|
||||
RecalculateGroupResourceIds,
|
||||
StoreSavedView,
|
||||
StoreSavedViewGroup,
|
||||
GetSavedViews
|
||||
} from '@/modules/viewer/domain/operations/savedViews'
|
||||
import {
|
||||
SavedViewVisibility,
|
||||
type SavedView,
|
||||
type SavedViewGroup
|
||||
} from '@/modules/viewer/domain/types/savedViews'
|
||||
import type { DefaultGroupMetadata } from '@/modules/viewer/helpers/savedViews'
|
||||
import {
|
||||
buildDefaultGroupId,
|
||||
decodeDefaultGroupId,
|
||||
formatResourceIdsForGroup
|
||||
} from '@/modules/viewer/helpers/savedViews'
|
||||
import { resourceBuilder } from '@speckle/shared/viewer/route'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import dayjs from 'dayjs'
|
||||
import { type Knex } from 'knex'
|
||||
import { clamp, isUndefined } from 'lodash-es'
|
||||
|
||||
const SavedViews = buildTableHelper('saved_views', [
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'projectId',
|
||||
'authorId',
|
||||
'groupId',
|
||||
'resourceIds',
|
||||
'groupResourceIds',
|
||||
'isHomeView',
|
||||
'visibility',
|
||||
'viewerState',
|
||||
'screenshot',
|
||||
'position',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
])
|
||||
|
||||
const SavedViewGroups = buildTableHelper('saved_view_groups', [
|
||||
'id',
|
||||
'authorId',
|
||||
'projectId',
|
||||
'resourceIds',
|
||||
'name',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
])
|
||||
|
||||
const savedGroupCursorUtils = () =>
|
||||
compositeCursorTools({
|
||||
schema: SavedViewGroups,
|
||||
cols: ['updatedAt', 'id']
|
||||
})
|
||||
|
||||
const generateId = () => cryptoRandomString({ length: 10 })
|
||||
|
||||
const buildDefaultGroup = (params: {
|
||||
resourceIds: string[]
|
||||
projectId: string
|
||||
}): SavedViewGroup => {
|
||||
const { resourceIds, projectId } = params
|
||||
|
||||
return {
|
||||
id: buildDefaultGroupId({ projectId, resourceIds }),
|
||||
authorId: null,
|
||||
projectId,
|
||||
resourceIds,
|
||||
name: null,
|
||||
createdAt: dayjs(0).toDate(),
|
||||
updatedAt: dayjs(0).toDate()
|
||||
}
|
||||
}
|
||||
|
||||
const tables = {
|
||||
savedViews: (db: Knex) => db<SavedView>(SavedViews.name),
|
||||
savedViewGroups: (db: Knex) => db<SavedViewGroup>(SavedViewGroups.name)
|
||||
}
|
||||
|
||||
export const storeSavedViewFactory =
|
||||
(deps: { db: Knex }): StoreSavedView =>
|
||||
async ({ view }) => {
|
||||
const [insertedItem] = await tables.savedViews(deps.db).insert(
|
||||
{
|
||||
id: generateId(),
|
||||
...view
|
||||
},
|
||||
'*'
|
||||
)
|
||||
return insertedItem
|
||||
}
|
||||
|
||||
export const storeSavedViewGroupFactory =
|
||||
(deps: { db: Knex }): StoreSavedViewGroup =>
|
||||
async ({ group }) => {
|
||||
const [insertedItem] = await tables.savedViewGroups(deps.db).insert(
|
||||
{
|
||||
id: generateId(),
|
||||
...group
|
||||
},
|
||||
'*'
|
||||
)
|
||||
return insertedItem
|
||||
}
|
||||
|
||||
export const getStoredViewCountFactory =
|
||||
(deps: { db: Knex }): GetStoredViewCount =>
|
||||
async ({ projectId }) => {
|
||||
const [count] = await tables.savedViews(deps.db).where({ projectId }).count()
|
||||
return parseInt(count.count + '')
|
||||
}
|
||||
|
||||
const getProjectSavedViewGroupsBaseQueryFactory =
|
||||
(deps: { db: Knex }) => async (params: GetProjectSavedViewGroupsBaseParams) => {
|
||||
const { projectId, resourceIdString, search, userId } = params
|
||||
const onlyAuthored = params.onlyAuthored && userId
|
||||
const isFiltering = search || onlyAuthored
|
||||
const resourceIds = formatResourceIdsForGroup(resourceIdString)
|
||||
|
||||
/**
|
||||
* When looking for default group items, the group doesn't exist, so we have to apply
|
||||
* the same filters to the views table instead
|
||||
*/
|
||||
const applyFilters = (query: Knex.QueryBuilder, mode: 'view' | 'group') => {
|
||||
const isGroupQuery = mode === 'group'
|
||||
const isViewQuery = mode === 'view'
|
||||
|
||||
if (isViewQuery) {
|
||||
// empty groupId - we're looking for ungrouped views only
|
||||
query.andWhere({
|
||||
[SavedViews.col.groupId]: null
|
||||
})
|
||||
}
|
||||
|
||||
// group's or view's authorId
|
||||
query.andWhere({
|
||||
[isGroupQuery ? SavedViewGroups.col.projectId : SavedViews.col.projectId]:
|
||||
projectId
|
||||
})
|
||||
|
||||
// group's or view's resourceIds
|
||||
if (resourceIds.length) {
|
||||
// Col contains at least one of the resources
|
||||
query.andWhereRaw('?? && ?', [
|
||||
isGroupQuery
|
||||
? SavedViewGroups.col.resourceIds
|
||||
: SavedViews.col.groupResourceIds,
|
||||
resourceIds
|
||||
])
|
||||
} else {
|
||||
// Make query exit early - no resources
|
||||
query.andWhereRaw('false')
|
||||
}
|
||||
|
||||
// checking authored only on views (in case of groups, they're joined on)
|
||||
if (onlyAuthored) {
|
||||
query.andWhere({ [SavedViews.col.authorId]: userId })
|
||||
} else if (mode === 'view') {
|
||||
query.andWhere((w1) => {
|
||||
w1.andWhere(SavedViews.col.visibility, SavedViewVisibility.public)
|
||||
if (userId) {
|
||||
w1.orWhere(SavedViews.col.authorId, userId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// checking search only on views
|
||||
if (search) {
|
||||
query.andWhere(SavedViews.col.name, 'ilike', `%${search}%`)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
const q = tables
|
||||
.savedViewGroups(deps.db)
|
||||
.select<SavedViewGroup[]>(SavedViewGroups.cols)
|
||||
|
||||
if (isFiltering) {
|
||||
q.innerJoin(SavedViews.name, SavedViews.col.groupId, SavedViewGroups.col.id)
|
||||
}
|
||||
|
||||
applyFilters(q, 'group')
|
||||
|
||||
// Group by groupId
|
||||
q.groupBy(SavedViewGroups.col.id)
|
||||
|
||||
/**
|
||||
* Check if default group should be shown
|
||||
*/
|
||||
const ungroupedViewFound = await applyFilters(
|
||||
tables.savedViews(deps.db),
|
||||
'view'
|
||||
).first()
|
||||
const includeDefaultGroup = Boolean(ungroupedViewFound)
|
||||
|
||||
return { q, resourceIds, isFiltering, includeDefaultGroup }
|
||||
}
|
||||
|
||||
export const getProjectSavedViewGroupsTotalCountFactory =
|
||||
(deps: { db: Knex }): GetProjectSavedViewGroupsTotalCount =>
|
||||
async (params) => {
|
||||
const { q, includeDefaultGroup } = await getProjectSavedViewGroupsBaseQueryFactory(
|
||||
deps
|
||||
)(params)
|
||||
const countQ = deps.db.count<{ count: string }[]>().from(q.as('sq1'))
|
||||
const [count] = await countQ
|
||||
|
||||
const numberCount = parseInt(count.count + '') + (includeDefaultGroup ? 1 : 0)
|
||||
return numberCount
|
||||
}
|
||||
|
||||
export const getProjectSavedViewGroupsPageItemsFactory =
|
||||
(deps: { db: Knex }): GetProjectSavedViewGroupsPageItems =>
|
||||
async (params) => {
|
||||
const { projectId } = params
|
||||
const { q, resourceIds, includeDefaultGroup } =
|
||||
await getProjectSavedViewGroupsBaseQueryFactory(deps)(params)
|
||||
const { applyCursorSortAndFilter, resolveNewCursor } = savedGroupCursorUtils()
|
||||
|
||||
const limit = clamp(params.limit ?? 10, 0, 100)
|
||||
q.limit(limit)
|
||||
|
||||
// Apply cursor filter and sort
|
||||
applyCursorSortAndFilter({
|
||||
query: q,
|
||||
cursor: params.cursor
|
||||
})
|
||||
|
||||
const items: SavedViewGroup[] = await q
|
||||
|
||||
// If first page and allowed, add the default/unsorted group
|
||||
if (!params.cursor && includeDefaultGroup) {
|
||||
const defaultGroup: SavedViewGroup = buildDefaultGroup({
|
||||
resourceIds,
|
||||
projectId
|
||||
})
|
||||
|
||||
// Before we add the group, we need to potentially pop the last item
|
||||
// if the limit was reached
|
||||
if (items.length >= limit) {
|
||||
items.pop()
|
||||
}
|
||||
items.unshift(defaultGroup)
|
||||
}
|
||||
|
||||
const newCursor = resolveNewCursor(items)
|
||||
|
||||
return {
|
||||
items,
|
||||
cursor: newCursor
|
||||
}
|
||||
}
|
||||
|
||||
const getGroupSavedViewsBaseQueryFactory =
|
||||
(deps: { db: Knex }) => (params: GetGroupSavedViewsBaseParams) => {
|
||||
const { projectId, groupResourceIdString, groupId, search, userId } = params
|
||||
const onlyAuthored = params.onlyAuthored && userId
|
||||
|
||||
const q = tables
|
||||
.savedViews(deps.db)
|
||||
.where({ [SavedViews.col.projectId]: projectId })
|
||||
|
||||
const groupResourceIds = formatResourceIdsForGroup(groupResourceIdString)
|
||||
|
||||
if (!groupResourceIds.length && !groupId) {
|
||||
// If no resources and no groupId, exit early
|
||||
q.whereRaw('false')
|
||||
}
|
||||
|
||||
// Set group filter
|
||||
if (!isUndefined(groupId)) {
|
||||
q.where({ [SavedViews.col.groupId]: groupId })
|
||||
}
|
||||
|
||||
// If no groupId, filter by resourceIds
|
||||
if (groupResourceIds.length && !groupId) {
|
||||
q.whereRaw('?? && ?', [SavedViews.col.groupResourceIds, groupResourceIds])
|
||||
}
|
||||
|
||||
if (onlyAuthored) {
|
||||
q.where({ [SavedViews.col.authorId]: userId })
|
||||
} else {
|
||||
q.andWhere((w1) => {
|
||||
w1.andWhere(SavedViews.col.visibility, SavedViewVisibility.public)
|
||||
if (userId) {
|
||||
w1.orWhere(SavedViews.col.authorId, userId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (search) {
|
||||
q.where(SavedViews.col.name, 'ilike', `%${search}%`)
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
export const getGroupSavedViewsTotalCountFactory =
|
||||
(deps: { db: Knex }): GetGroupSavedViewsTotalCount =>
|
||||
async (params) => {
|
||||
const q = getGroupSavedViewsBaseQueryFactory(deps)(params)
|
||||
const countQ = deps.db.count<{ count: string }[]>().from(q.as('sq1'))
|
||||
const [count] = await countQ
|
||||
return parseInt(count.count + '')
|
||||
}
|
||||
|
||||
export const getGroupSavedViewsPageItemsFactory =
|
||||
(deps: { db: Knex }): GetGroupSavedViewsPageItems =>
|
||||
async (params) => {
|
||||
const sortByCol = params.sortBy || 'updatedAt'
|
||||
const sortDir = params.sortDirection || 'desc'
|
||||
|
||||
const q = getGroupSavedViewsBaseQueryFactory(deps)(params)
|
||||
const { applyCursorSortAndFilter, resolveNewCursor } = compositeCursorTools({
|
||||
schema: SavedViews,
|
||||
cols: [sortByCol, 'id']
|
||||
})
|
||||
|
||||
const limit = clamp(params.limit ?? 10, 0, 100)
|
||||
q.limit(limit)
|
||||
|
||||
// Apply cursor filter and sort
|
||||
applyCursorSortAndFilter({ query: q, cursor: params.cursor, sort: sortDir })
|
||||
|
||||
const items = await q
|
||||
const newCursor = resolveNewCursor(items)
|
||||
|
||||
return {
|
||||
items,
|
||||
cursor: newCursor
|
||||
}
|
||||
}
|
||||
|
||||
export const getSavedViewGroupFactory =
|
||||
(deps: { db: Knex }): GetSavedViewGroup =>
|
||||
async ({ id, projectId }) => {
|
||||
// Check if default group ID
|
||||
const defaultGroupMetadata = decodeDefaultGroupId(id)
|
||||
if (defaultGroupMetadata) {
|
||||
if (projectId && defaultGroupMetadata.projectId !== projectId) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return buildDefaultGroup({
|
||||
resourceIds: defaultGroupMetadata.resourceIds,
|
||||
projectId: defaultGroupMetadata.projectId
|
||||
})
|
||||
}
|
||||
|
||||
const q = tables.savedViewGroups(deps.db).where({
|
||||
[SavedViewGroups.col.id]: id
|
||||
})
|
||||
|
||||
if (projectId) {
|
||||
q.andWhere({ [SavedViewGroups.col.projectId]: projectId })
|
||||
}
|
||||
|
||||
const group = await q.first()
|
||||
return group
|
||||
}
|
||||
|
||||
export const getUngroupedSavedViewsGroupFactory =
|
||||
(): GetUngroupedSavedViewsGroup =>
|
||||
({ projectId, resourceIdString }) =>
|
||||
buildDefaultGroup({
|
||||
resourceIds: resourceBuilder()
|
||||
.addFromString(resourceIdString)
|
||||
.map((r) => r.toString()),
|
||||
projectId
|
||||
})
|
||||
|
||||
export const recalculateGroupResourceIdsFactory =
|
||||
(deps: { db: Knex }): RecalculateGroupResourceIds =>
|
||||
async ({ groupId }) => {
|
||||
const RawSavedViews = SavedViews.with({ quoted: true, withCustomTablePrefix: 'v' })
|
||||
const RawSavedViewGroups = SavedViewGroups.with({ quoted: true })
|
||||
|
||||
const q = tables
|
||||
.savedViewGroups(deps.db)
|
||||
.where({ [SavedViewGroups.col.id]: groupId })
|
||||
.update(
|
||||
{
|
||||
// Recalculate the groups resourceIds based on the views in the group
|
||||
[SavedViewGroups.withoutTablePrefix.col.resourceIds]: deps.db.raw(
|
||||
`(
|
||||
SELECT ARRAY(
|
||||
SELECT DISTINCT unnest
|
||||
FROM ${RawSavedViews.name},
|
||||
unnest(${RawSavedViews.col.resourceIds}) AS unnest
|
||||
WHERE ${RawSavedViews.col.groupId} = ${RawSavedViewGroups.col.id}
|
||||
)
|
||||
)`
|
||||
)
|
||||
},
|
||||
'*'
|
||||
)
|
||||
|
||||
const results = await q
|
||||
return results.at(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saved groups by IDs. Can handle unpersisted ungrouped groups too.
|
||||
*/
|
||||
export const getSavedViewGroupsFactory =
|
||||
(deps: { db: Knex }): GetSavedViewGroups =>
|
||||
async ({ groupIds }) => {
|
||||
if (!groupIds.length) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const defaultGroupsMetadata: { [groupId: string]: DefaultGroupMetadata } = {}
|
||||
const persistedGroupIds: Array<{ groupId: string; projectId: string }> = []
|
||||
for (const { groupId, projectId } of groupIds) {
|
||||
const defaultGroupMetadata = decodeDefaultGroupId(groupId)
|
||||
if (defaultGroupMetadata) {
|
||||
if (defaultGroupMetadata.projectId === projectId) {
|
||||
defaultGroupsMetadata[groupId] = defaultGroupMetadata
|
||||
}
|
||||
} else {
|
||||
persistedGroupIds.push({ groupId, projectId })
|
||||
}
|
||||
}
|
||||
|
||||
let persistedGroups: SavedViewGroup[] = []
|
||||
if (persistedGroupIds.length) {
|
||||
const q = tables.savedViewGroups(deps.db).whereIn(
|
||||
[SavedViewGroups.col.id, SavedViewGroups.col.projectId],
|
||||
persistedGroupIds.map((g) => [g.groupId, g.projectId])
|
||||
)
|
||||
persistedGroups = await q
|
||||
}
|
||||
|
||||
const groupsMap: { [groupId: string]: SavedViewGroup | undefined } = {}
|
||||
for (const { groupId, projectId } of groupIds) {
|
||||
const defaultGroupMetadata = defaultGroupsMetadata[groupId]
|
||||
if (defaultGroupMetadata) {
|
||||
groupsMap[groupId] = buildDefaultGroup({
|
||||
resourceIds: defaultGroupMetadata.resourceIds,
|
||||
projectId: defaultGroupMetadata.projectId
|
||||
})
|
||||
} else {
|
||||
groupsMap[groupId] =
|
||||
persistedGroups.find((g) => g.id === groupId && g.projectId === projectId) ||
|
||||
undefined
|
||||
}
|
||||
}
|
||||
return groupsMap
|
||||
}
|
||||
|
||||
export const getSavedViewsFactory =
|
||||
(deps: { db: Knex }): GetSavedViews =>
|
||||
async ({ viewIds }) => {
|
||||
if (!viewIds.length) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const q = tables.savedViews(deps.db).whereIn(
|
||||
[SavedViews.col.id, SavedViews.col.projectId],
|
||||
viewIds.map((v) => [v.viewId, v.projectId])
|
||||
)
|
||||
|
||||
const views = await q
|
||||
|
||||
const viewsMap: { [viewId: string]: SavedView | undefined } = {}
|
||||
for (const view of views) {
|
||||
viewsMap[view.id] = view
|
||||
}
|
||||
return viewsMap
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
import type {
|
||||
CreateSavedView,
|
||||
CreateSavedViewGroup,
|
||||
GetGroupSavedViews,
|
||||
GetGroupSavedViewsPageItems,
|
||||
GetGroupSavedViewsTotalCount,
|
||||
GetProjectSavedViewGroups,
|
||||
GetProjectSavedViewGroupsPageItems,
|
||||
GetProjectSavedViewGroupsTotalCount,
|
||||
GetSavedViewGroup,
|
||||
GetStoredViewCount,
|
||||
RecalculateGroupResourceIds,
|
||||
StoreSavedView,
|
||||
StoreSavedViewGroup
|
||||
} from '@/modules/viewer/domain/operations/savedViews'
|
||||
import { SavedViewVisibility } from '@/modules/viewer/domain/types/savedViews'
|
||||
import {
|
||||
SavedViewCreationValidationError,
|
||||
SavedViewGroupCreationValidationError,
|
||||
SavedViewInvalidResourceTargetError
|
||||
} from '@/modules/viewer/errors/savedViews'
|
||||
import { resourceBuilder } from '@speckle/shared/viewer/route'
|
||||
import { inputToVersionedState } from '@speckle/shared/viewer/state'
|
||||
import { isValidBase64Image } from '@speckle/shared/images/base64'
|
||||
import type { GetViewerResourceGroups } from '@/modules/viewer/domain/operations/resources'
|
||||
import { formatResourceIdsForGroup } from '@/modules/viewer/helpers/savedViews'
|
||||
|
||||
/**
|
||||
* Validates an incoming resourceIdString against the resources in the project and returns the validated list (as a builder)
|
||||
*/
|
||||
const validateProjectResourceIdStringFactory =
|
||||
(deps: { getViewerResourceGroups: GetViewerResourceGroups }) =>
|
||||
async (params: {
|
||||
resourceIdString: string
|
||||
projectId: string
|
||||
errorMetadata: Record<string, unknown>
|
||||
}) => {
|
||||
const { resourceIdString, errorMetadata, projectId } = params
|
||||
|
||||
// Validate resourceIdString - it should only point to valid resources belonging to the project
|
||||
const resourceIds = resourceBuilder().addFromString(resourceIdString)
|
||||
if (!resourceIds.length) {
|
||||
throw new SavedViewInvalidResourceTargetError(
|
||||
"No valid resources referenced in 'resourceIdString'",
|
||||
{
|
||||
info: errorMetadata
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const resourceGroups = await deps.getViewerResourceGroups({
|
||||
projectId,
|
||||
loadedVersionsOnly: true,
|
||||
resourceIdString: resourceIds.toString(),
|
||||
allowEmptyModels: true
|
||||
})
|
||||
|
||||
// Check if any of the resources could not be found
|
||||
const failingResources = resourceIds.clone().filter((rId) => {
|
||||
const resourceGroup = resourceGroups.find(
|
||||
(rg) => rg.identifier === rId.toString()
|
||||
)
|
||||
if (!resourceGroup) return true
|
||||
return false
|
||||
})
|
||||
if (failingResources.length) {
|
||||
throw new SavedViewInvalidResourceTargetError(
|
||||
'One or more resources could not be found in the project: {resourceIdString}',
|
||||
{
|
||||
info: {
|
||||
...errorMetadata,
|
||||
resourceIdString: failingResources.toString()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return resourceIds
|
||||
}
|
||||
|
||||
export const createSavedViewFactory =
|
||||
(deps: {
|
||||
getViewerResourceGroups: GetViewerResourceGroups
|
||||
getStoredViewCount: GetStoredViewCount
|
||||
storeSavedView: StoreSavedView
|
||||
getSavedViewGroup: GetSavedViewGroup
|
||||
recalculateGroupResourceIds: RecalculateGroupResourceIds
|
||||
}): CreateSavedView =>
|
||||
async ({ input, authorId }) => {
|
||||
const { resourceIdString, projectId } = input
|
||||
const visibility = input.visibility || SavedViewVisibility.public
|
||||
const position = 0 // TODO: Resolve based on existing views
|
||||
const groupId = input.groupId?.trim() || null
|
||||
const description = input.description?.trim() || null
|
||||
const isHomeView = input.isHomeView || false
|
||||
|
||||
// Validate resourceIdString - it should only point to valid resources belonging to the project
|
||||
const resourceIds = await validateProjectResourceIdStringFactory(deps)({
|
||||
resourceIdString,
|
||||
projectId,
|
||||
errorMetadata: {
|
||||
input,
|
||||
authorId
|
||||
}
|
||||
})
|
||||
|
||||
const screenshot = input.screenshot.trim()
|
||||
if (!isValidBase64Image(screenshot)) {
|
||||
throw new SavedViewCreationValidationError(
|
||||
'Invalid screenshot provided. Must be a valid base64 encoded image.',
|
||||
{
|
||||
info: {
|
||||
input,
|
||||
authorId
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const state = inputToVersionedState(input.viewerState)
|
||||
if (!state) {
|
||||
throw new SavedViewCreationValidationError(
|
||||
'Invalid viewer state provided. Must be a valid SerializedViewerState.',
|
||||
{
|
||||
info: {
|
||||
input,
|
||||
authorId
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Validate state match
|
||||
if (state.state.resources.request.resourceIdString !== input.resourceIdString) {
|
||||
throw new SavedViewCreationValidationError(
|
||||
'Viewer state does not match the provided resourceIdString.',
|
||||
{
|
||||
info: {
|
||||
input,
|
||||
authorId
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
if (state.state.projectId !== projectId) {
|
||||
throw new SavedViewCreationValidationError(
|
||||
'Viewer state projectId does not match the provided projectId.',
|
||||
{
|
||||
info: {
|
||||
input,
|
||||
authorId
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Validate groupId - group is a valid and accessible group in the project
|
||||
if (groupId) {
|
||||
const group = await deps.getSavedViewGroup({
|
||||
id: groupId,
|
||||
projectId
|
||||
})
|
||||
if (!group) {
|
||||
throw new SavedViewCreationValidationError(
|
||||
'Provided groupId does not exist in the project.',
|
||||
{
|
||||
info: {
|
||||
input,
|
||||
authorId
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-generate name, if one not set
|
||||
let name = input.name?.trim()
|
||||
if (!name?.length) {
|
||||
const viewCount = await deps.getStoredViewCount({ projectId })
|
||||
name = `Scene - ${String(viewCount + 1).padStart(3, '0')}`
|
||||
}
|
||||
|
||||
const concreteResourceIds = resourceIds.toResources().map((r) => r.toString())
|
||||
const ret = await deps.storeSavedView({
|
||||
view: {
|
||||
projectId,
|
||||
resourceIds: concreteResourceIds,
|
||||
groupResourceIds: formatResourceIdsForGroup(concreteResourceIds),
|
||||
groupId,
|
||||
name,
|
||||
description,
|
||||
viewerState: state,
|
||||
screenshot,
|
||||
visibility,
|
||||
position,
|
||||
authorId,
|
||||
isHomeView
|
||||
}
|
||||
})
|
||||
|
||||
// If grouped view, recalculate its resourceIds
|
||||
if (groupId) {
|
||||
await deps.recalculateGroupResourceIds({ groupId })
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
export const createSavedViewGroupFactory =
|
||||
(deps: {
|
||||
storeSavedViewGroup: StoreSavedViewGroup
|
||||
getViewerResourceGroups: GetViewerResourceGroups
|
||||
}): CreateSavedViewGroup =>
|
||||
async ({ input, authorId }) => {
|
||||
const { projectId, resourceIdString } = input
|
||||
const groupName = input.groupName.trim()
|
||||
if (groupName.length < 1 || groupName.length > 255) {
|
||||
throw new SavedViewGroupCreationValidationError(
|
||||
'Group name must be between 1 and 255 characters long',
|
||||
{
|
||||
info: {
|
||||
input,
|
||||
authorId
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Validate resourceIdString - it should only point to valid resources belonging to the project
|
||||
const resourceIds = await validateProjectResourceIdStringFactory(deps)({
|
||||
resourceIdString,
|
||||
projectId,
|
||||
errorMetadata: {
|
||||
input,
|
||||
authorId
|
||||
}
|
||||
})
|
||||
|
||||
// Insert
|
||||
const group = await deps.storeSavedViewGroup({
|
||||
group: {
|
||||
projectId,
|
||||
resourceIds: resourceIds.toResources().map((r) => r.toString()),
|
||||
name: groupName,
|
||||
authorId
|
||||
}
|
||||
})
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
export const getProjectSavedViewGroupsFactory =
|
||||
(deps: {
|
||||
getProjectSavedViewGroupsPageItems: GetProjectSavedViewGroupsPageItems
|
||||
getProjectSavedViewGroupsTotalCount: GetProjectSavedViewGroupsTotalCount
|
||||
}): GetProjectSavedViewGroups =>
|
||||
async (params) => {
|
||||
const noItemsNeeded = params.limit === 0
|
||||
const [totalCount, pageItems] = await Promise.all([
|
||||
deps.getProjectSavedViewGroupsTotalCount(params),
|
||||
noItemsNeeded
|
||||
? Promise.resolve({ items: [], cursor: null })
|
||||
: deps.getProjectSavedViewGroupsPageItems(params)
|
||||
])
|
||||
|
||||
return {
|
||||
totalCount,
|
||||
...pageItems
|
||||
}
|
||||
}
|
||||
|
||||
export const getGroupSavedViewsFactory =
|
||||
(deps: {
|
||||
getGroupSavedViewsPageItems: GetGroupSavedViewsPageItems
|
||||
getGroupSavedViewsTotalCount: GetGroupSavedViewsTotalCount
|
||||
}): GetGroupSavedViews =>
|
||||
async (params) => {
|
||||
const noItemsNeeded = params.limit === 0
|
||||
const [totalCount, pageItems] = await Promise.all([
|
||||
deps.getGroupSavedViewsTotalCount(params),
|
||||
noItemsNeeded
|
||||
? Promise.resolve({ items: [], cursor: null })
|
||||
: deps.getGroupSavedViewsPageItems(params)
|
||||
])
|
||||
|
||||
return {
|
||||
totalCount,
|
||||
...pageItems
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
import type {
|
||||
GetBranchesByIds,
|
||||
GetBranchLatestCommits,
|
||||
GetStreamBranchesByName
|
||||
} from '@/modules/core/domain/branches/operations'
|
||||
import type {
|
||||
GetAllBranchCommits,
|
||||
GetSpecificBranchCommits
|
||||
} from '@/modules/core/domain/commits/operations'
|
||||
import type { GetStreamObjects } from '@/modules/core/domain/objects/operations'
|
||||
import type {
|
||||
ViewerResourceGroup,
|
||||
ViewerResourceItem,
|
||||
ViewerUpdateTrackingTarget
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import type { CommitRecord } from '@/modules/core/helpers/types'
|
||||
import type {
|
||||
GetViewerResourceGroups,
|
||||
GetViewerResourceItemsUngrouped
|
||||
} from '@/modules/viewer/domain/operations/resources'
|
||||
import type { Optional } from '@speckle/shared'
|
||||
import { SpeckleViewer } from '@speckle/shared'
|
||||
import type { ViewerModelResource } from '@speckle/shared/viewer/route'
|
||||
import { flatten, keyBy, uniq, uniqWith } from 'lodash-es'
|
||||
|
||||
export function isResourceItemEqual(a: ViewerResourceItem, b: ViewerResourceItem) {
|
||||
if (a.modelId !== b.modelId) return false
|
||||
if (a.objectId !== b.objectId) return false
|
||||
if (a.versionId !== b.versionId) return false
|
||||
return true
|
||||
}
|
||||
|
||||
export type GetObjectResourceGroupsDeps = {
|
||||
getStreamObjects: GetStreamObjects
|
||||
}
|
||||
|
||||
export const getObjectResourceGroupsFactory =
|
||||
(deps: GetObjectResourceGroupsDeps) =>
|
||||
async (
|
||||
projectId: string,
|
||||
resources: SpeckleViewer.ViewerRoute.ViewerObjectResource[]
|
||||
) => {
|
||||
const objects = keyBy(
|
||||
await deps.getStreamObjects(
|
||||
projectId,
|
||||
resources.map((r) => r.objectId)
|
||||
),
|
||||
'id'
|
||||
)
|
||||
|
||||
const results: ViewerResourceGroup[] = []
|
||||
for (const objectResource of resources) {
|
||||
if (!objects[objectResource.objectId]) continue
|
||||
|
||||
results.push({
|
||||
identifier: objectResource.toString(),
|
||||
items: [{ modelId: null, versionId: null, objectId: objectResource.objectId }]
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
type GetVersionResourceGroupsIncludingAllVersionsFactoryDeps = {
|
||||
getStreamBranchesByName: GetStreamBranchesByName
|
||||
getAllBranchCommits: GetAllBranchCommits
|
||||
}
|
||||
|
||||
const getVersionResourceGroupsIncludingAllVersionsFactory =
|
||||
(deps: GetVersionResourceGroupsIncludingAllVersionsFactoryDeps) =>
|
||||
async (
|
||||
projectId: string,
|
||||
params: {
|
||||
modelResources?: SpeckleViewer.ViewerRoute.ViewerModelResource[]
|
||||
folderResources?: SpeckleViewer.ViewerRoute.ViewerModelFolderResource[]
|
||||
}
|
||||
) => {
|
||||
// by default we pull all versions of all relevant branches, but if loadedVersionsOnly is set, we only pull
|
||||
// specifically requested versions (if version isn't set in identifier, then latest version)
|
||||
|
||||
const { modelResources = [], folderResources = [] } = params
|
||||
const results: ViewerResourceGroup[] = []
|
||||
|
||||
const foldersModels = await deps.getStreamBranchesByName(
|
||||
projectId,
|
||||
folderResources.map((r) => r.folderName),
|
||||
{ startsWithName: true }
|
||||
)
|
||||
|
||||
const allBranchIds = [
|
||||
...foldersModels.map((m) => m.id),
|
||||
...modelResources.map((m) => m.modelId)
|
||||
]
|
||||
|
||||
// get all versions of all referenced branches
|
||||
const branchCommits = await deps.getAllBranchCommits({ branchIds: allBranchIds })
|
||||
|
||||
for (const folderResource of folderResources) {
|
||||
const prefix = folderResource.folderName
|
||||
const folderModels = foldersModels.filter((m) =>
|
||||
m.name.toLowerCase().startsWith(prefix)
|
||||
)
|
||||
if (!folderModels.length) continue
|
||||
|
||||
const items: ViewerResourceItem[] = []
|
||||
for (const folderModel of folderModels) {
|
||||
const modelVersions = branchCommits[folderModel.id]
|
||||
if (!modelVersions?.length) continue
|
||||
|
||||
for (const modelVersion of modelVersions) {
|
||||
items.push({
|
||||
modelId: folderModel.id,
|
||||
versionId: modelVersion.id,
|
||||
objectId: modelVersion.referencedObject
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
results.push({
|
||||
identifier: folderResource.toString(),
|
||||
items
|
||||
})
|
||||
}
|
||||
|
||||
for (const modelResource of modelResources) {
|
||||
const modelVersions = branchCommits[modelResource.modelId] || []
|
||||
|
||||
const items: ViewerResourceItem[] = []
|
||||
for (const modelVersion of modelVersions) {
|
||||
items.push({
|
||||
modelId: modelResource.modelId,
|
||||
versionId: modelVersion.id,
|
||||
objectId: modelVersion.referencedObject
|
||||
})
|
||||
}
|
||||
|
||||
results.push({
|
||||
identifier: modelResource.toString(),
|
||||
items
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
type GetVersionResourceGroupsLoadedVersionsOnlyDeps = {
|
||||
getStreamBranchesByName: GetStreamBranchesByName
|
||||
getBranchesByIds: GetBranchesByIds
|
||||
getSpecificBranchCommits: GetSpecificBranchCommits
|
||||
getBranchLatestCommits: GetBranchLatestCommits
|
||||
}
|
||||
|
||||
const getVersionResourceGroupsLoadedVersionsOnlyFactory =
|
||||
(deps: GetVersionResourceGroupsLoadedVersionsOnlyDeps) =>
|
||||
async (
|
||||
projectId: string,
|
||||
params: {
|
||||
modelResources?: SpeckleViewer.ViewerRoute.ViewerModelResource[]
|
||||
folderResources?: SpeckleViewer.ViewerRoute.ViewerModelFolderResource[]
|
||||
allowEmptyModels?: boolean
|
||||
}
|
||||
) => {
|
||||
// by default we pull all versions of all relevant branches, but if loadedVersionsOnly is set, we only pull
|
||||
// specifically requested versions (if version isn't set in identifier, then latest version)
|
||||
|
||||
const { modelResources = [], folderResources = [], allowEmptyModels } = params
|
||||
const results: ViewerResourceGroup[] = []
|
||||
|
||||
const foldersModels = await deps.getStreamBranchesByName(
|
||||
projectId,
|
||||
folderResources.map((r) => r.folderName),
|
||||
{ startsWithName: true }
|
||||
)
|
||||
|
||||
const specificVersionPairs = modelResources
|
||||
.filter(
|
||||
(
|
||||
r
|
||||
): r is SpeckleViewer.ViewerRoute.ViewerModelResource & { versionId: string } =>
|
||||
!!r.versionId
|
||||
)
|
||||
.map((r) => ({ branchId: r.modelId, commitId: r.versionId }))
|
||||
|
||||
const latestVersionModelIds = uniq([
|
||||
...modelResources.filter((r) => !r.versionId).map((r) => r.modelId),
|
||||
...foldersModels.map((m) => m.id)
|
||||
])
|
||||
|
||||
const [specificVersions, latestVersions] = await Promise.all([
|
||||
deps.getSpecificBranchCommits(specificVersionPairs),
|
||||
deps.getBranchLatestCommits(latestVersionModelIds)
|
||||
])
|
||||
const modelLatestVersions = keyBy(latestVersions, 'branchId')
|
||||
|
||||
for (const folderResource of folderResources) {
|
||||
const prefix = folderResource.folderName
|
||||
const folderModels = foldersModels.filter((m) =>
|
||||
m.name.toLowerCase().startsWith(prefix)
|
||||
)
|
||||
if (!folderModels.length) continue
|
||||
|
||||
const items: ViewerResourceItem[] = []
|
||||
for (const folderModel of folderModels) {
|
||||
const latestVersion = modelLatestVersions[folderModel.id]
|
||||
if (!latestVersion) continue
|
||||
|
||||
items.push({
|
||||
modelId: folderModel.id,
|
||||
versionId: latestVersion.id,
|
||||
objectId: latestVersion.referencedObject
|
||||
})
|
||||
}
|
||||
|
||||
results.push({
|
||||
identifier: folderResource.toString(),
|
||||
items
|
||||
})
|
||||
}
|
||||
|
||||
const emptyModels: ViewerModelResource[] = []
|
||||
for (const modelResource of modelResources) {
|
||||
let item: Optional<CommitRecord & { branchId: string }> = undefined
|
||||
if (modelResource.versionId) {
|
||||
item = specificVersions.find(
|
||||
(v) =>
|
||||
v.branchId === modelResource.modelId && v.id === modelResource.versionId
|
||||
)
|
||||
} else {
|
||||
item = modelLatestVersions[modelResource.modelId]
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
if (allowEmptyModels && !modelResource.versionId) {
|
||||
emptyModels.push(modelResource)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
results.push({
|
||||
identifier: modelResource.toString(),
|
||||
items: [
|
||||
{
|
||||
modelId: item.branchId,
|
||||
versionId: item.id,
|
||||
objectId: item.referencedObject
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// Validate that empty model resources are actually real models
|
||||
if (emptyModels.length && allowEmptyModels) {
|
||||
const emptyModelRecords = await deps.getBranchesByIds(
|
||||
emptyModels.map((r) => r.modelId),
|
||||
{ streamId: projectId }
|
||||
)
|
||||
const emptyModelIds = new Set(emptyModelRecords.map((m) => m.id))
|
||||
for (const emptyModelId of emptyModelIds) {
|
||||
results.push({
|
||||
identifier: emptyModelId,
|
||||
items: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
type GetAllModelsResourceGroupDeps = {
|
||||
getBranchLatestCommits: GetBranchLatestCommits
|
||||
}
|
||||
|
||||
const getAllModelsResourceGroupFactory =
|
||||
(deps: GetAllModelsResourceGroupDeps) =>
|
||||
async (projectId: string): Promise<ViewerResourceGroup> => {
|
||||
const allBranchCommits = await deps.getBranchLatestCommits(undefined, projectId)
|
||||
return {
|
||||
identifier: 'all',
|
||||
items: allBranchCommits.map(
|
||||
(c): ViewerResourceItem => ({
|
||||
modelId: c.branchId,
|
||||
versionId: c.id,
|
||||
objectId: c.referencedObject
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type GetVersionResourceGroupsDeps = GetAllModelsResourceGroupDeps &
|
||||
GetVersionResourceGroupsLoadedVersionsOnlyDeps &
|
||||
GetVersionResourceGroupsIncludingAllVersionsFactoryDeps
|
||||
|
||||
/**
|
||||
* Version resources can be resolved 2 ways:
|
||||
* * Default - Specific version IDs referenced in identifiers are ignored and the identifiers always
|
||||
* refer to all versions of any referenced branch/branches of folders.
|
||||
* * Loaded versions only - Identifiers only refer to specific version IDs referenced in resource
|
||||
* identifiers, or if none are specified then only the latest version is referenced (e.g. in folder
|
||||
* resources & model resources w/ an empty version ID)
|
||||
*/
|
||||
const getVersionResourceGroupsFactory =
|
||||
(deps: GetVersionResourceGroupsDeps) =>
|
||||
async (
|
||||
projectId: string,
|
||||
params: {
|
||||
modelResources?: SpeckleViewer.ViewerRoute.ViewerModelResource[]
|
||||
folderResources?: SpeckleViewer.ViewerRoute.ViewerModelFolderResource[]
|
||||
allModelsResource?: SpeckleViewer.ViewerRoute.ViewerAllModelsResource
|
||||
loadedVersionsOnly?: boolean
|
||||
allowEmptyModels?: boolean
|
||||
}
|
||||
) => {
|
||||
const allModelsGroup = params.allModelsResource
|
||||
? await getAllModelsResourceGroupFactory(deps)(projectId)
|
||||
: null
|
||||
|
||||
const groups = params.loadedVersionsOnly
|
||||
? await getVersionResourceGroupsLoadedVersionsOnlyFactory(deps)(projectId, params)
|
||||
: await getVersionResourceGroupsIncludingAllVersionsFactory(deps)(
|
||||
projectId,
|
||||
params
|
||||
)
|
||||
|
||||
return [...(allModelsGroup ? [allModelsGroup] : []), ...groups]
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate requested resource identifiers and build viewer resource groups & items with
|
||||
* the metadata that the viewer needs to work with these
|
||||
*/
|
||||
export const getViewerResourceGroupsFactory =
|
||||
(
|
||||
deps: GetObjectResourceGroupsDeps & GetVersionResourceGroupsDeps
|
||||
): GetViewerResourceGroups =>
|
||||
async (
|
||||
target: ViewerUpdateTrackingTarget & {
|
||||
/**
|
||||
* By default this only returns groups w/ resources in them. W/ this flag set, it will also
|
||||
* return valid model groups that have no resources in them
|
||||
*/
|
||||
allowEmptyModels?: boolean
|
||||
}
|
||||
): Promise<ViewerResourceGroup[]> => {
|
||||
const { resourceIdString, projectId, loadedVersionsOnly, allowEmptyModels } = target
|
||||
if (!resourceIdString?.trim().length) return []
|
||||
const resources = SpeckleViewer.ViewerRoute.parseUrlParameters(resourceIdString)
|
||||
|
||||
const allModelsResource = resources.find(
|
||||
SpeckleViewer.ViewerRoute.isAllModelsResource
|
||||
)
|
||||
const objectResources = resources.filter(SpeckleViewer.ViewerRoute.isObjectResource)
|
||||
const modelResources = resources.filter(SpeckleViewer.ViewerRoute.isModelResource)
|
||||
const folderResources = resources.filter(
|
||||
SpeckleViewer.ViewerRoute.isModelFolderResource
|
||||
)
|
||||
|
||||
const results: ViewerResourceGroup[] = flatten(
|
||||
await Promise.all([
|
||||
getObjectResourceGroupsFactory(deps)(projectId, objectResources),
|
||||
getVersionResourceGroupsFactory(deps)(projectId, {
|
||||
modelResources,
|
||||
folderResources,
|
||||
allModelsResource,
|
||||
loadedVersionsOnly: loadedVersionsOnly || false,
|
||||
allowEmptyModels
|
||||
})
|
||||
])
|
||||
)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
export const getViewerResourceItemsUngroupedFactory =
|
||||
(deps: {
|
||||
getViewerResourceGroups: GetViewerResourceGroups
|
||||
}): GetViewerResourceItemsUngrouped =>
|
||||
async (target: ViewerUpdateTrackingTarget): Promise<ViewerResourceItem[]> => {
|
||||
const { resourceIdString } = target
|
||||
if (!resourceIdString?.trim().length) return []
|
||||
|
||||
let results: ViewerResourceItem[] = []
|
||||
const groups = await deps.getViewerResourceGroups(target)
|
||||
for (const group of groups) {
|
||||
results = results.concat(group.items)
|
||||
}
|
||||
|
||||
return uniqWith(results, isResourceItemEqual)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any of the resource items match
|
||||
*/
|
||||
export function doViewerResourcesFit(
|
||||
requestedResources: ViewerResourceItem[],
|
||||
incomingResources: ViewerResourceItem[]
|
||||
) {
|
||||
return incomingResources.some((ir) =>
|
||||
requestedResources.some((rr) => isResourceItemEqual(ir, rr))
|
||||
)
|
||||
}
|
||||
|
||||
export function viewerResourcesToString(resources: ViewerResourceItem[]): string {
|
||||
const builder = SpeckleViewer.ViewerRoute.resourceBuilder()
|
||||
for (const resource of resources) {
|
||||
if (resource.modelId && resource.versionId) {
|
||||
builder.addModel(resource.modelId, resource.versionId)
|
||||
} else {
|
||||
builder.addObject(resource.objectId)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const basicSavedViewFragment = gql`
|
||||
fragment BasicSavedView on SavedView {
|
||||
id
|
||||
name
|
||||
description
|
||||
author {
|
||||
id
|
||||
}
|
||||
groupId
|
||||
group {
|
||||
id
|
||||
title
|
||||
isUngroupedViewsGroup
|
||||
}
|
||||
createdAt
|
||||
updatedAt
|
||||
resourceIdString
|
||||
resourceIds
|
||||
isHomeView
|
||||
visibility
|
||||
viewerState
|
||||
screenshot
|
||||
position
|
||||
projectId
|
||||
}
|
||||
`
|
||||
|
||||
const basicSavedViewGroupFragment = gql`
|
||||
fragment BasicSavedViewGroup on SavedViewGroup {
|
||||
id
|
||||
projectId
|
||||
resourceIds
|
||||
title
|
||||
isUngroupedViewsGroup
|
||||
views(input: $viewsInput) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
...BasicSavedView
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const createSavedViewMutation = gql`
|
||||
mutation CreateSavedView($input: CreateSavedViewInput!) {
|
||||
projectMutations {
|
||||
savedViewMutations {
|
||||
createView(input: $input) {
|
||||
...BasicSavedView
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${basicSavedViewFragment}
|
||||
`
|
||||
|
||||
export const createSavedGroupMutation = gql`
|
||||
mutation CreateSavedViewGroup(
|
||||
$input: CreateSavedViewGroupInput!
|
||||
$viewsInput: SavedViewGroupViewsInput! = { limit: 10 }
|
||||
) {
|
||||
projectMutations {
|
||||
savedViewMutations {
|
||||
createGroup(input: $input) {
|
||||
...BasicSavedViewGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const getProjectSavedViewGroupsQuery = gql`
|
||||
query GetProjectSavedViewGroups(
|
||||
$projectId: String!
|
||||
$input: SavedViewGroupsInput!
|
||||
$viewsInput: SavedViewGroupViewsInput! = { limit: 10 }
|
||||
) {
|
||||
project(id: $projectId) {
|
||||
savedViewGroups(input: $input) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
...BasicSavedViewGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${basicSavedViewGroupFragment}
|
||||
`
|
||||
|
||||
export const getProjectSavedViewGroupQuery = gql`
|
||||
query GetProjectSavedViewGroup(
|
||||
$projectId: String!
|
||||
$groupId: ID!
|
||||
$viewsInput: SavedViewGroupViewsInput! = { limit: 10 }
|
||||
) {
|
||||
project(id: $projectId) {
|
||||
savedViewGroup(id: $groupId) {
|
||||
...BasicSavedViewGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${basicSavedViewGroupFragment}
|
||||
`
|
||||
|
||||
export const getProjectUngroupedViewGroupQuery = gql`
|
||||
query GetProjectUngroupedViewGroup(
|
||||
$projectId: String!
|
||||
$input: GetUngroupedViewGroupInput!
|
||||
$viewsInput: SavedViewGroupViewsInput! = { limit: 10 }
|
||||
) {
|
||||
project(id: $projectId) {
|
||||
ungroupedViewGroup(input: $input) {
|
||||
...BasicSavedViewGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const getProjectSavedViewQuery = gql`
|
||||
query GetProjectSavedView($projectId: String!, $viewId: ID!) {
|
||||
project(id: $projectId) {
|
||||
savedView(id: $viewId) {
|
||||
...BasicSavedView
|
||||
}
|
||||
}
|
||||
}
|
||||
${basicSavedViewFragment}
|
||||
`
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,355 @@
|
||||
import { db } from '@/db/knex'
|
||||
import {
|
||||
getBranchesByIdsFactory,
|
||||
getBranchLatestCommitsFactory,
|
||||
getStreamBranchesByNameFactory
|
||||
} from '@/modules/core/repositories/branches'
|
||||
import {
|
||||
getAllBranchCommitsFactory,
|
||||
getSpecificBranchCommitsFactory
|
||||
} from '@/modules/core/repositories/commits'
|
||||
import { getStreamObjectsFactory } from '@/modules/core/repositories/objects'
|
||||
import { buildBasicTestProject } from '@/modules/core/tests/helpers/creation'
|
||||
import {
|
||||
doViewerResourcesFit,
|
||||
getViewerResourceGroupsFactory,
|
||||
isResourceItemEqual,
|
||||
viewerResourcesToString
|
||||
} from '@/modules/viewer/services/viewerResources'
|
||||
import { itEach } from '@/test/assertionHelper'
|
||||
import type { BasicTestUser } from '@/test/authHelper'
|
||||
import { buildBasicTestUser, createTestUser } from '@/test/authHelper'
|
||||
import {
|
||||
createTestBranch,
|
||||
type BasicTestBranch
|
||||
} from '@/test/speckle-helpers/branchHelper'
|
||||
import type { BasicTestCommit } from '@/test/speckle-helpers/commitHelper'
|
||||
import { createTestCommit } from '@/test/speckle-helpers/commitHelper'
|
||||
import type { BasicTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import { createTestStream } from '@/test/speckle-helpers/streamHelper'
|
||||
import {
|
||||
resourceBuilder,
|
||||
ViewerAllModelsResource,
|
||||
ViewerModelResource,
|
||||
ViewerObjectResource
|
||||
} from '@speckle/shared/viewer/route'
|
||||
import { expect } from 'chai'
|
||||
import { times } from 'lodash-es'
|
||||
|
||||
describe('Viewer Resources Collection Service', () => {
|
||||
describe('getViewerResourceGroupsFactory', () => {
|
||||
let me: BasicTestUser
|
||||
let myProject: BasicTestStream
|
||||
let myModels: BasicTestBranch[]
|
||||
let myVersions: {
|
||||
[modelId: string]: BasicTestCommit[]
|
||||
}
|
||||
|
||||
const buildSUT = () =>
|
||||
getViewerResourceGroupsFactory({
|
||||
getStreamObjects: getStreamObjectsFactory({ db }),
|
||||
getBranchLatestCommits: getBranchLatestCommitsFactory({ db }),
|
||||
getStreamBranchesByName: getStreamBranchesByNameFactory({ db }),
|
||||
getSpecificBranchCommits: getSpecificBranchCommitsFactory({ db }),
|
||||
getAllBranchCommits: getAllBranchCommitsFactory({ db }),
|
||||
getBranchesByIds: getBranchesByIdsFactory({ db })
|
||||
})
|
||||
|
||||
const allVersions = (): BasicTestCommit[] => {
|
||||
return Object.values(myVersions).flat()
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
me = await createTestUser(buildBasicTestUser())
|
||||
myProject = await createTestStream(buildBasicTestProject(), me)
|
||||
|
||||
// Add 3 models
|
||||
myModels = await Promise.all(
|
||||
times(3, (i) =>
|
||||
createTestBranch({
|
||||
branch: {
|
||||
name: `Model ${i + 1}`,
|
||||
description: `Description for model ${i + 1}`,
|
||||
streamId: myProject.id,
|
||||
authorId: me.id,
|
||||
id: ''
|
||||
},
|
||||
stream: myProject,
|
||||
owner: me
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
// Add 3 versions to each model
|
||||
const dateGen = (i: number) => new Date(Date.now() - i * 1000)
|
||||
|
||||
myVersions = {}
|
||||
await Promise.all(
|
||||
myModels.map(async (model) => {
|
||||
myVersions[model.id] = await Promise.all(
|
||||
times(3, (i) =>
|
||||
createTestCommit({
|
||||
streamId: myProject.id,
|
||||
authorId: me.id,
|
||||
message: `Version ${i + 1} for model ${model.name}`,
|
||||
createdAt: dateGen(i),
|
||||
id: '',
|
||||
objectId: '',
|
||||
branchId: model.id
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
itEach(
|
||||
['all', 'specific', 'latest'],
|
||||
(type) => `successfully resolves model groups (gets ${type} versions for each)`,
|
||||
async (type) => {
|
||||
const sut = buildSUT()
|
||||
const resourceIds = resourceBuilder().addResources(
|
||||
myModels.map(
|
||||
(m) =>
|
||||
new ViewerModelResource(
|
||||
m.id,
|
||||
type === 'specific' ? myVersions[m.id].at(-1)?.id : undefined
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const result = await sut({
|
||||
projectId: myProject.id,
|
||||
resourceIdString: resourceIds.toString().toString(),
|
||||
loadedVersionsOnly: type !== 'all'
|
||||
})
|
||||
|
||||
expect(result.length).to.equal(myModels.length)
|
||||
for (const group of result) {
|
||||
const model = myModels.find((m) => group.identifier.startsWith(m.id))
|
||||
expect(model).to.be.ok
|
||||
|
||||
const versions = myVersions[model!.id]
|
||||
expect(group.items).to.have.length(type === 'all' ? 3 : 1)
|
||||
|
||||
if (type === 'all') {
|
||||
for (const item of group.items) {
|
||||
const version = versions.find((v) => v.id === item.versionId)
|
||||
expect(version).to.exist
|
||||
expect(item.modelId).to.include(model!.id)
|
||||
expect(item.objectId).to.equal(version?.objectId)
|
||||
}
|
||||
} else {
|
||||
let versionToCompareTo: BasicTestCommit
|
||||
if (type === 'specific') {
|
||||
const latestVersion = versions.at(-1) // we targeted the last one
|
||||
expect(latestVersion).to.be.ok
|
||||
versionToCompareTo = latestVersion!
|
||||
} else {
|
||||
const latestVersion = versions
|
||||
.sort((a, b) => b.createdAt!.getTime() - a.createdAt!.getTime())
|
||||
.at(0)
|
||||
expect(latestVersion).to.be.ok
|
||||
versionToCompareTo = latestVersion!
|
||||
}
|
||||
|
||||
expect(group.items.length).to.equal(1) // one item per version
|
||||
const item = group.items[0]
|
||||
|
||||
expect(item.modelId).to.equal(model!.id)
|
||||
expect(item.objectId).to.equal(versionToCompareTo.objectId)
|
||||
expect(item.versionId).to.equal(versionToCompareTo.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
it('return empty array on empty resourceIdString', async () => {
|
||||
const sut = buildSUT()
|
||||
const result = await sut({
|
||||
projectId: myProject.id,
|
||||
resourceIdString: ''
|
||||
})
|
||||
|
||||
expect(result).to.have.length(0)
|
||||
})
|
||||
|
||||
it('successfully returns objectId based groups', async () => {
|
||||
const sut = buildSUT()
|
||||
const versions = allVersions()
|
||||
const resourceIds = resourceBuilder().addResources(
|
||||
versions.map((v) => new ViewerObjectResource(v.objectId))
|
||||
)
|
||||
|
||||
const result = await sut({
|
||||
projectId: myProject.id,
|
||||
resourceIdString: resourceIds.toString()
|
||||
})
|
||||
|
||||
expect(result.length).to.equal(versions.length)
|
||||
for (const group of result) {
|
||||
const version = versions.find((v) => v.objectId === group.identifier)
|
||||
expect(version).to.be.ok
|
||||
expect(group.identifier).to.equal(version!.objectId)
|
||||
expect(group.items.length).to.equal(1) // one item per version
|
||||
|
||||
const item = group.items[0]
|
||||
expect(item.objectId).to.equal(version!.objectId)
|
||||
expect(item.versionId).to.not.be.ok
|
||||
expect(item.modelId).to.not.be.ok
|
||||
}
|
||||
})
|
||||
|
||||
it('successfully resolves all group (each models latest version)', async () => {
|
||||
const sut = buildSUT()
|
||||
const resourceIds = resourceBuilder().addResources([
|
||||
new ViewerAllModelsResource()
|
||||
])
|
||||
|
||||
const result = await sut({
|
||||
projectId: myProject.id,
|
||||
resourceIdString: resourceIds.toString()
|
||||
})
|
||||
|
||||
expect(result.length).to.equal(1)
|
||||
const group = result[0]
|
||||
expect(group.identifier).to.equal('all')
|
||||
expect(group.items.length).to.equal(myModels.length)
|
||||
|
||||
for (const item of group.items) {
|
||||
const model = myModels.find((m) => m.id === item.modelId)
|
||||
expect(model).to.be.ok
|
||||
expect(item.modelId).to.equal(model!.id)
|
||||
|
||||
// Sort versions by createdAt, descending
|
||||
const latestVersion = myVersions[model!.id]
|
||||
.slice()
|
||||
.sort((a, b) => b.createdAt!.getTime() - a.createdAt!.getTime())
|
||||
.at(0)
|
||||
|
||||
expect(latestVersion).to.be.ok
|
||||
expect(item.objectId).to.equal(latestVersion!.objectId)
|
||||
expect(item.versionId).to.equal(latestVersion!.id)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('isResourceItemEqual', () => {
|
||||
it('returns true for identical ViewerResourceItems', () => {
|
||||
const itemA = { modelId: 'model1', objectId: 'obj1', versionId: 'ver1' }
|
||||
const itemB = { modelId: 'model1', objectId: 'obj1', versionId: 'ver1' }
|
||||
expect(isResourceItemEqual(itemA, itemB)).to.be.true
|
||||
})
|
||||
|
||||
it('returns false if modelId differs', () => {
|
||||
const itemA = { modelId: 'model1', objectId: 'obj1', versionId: 'ver1' }
|
||||
const itemB = { modelId: 'model2', objectId: 'obj1', versionId: 'ver1' }
|
||||
expect(isResourceItemEqual(itemA, itemB)).to.be.false
|
||||
})
|
||||
|
||||
it('returns false if objectId differs', () => {
|
||||
const itemA = { modelId: 'model1', objectId: 'obj1', versionId: 'ver1' }
|
||||
const itemB = { modelId: 'model1', objectId: 'obj2', versionId: 'ver1' }
|
||||
expect(isResourceItemEqual(itemA, itemB)).to.be.false
|
||||
})
|
||||
|
||||
it('returns false if versionId differs', () => {
|
||||
const itemA = { modelId: 'model1', objectId: 'obj1', versionId: 'ver1' }
|
||||
const itemB = { modelId: 'model1', objectId: 'obj1', versionId: 'ver2' }
|
||||
expect(isResourceItemEqual(itemA, itemB)).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('doViewerResourcesFit', () => {
|
||||
it('returns true if any incoming resource matches any requested resource', () => {
|
||||
const requested = [
|
||||
{ modelId: 'model1', objectId: 'obj1', versionId: 'ver1' },
|
||||
{ modelId: 'model2', objectId: 'obj2', versionId: 'ver2' }
|
||||
]
|
||||
const incoming = [
|
||||
{ modelId: 'model3', objectId: 'obj3', versionId: 'ver3' },
|
||||
{ modelId: 'model2', objectId: 'obj2', versionId: 'ver2' }
|
||||
]
|
||||
expect(doViewerResourcesFit(requested, incoming)).to.be.true
|
||||
})
|
||||
|
||||
it('returns false if no incoming resource matches any requested resource', () => {
|
||||
const requested = [{ modelId: 'model1', objectId: 'obj1', versionId: 'ver1' }]
|
||||
const incoming = [{ modelId: 'model2', objectId: 'obj2', versionId: 'ver2' }]
|
||||
expect(doViewerResourcesFit(requested, incoming)).to.be.false
|
||||
})
|
||||
|
||||
it('returns false if both arrays are empty', () => {
|
||||
expect(doViewerResourcesFit([], [])).to.be.false
|
||||
})
|
||||
|
||||
it('returns false if incomingResources is empty', () => {
|
||||
const requested = [{ modelId: 'model1', objectId: 'obj1', versionId: 'ver1' }]
|
||||
expect(doViewerResourcesFit(requested, [])).to.be.false
|
||||
})
|
||||
|
||||
it('returns false if requestedResources is empty', () => {
|
||||
const incoming = [{ modelId: 'model1', objectId: 'obj1', versionId: 'ver1' }]
|
||||
expect(doViewerResourcesFit([], incoming)).to.be.false
|
||||
})
|
||||
|
||||
it('returns true if multiple matches exist', () => {
|
||||
const requested = [
|
||||
{ modelId: 'model1', objectId: 'obj1', versionId: 'ver1' },
|
||||
{ modelId: 'model2', objectId: 'obj2', versionId: 'ver2' }
|
||||
]
|
||||
const incoming = [
|
||||
{ modelId: 'model1', objectId: 'obj1', versionId: 'ver1' },
|
||||
{ modelId: 'model2', objectId: 'obj2', versionId: 'ver2' }
|
||||
]
|
||||
expect(doViewerResourcesFit(requested, incoming)).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('viewerResourcesToString', () => {
|
||||
it('returns correct string for model resources with modelId and versionId', () => {
|
||||
const resources = [
|
||||
{ modelId: 'model1', objectId: 'obj1', versionId: 'ver1' },
|
||||
{ modelId: 'model2', objectId: 'obj2', versionId: 'ver2' }
|
||||
]
|
||||
// The builder should call addModel for each
|
||||
const str = viewerResourcesToString(resources)
|
||||
|
||||
// Should contain both modelId/versionId pairs, and not just objectIds
|
||||
expect(str).to.include('model1')
|
||||
expect(str).to.include('ver1')
|
||||
expect(str).to.include('model2')
|
||||
expect(str).to.include('ver2')
|
||||
})
|
||||
|
||||
it('returns correct string for object resources with only objectId', () => {
|
||||
const resources = [
|
||||
{ modelId: null, objectId: 'obj1', versionId: null },
|
||||
{ modelId: undefined, objectId: 'obj2', versionId: undefined }
|
||||
]
|
||||
const str = viewerResourcesToString(resources)
|
||||
expect(str).to.include('obj1')
|
||||
expect(str).to.include('obj2')
|
||||
|
||||
// Should not contain "model" or "ver"
|
||||
expect(str).to.not.include('model')
|
||||
expect(str).to.not.include('ver')
|
||||
})
|
||||
|
||||
it('returns correct string for mixed model and object resources', () => {
|
||||
const resources = [
|
||||
{ modelId: 'model1', objectId: 'obj1', versionId: 'ver1' },
|
||||
{ modelId: null, objectId: 'obj2', versionId: null }
|
||||
]
|
||||
const str = viewerResourcesToString(resources)
|
||||
expect(str).to.include('model1')
|
||||
expect(str).to.include('ver1')
|
||||
expect(str).to.include('obj2')
|
||||
})
|
||||
|
||||
it('returns empty string for empty resources array', () => {
|
||||
const str = viewerResourcesToString([])
|
||||
expect(str).to.equal('')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -169,7 +169,7 @@ export const createTestWorkspace = async (
|
||||
regionKey?: string
|
||||
addCreationState?: Pick<WorkspaceCreationState, 'completed' | 'state'>
|
||||
}
|
||||
) => {
|
||||
): Promise<BasicTestWorkspace> => {
|
||||
const {
|
||||
domain,
|
||||
addPlan = true,
|
||||
@@ -185,7 +185,8 @@ export const createTestWorkspace = async (
|
||||
// be created as if it was not assigned to a workspace, allowing tests to still work
|
||||
// (Surely if you explicitly invoke createTestWorkspace with FFs off, you know what you're doing)
|
||||
workspace.id = undefined as unknown as string
|
||||
return
|
||||
workspace.slug = undefined as unknown as string
|
||||
return workspace as BasicTestWorkspace
|
||||
}
|
||||
|
||||
const upsertWorkspacePlan = upsertWorkspacePlanFactory({ db })
|
||||
@@ -362,6 +363,14 @@ export const createTestWorkspace = async (
|
||||
workspaceInput: { domainBasedMembershipProtectionEnabled: true }
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...workspace,
|
||||
...newWorkspace,
|
||||
description: workspace.description || undefined,
|
||||
logo: workspace.logo || undefined,
|
||||
ownerId: owner.id
|
||||
}
|
||||
}
|
||||
|
||||
export const buildBasicTestWorkspace = (
|
||||
|
||||
@@ -10,6 +10,7 @@ export const fullPermissionCheckResultFragment = gql(`
|
||||
code
|
||||
message
|
||||
payload
|
||||
errorMessage
|
||||
}
|
||||
`)
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export const emailLogger = extendLoggerComponent(logger, 'email')
|
||||
export const taskSchedulerLogger = extendLoggerComponent(logger, 'task-scheduler')
|
||||
export const cacheLogger = extendLoggerComponent(logger, 'cache')
|
||||
export const previewLogger = extendLoggerComponent(logger, 'preview')
|
||||
export const viewerLogger = extendLoggerComponent(logger, 'viewer')
|
||||
|
||||
export type Logger = typeof logger
|
||||
export { extendLoggerComponent, Observability }
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^22.6.0"
|
||||
"node": "^22.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p ./tsconfig.build.json",
|
||||
@@ -24,25 +24,25 @@
|
||||
"dev:js": "concurrently \"npm:build:watch\" \"npm:run:watch:js\" \"yarn gqlgen:watch\" -n tsc,server,gqlgen",
|
||||
"build:clean": "rimraf ./dist && yarn build",
|
||||
"dev:clean": "yarn build:clean && yarn dev",
|
||||
"ts-mocha": "tsx ./bin/mocha",
|
||||
"ts-gqlgen": "tsx ./bin/gqlgen",
|
||||
"test": "cross-env NODE_ENV=test LOG_FILTER=test LOG_PRETTY=true TSX=true yarn ts-mocha",
|
||||
"ts-mocha": "node --experimental-strip-types --experimental-transform-types --import ./esmLoader.js ./bin/mocha",
|
||||
"ts-gqlgen": "tsx --import ./esmLoader.js ./bin/gqlgen",
|
||||
"test": "cross-env TSX=true NODE_ENV=test LOG_FILTER=test LOG_PRETTY=true yarn ts-mocha",
|
||||
"test:all-ff": "cross-env ENABLE_ALL_FFS=true yarn test",
|
||||
"test:multiregion": "cross-env RUN_TESTS_IN_MULTIREGION_MODE=true FF_WORKSPACES_MODULE_ENABLED=true FF_WORKSPACES_MULTI_REGION_ENABLED=true yarn test --grep @multiregion",
|
||||
"test:no-ff": "cross-env DISABLE_ALL_FFS=true yarn test",
|
||||
"test:coverage": "cross-env NODE_ENV=test LOG_FILTER=test LOG_PRETTY=true c8 yarn ts-mocha",
|
||||
"test:coverage": "cross-env NODE_ENV=test LOG_FILTER=test LOG_PRETTY=true c8 yarn test",
|
||||
"test:report": "MOCHA_FILE=reports/test-results.xml yarn test:coverage -- --reporter mocha-multi --reporter-options spec=-,mocha-junit-reporter=reports/test-results.xml",
|
||||
"lint": "yarn lint:tsc && yarn lint:eslint",
|
||||
"lint:ci": "yarn lint:tsc",
|
||||
"lint:tsc": "tsc --noEmit",
|
||||
"lint:eslint": "eslint .",
|
||||
"cli": "cross-env LOG_LEVEL=debug LOG_PRETTY=true NODE_ENV=development TSX=true tsx ./modules/cli/index.ts",
|
||||
"cli:test": "cross-env LOG_LEVEL=debug LOG_PRETTY=true NODE_ENV=test TSX=true tsx ./modules/cli/index.ts",
|
||||
"cli": "cross-env LOG_LEVEL=debug LOG_PRETTY=true NODE_ENV=development TSX=true tsx --import ./esmLoader.js ./modules/cli/index.ts",
|
||||
"cli:test": "cross-env LOG_LEVEL=debug LOG_PRETTY=true NODE_ENV=test TSX=true tsx --import ./esmLoader.js ./modules/cli/index.ts",
|
||||
"cli:test:multiregion": "cross-env RUN_TESTS_IN_MULTIREGION_MODE=true yarn cli:test",
|
||||
"cli:download:commit": "cross-env LOG_PRETTY=true LOG_LEVEL=debug yarn cli download commit",
|
||||
"cli:purge-test-dbs": "yarn cli:test db purge-test-dbs",
|
||||
"migrate": "yarn cli db migrate",
|
||||
"migrate:test": "cross-env NODE_ENV=test tsx ./modules/cli/index.js db migrate",
|
||||
"migrate:test": "cross-env NODE_ENV=test tsx --import ./esmLoader.js ./modules/cli/index.js db migrate",
|
||||
"gqlgen": "yarn ts-gqlgen --config codegen.ts",
|
||||
"gqlgen:watch": "yarn gqlgen --watch",
|
||||
"stripe:listen": "stripe listen --forward-to 127.0.0.1:3000/api/v1/billing/webhooks"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user