Add workspaceLastAdminCheckQuery

This commit is contained in:
andrewwallacespeckle
2025-03-27 15:16:24 +00:00
parent f8c8c5412e
commit 0c7276da46
8 changed files with 80 additions and 20 deletions
@@ -28,13 +28,13 @@ import type {
} from '~/lib/common/generated/gql/graphql'
import { useActiveUser } from '~/lib/auth/composables/activeUser'
import type { MaybeNullOrUndefined } from '@speckle/shared'
import { Roles } from '@speckle/shared'
const props = defineProps<{
workspace: MaybeNullOrUndefined<
| SettingsWorkspacesNewMembersTable_WorkspaceFragment
| SettingsWorkspacesMembersNewGuestsTable_WorkspaceFragment
>
isOnlyAdmin: boolean
}>()
const emit = defineEmits<{
@@ -46,13 +46,6 @@ const open = defineModel<boolean>('open', { required: true })
const { activeUser } = useActiveUser()
const updateUserRole = useWorkspaceUpdateRole()
const isOnlyAdmin = computed(() => {
const adminUsers = props.workspace?.team.items.filter(
(user) => user.role === Roles.Workspace.Admin
)
return adminUsers?.length === 1
})
const handleConfirm = async () => {
if (!props.workspace?.id || !activeUser.value?.id) return
@@ -75,7 +68,7 @@ const dialogButtons = computed((): LayoutDialogButton[] => [
{
text: 'Leave',
onClick: handleConfirm,
disabled: isOnlyAdmin.value
disabled: props.isOnlyAdmin
}
])
</script>
@@ -24,7 +24,7 @@
:workspace="workspace"
:new-role="newRole"
:is-active-user-target-user="isActiveUserTargetUser"
:is-only-admin="isOnlyAdmin"
:is-only-admin="hasSingleAdmin"
:is-domain-compliant="targetUser.workspaceDomainPolicyCompliant"
@success="onDialogSuccess"
/>
@@ -35,7 +35,7 @@
:user="targetUser"
:workspace="workspace"
:is-active-user-target-user="isActiveUserTargetUser"
:is-only-admin="isOnlyAdmin"
:is-only-admin="hasSingleAdmin"
:action="adminAction"
@success="onDialogSuccess"
/>
@@ -60,6 +60,7 @@
v-if="dialogToShow.leaveWorkspace"
v-model:open="showDialog"
:workspace="workspace"
:is-only-admin="hasSingleAdmin"
@success="onDialogSuccess"
/>
</div>
@@ -78,6 +79,7 @@ import type {
SettingsWorkspacesMembersNewGuestsTable_WorkspaceFragment,
SettingsWorkspacesNewMembersTable_WorkspaceFragment
} from '~/lib/common/generated/gql/graphql'
import { useWorkspaceLastAdminCheck } from '~/lib/workspaces/composables/management'
const props = defineProps<{
targetUser: UserItem
@@ -93,6 +95,10 @@ const showMenu = ref(false)
const showDialog = ref(false)
const dialogType = ref<WorkspaceUserActionTypes>()
const { hasSingleAdmin } = useWorkspaceLastAdminCheck({
workspaceSlug: props.workspace?.slug || ''
})
const isActiveUserWorkspaceAdmin = computed(
() => props.workspace?.role === Roles.Workspace.Admin
)
@@ -116,13 +122,6 @@ const {
targetUser: props.targetUser
})
const isOnlyAdmin = computed(() => {
const adminUsers = props.workspace?.team.items.filter(
(user) => user.role === Roles.Workspace.Admin
)
return adminUsers?.length === 1
})
const filteredActionsItems = computed(() => {
const mainItems: LayoutMenuItem[] = []
const footerItems: LayoutMenuItem[] = []
@@ -378,6 +378,7 @@ type Documents = {
"\n fragment WorkspaceInvitedTeam_Workspace on Workspace {\n id\n invitedTeam(filter: $invitesFilter) {\n id\n role\n email\n }\n }\n": typeof types.WorkspaceInvitedTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team(limit: 250) {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n items {\n status\n id\n }\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n": typeof types.WorkspaceTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n }\n }\n": typeof types.WorkspaceSecurity_WorkspaceFragmentDoc,
"\n fragment WorkspaceLastAdminCheck_Workspace on Workspace {\n id\n team(limit: 50, filter: { roles: [\"workspace:admin\"] }) {\n items {\n id\n }\n }\n }\n": typeof types.WorkspaceLastAdminCheck_WorkspaceFragmentDoc,
"\n mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {\n workspaceMutations {\n updateRole(input: $input) {\n team {\n items {\n id\n role\n }\n }\n }\n }\n }\n": typeof types.UpdateRoleDocument,
"\n mutation WorkspacesUpdateSeatType($input: WorkspaceUpdateSeatTypeInput!) {\n workspaceMutations {\n updateSeatType(input: $input) {\n team {\n items {\n id\n seatType\n }\n }\n }\n }\n }\n": typeof types.WorkspacesUpdateSeatTypeDocument,
"\n mutation InviteToWorkspace(\n $workspaceId: String!\n $input: [WorkspaceInviteCreateInput!]!\n ) {\n workspaceMutations {\n invites {\n batchCreate(workspaceId: $workspaceId, input: $input) {\n id\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n }\n }\n }\n": typeof types.InviteToWorkspaceDocument,
@@ -404,6 +405,7 @@ type Documents = {
"\n query DiscoverableWorkspaces {\n activeUser {\n id\n ...DiscoverableList_Discoverable\n }\n }\n": typeof types.DiscoverableWorkspacesDocument,
"\n query DiscoverableWorkspacesRequests {\n activeUser {\n id\n ...DiscoverableList_Requests\n }\n }\n": typeof types.DiscoverableWorkspacesRequestsDocument,
"\n query WorkspacePlan($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacesPlan_Workspace\n }\n }\n": typeof types.WorkspacePlanDocument,
"\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n": typeof types.WorkspaceLastAdminCheckDocument,
"\n subscription onWorkspaceUpdated(\n $workspaceId: String\n $workspaceSlug: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceUpdated(workspaceId: $workspaceId, workspaceSlug: $workspaceSlug) {\n id\n workspace {\n id\n ...WorkspaceProjectList_Workspace\n }\n }\n }\n": typeof types.OnWorkspaceUpdatedDocument,
"\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\n }\n }\n }\n": typeof types.LegacyBranchRedirectMetadataDocument,
"\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\n id\n }\n }\n }\n }\n": typeof types.LegacyViewerCommitRedirectMetadataDocument,
@@ -791,6 +793,7 @@ const documents: Documents = {
"\n fragment WorkspaceInvitedTeam_Workspace on Workspace {\n id\n invitedTeam(filter: $invitesFilter) {\n id\n role\n email\n }\n }\n": types.WorkspaceInvitedTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceTeam_Workspace on Workspace {\n id\n slug\n team(limit: 250) {\n totalCount\n items {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n }\n adminWorkspacesJoinRequests {\n totalCount\n items {\n status\n id\n }\n }\n ...WorkspaceInvitedTeam_Workspace\n }\n": types.WorkspaceTeam_WorkspaceFragmentDoc,
"\n fragment WorkspaceSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n }\n }\n": types.WorkspaceSecurity_WorkspaceFragmentDoc,
"\n fragment WorkspaceLastAdminCheck_Workspace on Workspace {\n id\n team(limit: 50, filter: { roles: [\"workspace:admin\"] }) {\n items {\n id\n }\n }\n }\n": types.WorkspaceLastAdminCheck_WorkspaceFragmentDoc,
"\n mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {\n workspaceMutations {\n updateRole(input: $input) {\n team {\n items {\n id\n role\n }\n }\n }\n }\n }\n": types.UpdateRoleDocument,
"\n mutation WorkspacesUpdateSeatType($input: WorkspaceUpdateSeatTypeInput!) {\n workspaceMutations {\n updateSeatType(input: $input) {\n team {\n items {\n id\n seatType\n }\n }\n }\n }\n }\n": types.WorkspacesUpdateSeatTypeDocument,
"\n mutation InviteToWorkspace(\n $workspaceId: String!\n $input: [WorkspaceInviteCreateInput!]!\n ) {\n workspaceMutations {\n invites {\n batchCreate(workspaceId: $workspaceId, input: $input) {\n id\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n }\n }\n }\n": types.InviteToWorkspaceDocument,
@@ -817,6 +820,7 @@ const documents: Documents = {
"\n query DiscoverableWorkspaces {\n activeUser {\n id\n ...DiscoverableList_Discoverable\n }\n }\n": types.DiscoverableWorkspacesDocument,
"\n query DiscoverableWorkspacesRequests {\n activeUser {\n id\n ...DiscoverableList_Requests\n }\n }\n": types.DiscoverableWorkspacesRequestsDocument,
"\n query WorkspacePlan($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacesPlan_Workspace\n }\n }\n": types.WorkspacePlanDocument,
"\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n": types.WorkspaceLastAdminCheckDocument,
"\n subscription onWorkspaceUpdated(\n $workspaceId: String\n $workspaceSlug: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceUpdated(workspaceId: $workspaceId, workspaceSlug: $workspaceSlug) {\n id\n workspace {\n id\n ...WorkspaceProjectList_Workspace\n }\n }\n }\n": types.OnWorkspaceUpdatedDocument,
"\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\n }\n }\n }\n": types.LegacyBranchRedirectMetadataDocument,
"\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\n id\n }\n }\n }\n }\n": types.LegacyViewerCommitRedirectMetadataDocument,
@@ -2310,6 +2314,10 @@ export function graphql(source: "\n fragment WorkspaceTeam_Workspace on Workspa
* 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 WorkspaceSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\n }\n }\n"): (typeof documents)["\n fragment WorkspaceSecurity_Workspace on Workspace {\n id\n slug\n domains {\n id\n domain\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 WorkspaceLastAdminCheck_Workspace on Workspace {\n id\n team(limit: 50, filter: { roles: [\"workspace:admin\"] }) {\n items {\n id\n }\n }\n }\n"): (typeof documents)["\n fragment WorkspaceLastAdminCheck_Workspace on Workspace {\n id\n team(limit: 50, filter: { roles: [\"workspace:admin\"] }) {\n items {\n id\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -2414,6 +2422,10 @@ export function graphql(source: "\n query DiscoverableWorkspacesRequests {\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 WorkspacePlan($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacesPlan_Workspace\n }\n }\n"): (typeof documents)["\n query WorkspacePlan($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacesPlan_Workspace\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n"): (typeof documents)["\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\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
@@ -156,7 +156,10 @@ export const useSettingsMembersActions = (props: {
)
const canMakeGuest = computed(
() => canModifyUser.value && props.targetUser.role !== Roles.Workspace.Guest
() =>
canModifyUser.value &&
props.targetUser.role !== Roles.Workspace.Guest &&
props.targetUser.role !== Roles.Workspace.Admin
)
const canMakeMember = computed(
@@ -5,7 +5,12 @@ import {
type Optional,
type WorkspaceSeatType
} from '@speckle/shared'
import { useApolloClient, useMutation, useSubscription } from '@vue/apollo-composable'
import {
useApolloClient,
useMutation,
useSubscription,
useQuery
} from '@vue/apollo-composable'
import { graphql } from '~/lib/common/generated/gql'
import type {
OnWorkspaceUpdatedSubscription,
@@ -42,6 +47,7 @@ import { onWorkspaceUpdatedSubscription } from '~/lib/workspaces/graphql/subscri
import { useLock } from '~/lib/common/composables/singleton'
import type { Get } from 'type-fest'
import type { ApolloCache } from '@apollo/client/core'
import { workspaceLastAdminCheckQuery } from '../graphql/queries'
export const useInviteUserToWorkspace = () => {
const { activeUser } = useActiveUser()
@@ -620,3 +626,20 @@ export const useOnWorkspaceUpdated = (params: {
})
}
}
export const useWorkspaceLastAdminCheck = (params: { workspaceSlug: string }) => {
const { workspaceSlug } = params
const { result } = useQuery(workspaceLastAdminCheckQuery, {
slug: workspaceSlug
})
const hasSingleAdmin = computed(() => {
const admins = result.value?.workspaceBySlug?.team.items || []
return admins.length === 1
})
return {
hasSingleAdmin
}
}
@@ -71,3 +71,14 @@ export const workspaceSecurityFragment = graphql(`
}
}
`)
export const workspaceLastAdminCheckFragment = graphql(`
fragment WorkspaceLastAdminCheck_Workspace on Workspace {
id
team(limit: 50, filter: { roles: ["workspace:admin"] }) {
items {
id
}
}
}
`)
@@ -149,3 +149,11 @@ export const workspacePlanQuery = graphql(`
}
}
`)
export const workspaceLastAdminCheckQuery = graphql(`
query WorkspaceLastAdminCheck($slug: String!) {
workspaceBySlug(slug: $slug) {
...WorkspaceLastAdminCheck_Workspace
}
}
`)