feat: Workspace/ProjectCollaborator/WorkspaceCollaborator seatType (#4284)
* Workspace & ProjectCollaborator seat type * minor adjustment to FE * minor adjustment to FE
This commit is contained in:
committed by
GitHub
parent
8d1c45e6f8
commit
a83bae8d84
@@ -77,16 +77,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
SettingsWorkspacesMembersNewGuestsTable_WorkspaceFragment,
|
||||
WorkspaceCollaborator
|
||||
import {
|
||||
WorkspaceSeatType,
|
||||
type SettingsWorkspacesMembersNewGuestsTable_WorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import {
|
||||
Roles,
|
||||
type MaybeNullOrUndefined,
|
||||
type WorkspaceSeatType
|
||||
} from '@speckle/shared'
|
||||
import { Roles, type MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import { settingsWorkspacesMembersSearchQuery } from '~~/lib/settings/graphql/queries'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { LearnMoreRolesSeatsUrl } from '~~/lib/common/helpers/route'
|
||||
@@ -96,10 +92,12 @@ graphql(`
|
||||
id
|
||||
role
|
||||
seatType
|
||||
joinDate
|
||||
user {
|
||||
id
|
||||
avatar
|
||||
name
|
||||
workspaceDomainPolicyCompliant
|
||||
}
|
||||
projectRoles {
|
||||
role
|
||||
@@ -154,9 +152,8 @@ const guests = computed(() => {
|
||||
: props.workspace?.team.items
|
||||
|
||||
return (guestArray || [])
|
||||
.filter(
|
||||
(item): item is WorkspaceCollaborator => item.role === Roles.Workspace.Guest
|
||||
)
|
||||
.map((g) => ({ ...g, seatType: g.seatType || WorkspaceSeatType.Viewer }))
|
||||
.filter((item) => item.role === Roles.Workspace.Guest)
|
||||
.filter((item) => !seatTypeFilter.value || item.seatType === seatTypeFilter.value)
|
||||
})
|
||||
|
||||
|
||||
@@ -85,15 +85,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Roles,
|
||||
type WorkspaceRoles,
|
||||
type MaybeNullOrUndefined,
|
||||
type WorkspaceSeatType
|
||||
} from '@speckle/shared'
|
||||
import { Roles, type WorkspaceRoles, type MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import { settingsWorkspacesMembersSearchQuery } from '~~/lib/settings/graphql/queries'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import type { SettingsWorkspacesNewMembersTable_WorkspaceFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
import {
|
||||
WorkspaceSeatType,
|
||||
type SettingsWorkspacesNewMembersTable_WorkspaceFragment
|
||||
} from '~~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { ExclamationCircleIcon } from '@heroicons/vue/24/outline'
|
||||
import { LearnMoreRolesSeatsUrl } from '~~/lib/common/helpers/route'
|
||||
@@ -163,7 +161,7 @@ const members = computed(() => {
|
||||
return (memberArray || [])
|
||||
.map(({ user, seatType, ...rest }) => ({
|
||||
...user,
|
||||
seatType,
|
||||
seatType: seatType || WorkspaceSeatType.Viewer,
|
||||
...rest
|
||||
}))
|
||||
.filter((user) => user.role !== Roles.Workspace.Guest)
|
||||
|
||||
@@ -134,7 +134,7 @@ type Documents = {
|
||||
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n workspaceDomainPolicyCompliant\n }\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n": typeof types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n user {\n id\n avatar\n name\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersNewGuestsTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsSharedDeleteUserDialog_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersNewGuestsTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesNewMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant\n }\n }\n": typeof types.SettingsWorkspacesNewMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesNewMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesNewMembersTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesNewMembersTable_WorkspaceFragmentDoc,
|
||||
@@ -555,7 +555,7 @@ const documents: Documents = {
|
||||
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n user {\n id\n avatar\n name\n company\n workspaceDomainPolicyCompliant\n }\n }\n": types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsWorkspacesMembersChangeRoleDialog_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n": types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n user {\n id\n avatar\n name\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": types.SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": types.SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersNewGuestsTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n ...SettingsSharedDeleteUserDialog_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersNewGuestsTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesNewMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant\n }\n }\n": types.SettingsWorkspacesNewMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesNewMembersTable_Workspace on Workspace {\n id\n name\n ...SettingsSharedDeleteUserDialog_Workspace\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesNewMembersTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesNewMembersTable_WorkspaceFragmentDoc,
|
||||
@@ -1353,7 +1353,7 @@ export function graphql(source: "\n fragment SettingsWorkspacesMembersTableHead
|
||||
/**
|
||||
* 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 SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n user {\n id\n avatar\n name\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n user {\n id\n avatar\n name\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersNewGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,7 +4,21 @@ enum WorkspaceSeatType {
|
||||
}
|
||||
|
||||
extend type WorkspaceCollaborator {
|
||||
seatType: WorkspaceSeatType!
|
||||
seatType: WorkspaceSeatType
|
||||
}
|
||||
|
||||
extend type ProjectCollaborator {
|
||||
"""
|
||||
The collaborator's workspace seat type for the workspace this project is in
|
||||
"""
|
||||
seatType: WorkspaceSeatType
|
||||
}
|
||||
|
||||
extend type Workspace {
|
||||
"""
|
||||
Active user's seat type for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you.
|
||||
"""
|
||||
seatType: WorkspaceSeatType
|
||||
}
|
||||
|
||||
input WorkspaceUpdateSeatTypeInput {
|
||||
|
||||
@@ -40,6 +40,7 @@ generates:
|
||||
Comment: '@/modules/comments/helpers/graphTypes#CommentGraphQLReturn'
|
||||
PendingStreamCollaborator: '@/modules/serverinvites/helpers/graphTypes#PendingStreamCollaboratorGraphQLReturn'
|
||||
StreamCollaborator: '@/modules/core/helpers/graphTypes#StreamCollaboratorGraphQLReturn'
|
||||
ProjectCollaborator: '@/modules/core/helpers/graphTypes#ProjectCollaboratorGraphQLReturn'
|
||||
FileUpload: '@/modules/fileuploads/helpers/types#FileUploadGraphQLReturn'
|
||||
AutomateFunction: '@/modules/automate/helpers/graphTypes#AutomateFunctionGraphQLReturn'
|
||||
AutomateFunctionRelease: '@/modules/automate/helpers/graphTypes#AutomateFunctionReleaseGraphQLReturn'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
|
||||
import { StreamGraphQLReturn, CommitGraphQLReturn, ProjectGraphQLReturn, ObjectGraphQLReturn, VersionGraphQLReturn, ServerInviteGraphQLReturnType, ModelGraphQLReturn, ModelsTreeItemGraphQLReturn, MutationsObjectGraphQLReturn, LimitedUserGraphQLReturn, UserGraphQLReturn, GraphQLEmptyReturn, StreamCollaboratorGraphQLReturn, ServerInfoGraphQLReturn, BranchGraphQLReturn } from '@/modules/core/helpers/graphTypes';
|
||||
import { StreamGraphQLReturn, CommitGraphQLReturn, ProjectGraphQLReturn, ObjectGraphQLReturn, VersionGraphQLReturn, ServerInviteGraphQLReturnType, ModelGraphQLReturn, ModelsTreeItemGraphQLReturn, MutationsObjectGraphQLReturn, LimitedUserGraphQLReturn, UserGraphQLReturn, GraphQLEmptyReturn, StreamCollaboratorGraphQLReturn, ProjectCollaboratorGraphQLReturn, ServerInfoGraphQLReturn, BranchGraphQLReturn } from '@/modules/core/helpers/graphTypes';
|
||||
import { StreamAccessRequestGraphQLReturn, ProjectAccessRequestGraphQLReturn } from '@/modules/accessrequests/helpers/graphTypes';
|
||||
import { CommentReplyAuthorCollectionGraphQLReturn, CommentGraphQLReturn } from '@/modules/comments/helpers/graphTypes';
|
||||
import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/helpers/graphTypes';
|
||||
@@ -2253,6 +2253,8 @@ export type ProjectCollaborator = {
|
||||
__typename?: 'ProjectCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
role: Scalars['String']['output'];
|
||||
/** The collaborator's workspace seat type for the workspace this project is in */
|
||||
seatType?: Maybe<WorkspaceSeatType>;
|
||||
user: LimitedUser;
|
||||
};
|
||||
|
||||
@@ -4350,6 +4352,8 @@ export type Workspace = {
|
||||
readOnly: Scalars['Boolean']['output'];
|
||||
/** Active user's role for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
role?: Maybe<Scalars['String']['output']>;
|
||||
/** Active user's seat type for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
seatType?: Maybe<WorkspaceSeatType>;
|
||||
slug: Scalars['String']['output'];
|
||||
/** Information about the workspace's SSO configuration and the current user's SSO session, if present */
|
||||
sso?: Maybe<WorkspaceSso>;
|
||||
@@ -5141,7 +5145,7 @@ export type ResolversTypes = {
|
||||
ProjectAutomationUpdateInput: ProjectAutomationUpdateInput;
|
||||
ProjectAutomationsUpdatedMessage: ResolverTypeWrapper<ProjectAutomationsUpdatedMessageGraphQLReturn>;
|
||||
ProjectAutomationsUpdatedMessageType: ProjectAutomationsUpdatedMessageType;
|
||||
ProjectCollaborator: ResolverTypeWrapper<Omit<ProjectCollaborator, 'user'> & { user: ResolversTypes['LimitedUser'] }>;
|
||||
ProjectCollaborator: ResolverTypeWrapper<ProjectCollaboratorGraphQLReturn>;
|
||||
ProjectCollection: ResolverTypeWrapper<Omit<ProjectCollection, 'items'> & { items: Array<ResolversTypes['Project']> }>;
|
||||
ProjectCommentCollection: ResolverTypeWrapper<Omit<ProjectCommentCollection, 'items'> & { items: Array<ResolversTypes['Comment']> }>;
|
||||
ProjectCommentsFilter: ProjectCommentsFilter;
|
||||
@@ -5453,7 +5457,7 @@ export type ResolversParentTypes = {
|
||||
ProjectAutomationRevisionCreateInput: ProjectAutomationRevisionCreateInput;
|
||||
ProjectAutomationUpdateInput: ProjectAutomationUpdateInput;
|
||||
ProjectAutomationsUpdatedMessage: ProjectAutomationsUpdatedMessageGraphQLReturn;
|
||||
ProjectCollaborator: Omit<ProjectCollaborator, 'user'> & { user: ResolversParentTypes['LimitedUser'] };
|
||||
ProjectCollaborator: ProjectCollaboratorGraphQLReturn;
|
||||
ProjectCollection: Omit<ProjectCollection, 'items'> & { items: Array<ResolversParentTypes['Project']> };
|
||||
ProjectCommentCollection: Omit<ProjectCommentCollection, 'items'> & { items: Array<ResolversParentTypes['Comment']> };
|
||||
ProjectCommentsFilter: ProjectCommentsFilter;
|
||||
@@ -6417,6 +6421,7 @@ export type ProjectAutomationsUpdatedMessageResolvers<ContextType = GraphQLConte
|
||||
export type ProjectCollaboratorResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['ProjectCollaborator'] = ResolversParentTypes['ProjectCollaborator']> = {
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
role?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
seatType?: Resolver<Maybe<ResolversTypes['WorkspaceSeatType']>, ParentType, ContextType>;
|
||||
user?: Resolver<ResolversTypes['LimitedUser'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
@@ -7085,6 +7090,7 @@ export type WorkspaceResolvers<ContextType = GraphQLContext, ParentType extends
|
||||
projects?: Resolver<ResolversTypes['ProjectCollection'], ParentType, ContextType, RequireFields<WorkspaceProjectsArgs, 'limit'>>;
|
||||
readOnly?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
role?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
seatType?: Resolver<Maybe<ResolversTypes['WorkspaceSeatType']>, ParentType, ContextType>;
|
||||
slug?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
sso?: Resolver<Maybe<ResolversTypes['WorkspaceSso']>, ParentType, ContextType>;
|
||||
subscription?: Resolver<Maybe<ResolversTypes['WorkspaceSubscription']>, ParentType, ContextType>;
|
||||
|
||||
@@ -382,7 +382,8 @@ export = {
|
||||
return users.map((u) => ({
|
||||
user: u,
|
||||
role: u.streamRole,
|
||||
id: u.id
|
||||
id: u.id,
|
||||
projectId: parent.id
|
||||
}))
|
||||
},
|
||||
async sourceApps(parent, _args, ctx) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Roles, ServerRoles, StreamRoles } from '@/modules/core/helpers/mainCons
|
||||
import {
|
||||
BranchRecord,
|
||||
CommitRecord,
|
||||
LimitedUserRecord,
|
||||
ObjectRecord,
|
||||
ServerInfo,
|
||||
StreamRecord,
|
||||
@@ -128,3 +129,10 @@ export type StreamCollaboratorGraphQLReturn = {
|
||||
}
|
||||
|
||||
export type ServerInfoGraphQLReturn = ServerInfo
|
||||
|
||||
export type ProjectCollaboratorGraphQLReturn = {
|
||||
id: string
|
||||
user: LimitedUserRecord
|
||||
role: StreamRoles
|
||||
projectId: string
|
||||
}
|
||||
|
||||
@@ -2233,6 +2233,8 @@ export type ProjectCollaborator = {
|
||||
__typename?: 'ProjectCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
role: Scalars['String']['output'];
|
||||
/** The collaborator's workspace seat type for the workspace this project is in */
|
||||
seatType?: Maybe<WorkspaceSeatType>;
|
||||
user: LimitedUser;
|
||||
};
|
||||
|
||||
@@ -4330,6 +4332,8 @@ export type Workspace = {
|
||||
readOnly: Scalars['Boolean']['output'];
|
||||
/** Active user's role for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
role?: Maybe<Scalars['String']['output']>;
|
||||
/** Active user's seat type for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
seatType?: Maybe<WorkspaceSeatType>;
|
||||
slug: Scalars['String']['output'];
|
||||
/** Information about the workspace's SSO configuration and the current user's SSO session, if present */
|
||||
sso?: Maybe<WorkspaceSso>;
|
||||
|
||||
@@ -59,3 +59,25 @@ export type GetWorkspaceUserSeat = (params: {
|
||||
workspaceId: string
|
||||
userId: string
|
||||
}) => Promise<Optional<WorkspaceSeat>>
|
||||
|
||||
export type GetWorkspacesUsersSeats = (params: {
|
||||
requests: Array<{
|
||||
userId: string
|
||||
workspaceId: string
|
||||
}>
|
||||
}) => Promise<{
|
||||
[workspaceId: string]: {
|
||||
[userId: string]: WorkspaceSeat
|
||||
}
|
||||
}>
|
||||
|
||||
export type GetProjectsUsersSeats = (params: {
|
||||
requests: Array<{
|
||||
userId: string
|
||||
projectId: string
|
||||
}>
|
||||
}) => Promise<{
|
||||
[projectId: string]: {
|
||||
[userId: string]: WorkspaceSeat
|
||||
}
|
||||
}>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { WorkspaceSeat } from '@/modules/gatekeeper/domain/billing'
|
||||
import {
|
||||
getProjectsUsersSeatsFactory,
|
||||
getWorkspacesUsersSeatsFactory
|
||||
} from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { defineRequestDataloaders } from '@/modules/shared/helpers/graphqlHelper'
|
||||
|
||||
const { FF_GATEKEEPER_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
declare module '@/modules/core/loaders' {
|
||||
interface ModularizedDataLoaders
|
||||
extends Partial<ReturnType<typeof dataLoadersDefinition>> {}
|
||||
}
|
||||
|
||||
const dataLoadersDefinition = defineRequestDataloaders(
|
||||
({ createLoader, deps: { db } }) => {
|
||||
const getWorkspacesUsersSeats = getWorkspacesUsersSeatsFactory({ db })
|
||||
const getProjectsUsersSeats = getProjectsUsersSeatsFactory({ db })
|
||||
|
||||
return {
|
||||
gatekeeper: {
|
||||
getUserWorkspaceSeat: createLoader<
|
||||
{ workspaceId: string; userId: string },
|
||||
WorkspaceSeat | null,
|
||||
string
|
||||
>(
|
||||
async (requests) => {
|
||||
const results = await getWorkspacesUsersSeats({
|
||||
requests: requests.slice()
|
||||
})
|
||||
|
||||
return requests.map(
|
||||
({ workspaceId, userId }) => results[workspaceId]?.[userId] || null
|
||||
)
|
||||
},
|
||||
{
|
||||
cacheKeyFn: ({ workspaceId, userId }) => `${workspaceId}-${userId}`
|
||||
}
|
||||
),
|
||||
getUserProjectSeat: createLoader<
|
||||
{ projectId: string; userId: string },
|
||||
WorkspaceSeat | null,
|
||||
string
|
||||
>(
|
||||
async (requests) => {
|
||||
const results = await getProjectsUsersSeats({
|
||||
requests: requests.slice()
|
||||
})
|
||||
|
||||
return requests.map(
|
||||
({ projectId, userId }) => results[projectId]?.[userId] || null
|
||||
)
|
||||
},
|
||||
{
|
||||
cacheKeyFn: ({ projectId, userId }) => `${projectId}-${userId}`
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default FF_GATEKEEPER_MODULE_ENABLED ? dataLoadersDefinition : undefined
|
||||
@@ -157,6 +157,17 @@ export = FF_GATEKEEPER_MODULE_ENABLED
|
||||
return await isWorkspaceReadOnlyFactory({ getWorkspacePlan })({
|
||||
workspaceId: parent.id
|
||||
})
|
||||
},
|
||||
seatType: async (parent, _args, context) => {
|
||||
if (!context.userId) return null
|
||||
|
||||
const seat = await context.loaders.gatekeeper!.getUserWorkspaceSeat.load({
|
||||
workspaceId: parent.id,
|
||||
userId: context.userId
|
||||
})
|
||||
|
||||
// Defaults to Editor for old plans that don't have seat types
|
||||
return seat?.type || WorkspaceSeatType.Editor
|
||||
}
|
||||
},
|
||||
WorkspaceSubscription: {
|
||||
@@ -200,9 +211,10 @@ export = FF_GATEKEEPER_MODULE_ENABLED
|
||||
},
|
||||
WorkspaceCollaborator: {
|
||||
seatType: async (parent, _args, context) => {
|
||||
const seat = await context.loaders
|
||||
.gatekeeper!.getUserWorkspaceSeatType.forWorkspace(parent.workspaceId)
|
||||
.load(parent.id)
|
||||
const seat = await context.loaders.gatekeeper!.getUserWorkspaceSeat.load({
|
||||
workspaceId: parent.workspaceId,
|
||||
userId: parent.id
|
||||
})
|
||||
|
||||
// Defaults to Editor for old plans that don't have seat types
|
||||
return seat?.type || WorkspaceSeatType.Editor
|
||||
@@ -224,6 +236,17 @@ export = FF_GATEKEEPER_MODULE_ENABLED
|
||||
}))
|
||||
}
|
||||
},
|
||||
ProjectCollaborator: {
|
||||
seatType: async (parent, _args, context) => {
|
||||
const seat = await context.loaders.gatekeeper!.getUserProjectSeat.load({
|
||||
projectId: parent.projectId,
|
||||
userId: parent.id
|
||||
})
|
||||
|
||||
// Defaults to Editor for old plans that don't have seat types
|
||||
return seat?.type || WorkspaceSeatType.Editor
|
||||
}
|
||||
},
|
||||
WorkspaceMutations: {
|
||||
billing: () => ({}),
|
||||
updateSeatType: async (_parent, args, ctx) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { buildTableHelper } from '@/modules/core/dbSchema'
|
||||
import { buildTableHelper, StreamAcl, Streams } from '@/modules/core/dbSchema'
|
||||
import { StreamAclRecord, StreamRecord } from '@/modules/core/helpers/types'
|
||||
import {
|
||||
GetWorkspaceRoleAndSeat,
|
||||
GetWorkspaceRolesAndSeats,
|
||||
@@ -8,6 +9,8 @@ import {
|
||||
CountSeatsByTypeInWorkspace,
|
||||
CreateWorkspaceSeat,
|
||||
DeleteWorkspaceSeat,
|
||||
GetProjectsUsersSeats,
|
||||
GetWorkspacesUsersSeats,
|
||||
GetWorkspaceUserSeat,
|
||||
GetWorkspaceUserSeats
|
||||
} from '@/modules/gatekeeper/domain/operations'
|
||||
@@ -26,7 +29,9 @@ const WorkspaceSeats = buildTableHelper('workspace_seats', [
|
||||
|
||||
const tables = {
|
||||
workspaceSeats: (db: Knex) => db<WorkspaceSeat>(WorkspaceSeats.name),
|
||||
workspaceAcl: (db: Knex) => db<WorkspaceAclRecord>(WorkspaceAcl.name)
|
||||
workspaceAcl: (db: Knex) => db<WorkspaceAclRecord>(WorkspaceAcl.name),
|
||||
streamAcl: (db: Knex) => db<StreamAclRecord>(StreamAcl.name),
|
||||
streams: (db: Knex) => db<StreamRecord>(Streams.name)
|
||||
}
|
||||
|
||||
export const countSeatsByTypeInWorkspaceFactory =
|
||||
@@ -136,3 +141,67 @@ export const getWorkspaceRoleAndSeatFactory =
|
||||
})
|
||||
return rolesAndSeats[userId]
|
||||
}
|
||||
|
||||
export const getWorkspacesUsersSeatsFactory =
|
||||
(deps: { db: Knex }): GetWorkspacesUsersSeats =>
|
||||
async (params) => {
|
||||
const { requests } = params
|
||||
const q = tables.workspaceSeats(deps.db).whereIn(
|
||||
[WorkspaceSeats.col.workspaceId, WorkspaceSeats.col.userId],
|
||||
requests.map(({ userId, workspaceId }) => [workspaceId, userId])
|
||||
)
|
||||
const results = await q
|
||||
|
||||
return results.reduce((acc, seat) => {
|
||||
const { userId, workspaceId } = seat
|
||||
if (!acc[workspaceId]) {
|
||||
acc[workspaceId] = {}
|
||||
}
|
||||
|
||||
if (!acc[workspaceId][userId]) {
|
||||
acc[workspaceId][userId] = seat
|
||||
}
|
||||
return acc
|
||||
}, {} as Awaited<ReturnType<GetWorkspacesUsersSeats>>)
|
||||
}
|
||||
|
||||
export const getProjectsUsersSeatsFactory =
|
||||
(deps: { db: Knex }): GetProjectsUsersSeats =>
|
||||
async (params: {
|
||||
requests: Array<{
|
||||
userId: string
|
||||
projectId: string
|
||||
}>
|
||||
}) => {
|
||||
const { requests } = params
|
||||
const idPairs = requests.map(({ userId, projectId }) => [userId, projectId])
|
||||
|
||||
const q = tables
|
||||
.streamAcl(deps.db)
|
||||
.whereIn([StreamAcl.col.userId, StreamAcl.col.resourceId], idPairs)
|
||||
.innerJoin(Streams.name, Streams.col.id, StreamAcl.col.resourceId)
|
||||
.leftJoin(WorkspaceSeats.name, (j1) => {
|
||||
j1.on(WorkspaceSeats.col.userId, StreamAcl.col.userId).andOn(
|
||||
WorkspaceSeats.col.workspaceId,
|
||||
Streams.col.workspaceId
|
||||
)
|
||||
})
|
||||
.select<Array<StreamAclRecord & WorkspaceSeat>>([
|
||||
...StreamAcl.cols,
|
||||
...WorkspaceSeats.cols
|
||||
])
|
||||
|
||||
const results = await q
|
||||
|
||||
return results.reduce((acc, row) => {
|
||||
const { userId, resourceId: projectId } = row
|
||||
if (!acc[projectId]) {
|
||||
acc[projectId] = {}
|
||||
}
|
||||
|
||||
if (!acc[projectId][userId]) {
|
||||
acc[projectId][userId] = row
|
||||
}
|
||||
return acc
|
||||
}, {} as Awaited<ReturnType<GetProjectsUsersSeats>>)
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { WorkspaceSeat } from '@/modules/gatekeeper/domain/billing'
|
||||
import { getWorkspaceUserSeatsFactory } from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
import { defineRequestDataloaders } from '@/modules/shared/helpers/graphqlHelper'
|
||||
import DataLoader from 'dataloader'
|
||||
|
||||
const { FF_GATEKEEPER_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
declare module '@/modules/core/loaders' {
|
||||
interface ModularizedDataLoaders
|
||||
extends Partial<ReturnType<typeof dataLoadersDefinition>> {}
|
||||
}
|
||||
|
||||
const dataLoadersDefinition = defineRequestDataloaders(
|
||||
({ createLoader, deps: { db } }) => {
|
||||
const getUserSeats = getWorkspaceUserSeatsFactory({ db })
|
||||
|
||||
return {
|
||||
gatekeeper: {
|
||||
getUserWorkspaceSeatType: (() => {
|
||||
type LoaderType = DataLoader<string, WorkspaceSeat | null>
|
||||
const workspaceLoaders = new Map<string, LoaderType>()
|
||||
return {
|
||||
clearAll: () => workspaceLoaders.clear(),
|
||||
forWorkspace(workspaceId: string): LoaderType {
|
||||
let loader = workspaceLoaders.get(workspaceId)
|
||||
if (!loader) {
|
||||
loader = createLoader<string, WorkspaceSeat | null>(async (ids) => {
|
||||
const results = await getUserSeats({
|
||||
userIds: ids.slice(),
|
||||
workspaceId
|
||||
})
|
||||
return ids.map((id) => results[id] || null)
|
||||
})
|
||||
workspaceLoaders.set(workspaceId, loader)
|
||||
}
|
||||
|
||||
return loader
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default FF_GATEKEEPER_MODULE_ENABLED ? dataLoadersDefinition : undefined
|
||||
@@ -20,6 +20,11 @@ const resolvers: Resolvers = FF_GATEKEEPER_MODULE_ENABLED
|
||||
throw new GatekeeperModuleDisabledError()
|
||||
}
|
||||
},
|
||||
ProjectCollaborator: {
|
||||
seatType: () => {
|
||||
throw new GatekeeperModuleDisabledError()
|
||||
}
|
||||
},
|
||||
ServerWorkspacesInfo: {
|
||||
planPrices: () => []
|
||||
}
|
||||
|
||||
@@ -160,12 +160,12 @@ export const graphDataloadersBuilders = (): RequestDataLoadersBuilder<any>[] =>
|
||||
const codeModuleDirs = fs.readdirSync(`${appRoot}/modules`)
|
||||
codeModuleDirs.forEach((file) => {
|
||||
if (!enabledModuleNames.includes(file)) return
|
||||
const fullPath = path.join(`${appRoot}/modules`, file)
|
||||
const modulePath = path.join(`${appRoot}/modules`, file)
|
||||
|
||||
// load dataloaders
|
||||
const directivesPath = path.join(fullPath, 'graph', 'dataloaders')
|
||||
if (fs.existsSync(directivesPath)) {
|
||||
const newLoaders = values(autoloadFromDirectory(directivesPath))
|
||||
const fullPath = path.join(modulePath, 'graph', 'dataloaders')
|
||||
if (fs.existsSync(fullPath)) {
|
||||
const newLoaders = values(autoloadFromDirectory(fullPath))
|
||||
.map((l) => l.default)
|
||||
.filter(isNonNullable)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const { getLogger, extendLoggerComponent } = Observability
|
||||
|
||||
export const logger = getLogger(
|
||||
process.env.LOG_LEVEL || 'info',
|
||||
process.env.LOG_PRETTY === 'true'
|
||||
process.env.LOG_PRETTY === 'true' && !process.env.FORCE_NO_PRETTY
|
||||
)
|
||||
// loggers for phases of operation
|
||||
export const startupLogger = logger.child({ phase: 'startup' })
|
||||
|
||||
@@ -2234,6 +2234,8 @@ export type ProjectCollaborator = {
|
||||
__typename?: 'ProjectCollaborator';
|
||||
id: Scalars['ID']['output'];
|
||||
role: Scalars['String']['output'];
|
||||
/** The collaborator's workspace seat type for the workspace this project is in */
|
||||
seatType?: Maybe<WorkspaceSeatType>;
|
||||
user: LimitedUser;
|
||||
};
|
||||
|
||||
@@ -4331,6 +4333,8 @@ export type Workspace = {
|
||||
readOnly: Scalars['Boolean']['output'];
|
||||
/** Active user's role for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
role?: Maybe<Scalars['String']['output']>;
|
||||
/** Active user's seat type for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
seatType?: Maybe<WorkspaceSeatType>;
|
||||
slug: Scalars['String']['output'];
|
||||
/** Information about the workspace's SSO configuration and the current user's SSO session, if present */
|
||||
sso?: Maybe<WorkspaceSso>;
|
||||
|
||||
Reference in New Issue
Block a user