feat(workspaces): toggle embedded viewer branding (#4762)

* feat(viewer): embedded viewer option to hide speckle branding

* chore(viewer): can edit embed options policy

* chore(embeds): tests for new policy and gql
This commit is contained in:
Chuck Driesler
2025-05-19 13:19:38 +01:00
committed by GitHub
parent 0a284dd7ae
commit 969ca64a1b
27 changed files with 657 additions and 18 deletions
@@ -30,6 +30,14 @@ export default {
workspaceId: parent.workspaceId
})
return Authz.toGraphqlResult(canMoveProjectToWorkspace)
},
canEditEmbedOptions: async (parent, _args, ctx) => {
const canEditEmbedOptions =
await ctx.authPolicies.workspace.canUpdateEmbedOptions({
userId: ctx.userId,
workspaceId: parent.workspaceId
})
return Authz.toGraphqlResult(canEditEmbedOptions)
}
}
} as Resolvers
@@ -10,6 +10,7 @@ import {
countInvitableCollaboratorsByProjectIdFactory,
getInvitableCollaboratorsByProjectIdFactory
} from '@/modules/workspaces/repositories/users'
import { getWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces'
import { getMoveProjectToWorkspaceDryRunFactory } from '@/modules/workspaces/services/projects'
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
@@ -61,6 +62,21 @@ export default FF_WORKSPACES_MODULE_ENABLED
})({ projectId, workspaceId })
return addedToWorkspace
},
embedOptions: async (parent) => {
const { workspaceId } = parent
if (!workspaceId) {
return {
hideSpeckleBranding: false
}
}
const workspace = await getWorkspaceFactory({ db })({ workspaceId })
return {
hideSpeckleBranding: workspace?.isEmbedSpeckleBrandingHidden ?? false
}
}
},
ProjectMoveToWorkspaceDryRun: {
@@ -1042,6 +1042,44 @@ export = FF_WORKSPACES_MODULE_ENABLED
)
return true
},
updateEmbedOptions: async (parent, args, context) => {
const { workspaceId, hideSpeckleBranding } = args.input
const logger = context.log.child({ workspaceId })
return await asOperation(
async ({ db, emit }) => {
const workspace = await updateWorkspaceFactory({
validateSlug: validateSlugFactory({
getWorkspaceBySlug: getWorkspaceBySlugFactory({ db })
}),
getWorkspace: getWorkspaceWithDomainsFactory({ db }),
getWorkspaceSsoProviderRecord: getWorkspaceSsoProviderFactory({
db,
decrypt: getDecryptor()
}),
upsertWorkspace: upsertWorkspaceFactory({ db }),
emitWorkspaceEvent: emit
})({
workspaceId,
workspaceInput: {
isEmbedSpeckleBrandingHidden: hideSpeckleBranding
}
})
return {
hideSpeckleBranding: workspace.isEmbedSpeckleBrandingHidden
}
},
{
logger,
name: 'updateWorkspaceEmbedOptions',
description:
'Update workspace-level configuration for the embedded viewer',
transaction: true
}
)
},
invites: () => ({}),
projects: () => ({}),
dismiss: async (_parent, args, ctx) => {
@@ -1652,6 +1690,11 @@ export = FF_WORKSPACES_MODULE_ENABLED
return await getWorkspaceSsoProviderRecordFactory({ db })({
workspaceId: parent.id
})
},
embedOptions: async (parent) => {
return {
hideSpeckleBranding: parent.isEmbedSpeckleBrandingHidden
}
}
},
WorkspaceSso: {
@@ -262,7 +262,8 @@ export const upsertWorkspaceFactory =
'name',
'updatedAt',
'domainBasedMembershipProtectionEnabled',
'discoverabilityEnabled'
'discoverabilityEnabled',
'isEmbedSpeckleBrandingHidden'
])
}
@@ -163,7 +163,8 @@ export const createWorkspaceFactory =
createdAt: new Date(),
updatedAt: new Date(),
domainBasedMembershipProtectionEnabled: false,
discoverabilityEnabled: false
discoverabilityEnabled: false,
isEmbedSpeckleBrandingHidden: false
}
await upsertWorkspace({ workspace })
@@ -30,7 +30,10 @@ import {
CreateWorkspaceProjectDocument,
DismissWorkspaceDocument,
GetActiveUserDiscoverableWorkspacesDocument,
GetWorkspaceWithMembersByRoleDocument
GetWorkspaceWithMembersByRoleDocument,
UpdateEmbedOptionsDocument,
WorkspaceEmbedOptionsDocument,
ProjectEmbedOptionsDocument
} from '@/test/graphql/generated/graphql'
import { beforeEachContext } from '@/test/hooks'
import { AllScopes } from '@/modules/core/helpers/mainConstants'
@@ -1359,5 +1362,72 @@ describe('Workspaces GQL CRUD', () => {
).to.false
})
})
describe('mutation workspaceMutations.updateEmbedOptions', () => {
const workspace: BasicTestWorkspace = {
id: '',
ownerId: '',
slug: cryptoRandomString({ length: 10 }),
name: 'My Test Workspace'
}
const workspaceProject: BasicTestStream = {
id: '',
ownerId: '',
name: 'My Test Project',
isPublic: false
}
before(async () => {
await createTestWorkspace(workspace, testAdminUser, { addPlan: false })
workspaceProject.workspaceId = workspace.id
await createTestStream(workspaceProject, testAdminUser)
})
beforeEach(async () => {
await apollo.execute(UpdateEmbedOptionsDocument, {
input: {
workspaceId: workspace.id,
hideSpeckleBranding: false
}
})
})
it('should update options at workspace level', async () => {
const resA = await apollo.execute(UpdateEmbedOptionsDocument, {
input: {
workspaceId: workspace.id,
hideSpeckleBranding: true
}
})
expect(resA).to.not.haveGraphQLErrors()
const resB = await apollo.execute(WorkspaceEmbedOptionsDocument, {
workspaceId: workspace.id
})
expect(resB).to.not.haveGraphQLErrors()
expect(resB?.data?.workspace.embedOptions.hideSpeckleBranding).to.equal(true)
})
it('should update options at workspace project level', async () => {
const resA = await apollo.execute(UpdateEmbedOptionsDocument, {
input: {
workspaceId: workspace.id,
hideSpeckleBranding: true
}
})
expect(resA).to.not.haveGraphQLErrors()
const resB = await apollo.execute(ProjectEmbedOptionsDocument, {
projectId: workspaceProject.id
})
expect(resB).to.not.haveGraphQLErrors()
expect(resB.data?.project.embedOptions.hideSpeckleBranding).to.equal(true)
})
})
})
})
@@ -260,6 +260,7 @@ describe('Workspace services', () => {
logo: null,
discoverabilityEnabled: false,
domainBasedMembershipProtectionEnabled: false,
isEmbedSpeckleBrandingHidden: false,
domains: []
}
return merge(workspace, input)
@@ -1139,7 +1140,8 @@ describe('Workspace role services', () => {
updatedAt: new Date(),
description: null,
discoverabilityEnabled: false,
domainBasedMembershipProtectionEnabled: false
domainBasedMembershipProtectionEnabled: false,
isEmbedSpeckleBrandingHidden: false
}
},
getDomains: async () => {
@@ -1178,7 +1180,8 @@ describe('Workspace role services', () => {
updatedAt: new Date(),
description: null,
discoverabilityEnabled: false,
domainBasedMembershipProtectionEnabled: false
domainBasedMembershipProtectionEnabled: false,
isEmbedSpeckleBrandingHidden: false
}
await addDomainToWorkspaceFactory({
@@ -383,6 +383,7 @@ describe('Workspace SSO services', () => {
logo: null,
domainBasedMembershipProtectionEnabled: false,
discoverabilityEnabled: false,
isEmbedSpeckleBrandingHidden: false,
createdAt: new Date(),
updatedAt: new Date()
}