UBERF-6001: Roles management (#4994)

Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
Alexey Zinoviev
2024-03-27 13:49:36 +05:00
committed by GitHub
parent bee1986299
commit 418b4b1bbd
112 changed files with 3448 additions and 874 deletions
@@ -13,9 +13,20 @@
// limitations under the License.
-->
<script lang="ts">
import { deepEqual } from 'fast-equals'
import { Employee } from '@hcengineering/contact'
import { AccountArrayEditor, AssigneeBox } from '@hcengineering/contact-resources'
import core, { Account, Data, DocumentUpdate, Ref, generateId, getCurrentAccount } from '@hcengineering/core'
import core, {
Account,
Data,
DocumentUpdate,
RolesAssignment,
Ref,
Role,
SortingOrder,
generateId,
getCurrentAccount
} from '@hcengineering/core'
import { Asset } from '@hcengineering/platform'
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
import task, { ProjectType } from '@hcengineering/task'
@@ -25,11 +36,9 @@
Button,
Component,
EditBox,
IconEdit,
IconWithEmoji,
Label,
Toggle,
eventToHTMLElement,
getColorNumberByText,
getPlatformColorDef,
getPlatformColorForTextDef,
@@ -38,14 +47,12 @@
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { IconPicker } from '@hcengineering/view-resources'
import { typeStore, taskTypeStore } from '@hcengineering/task-resources'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import StatusSelector from '../issues/StatusSelector.svelte'
import ChangeIdentity from './ChangeIdentity.svelte'
import { typeStore, taskTypeStore } from '@hcengineering/task-resources'
export let project: Project | undefined = undefined
export let namePlaceholder: string = ''
export let descriptionPlaceholder: string = ''
@@ -65,6 +72,7 @@
let projectsIdentifiers = new Set<string>()
let isSaving = false
let defaultStatus: Ref<IssueStatus> | undefined = project?.defaultIssueStatus
let rolesAssignment: RolesAssignment | undefined
let changeIdentityRef: HTMLElement
@@ -99,8 +107,22 @@
}
}
function getRolesAssignment (): RolesAssignment {
if (project === undefined || typeType?.targetClass === undefined || roles === undefined) {
return {}
}
const asMixin = hierarchy.as(project, typeType?.targetClass)
return roles.reduce<RolesAssignment>((prev, { _id }) => {
prev[_id] = (asMixin as any)[_id]
return prev
}, {})
}
async function updateProject (): Promise<void> {
if (!project) {
if (!project || typeType?.targetClass === undefined) {
return
}
@@ -147,11 +169,20 @@
isSaving = false
}
if (rolesAssignment && !deepEqual(rolesAssignment, getRolesAssignment())) {
await client.updateMixin(
project._id,
tracker.class.Project,
core.space.Space,
typeType.targetClass,
rolesAssignment
)
}
close()
}
let typeId: Ref<ProjectType> | undefined = project?.type
$: typeType = typeId !== undefined ? $typeStore.get(typeId) : undefined
$: if (defaultStatus === undefined && typeType !== undefined) {
@@ -171,8 +202,14 @@
const succeeded = await ops.commit()
if (succeeded) {
// Add vacancy mixin
await client.createMixin(projectId, tracker.class.Project, core.space.Space, typeType.targetClass, {})
// Add space type's mixin with roles assignments
await client.createMixin(
projectId,
tracker.class.Project,
core.space.Space,
typeType.targetClass,
rolesAssignment ?? {}
)
close(projectId)
} else {
@@ -207,6 +244,52 @@
}
$: identifier = identifier.toLocaleUpperCase().replaceAll('-', '_').replaceAll(' ', '_').substring(0, 5)
let roles: Role[] = []
const rolesQuery = createQuery()
$: if (typeType !== undefined) {
rolesQuery.query(
core.class.Role,
{ attachedTo: typeType._id },
(res) => {
roles = res
if (rolesAssignment === undefined && typeType !== undefined) {
rolesAssignment = getRolesAssignment()
}
},
{
sort: {
name: SortingOrder.Ascending
}
}
)
} else {
rolesQuery.unsubscribe()
}
function handleMembersChanged (newMembers: Ref<Account>[]): void {
// If a member was removed we need to remove it from any roles assignments as well
const newMembersSet = new Set(newMembers)
const removedMembersSet = new Set(members.filter((m) => !newMembersSet.has(m)))
if (removedMembersSet.size > 0 && rolesAssignment !== undefined) {
for (const [key, value] of Object.entries(rolesAssignment)) {
rolesAssignment[key as Ref<Role>] =
value !== undefined ? value.filter((m) => !removedMembersSet.has(m)) : undefined
}
}
members = newMembers
}
function handleRoleAssignmentChanged (roleId: Ref<Role>, newMembers: Ref<Account>[]): void {
if (rolesAssignment === undefined) {
rolesAssignment = {}
}
rolesAssignment[roleId] = newMembers
}
</script>
<Card
@@ -326,19 +409,6 @@
<Toggle bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={tracker.string.Members} />
</div>
<AccountArrayEditor
value={members}
label={tracker.string.Members}
onChange={(refs) => (members = refs)}
kind={'regular'}
size={'large'}
/>
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={tracker.string.DefaultAssignee} />
@@ -369,6 +439,38 @@
size={'large'}
/>
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={tracker.string.Members} />
</div>
<AccountArrayEditor
value={members}
label={tracker.string.Members}
onChange={handleMembersChanged}
kind={'regular'}
size={'large'}
/>
</div>
{#each roles as role}
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={tracker.string.RoleLabel} params={{ role: role.name }} />
</div>
<AccountArrayEditor
value={rolesAssignment?.[role._id] ?? []}
label={tracker.string.Members}
includeItems={members}
readonly={members.length === 0}
onChange={(refs) => {
handleRoleAssignmentChanged(role._id, refs)
}}
kind={'regular'}
size={'large'}
/>
</div>
{/each}
</div>
</Card>