Skip to content

Commit

Permalink
UBERF-9504: Add role to employee mixin (#8072)
Browse files Browse the repository at this point in the history
Signed-off-by: Alexey Zinoviev <[email protected]>
  • Loading branch information
lexiv0re authored Mar 5, 2025
1 parent efdbf4f commit 3bac42a
Show file tree
Hide file tree
Showing 18 changed files with 119 additions and 26 deletions.
5 changes: 5 additions & 0 deletions models/contact/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ export class TEmployee extends TPerson implements Employee {
@Hidden()
active!: boolean

@Prop(TypeString(), contact.string.Role)
@ReadOnly()
@Hidden()
role?: 'USER' | 'GUEST'

@Prop(CollectionType(contact.class.Status), contact.string.Status)
@Hidden()
statuses?: number
Expand Down
57 changes: 56 additions & 1 deletion models/contact/src/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { AvatarType, type Person, type Contact, type SocialIdentity } from '@hcengineering/contact'
import {
type AccountRole,
AccountRole,
buildSocialIdString,
type Class,
type Doc,
Expand Down Expand Up @@ -176,6 +176,57 @@ async function assignWorkspaceRoles (client: MigrationClient): Promise<void> {
ctx.info('finished assigning workspace roles', { users: oldPersonAccounts.length })
}

async function assignEmployeeRoles (client: MigrationClient): Promise<void> {
const ctx = new MeasureMetricsContext('contact assignEmployeeRoles', {})
ctx.info('assigning roles to employees...')

const wsMembers = await client.accountClient.getWorkspaceMembers()
const persons = await client.traverse<Person>(DOMAIN_CONTACT, {
_class: contact.class.Person
})

try {
while (true) {
const docs = await persons.next(50)
if (docs === null || docs?.length === 0) {
break
}

const updates: { filter: MigrationDocumentQuery<Contact>, update: MigrateUpdate<Contact> }[] = []
for (const d of docs) {
const employee = client.hierarchy.as(d, contact.mixin.Employee)
if (employee === undefined || !employee.active) {
continue
}
if (!employee.active) continue

const memberInfo = wsMembers.find((m) => m.person === employee.personUuid)
if (memberInfo === undefined) {
continue
}

const role = memberInfo.role === AccountRole.Guest ? 'GUEST' : 'USER'

updates.push({
filter: { _id: d._id },
update: {
[contact.mixin.Employee]: {
...(d as any)[contact.mixin.Employee],
role
}
}
})
}
if (updates.length > 0) {
await client.bulk(DOMAIN_CONTACT, updates)
}
}
} finally {
await persons.close()
ctx.info('finished assigning roles to employees...')
}
}

async function createSocialIdentities (client: MigrationClient): Promise<void> {
const ctx = new MeasureMetricsContext('createSocialIdentities', {})
ctx.info('processing person accounts ', {})
Expand Down Expand Up @@ -362,6 +413,10 @@ export const contactOperation: MigrateOperation = {
{
state: 'fill-account-uuids',
func: fillAccountUuids
},
{
state: 'assign-employee-roles-v1',
func: assignEmployeeRoles
}
])
},
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "Zaměstnanec",
"Inactive": "Neaktivní",
"Active": "Aktivní",
"Role": "Typ",
"Birthday": "Narozeniny",
"UseImage": "Přiložená fotografie",
"UseGravatar": "Gravatar",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "Mitarbeiter",
"Inactive": "Inaktiv",
"Active": "Aktiv",
"Role": "Rolle",
"Birthday": "Geburtstag",
"UseImage": "Angehängtes Foto",
"UseGravatar": "Gravatar",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "Employee",
"Inactive": "Inactive",
"Active": "Active",
"Role": "Role",
"Birthday": "Birthday",
"UseImage": "Attached photo",
"UseGravatar": "Gravatar",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "Empleado",
"Inactive": "Inactivo",
"Active": "Activo",
"Role": "Rol",
"Birthday": "Cumpleaños",
"UseImage": "Foto adjunta",
"UseGravatar": "Gravatar",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "Employé",
"Inactive": "Inactif",
"Active": "Actif",
"Role": "Rôle",
"Birthday": "Anniversaire",
"UseImage": "Photo jointe",
"UseGravatar": "Gravatar",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "Dipendente",
"Inactive": "Inattivo",
"Active": "Attivo",
"Role": "Ruolo",
"Birthday": "Compleanno",
"UseImage": "Foto allegata",
"UseGravatar": "Gravatar",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "Criar funcionário",
"Inactive": "Inativo",
"Active": "Ativo",
"Role": "Função",
"Birthday": "Aniversário",
"UseImage": "Foto anexada",
"UseGravatar": "Gravatar",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "Сотрудник",
"Inactive": "Не активный",
"Active": "Активный",
"Role": "Роль",
"Birthday": "День рождения",
"UseImage": "Загруженное Фото",
"UseGravatar": "Граватар",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-assets/lang/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"CreateEmployee": "员工",
"Inactive": "非活动",
"Active": "活动",
"Role": "角色",
"Birthday": "生日",
"UseImage": "附加照片",
"UseGravatar": "头像",
Expand Down
1 change: 1 addition & 0 deletions plugins/contact-resources/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default mergeIds(contactId, contact, {
CreateEmployee: '' as IntlString,
Inactive: '' as IntlString,
Active: '' as IntlString,
Role: '' as IntlString,
NotSpecified: '' as IntlString,
MergePersons: '' as IntlString,
MergePersonsFrom: '' as IntlString,
Expand Down
1 change: 1 addition & 0 deletions plugins/contact/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export interface Status extends AttachedDoc {
*/
export interface Employee extends Person {
active: boolean
role?: 'USER' | 'GUEST' // Informational only
statuses?: number
position?: string | null
personUuid?: AccountUuid
Expand Down
47 changes: 26 additions & 21 deletions plugins/contact/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,30 +488,35 @@ export async function ensureEmployee (

// NOTE: it is important to create Employee after Person and SocialIdentities are ensured so all the triggers applied
// on Employee creation will be able to properly map things
if (me.role !== AccountRole.Guest) {
const employee = await client.findOne(contact.mixin.Employee, { _id: personRef as Ref<Employee> })
const employeeRole = me.role === AccountRole.Guest ? 'GUEST' : 'USER'
const employee = await client.findOne(contact.mixin.Employee, { _id: personRef as Ref<Employee> })

if (
employee === undefined ||
!Hierarchy.hasMixin(employee, contact.mixin.Employee) ||
!employee.active ||
employee.role !== employeeRole
) {
await ctx.with('create-employee', {}, async () => {
if (personRef === undefined) {
// something went wrong
console.error('Person not found')
return null
}

if (employee === undefined || !Hierarchy.hasMixin(employee, contact.mixin.Employee) || !employee.active) {
await ctx.with('create-employee', {}, async () => {
if (personRef === undefined) {
// something went wrong
console.error('Person not found')
return null
const createEmployeeTx = txFactory.createTxMixin(
personRef,
contact.class.Person,
contact.space.Contacts,
contact.mixin.Employee,
{
active: true,
role: employeeRole
}
)

const createEmployeeTx = txFactory.createTxMixin(
personRef,
contact.class.Person,
contact.space.Contacts,
contact.mixin.Employee,
{
active: true
}
)

await client.tx(createEmployeeTx)
})
}
await client.tx(createEmployeeTx)
})
}

// TODO: check for merged persons with this one and do the merge
Expand Down
4 changes: 4 additions & 0 deletions qms-tests/sanity/tests/documents/ES-40.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ test.describe('ISO 13485, 4.2.4 Control of documents ensure that documents of ex
await page.waitForURL((url) => {
return url.pathname.startsWith(`/workbench/${PlatformWs}/`)
})
// We should wait for the user's client to finish creating social ids and ensuring employee
// Otherwise, when done in parallel with kicking the workspace the employee creation
// finishes after the kick. Better later change to some more meaningful wait but it's not obvious now.
await page.waitForTimeout(2000)
})

await test.step('2. check if owner can kick user from workspace', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export class DocumentContentPage extends DocumentCommonPage {
this.employeeDropdown = page.locator('div:nth-child(4) > button').first()
this.kickEmployee = page.getByRole('button', { name: 'Kick employee' })
this.confirmKickEmployee = page.getByRole('button', { name: 'Ok' })
this.openTeam = page.getByText('Team')
this.openTeam = page.getByText('Team', { exact: true })
this.privateToggle = page.locator('#teamspace-private span')
this.inputTeamspaceName = page.getByPlaceholder('New teamspace')
this.teamspaceArrow = page.locator('.w-full > button:nth-child(2)')
Expand Down
7 changes: 4 additions & 3 deletions server/account/src/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ import {
signUpByEmail,
verifyAllowedServices,
verifyPassword,
wrap
wrap,
getWorkspaceRole
} from './utils'

// Move to config?
Expand Down Expand Up @@ -1229,7 +1230,7 @@ export async function getLoginInfoByToken (
}
}

const role = isSystem ? AccountRole.Owner : await db.getWorkspaceRole(accountUuid, workspace.uuid)
const role = await getWorkspaceRole(db, accountUuid, workspace.uuid)

if (role == null) {
// User might have been removed from the workspace
Expand Down Expand Up @@ -1340,7 +1341,7 @@ export async function getWorkspaceMembers (
throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid: workspace }))
}

const accRole = await db.getWorkspaceRole(account, workspace)
const accRole = await getWorkspaceRole(db, account, workspace)

if (accRole == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
Expand Down
12 changes: 12 additions & 0 deletions server/account/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1215,3 +1215,15 @@ export async function getInviteEmail (
to: email
}
}

export async function getWorkspaceRole (
db: AccountDB,
account: PersonUuid,
workspace: WorkspaceUuid
): Promise<AccountRole | null> {
if (account === systemAccountUuid) {
return AccountRole.Owner
}

return await db.getWorkspaceRole(account, workspace)
}

0 comments on commit 3bac42a

Please sign in to comment.