fix(workspaces): add email to existing account workflow bugfix (#2703)

* fix(fe2): properly handling invitedTeam auth error

* fix(fe2): disable invite dialog if not workspace admin

* fix(useremails): fix for default primary:true when creating new emails

* test fix
This commit is contained in:
Kristaps Fabians Geikins
2024-08-20 15:45:34 +03:00
committed by GitHub
parent 4d82e1f575
commit e4cc0cbc83
12 changed files with 66 additions and 31 deletions
@@ -14,7 +14,10 @@
v-on="on"
/>
</div>
<FormButton @click="() => (isInviteDialogOpen = !isInviteDialogOpen)">
<FormButton
:disabled="!isWorkspaceAdmin"
@click="() => (isInviteDialogOpen = !isInviteDialogOpen)"
>
Invite
</FormButton>
<WorkspaceInviteDialog
@@ -29,15 +32,17 @@ import { MagnifyingGlassIcon } from '@heroicons/vue/24/outline'
import { useDebouncedTextInput } from '@speckle/ui-components'
import type { SettingsWorkspacesMembersTableHeader_WorkspaceFragment } from '~/lib/common/generated/gql/graphql'
import { graphql } from '~/lib/common/generated/gql'
import { Roles } from '@speckle/shared'
graphql(`
fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {
id
role
...WorkspaceInviteDialog_Workspace
}
`)
defineProps<{
const props = defineProps<{
searchPlaceholder: string
workspaceId: string
workspace?: SettingsWorkspacesMembersTableHeader_WorkspaceFragment
@@ -46,4 +51,6 @@ defineProps<{
const search = defineModel<string>('search')
const { on, bind } = useDebouncedTextInput({ model: search })
const isInviteDialogOpen = ref(false)
const isWorkspaceAdmin = computed(() => props.workspace?.role === Roles.Workspace.Admin)
</script>
@@ -64,6 +64,7 @@ import type {
WorkspaceProjectList_ProjectCollectionFragment,
WorkspaceProjectsQueryQueryVariables
} from '~~/lib/common/generated/gql/graphql'
import { skipLoggingErrorsIfOneFieldError } from '~/lib/common/helpers/graphql'
import { workspaceRoute } from '~/lib/common/helpers/route'
graphql(`
@@ -94,12 +95,23 @@ const {
debouncedBy: 800
})
const { result: initialQueryResult } = useQuery(workspacePageQuery, {
workspaceId: props.workspaceId,
filter: {
search: (search.value || '').trim() || null
}
})
const { result: initialQueryResult } = useQuery(
workspacePageQuery,
{
workspaceId: props.workspaceId,
filter: {
search: (search.value || '').trim() || null
}
},
() => ({
// Custom error policy so that a failing invitedTeam resolver (due to access rights)
// doesn't kill the entire query
errorPolicy: 'all',
context: {
skipLoggingErrors: skipLoggingErrorsIfOneFieldError('invitedTeam')
}
})
)
const { query, identifier, onInfiniteLoad } = usePaginatedQuery<
{ workspace: { projects: WorkspaceProjectList_ProjectCollectionFragment } },
@@ -26,7 +26,11 @@
:users="team.map((teamMember) => teamMember.user)"
class="max-w-[104px]"
/>
<FormButton color="outline" @click="showInviteDialog = !showInviteDialog">
<FormButton
color="outline"
:disabled="!isWorkspaceAdmin"
@click="showInviteDialog = !showInviteDialog"
>
Invite
</FormButton>
</div>
@@ -39,12 +43,14 @@
</template>
<script setup lang="ts">
import { Roles } from '@speckle/shared'
import { graphql } from '~~/lib/common/generated/gql'
import type { WorkspaceHeader_WorkspaceFragment } from '~~/lib/common/generated/gql/graphql'
graphql(`
fragment WorkspaceHeader_Workspace on Workspace {
id
role
name
logo
description
@@ -70,4 +76,7 @@ const props = defineProps<{
const showInviteDialog = ref(false)
const team = computed(() => props.workspaceInfo.team || [])
const isWorkspaceAdmin = computed(
() => props.workspaceInfo.role === Roles.Workspace.Admin
)
</script>
@@ -111,14 +111,14 @@ const documents = {
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam(filter: $invitesFilter) {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n verified\n }\n }\n": types.SettingsWorkspacesMembersMembersTable_WorkspaceCollaboratorFragmentDoc,
"\n fragment SettingsWorkspacesMembersMembersTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team {\n id\n ...SettingsWorkspacesMembersMembersTable_WorkspaceCollaborator\n }\n }\n": types.SettingsWorkspacesMembersMembersTable_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n ...WorkspaceInviteDialog_Workspace\n }\n": types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...WorkspaceInviteDialog_Workspace\n }\n": types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
"\n fragment ModelPageProject on Project {\n id\n createdAt\n name\n visibility\n }\n": types.ModelPageProjectFragmentDoc,
"\n fragment ThreadCommentAttachment on Comment {\n text {\n attachments {\n id\n fileName\n fileType\n fileSize\n }\n }\n }\n": types.ThreadCommentAttachmentFragmentDoc,
"\n fragment ViewerCommentsListItem on Comment {\n id\n rawText\n archived\n author {\n ...LimitedUserAvatar\n }\n createdAt\n viewedAt\n replies {\n totalCount\n cursor\n items {\n ...ViewerCommentsReplyItem\n }\n }\n replyAuthors(limit: 4) {\n totalCount\n items {\n ...FormUsersSelectItem\n }\n }\n resources {\n resourceId\n resourceType\n }\n }\n": types.ViewerCommentsListItemFragmentDoc,
"\n fragment ViewerModelVersionCardItem on Version {\n id\n message\n referencedObject\n sourceApplication\n createdAt\n previewUrl\n authorUser {\n ...LimitedUserAvatar\n }\n }\n": types.ViewerModelVersionCardItemFragmentDoc,
"\n fragment WorkspaceInviteDialog_Workspace on Workspace {\n id\n team {\n id\n user {\n id\n }\n }\n invitedTeam(filter: $invitesFilter) {\n title\n user {\n id\n }\n }\n }\n": types.WorkspaceInviteDialog_WorkspaceFragmentDoc,
"\n fragment WorkspaceProjectList_ProjectCollection on ProjectCollection {\n totalCount\n items {\n ...ProjectDashboardItem\n }\n cursor\n }\n": types.WorkspaceProjectList_ProjectCollectionFragmentDoc,
"\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc,
"\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n": types.WorkspaceHeader_WorkspaceFragmentDoc,
"\n fragment WorkspaceInviteBanner_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBanner_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment WorkspaceInviteBanners_User on User {\n workspaceInvites {\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspaceInviteBanners_UserFragmentDoc,
"\n fragment WorkspaceInviteBlock_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n workspaceId\n workspaceName\n token\n user {\n id\n name\n ...LimitedUserAvatar\n }\n title\n email\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.WorkspaceInviteBlock_PendingWorkspaceCollaboratorFragmentDoc,
@@ -709,7 +709,7 @@ export function graphql(source: "\n fragment SettingsWorkspacesMembersMembersTa
/**
* 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 SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n ...WorkspaceInviteDialog_Workspace\n }\n"];
export function graphql(source: "\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...WorkspaceInviteDialog_Workspace\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -737,7 +737,7 @@ export function graphql(source: "\n fragment WorkspaceProjectList_ProjectCollec
/**
* 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 WorkspaceHeader_Workspace on Workspace {\n id\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"];
export function graphql(source: "\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\n }\n"): (typeof documents)["\n fragment WorkspaceHeader_Workspace on Workspace {\n id\n role\n name\n logo\n description\n totalProjects: projects {\n totalCount\n }\n team {\n id\n user {\n id\n name\n ...LimitedUserAvatar\n }\n }\n ...WorkspaceInviteDialog_Workspace\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
@@ -294,6 +294,9 @@ function createCache(): InMemoryCache {
invitedTeam: {
merge: (_existing, incoming) => incoming
},
team: {
merge: (_existing, incoming) => incoming
},
projects: {
keyArgs: ['filter', 'limit'],
merge: buildAbstractCollectionMergeFunction('ProjectCollection')
@@ -1804,6 +1804,7 @@ export type Project = {
visibility: ProjectVisibility;
webhooks: WebhookCollection;
workspace?: Maybe<Workspace>;
workspaceId?: Maybe<Scalars['String']['output']>;
};
@@ -5268,6 +5269,7 @@ export type ProjectResolvers<ContextType = GraphQLContext, ParentType extends Re
visibility?: Resolver<ResolversTypes['ProjectVisibility'], ParentType, ContextType>;
webhooks?: Resolver<ResolversTypes['WebhookCollection'], ParentType, ContextType, Partial<ProjectWebhooksArgs>>;
workspace?: Resolver<Maybe<ResolversTypes['Workspace']>, ParentType, ContextType>;
workspaceId?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -42,7 +42,6 @@ export const createUserEmailFactory =
const [row] = await db<UserEmail>(UserEmails.name).insert(
{
id,
primary: true,
email: email.toLowerCase().trim(),
...rest
},
@@ -145,7 +145,8 @@ module.exports = {
userEmail: {
email: user.email,
userId: user.id,
verified: user.verified
verified: user.verified,
primary: true
}
})
@@ -406,7 +406,7 @@ describe('Core @user-emails', () => {
const newEmail = createRandomEmail()
const createdEmail = (
await createUserEmail({
userEmail: { email: newEmail, userId }
userEmail: { email: newEmail, userId, primary: false }
})
)?.email
@@ -1793,6 +1793,7 @@ export type Project = {
visibility: ProjectVisibility;
webhooks: WebhookCollection;
workspace?: Maybe<Workspace>;
workspaceId?: Maybe<Scalars['String']['output']>;
};
@@ -1794,6 +1794,7 @@ export type Project = {
visibility: ProjectVisibility;
webhooks: WebhookCollection;
workspace?: Maybe<Workspace>;
workspaceId?: Maybe<Scalars['String']['output']>;
};