mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Tracker: Add relation (#2174)
Signed-off-by: Dvinyanin Alexandr <dvinyanin.alexandr@gmail.com>
This commit is contained in:
parent
ce71d23f49
commit
25afe12cc2
@ -15,6 +15,7 @@ HR:
|
||||
|
||||
Tracker:
|
||||
- Manual issues ordering
|
||||
- Issue relations
|
||||
|
||||
Workbench
|
||||
- Use application aliases in URL
|
||||
|
@ -853,7 +853,7 @@ export function createModel (builder: Builder): void {
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.Move,
|
||||
label: view.string.Move,
|
||||
label: tracker.string.MoveToTeam,
|
||||
icon: view.icon.Move,
|
||||
keyBinding: [],
|
||||
input: 'none',
|
||||
@ -862,9 +862,32 @@ export function createModel (builder: Builder): void {
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
tracker.action.CopyIssueLink
|
||||
tracker.action.MoveToTeam
|
||||
)
|
||||
// TODO: fix icon
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ValueSelector,
|
||||
actionPopup: tracker.component.RelationsPopup,
|
||||
actionProps: {
|
||||
attribute: ''
|
||||
},
|
||||
label: tracker.string.Relations,
|
||||
icon: tracker.icon.Document,
|
||||
keyBinding: [],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
tracker.action.Relations
|
||||
)
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
"dependencies": {
|
||||
"@anticrm/platform": "~0.6.6",
|
||||
"@anticrm/theme": "~0.6.0",
|
||||
"@anticrm/core": "~0.6.16",
|
||||
"svelte": "^3.47",
|
||||
"@types/jest": "~28.1.0"
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { afterUpdate, createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
import { generateId } from '@anticrm/core'
|
||||
import ui from '../plugin'
|
||||
import { closePopup, showPopup } from '../popups'
|
||||
import { Action } from '../types'
|
||||
@ -27,6 +28,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
const btns: HTMLElement[] = []
|
||||
let activeElement: HTMLElement
|
||||
const category = generateId()
|
||||
|
||||
const keyDown = (ev: KeyboardEvent): void => {
|
||||
if (ev.key === 'Tab') {
|
||||
@ -51,7 +53,7 @@
|
||||
}
|
||||
if (ev.key === 'ArrowLeft') {
|
||||
dispatch('update', 'left')
|
||||
closePopup('submenu')
|
||||
closePopup(category)
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
}
|
||||
@ -72,11 +74,11 @@
|
||||
}
|
||||
})
|
||||
onDestroy(() => {
|
||||
closePopup('submenu')
|
||||
closePopup(category)
|
||||
})
|
||||
|
||||
function showActionPopup (action: Action, target: HTMLElement): void {
|
||||
closePopup('submenu')
|
||||
closePopup(category)
|
||||
if (action.component !== undefined) {
|
||||
console.log(action.props)
|
||||
showPopup(
|
||||
@ -87,7 +89,7 @@
|
||||
dispatch('close')
|
||||
},
|
||||
undefined,
|
||||
{ category: 'submenu', overlay: false }
|
||||
{ category, overlay: false }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,7 @@
|
||||
"ProjectLeadSearchPlaceholder": "Set project lead\u2026",
|
||||
"ProjectMembersSearchPlaceholder": "Change project members\u2026",
|
||||
"Roadmap": "Roadmap",
|
||||
"MoveToTeam": "Move to team",
|
||||
|
||||
"GotoIssues": "Go to issues",
|
||||
"GotoActive": "Go to active issues",
|
||||
@ -141,6 +142,20 @@
|
||||
"Created": "Created",
|
||||
"Subscribed": "Subscribed",
|
||||
|
||||
"Relations": "Relations",
|
||||
"RemoveRelation": "Remove relation...",
|
||||
"AddBlockedBy": "Mark as blocked by...",
|
||||
"AddIsBlocking": "Mark as bloking...",
|
||||
"AddRelatedIssue": "Reference another issue...",
|
||||
"RelatedIssue": "Related issue {id} - {title}",
|
||||
"BlockedIssue": "Blocked issue {id} - {title}",
|
||||
"BlockingIssue": "Blocking issue {id} - {title}",
|
||||
"BlockedBySearchPlaceholder": "Search for issue to mark blocked by...",
|
||||
"IsBlockingSearchPlaceholder": "Search for issue to mark as blocking...",
|
||||
"RelatedIssueSearchPlaceholder": "Search for issue to reference...",
|
||||
"Blocks": "Blocks",
|
||||
"Related": "Related",
|
||||
|
||||
"EditIssue": "Edit {title}",
|
||||
|
||||
"Save": "Save",
|
||||
|
@ -123,6 +123,7 @@
|
||||
"ProjectLeadSearchPlaceholder": "Назначьте руководителя проекта\u2026",
|
||||
"ProjectMembersSearchPlaceholder": "Измененить участников проекта\u2026",
|
||||
"Roadmap": "Планирование",
|
||||
"MoveToTeam": "Изменить команду",
|
||||
|
||||
"GotoIssues": "Перейти к задачам",
|
||||
"GotoActive": "Перейти к активным задачам",
|
||||
@ -141,6 +142,20 @@
|
||||
"Created": "Созданные",
|
||||
"Subscribed": "Отслеживаемые",
|
||||
|
||||
"Relations": "Зависимости",
|
||||
"RemoveRelation": "Удалить зависимость...",
|
||||
"AddBlockedBy": "Отметить как блокируемую...",
|
||||
"AddIsBlocking": "Отметить как блокирующую...",
|
||||
"AddRelatedIssue": "Связать с задачей...",
|
||||
"RelatedIssue": "Связанная задача {id} - {title}",
|
||||
"BlockedIssue": "Блокируемая задача {id} - {title}",
|
||||
"BlockingIssue": "Блокирующая {id} - {title}",
|
||||
"BlockedBySearchPlaceholder": "Поиск блокирующей задачи...",
|
||||
"IsBlockingSearchPlaceholder": "Поиск блокируемой задачи...",
|
||||
"RelatedIssueSearchPlaceholder": "Поиск связанной задачи...",
|
||||
"Blocks": "Блокирует",
|
||||
"Related": "Связан",
|
||||
|
||||
"EditIssue": "Редактирование {title}",
|
||||
|
||||
"Save": "Сохранить",
|
||||
|
@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Issue } from '@anticrm/tracker'
|
||||
import { Action, closePopup, Menu, showPopup } from '@anticrm/ui'
|
||||
import SelectIssuePopup from './SelectIssuePopup.svelte'
|
||||
import SelectRelationPopup from './SelectRelationPopup.svelte'
|
||||
import tracker from '../plugin'
|
||||
import { updateIssueRelation } from '../issues'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
|
||||
export let value: Issue
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
$: relations = {
|
||||
blockedBy: value.blockedBy ?? [],
|
||||
relatedIssue: value.relatedIssue ?? [],
|
||||
isBlocking: isBlocking ?? []
|
||||
}
|
||||
let isBlocking: Ref<Issue>[] = []
|
||||
$: query.query(tracker.class.Issue, { blockedBy: value._id }, (result) => {
|
||||
isBlocking = result.map(({ _id }) => _id)
|
||||
})
|
||||
$: hasRelation = Object.values(relations).some(({ length }) => length)
|
||||
|
||||
async function updateRelation (issue: Issue, type: keyof typeof relations, operation: '$push' | '$pull') {
|
||||
const prop = type === 'isBlocking' ? 'blockedBy' : type
|
||||
if (type !== 'isBlocking') {
|
||||
await updateIssueRelation(client, value, issue._id, prop, operation)
|
||||
}
|
||||
if (type !== 'blockedBy') {
|
||||
await updateIssueRelation(client, issue, value._id, prop, operation)
|
||||
}
|
||||
}
|
||||
|
||||
const makeAddAction = (type: keyof typeof relations, placeholder: IntlString) => async () => {
|
||||
closePopup('popup')
|
||||
showPopup(
|
||||
SelectIssuePopup,
|
||||
{ ignoreObjects: [value._id, ...relations[type]], placeholder },
|
||||
undefined,
|
||||
async (issue: Issue | undefined) => {
|
||||
if (!issue) return
|
||||
await updateRelation(issue, type, '$push')
|
||||
}
|
||||
)
|
||||
}
|
||||
async function removeRelation () {
|
||||
closePopup('popup')
|
||||
showPopup(
|
||||
SelectRelationPopup,
|
||||
relations,
|
||||
undefined,
|
||||
async (result: { type: keyof typeof relations; issue: Issue } | undefined) => {
|
||||
if (!result) return
|
||||
await updateRelation(result.issue, result.type, '$pull')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const removeRelationAction: Action[] = [
|
||||
{
|
||||
action: removeRelation,
|
||||
icon: tracker.icon.Issue,
|
||||
label: tracker.string.RemoveRelation,
|
||||
group: '1'
|
||||
}
|
||||
]
|
||||
$: actions = [
|
||||
{
|
||||
action: makeAddAction('blockedBy', tracker.string.BlockedBySearchPlaceholder),
|
||||
icon: tracker.icon.Issue,
|
||||
label: tracker.string.AddBlockedBy
|
||||
},
|
||||
{
|
||||
action: makeAddAction('isBlocking', tracker.string.IsBlockingSearchPlaceholder),
|
||||
icon: tracker.icon.Issue,
|
||||
label: tracker.string.AddIsBlocking
|
||||
},
|
||||
{
|
||||
action: makeAddAction('relatedIssue', tracker.string.RelatedIssueSearchPlaceholder),
|
||||
icon: tracker.icon.Issue,
|
||||
label: tracker.string.AddRelatedIssue
|
||||
},
|
||||
...(hasRelation ? removeRelationAction : [])
|
||||
]
|
||||
</script>
|
||||
|
||||
<Menu {actions} />
|
@ -0,0 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { DocumentQuery, FindOptions, Ref, SortingOrder } from '@anticrm/core'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { ObjectPopup } from '@anticrm/presentation'
|
||||
import { Issue } from '@anticrm/tracker'
|
||||
import { Icon } from '@anticrm/ui'
|
||||
import { getIssueId } from '../issues'
|
||||
import tracker from '../plugin'
|
||||
|
||||
export let docQuery: DocumentQuery<Issue> | undefined = undefined
|
||||
export let ignoreObjects: Ref<Issue>[] | undefined = undefined
|
||||
export let placeholder: IntlString | undefined = undefined
|
||||
export let width: 'medium' | 'large' | 'full' = 'large'
|
||||
|
||||
const options: FindOptions<Issue> = {
|
||||
lookup: {
|
||||
status: [tracker.class.IssueStatus, { category: tracker.class.IssueStatusCategory }],
|
||||
space: tracker.class.Team
|
||||
},
|
||||
sort: { modifiedOn: SortingOrder.Descending }
|
||||
}
|
||||
</script>
|
||||
|
||||
<ObjectPopup
|
||||
_class={tracker.class.Issue}
|
||||
{options}
|
||||
{docQuery}
|
||||
{placeholder}
|
||||
{ignoreObjects}
|
||||
{width}
|
||||
searchField="title"
|
||||
groupBy="space"
|
||||
on:update
|
||||
on:close
|
||||
>
|
||||
<svelte:fragment slot="item" let:item={issue}>
|
||||
{@const { icon } = issue.$lookup?.status.$lookup?.category ?? {}}
|
||||
{@const issueId = getIssueId(issue.$lookup.space, issue)}
|
||||
{#if issueId && icon}
|
||||
<div class="flex-center clear-mins w-full h-9">
|
||||
<div class="icon mr-4 h-8">
|
||||
<Icon {icon} size="small" />
|
||||
</div>
|
||||
<span class="overflow-label flex-no-shrink mr-3">{issueId}</span>
|
||||
<span class="overflow-label w-full issue-title">{issue.title}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ObjectPopup>
|
@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Issue } from '@anticrm/tracker'
|
||||
import { SelectPopup, Loading } from '@anticrm/ui'
|
||||
import { translate } from '@anticrm/platform'
|
||||
import { getIssueId } from '../issues'
|
||||
import tracker from '../plugin'
|
||||
|
||||
export let blockedBy: Ref<Issue>[] = []
|
||||
export let isBlocking: Ref<Issue>[] = []
|
||||
export let relatedIssue: Ref<Issue>[] = []
|
||||
|
||||
// TODO: fix icons
|
||||
const config = {
|
||||
blockedBy: {
|
||||
label: tracker.string.BlockedIssue,
|
||||
icon: tracker.icon.Issue
|
||||
},
|
||||
isBlocking: {
|
||||
label: tracker.string.BlockingIssue,
|
||||
icon: tracker.icon.Issues
|
||||
},
|
||||
relatedIssue: {
|
||||
label: tracker.string.RelatedIssue,
|
||||
icon: tracker.icon.Team
|
||||
}
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function getValue () {
|
||||
const issues = await client.findAll(
|
||||
tracker.class.Issue,
|
||||
{ _id: { $in: [...blockedBy, ...isBlocking, ...relatedIssue] } },
|
||||
{ lookup: { space: tracker.class.Team } }
|
||||
)
|
||||
const valueFactory = (type: keyof typeof config) => async (issueId: Ref<Issue>) => {
|
||||
const issue = issues.find(({ _id }) => _id === issueId)
|
||||
if (!issue?.$lookup?.space) return
|
||||
const { label, icon } = config[type]
|
||||
const text = await translate(label, { id: getIssueId(issue.$lookup.space, issue), title: issue.title })
|
||||
return { text, icon, issue, type }
|
||||
}
|
||||
return (
|
||||
await Promise.all([
|
||||
...blockedBy.map(valueFactory('blockedBy')),
|
||||
...isBlocking.map(valueFactory('isBlocking')),
|
||||
...relatedIssue.map(valueFactory('relatedIssue'))
|
||||
])
|
||||
).map((val, id) => ({ ...val, id }))
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await getValue()}
|
||||
<Loading />
|
||||
{:then value}
|
||||
<SelectPopup
|
||||
{value}
|
||||
width="large"
|
||||
searchable
|
||||
placeholder={tracker.string.RemoveRelation}
|
||||
on:close={(e) => {
|
||||
if (e.detail === undefined) dispatch('close')
|
||||
else dispatch('close', value[e.detail])
|
||||
}}
|
||||
/>
|
||||
{/await}
|
@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import { WithLookup } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Issue } from '@anticrm/tracker'
|
||||
import { Icon, IconClose } from '@anticrm/ui'
|
||||
import { getIssueId, updateIssueRelation } from '../../issues'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
export let value: Issue
|
||||
export let type: 'isBlocking' | 'blockedBy' | 'relatedIssue'
|
||||
|
||||
const client = getClient()
|
||||
const issuesQuery = createQuery()
|
||||
|
||||
// TODO: fix icon
|
||||
$: icon = tracker.icon.Issue
|
||||
$: query = type === 'isBlocking' ? { blockedBy: value._id } : { _id: { $in: value[type] } }
|
||||
let issues: WithLookup<Issue>[] = []
|
||||
$: issuesQuery.query(
|
||||
tracker.class.Issue,
|
||||
query,
|
||||
(result) => {
|
||||
issues = result
|
||||
},
|
||||
{ lookup: { space: tracker.class.Team } }
|
||||
)
|
||||
|
||||
async function handleClick (issue: Issue) {
|
||||
const prop = type === 'isBlocking' ? 'blockedBy' : type
|
||||
if (type !== 'isBlocking') {
|
||||
await updateIssueRelation(client, value, issue._id, prop, '$pull')
|
||||
}
|
||||
if (type !== 'blockedBy') {
|
||||
await updateIssueRelation(client, issue, value._id, prop, '$pull')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-column">
|
||||
{#each issues as issue}
|
||||
{#if issue.$lookup?.space}
|
||||
<div class="tag-container">
|
||||
<Icon {icon} size={'small'} />
|
||||
<span class="overflow-label ml-1-5 caption-color">{getIssueId(issue.$lookup.space, issue)}</span>
|
||||
<button class="btn-close" on:click|stopPropagation={() => handleClick(issue)}>
|
||||
<Icon icon={IconClose} size={'x-small'} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.tag-container {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
padding-left: 0.5rem;
|
||||
height: 1.5rem;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border-radius: 0.5rem;
|
||||
width: fit-content;
|
||||
&:hover {
|
||||
border: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.125rem;
|
||||
padding: 0 0.25rem 0 0.125rem;
|
||||
height: 1.75rem;
|
||||
color: var(--content-color);
|
||||
border-left: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
border-left-color: var(--divider-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -15,6 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { WithLookup } from '@anticrm/core'
|
||||
import type { Issue, IssueStatus } from '@anticrm/tracker'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Component, Label } from '@anticrm/ui'
|
||||
import tags from '@anticrm/tags'
|
||||
import tracker from '../../../plugin'
|
||||
@ -23,9 +24,16 @@
|
||||
import ProjectEditor from '../../projects/ProjectEditor.svelte'
|
||||
import AssigneeEditor from '../AssigneeEditor.svelte'
|
||||
import DueDateEditor from '../DueDateEditor.svelte'
|
||||
import RelationEditor from '../RelationEditor.svelte'
|
||||
|
||||
export let issue: Issue
|
||||
export let issueStatuses: WithLookup<IssueStatus>[]
|
||||
|
||||
const query = createQuery()
|
||||
let showIsBlocking = false
|
||||
query.query(tracker.class.Issue, { blockedBy: issue._id }, (result) => {
|
||||
showIsBlocking = result.length > 0
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="content">
|
||||
@ -34,6 +42,25 @@
|
||||
</span>
|
||||
<StatusEditor value={issue} statuses={issueStatuses} shouldShowLabel />
|
||||
|
||||
{#if issue.blockedBy?.length}
|
||||
<span class="labelTop">
|
||||
<Label label={tracker.string.BlockedBy} />
|
||||
</span>
|
||||
<RelationEditor value={issue} type="blockedBy" />
|
||||
{/if}
|
||||
{#if showIsBlocking}
|
||||
<span class="labelTop">
|
||||
<Label label={tracker.string.Blocks} />
|
||||
</span>
|
||||
<RelationEditor value={issue} type="isBlocking" />
|
||||
{/if}
|
||||
{#if issue.relatedIssue?.length}
|
||||
<span class="labelTop">
|
||||
<Label label={tracker.string.Related} />
|
||||
</span>
|
||||
<RelationEditor value={issue} type="relatedIssue" />
|
||||
{/if}
|
||||
|
||||
<span class="label">
|
||||
<Label label={tracker.string.Priority} />
|
||||
</span>
|
||||
|
@ -59,6 +59,7 @@ import KanbanView from './components/issues/KanbanView.svelte'
|
||||
import tracker from './plugin'
|
||||
import { copyToClipboard, getIssueId, getIssueTitle, resolveLocation } from './issues'
|
||||
import CreateIssue from './components/CreateIssue.svelte'
|
||||
import RelationsPopup from './components/RelationsPopup.svelte'
|
||||
|
||||
export async function queryIssue<D extends Issue> (
|
||||
_class: Ref<Class<D>>,
|
||||
@ -149,6 +150,7 @@ export default async (): Promise<Resources> => ({
|
||||
TeamProjects,
|
||||
Roadmap,
|
||||
IssuePreview,
|
||||
RelationsPopup,
|
||||
CreateIssue
|
||||
},
|
||||
completion: {
|
||||
|
@ -95,3 +95,16 @@ export async function resolveLocation (loc: Location): Promise<Location | undefi
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export async function updateIssueRelation (
|
||||
client: TxOperations,
|
||||
value: Issue,
|
||||
id: Ref<Issue>,
|
||||
prop: 'blockedBy' | 'relatedIssue',
|
||||
operation: '$push' | '$pull'
|
||||
): Promise<void> {
|
||||
const update = Array.isArray(value[prop])
|
||||
? { [operation]: { [prop]: id } }
|
||||
: { [prop]: operation === '$push' ? [id] : [] }
|
||||
await client.update(value, update)
|
||||
}
|
||||
|
@ -140,6 +140,7 @@ export default mergeIds(trackerId, tracker, {
|
||||
List: '' as IntlString,
|
||||
NumberLabels: '' as IntlString,
|
||||
Roadmap: '' as IntlString,
|
||||
MoveToTeam: '' as IntlString,
|
||||
|
||||
IssueTitlePlaceholder: '' as IntlString,
|
||||
IssueDescriptionPlaceholder: '' as IntlString,
|
||||
@ -168,6 +169,20 @@ export default mergeIds(trackerId, tracker, {
|
||||
Created: '' as IntlString,
|
||||
Subscribed: '' as IntlString,
|
||||
|
||||
Relations: '' as IntlString,
|
||||
RemoveRelation: '' as IntlString,
|
||||
AddBlockedBy: '' as IntlString,
|
||||
AddIsBlocking: '' as IntlString,
|
||||
AddRelatedIssue: '' as IntlString,
|
||||
RelatedIssue: '' as IntlString,
|
||||
BlockedIssue: '' as IntlString,
|
||||
BlockingIssue: '' as IntlString,
|
||||
BlockedBySearchPlaceholder: '' as IntlString,
|
||||
IsBlockingSearchPlaceholder: '' as IntlString,
|
||||
RelatedIssueSearchPlaceholder: '' as IntlString,
|
||||
Blocks: '' as IntlString,
|
||||
Related: '' as IntlString,
|
||||
|
||||
DurMinutes: '' as IntlString,
|
||||
DurHours: '' as IntlString,
|
||||
DurDays: '' as IntlString,
|
||||
@ -215,6 +230,7 @@ export default mergeIds(trackerId, tracker, {
|
||||
Roadmap: '' as AnyComponent,
|
||||
TeamProjects: '' as AnyComponent,
|
||||
IssuePreview: '' as AnyComponent,
|
||||
RelationsPopup: '' as AnyComponent,
|
||||
CreateIssue: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
|
@ -284,6 +284,8 @@ export default plugin(trackerId, {
|
||||
SetProject: '' as Ref<Action>,
|
||||
CopyIssueId: '' as Ref<Action>,
|
||||
CopyIssueTitle: '' as Ref<Action>,
|
||||
MoveToTeam: '' as Ref<Action>,
|
||||
Relations: '' as Ref<Action>,
|
||||
CopyIssueLink: '' as Ref<Action>
|
||||
},
|
||||
team: {
|
||||
|
Loading…
Reference in New Issue
Block a user