Merge branch 'main' into fabians/core-ioc-28

This commit is contained in:
Kristaps Fabians Geikins
2024-10-08 14:01:07 +03:00
13 changed files with 117 additions and 82 deletions
@@ -1,6 +1,9 @@
<template>
<div>
<div class="bg-foundation divide-y divide-outline-3 mb-8 empty:mb-0">
<div
v-if="hasBanners"
class="bg-foundation divide-y divide-outline-3 mb-8 empty:mb-0"
>
<ProjectsInviteBanners
v-if="projectsInvites?.projectInvites?.length"
:invites="projectsInvites"
@@ -34,8 +37,16 @@ graphql(`
}
`)
defineProps<{
const props = defineProps<{
projectsInvites?: ProjectsDashboardHeaderProjects_UserFragment
workspacesInvites?: ProjectsDashboardHeaderWorkspaces_UserFragment
}>()
const hasBanners = computed(() => {
return (
props.projectsInvites?.projectInvites?.length ||
props.workspacesInvites?.workspaceInvites?.length ||
props.workspacesInvites?.discoverableWorkspaces?.length
)
})
</script>
@@ -310,7 +310,7 @@ const documents = {
"\n subscription OnViewerUserActivityBroadcasted(\n $target: ViewerUpdateTrackingTarget!\n $sessionId: String!\n ) {\n viewerUserActivityBroadcasted(target: $target, sessionId: $sessionId) {\n userName\n userId\n user {\n ...LimitedUserAvatar\n }\n state\n status\n sessionId\n }\n }\n": types.OnViewerUserActivityBroadcastedDocument,
"\n subscription OnViewerCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n id\n type\n comment {\n id\n parent {\n id\n }\n ...ViewerCommentThread\n }\n }\n }\n": types.OnViewerCommentsUpdatedDocument,
"\n fragment LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\n }\n }\n": types.LinkableCommentFragmentDoc,
"\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n user {\n id\n }\n }\n": types.UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n": types.UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n }\n": types.WorkspaceMixpanelUpdateGroup_WorkspaceCollaboratorFragmentDoc,
"\n fragment WorkspaceMixpanelUpdateGroup_Workspace on Workspace {\n id\n name\n description\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n billing {\n cost {\n total\n }\n versionsCount {\n current\n max\n }\n }\n team {\n totalCount\n items {\n ...WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator\n }\n }\n }\n": types.WorkspaceMixpanelUpdateGroup_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,
@@ -1541,7 +1541,7 @@ export function graphql(source: "\n fragment LinkableComment on Comment {\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 UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n user {\n id\n }\n }\n"): (typeof documents)["\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n user {\n id\n }\n }\n"];
export function graphql(source: "\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n"): (typeof documents)["\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\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
@@ -221,6 +221,7 @@ graphql(`
id
token
workspaceId
workspaceSlug
user {
id
}
@@ -283,6 +284,7 @@ export const useWorkspaceInviteManager = <
if (!token.value || !invite.value) return false
const workspaceId = invite.value.workspaceId
const workspaceSlug = invite.value.workspaceSlug
const shouldAddNewEmail = canAddNewEmail.value && addNewEmail
loading.value = true
@@ -302,8 +304,8 @@ export const useWorkspaceInviteManager = <
// Redirect
if (accept) {
if (workspaceId) {
window.location.href = workspaceRoute(workspaceId)
if (workspaceSlug) {
window.location.href = workspaceRoute(workspaceSlug)
} else {
window.location.reload()
}
@@ -381,6 +381,7 @@ type PendingWorkspaceCollaborator {
inviteId: String!
workspaceId: String!
workspaceName: String!
workspaceSlug: String!
"""
E-mail address if target is unregistered or primary e-mail of target registered user
if token was specified to retrieve this invite
@@ -211,48 +211,56 @@ export async function addStreamCreatedActivity(params: {
/**
* Save "stream permissions granted to user" activity item
*/
export async function addStreamPermissionsAddedActivity(params: {
streamId: string
activityUserId: string
targetUserId: string
role: StreamRoles
stream: StreamRecord
}) {
const { streamId, activityUserId, targetUserId, role, stream } = params
await Promise.all([
saveActivityFactory({ db })({
streamId,
resourceType: ResourceTypes.Stream,
resourceId: streamId,
actionType: ActionTypes.Stream.PermissionsAdd,
userId: activityUserId,
info: { targetUser: targetUserId, role },
message: `Permission granted to user ${targetUserId} (${role})`
}),
pubsub.publish(StreamPubsubEvents.UserStreamAdded, {
userStreamAdded: {
id: streamId,
sharedBy: activityUserId
},
ownerId: targetUserId
}),
publish(UserSubscriptions.UserProjectsUpdated, {
userProjectsUpdated: {
id: streamId,
type: UserProjectsUpdatedMessageType.Added,
project: stream
},
ownerId: targetUserId
}),
publish(ProjectSubscriptions.ProjectUpdated, {
projectUpdated: {
id: streamId,
type: ProjectUpdatedMessageType.Updated,
project: stream
}
})
])
}
export const addStreamPermissionsAddedActivityFactory =
({
saveActivity,
publish
}: {
saveActivity: SaveActivity
publish: PublishSubscription
}) =>
async (params: {
streamId: string
activityUserId: string
targetUserId: string
role: StreamRoles
stream: StreamRecord
}) => {
const { streamId, activityUserId, targetUserId, role, stream } = params
await Promise.all([
saveActivity({
streamId,
resourceType: ResourceTypes.Stream,
resourceId: streamId,
actionType: ActionTypes.Stream.PermissionsAdd,
userId: activityUserId,
info: { targetUser: targetUserId, role },
message: `Permission granted to user ${targetUserId} (${role})`
}),
publish(StreamPubsubEvents.UserStreamAdded, {
userStreamAdded: {
id: streamId,
sharedBy: activityUserId
},
ownerId: targetUserId
}),
publish(UserSubscriptions.UserProjectsUpdated, {
userProjectsUpdated: {
id: streamId,
type: UserProjectsUpdatedMessageType.Added,
project: stream
},
ownerId: targetUserId
}),
publish(ProjectSubscriptions.ProjectUpdated, {
projectUpdated: {
id: streamId,
type: ProjectUpdatedMessageType.Updated,
project: stream
}
})
])
}
/**
* Save "user accepted stream invite" activity item
@@ -1785,6 +1785,7 @@ export type PendingWorkspaceCollaborator = {
user?: Maybe<LimitedUser>;
workspaceId: Scalars['String']['output'];
workspaceName: Scalars['String']['output'];
workspaceSlug: Scalars['String']['output'];
};
export type PendingWorkspaceCollaboratorsFilter = {
@@ -5478,6 +5479,7 @@ export type PendingWorkspaceCollaboratorResolvers<ContextType = GraphQLContext,
user?: Resolver<Maybe<ResolversTypes['LimitedUser']>, ParentType, ContextType>;
workspaceId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
workspaceName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
workspaceSlug?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
@@ -11,9 +11,9 @@ const {
StreamAccessUpdateError
} = require('@/modules/core/errors/stream')
const {
addStreamPermissionsAddedActivity,
addStreamPermissionsRevokedActivityFactory,
addStreamInviteAcceptedActivityFactory
addStreamInviteAcceptedActivityFactory,
addStreamPermissionsAddedActivityFactory
} = require('@/modules/activitystream/services/streamActivity')
const {
getStream,
@@ -200,7 +200,10 @@ async function addOrUpdateStreamCollaborator(
stream
})
} else {
await addStreamPermissionsAddedActivity({
await addStreamPermissionsAddedActivityFactory({
saveActivity: saveActivityFactory({ db }),
publish
})({
streamId,
activityUserId: addedById,
targetUserId: userId,
@@ -1769,6 +1769,7 @@ export type PendingWorkspaceCollaborator = {
user?: Maybe<LimitedUser>;
workspaceId: Scalars['String']['output'];
workspaceName: Scalars['String']['output'];
workspaceSlug: Scalars['String']['output'];
};
export type PendingWorkspaceCollaboratorsFilter = {
@@ -46,8 +46,7 @@ describe('FileUploads @fileuploads', () => {
;({ token: userOneToken } = await createToken({
userId: userOneId,
name: 'test token',
scopes: [Scopes.Streams.Write],
lifespan: 3600
scopes: [Scopes.Streams.Write]
}))
})
@@ -862,6 +862,12 @@ export = FF_WORKSPACES_MODULE_ENABLED
)
return workspace!.name
},
workspaceSlug: async (parent, _args, ctx) => {
const workspace = await ctx.loaders.workspaces!.getWorkspace.load(
parent.workspaceId
)
return workspace!.slug
},
invitedBy: async (parent, _args, ctx) => {
const { invitedById } = parent
if (!invitedById) return null
@@ -389,19 +389,10 @@ export const updateWorkspaceRoleFactory =
// Return early if no work required
const previousWorkspaceRole = workspaceRoles.find((acl) => acl.userId === userId)
if (previousWorkspaceRole?.role === nextWorkspaceRole) {
return
}
// Protect against removing last admin
if (
isUserLastWorkspaceAdmin(workspaceRoles, userId) &&
nextWorkspaceRole !== Roles.Workspace.Admin
) {
throw new WorkspaceAdminRequiredError()
}
// prevent role downgrades (used during invite flow)
if (preventRoleDowngrade) {
if (previousWorkspaceRole) {
@@ -416,6 +407,14 @@ export const updateWorkspaceRoleFactory =
}
}
// Protect against removing last admin
if (
isUserLastWorkspaceAdmin(workspaceRoles, userId) &&
nextWorkspaceRole !== Roles.Workspace.Admin
) {
throw new WorkspaceAdminRequiredError()
}
// ensure domain compliance
if (nextWorkspaceRole !== Roles.Workspace.Guest) {
const workspace = await getWorkspaceWithDomains({ id: workspaceId })
@@ -1770,6 +1770,7 @@ export type PendingWorkspaceCollaborator = {
user?: Maybe<LimitedUser>;
workspaceId: Scalars['String']['output'];
workspaceName: Scalars['String']['output'];
workspaceSlug: Scalars['String']['output'];
};
export type PendingWorkspaceCollaboratorsFilter = {