mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
Export & Import functionality for issues (#5733)
Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
parent
f4d3518c3c
commit
14c1977660
@ -427,6 +427,23 @@ export function createModel (builder: Builder): void {
|
||||
task.action.Move
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
label: task.string.Export,
|
||||
action: task.actionImpl.ExportTasks,
|
||||
icon: view.icon.Move,
|
||||
input: 'selection',
|
||||
category: view.category.General,
|
||||
target: task.class.Task,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'tools'
|
||||
}
|
||||
},
|
||||
task.action.ExportTasks
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.StatusCategory,
|
||||
core.space.Model,
|
||||
|
@ -29,13 +29,15 @@ export default mergeIds(taskId, task, {
|
||||
ArchiveSpace: '' as Ref<Action>,
|
||||
UnarchiveSpace: '' as Ref<Action>,
|
||||
ArchiveState: '' as Ref<Action>,
|
||||
PublicLink: '' as Ref<Action<Doc, any>>
|
||||
PublicLink: '' as Ref<Action<Doc, any>>,
|
||||
ExportTasks: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
EditStatuses: '' as ViewAction,
|
||||
ArchiveSpace: '' as ViewAction,
|
||||
UnarchiveSpace: '' as ViewAction,
|
||||
SelectStatus: '' as ViewAction
|
||||
SelectStatus: '' as ViewAction,
|
||||
ExportTasks: '' as ViewAction
|
||||
},
|
||||
category: {
|
||||
Task: '' as Ref<ActionCategory>,
|
||||
@ -74,6 +76,7 @@ export default mergeIds(taskId, task, {
|
||||
ManageProjects: '' as IntlString,
|
||||
StateBacklog: '' as IntlString,
|
||||
StateActive: '' as IntlString,
|
||||
StateUnstarted: '' as IntlString
|
||||
StateUnstarted: '' as IntlString,
|
||||
Export: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -104,7 +104,8 @@ export default mergeIds(trackerId, tracker, {
|
||||
EditProject: '' as ViewAction,
|
||||
DeleteProject: '' as ViewAction,
|
||||
DeleteIssue: '' as ViewAction,
|
||||
DeleteMilestone: '' as ViewAction
|
||||
DeleteMilestone: '' as ViewAction,
|
||||
ImportIssues: '' as ViewAction
|
||||
},
|
||||
action: {
|
||||
NewRelatedIssue: '' as Ref<Action<Doc, any>>,
|
||||
|
@ -84,6 +84,7 @@
|
||||
"TaskCreated": "Task created",
|
||||
"TaskType": "Task type",
|
||||
"ManageProjects": "Project types",
|
||||
"Export": "Export",
|
||||
"CreateProjectType": "Create project type",
|
||||
"ClassicProject": "Classic project",
|
||||
"LastSave": "Last save",
|
||||
|
@ -84,6 +84,7 @@
|
||||
"TaskCreated": "Tarea creada",
|
||||
"TaskType": "Tipo de tarea",
|
||||
"ManageProjects": "Tipos de proyecto",
|
||||
"Export": "Exportar",
|
||||
"CreateProjectType": "Crear tipo de proyecto",
|
||||
"ClassicProject": "Proyecto clásico",
|
||||
"LastSave": "Último guardado",
|
||||
|
@ -84,6 +84,7 @@
|
||||
"TaskCreated": "Tarefa criada",
|
||||
"TaskType": "Tipo de tarefa",
|
||||
"ManageProjects": "Tipos de projetos",
|
||||
"Export": "Exportar",
|
||||
"CreateProjectType": "Criar tipo de projeto",
|
||||
"ClassicProject": "Projeto clássico",
|
||||
"Última gravação": "Última gravação",
|
||||
|
@ -84,6 +84,7 @@
|
||||
"TaskCreated": "Создана задача",
|
||||
"TaskType": "Тип задачи",
|
||||
"ManageProjects": "Управление проектами",
|
||||
"Export": "Экспортировать",
|
||||
"CreateProjectType": "Создать тип проекта",
|
||||
"ClassicProject": "Классический проект",
|
||||
"LastSave": "Последнее сохранение",
|
||||
|
@ -43,6 +43,7 @@
|
||||
"@hcengineering/task": "^0.6.20",
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/presentation": "^0.6.3",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
|
@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import {
|
||||
import core, {
|
||||
toIdMap,
|
||||
type Attribute,
|
||||
type Class,
|
||||
@ -38,8 +38,11 @@ import task, {
|
||||
} from '@hcengineering/task'
|
||||
import { getCurrentLocation, navigate, showPopup } from '@hcengineering/ui'
|
||||
import { type ViewletDescriptor } from '@hcengineering/view'
|
||||
import { CategoryQuery, statusStore } from '@hcengineering/view-resources'
|
||||
import { CategoryQuery, groupBy, statusStore } from '@hcengineering/view-resources'
|
||||
import { get, writable } from 'svelte/store'
|
||||
import { type Employee, type PersonAccount } from '@hcengineering/contact'
|
||||
import activity from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
|
||||
import AssignedTasks from './components/AssignedTasks.svelte'
|
||||
import Dashboard from './components/Dashboard.svelte'
|
||||
@ -72,6 +75,7 @@ import ProjectTypeTasksTypeSectionEditor from './components/projectTypes/Project
|
||||
import ProjectTypeAutomationsSectionEditor from './components/projectTypes/ProjectTypeAutomationsSectionEditor.svelte'
|
||||
import ProjectTypeCollectionsSectionEditor from './components/projectTypes/ProjectTypeCollectionsSectionEditor.svelte'
|
||||
import TaskTypeEditor from './components/taskTypes/TaskTypeEditor.svelte'
|
||||
import { employeeByIdStore, personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
|
||||
export { default as AssigneePresenter } from './components/AssigneePresenter.svelte'
|
||||
export { default as TypeSelector } from './components/TypeSelector.svelte'
|
||||
@ -86,6 +90,76 @@ async function editStatuses (object: Project, ev: Event): Promise<void> {
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
async function exportTasks (docs: Task | Task[]): Promise<void> {
|
||||
const client = getClient()
|
||||
const ddocs = Array.isArray(docs) ? docs : [docs]
|
||||
const docsStatuses = ddocs.map((doc) => doc.status)
|
||||
const statuses = await client.findAll(core.class.Status, { _id: { $in: docsStatuses } })
|
||||
const personAccountById = get(personAccountByIdStore)
|
||||
const personById = get(personByIdStore)
|
||||
const employeeById = get(employeeByIdStore)
|
||||
const statusMap = toIdMap(statuses)
|
||||
const activityMessages = await client.findAll(activity.class.ActivityMessage, {
|
||||
_class: chunter.class.ChatMessage,
|
||||
attachedToClass: { $in: ddocs.map((d) => d._class) },
|
||||
attachedTo: { $in: ddocs.map((d) => d._id) }
|
||||
})
|
||||
const activityByDoc = groupBy(activityMessages, 'attachedTo')
|
||||
|
||||
const toExport = ddocs.map((d) => {
|
||||
const statusName = statusMap.get(d.status)?.name ?? d.status
|
||||
const createdByAccount = personAccountById.get(d.createdBy as Ref<PersonAccount>)?.person
|
||||
const modeifedByAccount = personAccountById.get(d.modifiedBy as Ref<PersonAccount>)?.person
|
||||
const createdBy = personById.get(createdByAccount as Ref<Employee>)?.name ?? d.createdBy
|
||||
const modifiedBy = personById.get(modeifedByAccount as Ref<Employee>)?.name ?? d.modifiedBy
|
||||
const assignee = employeeById.get(d.assignee as Ref<Employee>)?.name ?? d.assignee
|
||||
const collaborators = ((d as any)['notification:mixin:Collaborators']?.collaborators ?? []).map(
|
||||
(id: Ref<PersonAccount>) => {
|
||||
const personAccount = personAccountById.get(id)?.person
|
||||
return personAccount !== undefined ? personById.get(personAccount)?.name ?? id : id
|
||||
}
|
||||
)
|
||||
const activityForDoc = (activityByDoc[d._id] ?? []).map((act) => {
|
||||
const activityCreatedByAccount = personAccountById.get(act.createdBy as Ref<PersonAccount>)?.person
|
||||
const activityModifiedByAccount = personAccountById.get(act.modifiedBy as Ref<PersonAccount>)?.person
|
||||
const activitycreatedBy =
|
||||
employeeById.get((activityCreatedByAccount as any as Ref<Employee>) ?? ('' as Ref<Employee>))?.name ??
|
||||
act.createdBy
|
||||
const activitymodifiedBy =
|
||||
employeeById.get((activityModifiedByAccount as any as Ref<Employee>) ?? ('' as Ref<Employee>))?.name ??
|
||||
act.modifiedBy
|
||||
return {
|
||||
...act,
|
||||
createdBy: activitycreatedBy,
|
||||
modifiedBy: activitymodifiedBy
|
||||
}
|
||||
})
|
||||
return {
|
||||
...d,
|
||||
status: statusName,
|
||||
createdBy,
|
||||
modifiedBy,
|
||||
assignee,
|
||||
'notification:mixin:Collaborators': {
|
||||
collaborators
|
||||
},
|
||||
activity: activityForDoc
|
||||
}
|
||||
})
|
||||
const filename = 'tasks' + new Date().toLocaleDateString() + '.json'
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.setAttribute('target', '_blank')
|
||||
link.setAttribute(
|
||||
'href',
|
||||
'data:application/json;charset=utf-8,%EF%BB%BF' + encodeURIComponent(JSON.stringify(toExport))
|
||||
)
|
||||
link.setAttribute('download', filename)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
async function selectStatus (
|
||||
doc: Task | Task[],
|
||||
ev: any,
|
||||
@ -137,7 +211,8 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
actionImpl: {
|
||||
EditStatuses: editStatuses,
|
||||
SelectStatus: selectStatus
|
||||
SelectStatus: selectStatus,
|
||||
ExportTasks: exportTasks
|
||||
},
|
||||
function: {
|
||||
GetAllStates: getAllStates,
|
||||
|
@ -44,6 +44,7 @@
|
||||
"ProjectTitlePlaceholder": "New project",
|
||||
"UsedInIssueIDs": "Used in issue IDs",
|
||||
"Identifier": "Identifier",
|
||||
"Import": "Import",
|
||||
"ProjectIdentifier": "Project Identifier",
|
||||
"IdentifierExists": "Project identifier already exists",
|
||||
"ProjectIdentifierPlaceholder": "PRJCT",
|
||||
|
@ -44,6 +44,7 @@
|
||||
"ProjectTitlePlaceholder": "Nuevo proyecto",
|
||||
"UsedInIssueIDs": "Utilizado en IDs de tareas",
|
||||
"Identifier": "Identificador",
|
||||
"Import": "Importar",
|
||||
"ProjectIdentifier": "Identificador del proyecto",
|
||||
"IdentifierExists": "El identificador del proyecto ya existe",
|
||||
"ProjectIdentifierPlaceholder": "PROJ",
|
||||
|
@ -44,6 +44,7 @@
|
||||
"ProjectTitlePlaceholder": "Novo projeto",
|
||||
"UsedInIssueIDs": "Usado em IDs de problemas",
|
||||
"Identifier": "Identificador",
|
||||
"Import": "Importar",
|
||||
"ProjectIdentifier": "Identificador do projeto",
|
||||
"IdentifierExists": "O identificador do projeto já existe",
|
||||
"ProjectIdentifierPlaceholder": "PROJ",
|
||||
|
@ -44,6 +44,7 @@
|
||||
"ProjectTitlePlaceholder": "Новый проект",
|
||||
"UsedInIssueIDs": "Используется в идентификаторах задач",
|
||||
"Identifier": "Идентификатор",
|
||||
"Import": "Импорт",
|
||||
"ProjectIdentifier": "Идентификатор проекта",
|
||||
"IdentifierExists": "Идентификатор уже существует проекта",
|
||||
"ProjectIdentifierPlaceholder": "ПКТ",
|
||||
|
@ -20,6 +20,8 @@
|
||||
import { onDestroy } from 'svelte'
|
||||
import tracker from '../plugin'
|
||||
import CreateIssue from './CreateIssue.svelte'
|
||||
import { importTasks } from '..'
|
||||
import { Project } from '@hcengineering/tracker'
|
||||
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
|
||||
@ -50,6 +52,10 @@
|
||||
{
|
||||
id: tracker.string.NewIssue,
|
||||
label
|
||||
},
|
||||
{
|
||||
id: tracker.string.Import,
|
||||
label: tracker.string.Import
|
||||
}
|
||||
]
|
||||
: [
|
||||
@ -61,6 +67,7 @@
|
||||
const client = getClient()
|
||||
|
||||
let keys: string[] | undefined = undefined
|
||||
let inputFile: HTMLInputElement
|
||||
async function dropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
|
||||
if (res == null) return
|
||||
|
||||
@ -69,15 +76,38 @@
|
||||
showPopup(tracker.component.CreateProject, {}, 'top', () => {
|
||||
closed = true
|
||||
})
|
||||
} else if (res === tracker.string.Import) {
|
||||
inputFile.click()
|
||||
} else {
|
||||
await newIssue()
|
||||
}
|
||||
}
|
||||
|
||||
async function fileSelected (): Promise<void> {
|
||||
const list = inputFile.files
|
||||
if (list === null || list.length === 0) return
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null && currentSpace != null) {
|
||||
await importTasks(file, currentSpace as Ref<Project>)
|
||||
}
|
||||
}
|
||||
inputFile.value = ''
|
||||
}
|
||||
client.findOne(view.class.Action, { _id: tracker.action.NewIssue }).then((p) => (keys = p?.keyBinding))
|
||||
</script>
|
||||
|
||||
<div class="antiNav-subheader">
|
||||
<input
|
||||
bind:this={inputFile}
|
||||
multiple
|
||||
type="file"
|
||||
name="file"
|
||||
id="tasksInput"
|
||||
accept="application/json"
|
||||
style="display: none"
|
||||
on:change={fileSelected}
|
||||
/>
|
||||
<ButtonWithDropdown
|
||||
icon={IconAdd}
|
||||
justify={'left'}
|
||||
|
@ -29,8 +29,10 @@ import core, {
|
||||
type DocumentQuery,
|
||||
type Ref,
|
||||
type RelatedDocument,
|
||||
type TxOperations
|
||||
type TxOperations,
|
||||
AccountRole
|
||||
} from '@hcengineering/core'
|
||||
import chunter, { type ChatMessage } from '@hcengineering/chunter'
|
||||
import { type Status, translate, type Resources } from '@hcengineering/platform'
|
||||
import { getClient, MessageBox, type ObjectSearchResult } from '@hcengineering/presentation'
|
||||
import { type Issue, type Milestone, type Project } from '@hcengineering/tracker'
|
||||
@ -163,6 +165,10 @@ import { settingId } from '@hcengineering/setting'
|
||||
import { getAllStates } from '@hcengineering/task-resources'
|
||||
import EstimationValueEditor from './components/issues/timereport/EstimationValueEditor.svelte'
|
||||
import TimePresenter from './components/issues/timereport/TimePresenter.svelte'
|
||||
import { personAccountByIdStore, personAccountPersonByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import contact, { AvatarType } from '@hcengineering/contact'
|
||||
import task, { type TaskType } from '@hcengineering/task'
|
||||
import notification, { type Collaborators } from '@hcengineering/notification'
|
||||
|
||||
export { default as AssigneeEditor } from './components/issues/AssigneeEditor.svelte'
|
||||
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
|
||||
@ -424,6 +430,167 @@ async function deleteMilestone (milestones: Milestone | Milestone[]): Promise<vo
|
||||
}
|
||||
}
|
||||
|
||||
type ImportIssue = Issue & { activity: ChatMessage[] }
|
||||
|
||||
export async function importTasks (tasks: File, space: Ref<Project>): Promise<void> {
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(tasks)
|
||||
|
||||
const personAccountById = get(personAccountByIdStore)
|
||||
const personAccountList = Array.from(personAccountById.values())
|
||||
const personAccountPersonById = get(personAccountPersonByIdStore)
|
||||
const personList = Array.from(get(personByIdStore).values())
|
||||
|
||||
const client = getClient()
|
||||
const statuses = await client.findAll(tracker.class.IssueStatus, {})
|
||||
reader.onload = async () => {
|
||||
let tasksArray: ImportIssue[] = Array.from(JSON.parse(reader.result as string))
|
||||
const personToImport = Array.from(
|
||||
new Set(tasksArray.flatMap((t) => [t.createdBy, t.modifiedBy, ...t.activity.flatMap((act) => act.modifiedBy)]))
|
||||
).filter((x) => x !== undefined) as string[]
|
||||
const peopleToAdd = personToImport.filter((p) => personList.find((x) => x.name === p) === undefined)
|
||||
if (peopleToAdd.length > 0) {
|
||||
console.log('Next people will be created to import properly', peopleToAdd)
|
||||
for (const personToCreate of peopleToAdd) {
|
||||
const personId = await client.createDoc(contact.class.Person, contact.space.Contacts, {
|
||||
name: personToCreate,
|
||||
avatarType: AvatarType.COLOR,
|
||||
city: '',
|
||||
comments: 0,
|
||||
channels: 0,
|
||||
attachments: 0
|
||||
})
|
||||
await client.createDoc(contact.class.PersonAccount, core.space.Model, {
|
||||
email: `imported:${personId}`,
|
||||
person: personId,
|
||||
role: AccountRole.User
|
||||
})
|
||||
}
|
||||
}
|
||||
const idsParent: Array<{ id: Ref<Issue>, identifier: string }> = []
|
||||
|
||||
while (tasksArray.length > 0) {
|
||||
let taskParsing: ImportIssue | undefined = tasksArray.find((t: ImportIssue) => t?.parents?.length === 0)
|
||||
if (taskParsing === undefined) {
|
||||
taskParsing = tasksArray.find((t: Issue) =>
|
||||
t?.parents?.every((p) => idsParent.findIndex((par) => par.id === p.parentId) !== -1)
|
||||
)
|
||||
}
|
||||
if (taskParsing != null) {
|
||||
tasksArray = tasksArray.filter((t) => t._id !== taskParsing?._id)
|
||||
const proj = await client.findOne(tracker.class.Project, { _id: space })
|
||||
const modifiedByPerson = personList.find((p) => p.name === taskParsing?.modifiedBy)?._id
|
||||
const assignee =
|
||||
taskParsing.assignee !== null ? personList.find((p) => p.name === taskParsing?.assignee)?._id ?? null : null
|
||||
if (modifiedByPerson === undefined) throw new Error('Person not found')
|
||||
const modifiedBy = personAccountList.find((pA) => pA.person === modifiedByPerson)?._id
|
||||
if (modifiedBy === undefined) throw new Error('modifiedBy account not found')
|
||||
|
||||
const collaborators = (taskParsing as any)['notification:mixin:Collaborators']?.collaborators
|
||||
const collaboratorsToImport =
|
||||
collaborators !== undefined
|
||||
? collaborators
|
||||
.map((name: string) => {
|
||||
const person = personList.find((p) => p.name === name)?._id
|
||||
if (person === undefined) return undefined
|
||||
const account = personAccountPersonById.get(person)
|
||||
return account?._id
|
||||
})
|
||||
.filter((c: any) => c !== undefined)
|
||||
: undefined
|
||||
|
||||
const incResult = await client.updateDoc(
|
||||
tracker.class.Project,
|
||||
core.space.Space,
|
||||
space,
|
||||
{
|
||||
$inc: { sequence: 1 }
|
||||
},
|
||||
true
|
||||
)
|
||||
const number = (incResult as any).object.sequence
|
||||
const identifier = `${proj?.identifier}-${number}`
|
||||
idsParent.push({ id: taskParsing._id, identifier })
|
||||
const taskKind = proj?.type !== undefined ? { parent: proj.type } : {}
|
||||
const kind = (await client.findOne(task.class.TaskType, taskKind)) as TaskType
|
||||
const status = statuses.find((s) => s.name === taskParsing?.status)?._id
|
||||
if (status === undefined) throw new Error('status not found')
|
||||
const taskToCreate = {
|
||||
title: taskParsing.title,
|
||||
description: taskParsing.description,
|
||||
component: taskParsing.component,
|
||||
milestone: taskParsing.milestone,
|
||||
number,
|
||||
status,
|
||||
priority: taskParsing.priority,
|
||||
rank: taskParsing.rank,
|
||||
comments: 0,
|
||||
subIssues: 0,
|
||||
dueDate: taskParsing.dueDate,
|
||||
parents: taskParsing.parents.map((p) => ({
|
||||
...p,
|
||||
space,
|
||||
identifier: idsParent.find((par) => par.id === p.parentId)?.identifier ?? p.identifier
|
||||
})),
|
||||
reportedTime: 0,
|
||||
remainingTime: 0,
|
||||
estimation: taskParsing.estimation,
|
||||
reports: 0,
|
||||
childInfo: taskParsing.childInfo,
|
||||
identifier,
|
||||
modifiedBy,
|
||||
assignee,
|
||||
kind: kind._id
|
||||
}
|
||||
await client.addCollection(
|
||||
tracker.class.Issue,
|
||||
space,
|
||||
taskParsing?.attachedTo ?? tracker.ids.NoParent,
|
||||
taskParsing._class,
|
||||
'subIssues',
|
||||
taskToCreate,
|
||||
taskParsing._id
|
||||
)
|
||||
|
||||
if (collaboratorsToImport !== undefined) {
|
||||
await client.createMixin<Doc, Collaborators>(
|
||||
taskParsing._id,
|
||||
taskParsing._class,
|
||||
space,
|
||||
notification.mixin.Collaborators,
|
||||
{
|
||||
collaborators: collaboratorsToImport
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Push activity
|
||||
if (taskParsing.activity !== undefined) {
|
||||
const act = taskParsing.activity.sort((a, b) => a.modifiedOn - b.modifiedOn)
|
||||
for (const activityMessage of act) {
|
||||
const modifiedByPerson = personList.find((p) => p.name === activityMessage.modifiedBy)?._id
|
||||
const modifiedBy = personAccountList.find((pA) => pA.person === modifiedByPerson)?._id
|
||||
if (modifiedBy === undefined) throw new Error('modifiedBy account not found')
|
||||
await client.addCollection(
|
||||
chunter.class.ChatMessage,
|
||||
space,
|
||||
taskParsing._id,
|
||||
tracker.class.Issue,
|
||||
'comments',
|
||||
{
|
||||
message: activityMessage.message
|
||||
},
|
||||
activityMessage._id,
|
||||
activityMessage.modifiedOn,
|
||||
modifiedBy
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
activity: {
|
||||
TxIssueCreated,
|
||||
@ -551,7 +718,8 @@ export default async (): Promise<Resources> => ({
|
||||
EditProject: editProject,
|
||||
DeleteMilestone: deleteMilestone,
|
||||
DeleteProject: deleteProject,
|
||||
DeleteIssue: deleteIssue
|
||||
DeleteIssue: deleteIssue,
|
||||
ImportIssues: importTasks
|
||||
},
|
||||
resolver: {
|
||||
Location: resolveLocation
|
||||
|
@ -101,6 +101,7 @@ export default mergeIds(trackerId, tracker, {
|
||||
Title: '' as IntlString,
|
||||
UsedInIssueIDs: '' as IntlString,
|
||||
Identifier: '' as IntlString,
|
||||
Import: '' as IntlString,
|
||||
ProjectIdentifier: '' as IntlString,
|
||||
IdentifierExists: '' as IntlString,
|
||||
Description: '' as IntlString,
|
||||
|
Loading…
Reference in New Issue
Block a user