feat: move view into group dialog (#5211)

* looking into dialog premature closing

* fixed group select

* rover update

* tests
This commit is contained in:
Kristaps Fabians Geikins
2025-08-12 13:32:38 +03:00
committed by GitHub
parent 011320880e
commit ea5dadbdb2
22 changed files with 398 additions and 68 deletions
@@ -49,7 +49,7 @@
name="editView"
class="shrink-0 opacity-0 group-hover:opacity-100"
:disabled="!canUpdate?.authorized || isLoading"
@click="showEditDialog = !showEditDialog"
@click="onEdit"
/>
</div>
</div>
@@ -63,7 +63,6 @@
</div>
</div>
</div>
<ViewerSavedViewsPanelViewEditDialog v-model:open="showEditDialog" :view="view" />
</div>
</template>
<script setup lang="ts">
@@ -88,7 +87,8 @@ const MenuItems = StringEnum([
'LoadOriginalVersions',
'CopyLink',
'ChangeVisibility',
'ReplaceView'
'ReplaceView',
'MoveToGroup'
])
type MenuItems = StringEnumValues<typeof MenuItems>
@@ -124,8 +124,8 @@ const deleteView = useDeleteSavedView()
const updateView = useUpdateSavedView()
const isLoading = useMutationLoading()
const { copyLink, applyView } = useViewerSavedViewsUtils()
const eventBus = useEventBus()
const showEditDialog = ref(false)
const showMenu = ref(false)
const menuId = useId()
@@ -141,7 +141,15 @@ const menuItems = computed((): LayoutMenuItem<MenuItems>[][] => [
},
{
id: MenuItems.ReplaceView,
title: 'Update view'
title: 'Update view',
disabled: !canUpdate.value?.authorized || isLoading.value,
disabledTooltip: canUpdate.value.errorMessage
},
{
id: MenuItems.MoveToGroup,
title: 'Move to group',
disabled: !canUpdate.value?.authorized || isLoading.value,
disabledTooltip: canUpdate.value.errorMessage
},
{
id: MenuItems.CopyLink,
@@ -152,7 +160,9 @@ const menuItems = computed((): LayoutMenuItem<MenuItems>[][] => [
{
id: MenuItems.ChangeVisibility,
title: 'Only visible to me',
active: !!isOnlyVisibleToMe.value
active: !!isOnlyVisibleToMe.value,
disabled: !canUpdate.value?.authorized || isLoading.value,
disabledTooltip: canUpdate.value.errorMessage
}
],
[
@@ -205,6 +215,12 @@ const onActionChosen = async (item: LayoutMenuItem<MenuItems>) => {
}
})
break
case MenuItems.MoveToGroup:
eventBus.emit(ViewerEventBusKeys.MarkSavedViewForEdit, {
type: 'move',
view: props.view
})
break
default:
throwUncoveredError(item.id)
}
@@ -215,4 +231,11 @@ const apply = async () => {
id: props.view.id
})
}
const onEdit = () => {
eventBus.emit(ViewerEventBusKeys.MarkSavedViewForEdit, {
type: 'edit',
view: props.view
})
}
</script>
@@ -18,6 +18,15 @@
hide-when-complete
@infinite="onInfiniteLoad"
/>
<ViewerSavedViewsPanelViewEditDialog
v-model:open="showEditDialog"
:view="viewBeingEdited"
/>
<ViewerSavedViewsPanelViewMoveDialog
v-model:open="showMoveDialog"
:view="viewBeingMoved"
@success="onMoveSuccess"
/>
</div>
</div>
</template>
@@ -25,6 +34,10 @@
import { omit } from 'lodash-es'
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
import { graphql } from '~/lib/common/generated/gql'
import type {
ViewerSavedViewsPanelViewEditDialog_SavedViewFragment,
ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment
} from '~/lib/common/generated/gql/graphql'
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
import { ViewsType } from '~/lib/viewer/helpers/savedViews'
@@ -68,8 +81,11 @@ const {
request: { resourceIdString }
}
} = useInjectedViewerState()
const eventBus = useEventBus()
const search = ref('')
const viewBeingEdited = ref<ViewerSavedViewsPanelViewEditDialog_SavedViewFragment>()
const viewBeingMoved = ref<ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment>()
const {
identifier,
@@ -111,6 +127,24 @@ const groups = computed(() => {
return result.value?.project.savedViewGroups.items || []
})
const showEditDialog = computed({
get: () => !!viewBeingEdited.value,
set: (value) => {
if (!value) {
viewBeingEdited.value = undefined
}
}
})
const showMoveDialog = computed({
get: () => !!viewBeingMoved.value,
set: (value) => {
if (!value) {
viewBeingMoved.value = undefined
}
}
})
watch(
groups,
(newGroups) => {
@@ -120,4 +154,16 @@ watch(
},
{ immediate: true }
)
eventBus.on(ViewerEventBusKeys.MarkSavedViewForEdit, ({ type, view }) => {
if (type === 'edit') {
viewBeingEdited.value = view
} else if (type === 'move') {
viewBeingMoved.value = view
}
})
const onMoveSuccess = (groupId: string) => {
selectedGroupId.value = groupId
}
</script>
@@ -77,7 +77,7 @@ type FormType = {
}
const props = defineProps<{
view: ViewerSavedViewsPanelViewEditDialog_SavedViewFragment
view: ViewerSavedViewsPanelViewEditDialog_SavedViewFragment | undefined
}>()
const open = defineModel<boolean>('open', {
@@ -126,6 +126,8 @@ const radioOptions = computed((): FormRadioGroupItem<SavedViewVisibility>[] => [
])
const onSubmit = handleSubmit(async (values) => {
if (!props.view) return
const name =
values.name.trim() && values.name.trim() !== props.view.name
? values.name.trim()
@@ -156,6 +158,8 @@ const onSubmit = handleSubmit(async (values) => {
})
watch(open, (newVal, oldVal) => {
if (!props.view) return
if (newVal && !oldVal) {
// Reset form state when dialog opens
setValues({
@@ -0,0 +1,115 @@
<template>
<LayoutDialog
v-model:open="open"
title="Move to group"
max-width="sm"
:buttons="buttons"
:on-submit="onSubmit"
>
<div class="flex flex-col gap-4">
<FormSelectSavedViewGroup
name="group"
label="Select group"
show-label
:project-id="projectId"
:resource-id-string="resourceIdString"
:rules="[isRequired]"
/>
</div>
</LayoutDialog>
</template>
<script setup lang="ts">
import type { LayoutDialogButton } from '@speckle/ui-components'
import { useForm } from 'vee-validate'
import { graphql } from '~/lib/common/generated/gql'
import type {
FormSelectSavedViewGroup_SavedViewGroupFragment,
ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment
} from '~/lib/common/generated/gql/graphql'
import { isRequired } from '~/lib/common/helpers/validation'
import { useUpdateSavedView } from '~/lib/viewer/composables/savedViews/management'
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
graphql(`
fragment ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {
id
group {
...FormSelectSavedViewGroup_SavedViewGroup
}
...UseUpdateSavedView_SavedView
}
`)
type FormType = {
group: FormSelectSavedViewGroup_SavedViewGroupFragment
}
const emit = defineEmits<{
success: [groupId: string]
}>()
const props = defineProps<{
view: ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment | undefined
}>()
const open = defineModel<boolean>('open', {
required: true
})
const { handleSubmit, setValues } = useForm<FormType>()
const {
projectId,
resources: {
request: { resourceIdString }
}
} = useInjectedViewerState()
const updateView = useUpdateSavedView()
const buttons = computed((): LayoutDialogButton[] => [
{
id: 'cancel',
text: 'Cancel',
props: {
color: 'outline'
},
onClick: () => {
open.value = false
}
},
{
id: 'save',
text: 'Save',
submit: true
}
])
const onSubmit = handleSubmit(async (values) => {
if (!props.view) return
const groupId = values.group.id !== props.view.group.id ? values.group.id : null
if (!groupId) return
const res = await updateView({
view: props.view,
input: {
id: props.view.id,
projectId: props.view.projectId,
groupId
}
})
if (res?.id) {
emit('success', groupId)
open.value = false
}
})
watch(open, (newVal, oldVal) => {
if (!props.view) return
if (newVal && !oldVal) {
// Reset form state when dialog opens
setValues({
group: props.view.group
})
}
})
</script>
+2 -1
View File
@@ -1,6 +1,7 @@
import { useActiveUser } from '~/lib/auth/composables/activeUser'
import { usePageQueryStandardFetchPolicy } from '~/lib/common/composables/graphql'
import { useGlobalToast } from '~/lib/common/composables/toast'
import { useEventBus } from '~/lib/core/composables/eventBus'
export const useIsAccModuleEnabled = () => {
const {
@@ -83,4 +84,4 @@ export const useIsRhinoFileImporterEnabled = () => {
return ref(FF_RHINO_FILE_IMPORTER_ENABLED)
}
export { useGlobalToast, useActiveUser, usePageQueryStandardFetchPolicy }
export { useGlobalToast, useActiveUser, usePageQueryStandardFetchPolicy, useEventBus }
@@ -171,6 +171,7 @@ type 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": 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 ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": typeof types.ViewerSavedViewsPanelViewEditDialog_SavedViewFragmentDoc,
"\n fragment ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": typeof types.ViewerSavedViewsPanelViewMoveDialog_SavedViewFragmentDoc,
"\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n isUngroupedViewsGroup\n ...ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup\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 fragment ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup on SavedViewGroup {\n id\n title\n }\n": typeof types.ViewerSavedViewsPanelViewsGroupInner_SavedViewGroupFragmentDoc,
@@ -665,6 +666,7 @@ const documents: 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": 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 ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": types.ViewerSavedViewsPanelViewEditDialog_SavedViewFragmentDoc,
"\n fragment ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n": types.ViewerSavedViewsPanelViewMoveDialog_SavedViewFragmentDoc,
"\n fragment ViewerSavedViewsPanelViewsGroup_SavedViewGroup on SavedViewGroup {\n id\n isUngroupedViewsGroup\n ...ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup\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 fragment ViewerSavedViewsPanelViewsGroupInner_SavedViewGroup on SavedViewGroup {\n id\n title\n }\n": types.ViewerSavedViewsPanelViewsGroupInner_SavedViewGroupFragmentDoc,
@@ -1644,6 +1646,10 @@ export function graphql(source: "\n query ViewerSavedViewsPanelViews_Groups(\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 ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {\n id\n name\n description\n visibility\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\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 ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\n }\n"): (typeof documents)["\n fragment ViewerSavedViewsPanelViewMoveDialog_SavedView on SavedView {\n id\n group {\n ...FormSelectSavedViewGroup_SavedViewGroup\n }\n ...UseUpdateSavedView_SavedView\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
@@ -41,7 +41,7 @@ export const useViewerSavedViewsUtils = () => {
const applyView = (settings: SavedViewUrlSettings) => {
// 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, settings)
eventBus.emit(ViewerEventBusKeys.ApplySavedView, settings)
}
return {
@@ -13,6 +13,7 @@ import {
onGroupViewRemovalCacheUpdates,
onNewGroupViewCacheUpdates
} from '~/lib/viewer/helpers/savedViews/cache'
import { isUngroupedGroup } from '@speckle/shared/saved-views'
const createSavedViewMutation = graphql(`
mutation CreateSavedView($input: CreateSavedViewInput!) {
@@ -320,7 +321,7 @@ export const useCreateSavedViewGroup = () => {
// default comes first, then new group
const defaultIdx = newItems.findIndex((i) =>
fromRef(i).id.startsWith('default-')
isUngroupedGroup(fromRef(i).id)
)
newItems.splice(defaultIdx + 1, 0, ref('SavedViewGroup', group.id))
@@ -1003,7 +1003,7 @@ const useViewerSavedViewSetup = () => {
}
// Allow force update
on(ViewerEventBusKeys.UpdateSavedView, async (settings) => {
on(ViewerEventBusKeys.ApplySavedView, async (settings) => {
await update({ settings })
})
@@ -1,10 +1,26 @@
import type { ViewerSavedViewEventBusPayloads } from '~/lib/viewer/helpers/savedViews'
import type {
ViewerSavedViewsPanelViewEditDialog_SavedViewFragment,
ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment
} from '~/lib/common/generated/gql/graphql'
import type { SavedViewUrlSettings } from '~/lib/viewer/helpers/savedViews'
export enum ViewerEventBusKeys {
UpdateSavedView = 'aaa'
ApplySavedView = 'UpdateSavedView',
MarkSavedViewForEdit = 'MarkSavedViewForEdit'
}
export type ViewerSavedViewEventBusPayloads = {
[ViewerEventBusKeys.ApplySavedView]: SavedViewUrlSettings
[ViewerEventBusKeys.MarkSavedViewForEdit]:
| {
type: 'edit'
view: ViewerSavedViewsPanelViewEditDialog_SavedViewFragment
}
| { type: 'move'; view: ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment }
}
// Add mappings between event keys and expected payloads here
export type ViewerEventBusKeyPayloadMap = {
[ViewerEventBusKeys.UpdateSavedView]: ViewerSavedViewEventBusPayloads[ViewerEventBusKeys.UpdateSavedView]
[ViewerEventBusKeys.ApplySavedView]: ViewerSavedViewEventBusPayloads[ViewerEventBusKeys.ApplySavedView]
[ViewerEventBusKeys.MarkSavedViewForEdit]: ViewerSavedViewEventBusPayloads[ViewerEventBusKeys.MarkSavedViewForEdit]
} & { [k in ViewerEventBusKeys]: unknown } & Record<string, unknown>
@@ -1,6 +1,5 @@
import type { StringEnumValues } from '@speckle/shared'
import { isObjectLike, isString } from 'lodash-es'
import type { ViewerEventBusKeys } from '~/lib/viewer/helpers/eventBus'
export const ViewsType = {
All: 'all-views',
@@ -23,10 +22,6 @@ export type SavedViewUrlSettings = {
loadOriginal?: boolean
}
export type ViewerSavedViewEventBusPayloads = {
[ViewerEventBusKeys.UpdateSavedView]: SavedViewUrlSettings
}
export const parseSavedViewUrlSettings = (
settingsString: string | null
): SavedViewUrlSettings | null => {
@@ -1,4 +1,5 @@
import type { ApolloCache } from '@apollo/client/cache'
import { isUngroupedGroup } from '@speckle/shared/saved-views'
/**
* Cache mutations for when a group gets a new view:
@@ -75,7 +76,7 @@ export const onGroupViewRemovalCacheUpdates = (
const { viewId: id, groupId, projectId } = params
// Check if default/ungrouped group
const isDefaultGroup = groupId.startsWith('default-')
const isDefaultGroup = isUngroupedGroup(groupId)
// If default group and its now empty - remove it as it doesn't exist otherwise
let shouldEvict
+3 -1
View File
@@ -13,6 +13,7 @@ import {
ROOT_SUBSCRIPTION
} from '~/lib/common/helpers/graphql'
import { checkIfIsInPlaceNavigation } from '~/lib/common/helpers/navigation'
import { ViewerEventBusKeys } from '~/lib/viewer/helpers/eventBus'
/**
* Debugging helper to ensure variables are available in debugging scope
@@ -38,5 +39,6 @@ export {
checkIfIsInPlaceNavigation,
ROOT_QUERY,
ROOT_MUTATION,
ROOT_SUBSCRIPTION
ROOT_SUBSCRIPTION,
ViewerEventBusKeys
}
@@ -1,5 +1,6 @@
import { base64Decode, base64Encode } from '@/modules/shared/helpers/cryptoHelper'
import type { Nullable } from '@speckle/shared'
import { isUngroupedGroup } from '@speckle/shared/saved-views'
import type { ViewerResourcesTarget } from '@speckle/shared/viewer/route'
import {
isModelResource,
@@ -29,7 +30,7 @@ export const buildDefaultGroupId = (params: {
export const decodeDefaultGroupId = (id: string): Nullable<DefaultGroupMetadata> => {
try {
if (!id.startsWith('default-')) return null
if (!isUngroupedGroup(id)) return null
const json = base64Decode(id.replace('default-', ''))
const obj = JSON.parse(json)
if (
@@ -31,6 +31,7 @@ import {
decodeDefaultGroupId,
formatResourceIdsForGroup
} from '@/modules/viewer/helpers/savedViews'
import { isUngroupedGroup } from '@speckle/shared/saved-views'
import { resourceBuilder } from '@speckle/shared/viewer/route'
import cryptoRandomString from 'crypto-random-string'
import dayjs from 'dayjs'
@@ -249,7 +250,7 @@ export const getProjectSavedViewGroupsPageItemsFactory =
// Adjust cursor, in case it points to non-existant default group
let cursor = decode(params.cursor)
if (cursor?.id.startsWith('default-')) {
if (cursor?.id && isUngroupedGroup(cursor.id)) {
// Default appears first, so just unset the cursor to get the real first item
cursor = null
}
@@ -33,9 +33,10 @@ 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'
import { omit } from 'lodash-es'
import { isUndefined, omit } from 'lodash-es'
import type { DependenciesOf } from '@/modules/shared/helpers/factory'
import { removeNullOrUndefinedKeys } from '@speckle/shared'
import { isUngroupedGroup } from '@speckle/shared/saved-views'
/**
* Validates an incoming resourceIdString against the resources in the project and returns the validated list (as a builder)
@@ -388,7 +389,14 @@ export const updateSavedViewFactory =
}
// Check if there's any actual changes
const changes = removeNullOrUndefinedKeys(omit(input, ['id', 'projectId']))
const changes = {
...removeNullOrUndefinedKeys(omit(input, ['id', 'projectId'])),
...(!isUndefined(input.groupId)
? {
groupId: input.groupId // we want to allow null, which means - no group
}
: {})
}
if (Object.keys(changes).length === 0) {
throw new SavedViewUpdateValidationError('No changes submitted with the input.', {
info: {
@@ -429,20 +437,26 @@ export const updateSavedViewFactory =
// Validate groupId - group is a valid and accessible group in the project
if (changes.groupId) {
const group = await deps.getSavedViewGroup({
id: changes.groupId,
projectId
})
if (!group) {
throw new SavedViewUpdateValidationError(
'Provided groupId does not exist in the project.',
{
info: {
input,
userId
// Check if default group (actually means - null group)
const isDefaultGroup = changes.groupId && isUngroupedGroup(changes.groupId)
if (isDefaultGroup) {
changes.groupId = null
} else {
const group = await deps.getSavedViewGroup({
id: changes.groupId,
projectId
})
if (!group) {
throw new SavedViewUpdateValidationError(
'Provided groupId does not exist in the project.',
{
info: {
input,
userId
}
}
}
)
)
}
}
}
@@ -186,6 +186,11 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
options?: ExecuteOperationOptions
) => apollo.execute(UpdateSavedViewDocument, input, options)
const getProjectViewGroups = (
input: GetProjectSavedViewGroupsQueryVariables,
options?: ExecuteOperationOptions
) => apollo.execute(GetProjectSavedViewGroupsDocument, input, options)
const model1ResourceIds = () => ViewerRoute.resourceBuilder().addModel(myModel1.id)
const model2ResourceIds = () => ViewerRoute.resourceBuilder().addModel(myModel2.id)
@@ -866,6 +871,73 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
expect(updatedView!.visibility).to.equal(input.visibility)
})
it('successfully sets and unsets a group', async () => {
const newGroupId = optionalGroup.id
const res = await updateView({
input: {
id: testView.id,
projectId: updatablesProject.id,
groupId: newGroupId
}
})
expect(res).to.not.haveGraphQLErrors()
const updatedView = res.data?.projectMutations.savedViewMutations.updateView
expect(updatedView).to.be.ok
expect(updatedView!.id).to.equal(testView.id)
expect(updatedView!.groupId).to.equal(newGroupId)
// Unset group
const res2 = await updateView({
input: {
id: testView.id,
projectId: updatablesProject.id,
groupId: null
}
})
expect(res2).to.not.haveGraphQLErrors()
const updatedView2 = res2.data?.projectMutations.savedViewMutations.updateView
expect(updatedView2).to.be.ok
expect(updatedView2!.id).to.equal(testView.id)
expect(updatedView2!.groupId).to.be.null
})
it('allow setting default group as group, which actually sets it to null', async () => {
// Get default group id
const groupsRes = await getProjectViewGroups(
{
projectId: updatablesProject.id,
input: {
limit: 1,
resourceIdString: models[0].id
}
},
{ assertNoErrors: true }
)
const defaultGroup = groupsRes.data?.project.savedViewGroups.items[0]
expect(defaultGroup).to.be.ok
// Update view to have that be the group
const update = await updateView(
{
input: {
id: testView.id,
projectId: updatablesProject.id,
groupId: defaultGroup!.id
}
},
{ assertNoErrors: true }
)
const updatedView = update.data?.projectMutations.savedViewMutations.updateView
expect(updatedView?.id).to.be.ok
expect(updatedView?.groupId).to.be.null
expect(updatedView?.group.id).to.equal(defaultGroup!.id)
})
it('fails if user has no access to update the view', async () => {
const newName = 'Updated View Name'
@@ -1130,11 +1202,6 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
modelIds.map((id) => new ViewerRoute.ViewerModelResource(id))
)
const getProjectViewGroups = (
input: GetProjectSavedViewGroupsQueryVariables,
options?: ExecuteOperationOptions
) => apollo.execute(GetProjectSavedViewGroupsDocument, input, options)
before(async () => {
otherReader = await createTestUser(buildBasicTestUser({ name: 'other-reader' }))
await assignToWorkspace(
@@ -1317,22 +1384,42 @@ const fakeViewerState = (overrides?: PartialDeep<ViewerState.SerializedViewerSta
expect(defaultGroupsFound).to.equal(1) // only 1 default group
})
// it('should return different groups in same project, if resource string differs', async () => {
// const res = await getProjectViewGroups({
// projectId: readTestProject.id,
// input: {
// limit: GROUP_COUNT, // all in 1 page
// resourceIdString: 'ayy'
// }
// })
it('should support default groups cursor', async () => {
const res1 = await getProjectViewGroups(
{
projectId: readTestProject.id,
input: {
limit: 1, // only get default group
resourceIdString: getAllReadModelResourceIds().toString()
}
},
{ assertNoErrors: true }
)
// expect(res).to.not.haveGraphQLErrors()
// const data = res.data?.project.savedViewGroups
// expect(data).to.be.ok
// expect(data!.totalCount).to.equal(1) // only default group returned
// expect(data!.items.length).to.equal(1)
// expect(data!.cursor).to.not.be.null
// })
const group = res1.data?.project.savedViewGroups.items[0]
const cursor = res1.data?.project.savedViewGroups.cursor
expect(group).to.be.ok
expect(group!.isUngroupedViewsGroup).to.be.true
expect(cursor).to.be.ok
const res2 = await getProjectViewGroups(
{
projectId: readTestProject.id,
input: {
limit: 1, // get first real item
resourceIdString: getAllReadModelResourceIds().toString(),
cursor
}
},
{ assertNoErrors: true }
)
const group2 = res2.data?.project.savedViewGroups.items[0]
expect(group2).to.be.ok
expect(group2!.isUngroupedViewsGroup).to.be.false
})
it('should respect search filter and filter out groups w/ views that dont have the search string in their name', async () => {
const res = await getProjectViewGroups({
+1 -1
View File
@@ -145,7 +145,7 @@
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@apollo/rover": "^0.23.0",
"@apollo/rover": "^0.35.0",
"@faker-js/faker": "^8.4.1",
"@graphql-codegen/cli": "^5.0.7",
"@graphql-codegen/typed-document-node": "^5.1.2",
@@ -0,0 +1,12 @@
import { describe, expect, it } from 'vitest'
import { isUngroupedGroup } from './values.js'
describe('isUngroupedGroup', () => {
it('should return true for ungrouped groups', () => {
expect(isUngroupedGroup('default-group')).toBe(true)
})
it('should return false for grouped groups', () => {
expect(isUngroupedGroup('custom-group')).toBe(false)
})
})
@@ -2,3 +2,5 @@
* Title used for the default 'Ungrouped Scenes' group in the saved views panel.
*/
export const ungroupedScenesGroupTitle = 'Ungrouped'
export const isUngroupedGroup = (groupId: string) => groupId.startsWith('default-')
+7 -7
View File
@@ -135,18 +135,18 @@ __metadata:
languageName: node
linkType: hard
"@apollo/rover@npm:^0.23.0":
version: 0.23.0
resolution: "@apollo/rover@npm:0.23.0"
"@apollo/rover@npm:^0.35.0":
version: 0.35.0
resolution: "@apollo/rover@npm:0.35.0"
dependencies:
axios: "npm:^1.6.5"
axios-proxy-builder: "npm:^0.1.1"
console.table: "npm:^0.10.0"
detect-libc: "npm:^2.0.0"
tar: "npm:^6.2.0"
tar: "npm:^7.0.0"
bin:
rover: run.js
checksum: 10/945f723c0a076503daf6622225653db761c3a0e2e80ee11f58b5f8cdc1cfe92eeaa732a5488881967b9123c49a92589800d3586ef61355767de16012be0af34a
checksum: 10/d3f87f65e2fe9f57360315a5f8cb57c00fd9eb7a3c6154d9aea2644cc023c882c60caa92a189db9008695223927e76d6941d760ca740619dae3bf811ee446616
languageName: node
linkType: hard
@@ -16278,7 +16278,7 @@ __metadata:
resolution: "@speckle/server@workspace:packages/server"
dependencies:
"@apollo/client": "npm:^3.7.0"
"@apollo/rover": "npm:^0.23.0"
"@apollo/rover": "npm:^0.35.0"
"@apollo/server": "npm:^4.11.0"
"@aws-sdk/client-s3": "npm:^3.276.0"
"@aws-sdk/lib-storage": "npm:^3.100.0"
@@ -47036,7 +47036,7 @@ __metadata:
languageName: node
linkType: hard
"tar@npm:^7.4.0":
"tar@npm:^7.0.0, tar@npm:^7.4.0":
version: 7.4.3
resolution: "tar@npm:7.4.3"
dependencies: