[UBER-334] Add categories to the assignee popup (#3324)

This commit is contained in:
Sergei Ogorelkov 2023-06-01 20:38:53 +04:00 committed by GitHub
parent eee6e69114
commit 8ce6d005df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 112 additions and 210 deletions

View File

@ -604,13 +604,9 @@ export function createModel (builder: Builder): void {
},
{
key: 'assignee',
presenter: tracker.component.AssigneePresenter,
presenter: tracker.component.AssigneeEditor,
displayProps: { key: 'assigee', fixed: 'right' },
props: {
key: 'assignee',
defaultClass: contact.class.Employee,
shouldShowLabel: false
}
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
}
],
options: {
@ -715,8 +711,8 @@ export function createModel (builder: Builder): void {
},
{
key: 'assignee',
presenter: tracker.component.AssigneePresenter,
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
presenter: tracker.component.AssigneeEditor,
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
}
]
},
@ -791,8 +787,8 @@ export function createModel (builder: Builder): void {
},
{
key: 'assignee',
presenter: tracker.component.AssigneePresenter,
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
presenter: tracker.component.AssigneeEditor,
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
}
]
},

View File

@ -86,12 +86,11 @@
"AvatarProvider": "Avatar provider",
"GravatarsManaged": "Gravatars are managed",
"Through": "through",
"CategoryProjectMembers": "Project members",
"AddMembersHeader": "Add members to {value}:",
"Assigned": "Assigned",
"Unassigned": "Unassigned",
"CategoryPreviousAssigned": "Previously assigned",
"CategoryProjectLead": "Project lead",
"CategoryComponentLead": "Component lead",
"CategoryCurrentUser": "Current user",
"CategoryOther": "Other",
"NumberMembers": "{count, plural, =0 {no members} =1 {1 member} other {# members}}",

View File

@ -92,8 +92,7 @@
"Unassigned": "Не назначен",
"CategoryCurrentUser": "Текущий пользователь",
"CategoryPreviousAssigned": "Ранее назначенные",
"CategoryProjectLead": "Руководитель проекта",
"CategoryProjectMembers": "Участники проекта",
"CategoryComponentLead": "Ответственный за компонент",
"CategoryOther": "Прочие",
"Position": "Должность",
"ConfigLabel": "Контакты",

View File

@ -4,21 +4,13 @@ import contact from './plugin'
/**
* @public
*/
export type AssigneeCategory =
| 'CurrentUser'
| 'Assigned'
| 'PreviouslyAssigned'
| 'ProjectLead'
| 'ProjectMembers'
| 'Members'
| 'Other'
export type AssigneeCategory = 'CurrentUser' | 'Assigned' | 'PreviouslyAssigned' | 'ComponentLead' | 'Members' | 'Other'
const assigneeCategoryTitleMap: Record<AssigneeCategory, IntlString> = Object.freeze({
CurrentUser: contact.string.CategoryCurrentUser,
Assigned: contact.string.Assigned,
PreviouslyAssigned: contact.string.CategoryPreviousAssigned,
ProjectLead: contact.string.CategoryProjectLead,
ProjectMembers: contact.string.CategoryProjectMembers,
ComponentLead: contact.string.CategoryComponentLead,
Members: contact.string.Members,
Other: contact.string.CategoryOther
})
@ -30,8 +22,7 @@ export const assigneeCategoryOrder: AssigneeCategory[] = [
'CurrentUser',
'Assigned',
'PreviouslyAssigned',
'ProjectLead',
'ProjectMembers',
'ComponentLead',
'Members',
'Other'
]

View File

@ -35,9 +35,11 @@
import view from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import presentation, { getClient } from '@hcengineering/presentation'
import { PersonLabelTooltip, employeeByIdStore } from '..'
import AssigneePopup from './AssigneePopup.svelte'
import IconPerson from './icons/Person.svelte'
import UserInfo from './UserInfo.svelte'
import EmployeePresenter from './EmployeePresenter.svelte'
export let _class: Ref<Class<Employee>> = contact.class.Employee
export let excluded: Ref<Contact>[] | undefined = undefined
@ -49,8 +51,7 @@
export let placeholder: IntlString = presentation.string.Search
export let value: Ref<Employee> | null | undefined
export let prevAssigned: Ref<Employee>[] | undefined = []
export let projectLead: Ref<Employee> | undefined = undefined
export let projectMembers: Ref<Employee>[] | undefined = []
export let componentLead: Ref<Employee> | undefined = undefined
export let members: Ref<Employee>[] | undefined = []
export let allowDeselect = true
export let titleDeselect: IntlString | undefined = undefined
@ -61,8 +62,9 @@
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let focusIndex = -1
export let showTooltip: LabelAndProps | undefined = undefined
export let showTooltip: LabelAndProps | PersonLabelTooltip | undefined = undefined
export let showNavigate = true
export let shouldShowName = true
export let id: string | undefined = undefined
export let short: boolean = false
@ -76,7 +78,7 @@
const client = getClient()
async function updateSelected (value: Ref<Employee> | null | undefined) {
selected = value ? await client.findOne(_class, { _id: value }) : undefined
selected = value ? $employeeByIdStore.get(value) ?? (await client.findOne(_class, { _id: value })) : undefined
}
$: updateSelected(value)
@ -85,6 +87,9 @@
const _click = (ev: MouseEvent): void => {
if (!readonly) {
ev.preventDefault()
ev.stopPropagation()
showPopup(
AssigneePopup,
{
@ -92,8 +97,7 @@
options,
docQuery,
prevAssigned,
projectLead,
projectMembers,
componentLead,
members,
ignoreUsers: excluded ?? [],
icon,
@ -131,6 +135,15 @@
>
<slot name="content" />
</div>
{:else if !shouldShowName}
<EmployeePresenter
value={selected}
{avatarSize}
tooltipLabels={showTooltip}
shouldShowName={false}
shouldShowPlaceholder
onEmployeeEdit={_click}
/>
{:else}
<Button {focusIndex} width={width ?? 'min-content'} {size} {kind} {justify} {showTooltip} on:click={_click}>
<span

View File

@ -39,8 +39,7 @@
export let selected: Ref<Person> | undefined
export let docQuery: DocumentQuery<Contact> | undefined = undefined
export let prevAssigned: Ref<Employee>[] | undefined = []
export let projectLead: Ref<Employee> | undefined = undefined
export let projectMembers: Ref<Employee>[] | undefined = []
export let componentLead: Ref<Employee> | undefined = undefined
export let members: Ref<Employee>[] | undefined = []
export let allowDeselect = true
export let titleDeselect: IntlString | undefined
@ -49,7 +48,6 @@
export let ignoreUsers: Ref<Person>[] = []
export let shadows: boolean = true
export let width: 'medium' | 'large' | 'full' = 'medium'
export let size: 'small' | 'medium' | 'large' = 'small'
export let searchField: string = 'name'
export let showCategories: boolean = true
export let icon: Asset | AnySvelteComponent | undefined = undefined
@ -83,22 +81,20 @@
{ ...(options ?? {}), limit: 200, sort: { name: 1 } }
)
$: updateCategories(objects, currentEmployee, prevAssigned, projectLead, members, projectMembers)
$: updateCategories(objects, currentEmployee, prevAssigned, componentLead, members)
function updateCategories (
objects: Contact[],
currentEmployee: Ref<Person>,
prevAssigned: Ref<Person>[] | undefined,
projectLead: Ref<Person> | undefined,
members: Ref<Person>[] | undefined,
projectMembers: Ref<Person>[] | undefined
componentLead: Ref<Person> | undefined,
members: Ref<Person>[] | undefined
) {
const persons = new Map<Ref<Person>, AssigneeCategory>(objects.map((t) => [t._id, 'Other']))
if (projectLead) {
persons.set(projectLead, 'ProjectLead')
if (componentLead) {
persons.set(componentLead, 'ComponentLead')
}
members?.forEach((p) => persons.set(p, 'Members'))
projectMembers?.forEach((p) => persons.set(p, 'ProjectMembers'))
prevAssigned?.forEach((p) => persons.set(p, 'PreviouslyAssigned'))
if (selected) {
persons.set(selected, 'Assigned')

View File

@ -47,6 +47,7 @@
label: getEmbeddedLabel(getName(value))
}
}
const direction = tooltipLabels?.direction
const component = value ? tooltipLabels.component : undefined
const label = value
? tooltipLabels.personLabel
@ -59,7 +60,8 @@
return {
component,
label,
props
props,
direction
}
}
</script>

View File

@ -19,7 +19,7 @@ import { Class, Client, DocumentQuery, Ref, RelatedDocument, WithLookup } from '
import login from '@hcengineering/login'
import { IntlString, Resources, getResource } from '@hcengineering/platform'
import { MessageBox, ObjectSearchResult, getClient, getFileUrl } from '@hcengineering/presentation'
import { AnyComponent, AnySvelteComponent, showPopup } from '@hcengineering/ui'
import { AnyComponent, AnySvelteComponent, TooltipAlignment, showPopup } from '@hcengineering/ui'
import AccountArrayEditor from './components/AccountArrayEditor.svelte'
import AccountBox from './components/AccountBox.svelte'
import AssigneeBox from './components/AssigneeBox.svelte'
@ -246,6 +246,7 @@ async function openChannelURL (doc: Channel): Promise<void> {
export interface PersonLabelTooltip {
personLabel?: IntlString
placeholderLabel?: IntlString
direction?: TooltipAlignment
component?: AnySvelteComponent | AnyComponent
props?: any
}

View File

@ -69,13 +69,12 @@ export default mergeIds(contactId, contact, {
Through: '' as IntlString,
AvatarProvider: '' as IntlString,
CategoryProjectMembers: '' as IntlString,
AddMembersHeader: '' as IntlString,
Assigned: '' as IntlString,
Unassigned: '' as IntlString,
CategoryCurrentUser: '' as IntlString,
CategoryPreviousAssigned: '' as IntlString,
CategoryProjectLead: '' as IntlString,
CategoryComponentLead: '' as IntlString,
CategoryOther: '' as IntlString,
DeleteEmployee: '' as IntlString
},

View File

@ -662,10 +662,9 @@
<div id="assignee-editor">
<AssigneeEditor
focusIndex={5}
value={object}
{object}
kind={'secondary'}
size={'large'}
width={'min-content'}
short
on:change={({ detail }) => {
isAssigneeTouched = true

View File

@ -15,50 +15,48 @@
<script lang="ts">
import { Employee, EmployeeAccount } from '@hcengineering/contact'
import { AssigneeBox, employeeAccountByIdStore } from '@hcengineering/contact-resources'
import { AttachedData, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Issue, IssueDraft, IssueTemplateData } from '@hcengineering/tracker'
import { Doc, DocumentQuery, Ref } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Issue, Project } from '@hcengineering/tracker'
import { ButtonKind, ButtonSize, IconSize, TooltipAlignment } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import { getPreviousAssignees } from '../../utils'
import tracker from '../../plugin'
export let value: Issue | AttachedData<Issue> | IssueTemplateData | IssueDraft
type Object = (Doc | {}) & Pick<Issue, 'space' | 'component' | 'assignee'>
export let object: Object
export let kind: ButtonKind = 'link'
export let size: ButtonSize = 'large'
export let avatarSize: IconSize = 'card'
export let tooltipAlignment: TooltipAlignment | undefined = undefined
export let width: string = '100%'
export let width: string = 'min-content'
export let focusIndex: number | undefined = undefined
export let short: boolean = false
export let shouldShowName = true
const client = getClient()
const dispatch = createEventDispatcher()
const projectQuery = createQuery()
let project: Project | undefined
let prevAssigned: Ref<Employee>[] = []
let projectLead: Ref<Employee> | undefined = undefined
let projectMembers: Ref<Employee>[] = []
let componentLead: Ref<Employee> | undefined = undefined
let members: Ref<Employee>[] = []
let docQuery: DocumentQuery<Employee> = { active: true }
$: '_class' in value &&
getPreviousAssignees(value).then((res) => {
$: '_class' in object &&
getPreviousAssignees(object._id).then((res) => {
prevAssigned = res
})
function hasSpace (issue: Issue | AttachedData<Issue> | IssueTemplateData | IssueDraft): issue is Issue {
return (issue as Issue).space !== undefined
}
async function updateComponentMembers (issue: Issue | AttachedData<Issue> | IssueTemplateData | IssueDraft) {
async function updateComponentMembers (project: Project, issue: Object) {
if (issue.component) {
const component = await client.findOne(tracker.class.Component, { _id: issue.component })
projectLead = component?.lead || undefined
componentLead = component?.lead || undefined
} else {
projectLead = undefined
componentLead = undefined
}
projectMembers = []
if (hasSpace(issue)) {
const project = await client.findOne(tracker.class.Project, { _id: issue.space })
if (project !== undefined) {
const accounts = project.members
.map((p) => $employeeAccountByIdStore.get(p as Ref<EmployeeAccount>))
@ -67,33 +65,36 @@
} else {
members = []
}
}
}
$: updateComponentMembers(value)
docQuery = project?.private ? { _id: { $in: members }, active: true } : { active: true }
}
const handleAssigneeChanged = async (newAssignee: Ref<Employee> | undefined) => {
if (newAssignee === undefined || value.assignee === newAssignee) {
if (newAssignee === undefined || object.assignee === newAssignee) {
return
}
dispatch('change', newAssignee)
if ('_class' in value) {
await client.update(value, { assignee: newAssignee })
if ('_class' in object) {
await client.update(object, { assignee: newAssignee })
}
}
$: projectQuery.query(tracker.class.Project, { _id: object.space }, (res) => ([project] = res))
$: project && updateComponentMembers(project, object)
$: docQuery = project?.private ? { _id: { $in: members }, active: true } : { active: true }
</script>
{#if value}
{#if object}
<AssigneeBox
{docQuery}
{focusIndex}
label={tracker.string.Assignee}
placeholder={tracker.string.Assignee}
value={value.assignee}
value={object.assignee}
{prevAssigned}
{projectLead}
{projectMembers}
{componentLead}
{members}
titleDeselect={tracker.string.Unassigned}
{size}
@ -101,9 +102,15 @@
{avatarSize}
{width}
{short}
{shouldShowName}
showNavigate={false}
justify={'left'}
showTooltip={{ label: tracker.string.AssignTo, direction: tooltipAlignment }}
showTooltip={{
label: tracker.string.AssignTo,
personLabel: tracker.string.AssignedTo,
placeholderLabel: tracker.string.Unassigned,
direction: tooltipAlignment
}}
on:change={({ detail }) => handleAssigneeChanged(detail)}
/>
{/if}

View File

@ -1,99 +0,0 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee } from '@hcengineering/contact'
import { UsersPopup } from '@hcengineering/contact-resources'
import { Class, Doc, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { Issue, IssueTemplate } from '@hcengineering/tracker'
import { eventToHTMLElement, showPopup, IconSize } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import { getObjectPresenter } from '@hcengineering/view-resources'
import tracker from '../../plugin'
export let value: Employee | Ref<Employee> | null | undefined
export let object: Issue | IssueTemplate
export let defaultClass: Ref<Class<Doc>> | undefined = undefined
export let isEditable: boolean = true
export let shouldShowLabel: boolean = false
export let defaultName: IntlString | undefined = undefined
export let avatarSize: IconSize = 'x-small'
const client = getClient()
let presenter: AttributeModel | undefined
$: if (value || defaultClass) {
if (value) {
getObjectPresenter(client, typeof value === 'string' ? contact.class.Employee : value._class, { key: '' }).then(
(p) => {
presenter = p
}
)
} else if (defaultClass) {
getObjectPresenter(client, defaultClass, { key: '' }).then((p) => {
presenter = p
})
}
}
const handleAssigneeChanged = async (result: Employee | null | undefined) => {
if (!isEditable || result === undefined) {
return
}
const newAssignee = result === null ? null : result._id
await client.update(object, { assignee: newAssignee })
}
const handleAssigneeEditorOpened = async (event: MouseEvent) => {
if (!isEditable) {
return
}
event?.preventDefault()
event?.stopPropagation()
showPopup(
UsersPopup,
{
_class: contact.class.Employee,
selected: typeof value === 'string' ? value : value?._id,
docQuery: {
active: true
},
allowDeselect: true,
placeholder: tracker.string.AssignTo
},
eventToHTMLElement(event),
handleAssigneeChanged
)
}
</script>
{#if presenter}
<svelte:component
this={presenter.presenter}
{value}
{defaultName}
{avatarSize}
disabled={false}
shouldShowPlaceholder={true}
shouldShowName={shouldShowLabel}
onEmployeeEdit={handleAssigneeEditorOpened}
tooltipLabels={{ personLabel: tracker.string.AssignedTo, placeholderLabel: tracker.string.Unassigned }}
/>
{/if}

View File

@ -83,7 +83,7 @@
<StatusEditor value={issue} shouldShowLabel kind={'secondary'} />
<PriorityEditor value={issue} shouldShowLabel kind={'secondary'} />
{#if issue.assignee}
<AssigneeEditor value={issue} width={'min-content'} kind={'secondary'} />
<AssigneeEditor object={issue} kind={'secondary'} />
{/if}
</div>

View File

@ -15,8 +15,6 @@
<script lang="ts">
import { AttachmentsPresenter } from '@hcengineering/attachment-resources'
import { CommentsPresenter } from '@hcengineering/chunter-resources'
import contact from '@hcengineering/contact'
import { employeeByIdStore } from '@hcengineering/contact-resources'
import {
CategoryType,
Class,
@ -77,7 +75,7 @@
import tracker from '../../plugin'
import ComponentEditor from '../components/ComponentEditor.svelte'
import CreateIssue from '../CreateIssue.svelte'
import AssigneePresenter from './AssigneePresenter.svelte'
import AssigneeEditor from './AssigneeEditor.svelte'
import DueDatePresenter from './DueDatePresenter.svelte'
import SubIssuesSelector from './edit/SubIssuesSelector.svelte'
import IssuePresenter from './IssuePresenter.svelte'
@ -366,13 +364,7 @@
</div>
<div class="flex-row-center gap-2 reverse flex-no-shrink">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
<AssigneePresenter
value={issue.assignee ? $employeeByIdStore.get(issue.assignee) : null}
defaultClass={contact.class.Employee}
object={issue}
isEditable={true}
avatarSize={'card'}
/>
<AssigneeEditor object={issue} avatarSize={'card'} shouldShowName={false} />
</div>
</div>
<div class="card-content text-md caption-color lines-limit-2">

View File

@ -161,7 +161,7 @@
<span class="label">
<Label label={tracker.string.Assignee} />
</span>
<AssigneeEditor value={issue} size={'medium'} avatarSize={'card'} />
<AssigneeEditor object={issue} size={'medium'} avatarSize={'card'} width="100%" />
<span class="labelTop">
<Label label={tracker.string.Labels} />

View File

@ -176,10 +176,9 @@
on:change={({ detail }) => (object.priority = detail)}
/>
<AssigneeEditor
value={object}
object={{ ...object, space }}
kind={'secondary'}
size={'large'}
width={'min-content'}
on:change={({ detail }) => (object.assignee = detail)}
/>
<Component

View File

@ -210,7 +210,7 @@
<div id="sub-issue-assignee-editor">
{#key object.assignee}
<AssigneeEditor
value={object}
{object}
size="small"
kind="no-border"
on:change={({ detail }) => (object.assignee = detail)}

View File

@ -163,7 +163,7 @@
}}
/>
<AssigneeEditor
value={issue}
object={issue}
on:change={(evt) => {
dispatch('update-issue', { id: issue._id, assignee: evt.detail })
issue.assignee = evt.detail

View File

@ -17,7 +17,13 @@
import presentation, { createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
import tags, { TagElement, TagReference } from '@hcengineering/tags'
import { StyledTextArea } from '@hcengineering/text-editor'
import { IssuePriority, IssueTemplateChild, Component as ComponentType, Milestone } from '@hcengineering/tracker'
import {
IssuePriority,
IssueTemplateChild,
Component as ComponentType,
Milestone,
Project
} from '@hcengineering/tracker'
import { Button, Component, EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
@ -25,6 +31,7 @@
import PriorityEditor from '../issues/PriorityEditor.svelte'
import EstimationEditor from './EstimationEditor.svelte'
export let projectId: Ref<Project>
export let milestone: Ref<Milestone> | null = null
export let component: Ref<ComponentType> | null = null
export let childIssue: IssueTemplateChild | undefined = undefined
@ -142,7 +149,7 @@
/>
{#key newIssue.assignee}
<AssigneeEditor
value={newIssue}
object={{ ...newIssue, space: projectId }}
size="small"
kind="no-border"
width="auto"

View File

@ -158,7 +158,7 @@
}}
/>
<AssigneeEditor
value={issue}
object={{ ...issue, space: project }}
on:change={(evt) => {
dispatch('update-issue', { id: issue.id, assignee: evt.detail })
issue.assignee = evt.detail

View File

@ -105,6 +105,7 @@
{#if isCreating}
<ExpandCollapse isExpanded={!isCollapsed} on:changeContent>
<IssueTemplateChildEditor
projectId={project}
{component}
{milestone}
{isScrollable}

View File

@ -73,7 +73,7 @@
<span class="label">
<Label label={tracker.string.Assignee} />
</span>
<AssigneeEditor value={issue} size={'medium'} />
<AssigneeEditor object={issue} size={'medium'} width="100%" />
<span class="labelTop">
<Label label={tracker.string.Labels} />

View File

@ -37,7 +37,7 @@ import LeadPresenter from './components/components/LeadPresenter.svelte'
import ProjectComponents from './components/components/ProjectComponents.svelte'
import CreateIssue from './components/CreateIssue.svelte'
import Inbox from './components/inbox/Inbox.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
import AssigneeEditor from './components/issues/AssigneeEditor.svelte'
import DueDatePresenter from './components/issues/DueDatePresenter.svelte'
import EditIssue from './components/issues/edit/EditIssue.svelte'
import IssueItem from './components/issues/IssueItem.svelte'
@ -395,7 +395,7 @@ export default async (): Promise<Resources> => ({
ComponentEditor,
StatusPresenter,
StatusEditor,
AssigneePresenter,
AssigneeEditor,
DueDatePresenter,
EditIssue,
NewIssueHeader,

View File

@ -326,7 +326,7 @@ export default mergeIds(trackerId, tracker, {
StatusPresenter: '' as AnyComponent,
StatusRefPresenter: '' as AnyComponent,
StatusEditor: '' as AnyComponent,
AssigneePresenter: '' as AnyComponent,
AssigneeEditor: '' as AnyComponent,
DueDatePresenter: '' as AnyComponent,
EditIssueTemplate: '' as AnyComponent,
CreateProject: '' as AnyComponent,

View File

@ -463,13 +463,13 @@ export function subIssueListProvider (subIssues: Issue[], target: Ref<Issue>): v
}
}
export async function getPreviousAssignees (issue: Issue): Promise<Array<Ref<Employee>>> {
export async function getPreviousAssignees (objectId: Ref<Doc>): Promise<Array<Ref<Employee>>> {
return await new Promise((resolve) => {
const query = createQuery(true)
query.query(
core.class.Tx,
{
'tx.objectId': issue._id,
'tx.objectId': objectId,
'tx.operations.assignee': { $exists: true }
},
(res) => {