Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions models/tracker/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,24 @@ export function createActions (builder: Builder, issuesId: string, componentsId:
override: [view.action.Open]
})

createAction(
builder,
{
action: tracker.actionImpl.CopyIssueTemplateToProject,
label: tracker.string.CopyToProject,
icon: view.icon.Copy,
input: 'focus',
category: tracker.category.Tracker,
target: tracker.class.IssueTemplate,
context: {
mode: ['context', 'browser'],
application: tracker.app.Tracker,
group: 'associate'
}
},
tracker.action.CopyIssueTemplateToProject
)

createAction(builder, {
action: view.actionImpl.ShowPopup,
actionProps: {
Expand Down
4 changes: 3 additions & 1 deletion models/tracker/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default mergeIds(trackerId, tracker, {
},
actionImpl: {
Move: '' as ViewAction,
CopyIssueTemplateToProject: '' as ViewAction,
CopyToClipboard: '' as ViewAction,
EditWorkflowStatuses: '' as ViewAction,
EditProject: '' as ViewAction,
Expand All @@ -111,7 +112,8 @@ export default mergeIds(trackerId, tracker, {
DeleteMilestone: '' as Ref<Action<Doc, Record<string, any>>>,
DeleteProject: '' as Ref<Action<Doc, Record<string, any>>>,
DeleteProjectClean: '' as Ref<Action<Doc, Record<string, any>>>,
DeleteIssue: '' as Ref<Action<Doc, Record<string, any>>>
DeleteIssue: '' as Ref<Action<Doc, Record<string, any>>>,
CopyIssueTemplateToProject: '' as Ref<Action<Doc, Record<string, any>>>
},

// For migration only
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"ComponentLeadSearchPlaceholder": "Nastavit vedoucího komponenty…",
"ComponentMembersSearchPlaceholder": "Změnit členy komponenty…",
"MoveToProject": "Přesunout do projektu",
"CopyToProject": "Kopírovat do projektu…",
"Duplicate": "Duplikovat",

"GotoIssues": "Přejít na úkoly",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
"ComponentLeadSearchPlaceholder": "Komponentenleitung festlegen...",
"ComponentMembersSearchPlaceholder": "Komponentenmitglieder ändern...",
"MoveToProject": "Zu Projekt verschieben",
"CopyToProject": "In Projekt kopieren…",
"Duplicate": "Duplizieren",

"GotoIssues": "Zu Aufgaben",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
"ComponentLeadSearchPlaceholder": "Set component lead\u2026",
"ComponentMembersSearchPlaceholder": "Change component members\u2026",
"MoveToProject": "Move to project",
"CopyToProject": "Copy to project…",
"Duplicate": "Duplicate",

"GotoIssues": "Go to issues",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"ComponentLeadSearchPlaceholder": "Establecer responsable del componente...",
"ComponentMembersSearchPlaceholder": "Cambiar miembros del componente...",
"MoveToProject": "Mover al proyecto",
"CopyToProject": "Copiar al proyecto…",
"Duplicate": "Duplicar",
"GotoIssues": "Ir a tareas",
"GotoActive": "Ir a tareas activas",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"ComponentLeadSearchPlaceholder": "Définir le responsable du composant...",
"ComponentMembersSearchPlaceholder": "Modifier les membres du composant...",
"MoveToProject": "Déplacer vers le projet",
"CopyToProject": "Copier vers le projet…",
"Duplicate": "Dupliquer",
"GotoIssues": "Aller aux issues",
"GotoActive": "Aller aux problèmes actifs",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"ComponentLeadSearchPlaceholder": "Imposta responsabile del componente…",
"ComponentMembersSearchPlaceholder": "Cambia membri del componente…",
"MoveToProject": "Sposta nel progetto",
"CopyToProject": "Copia nel progetto…",
"Duplicate": "Duplicato",
"GotoIssues": "Vai alle issue",
"GotoActive": "Vai alle issue attive",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"ComponentLeadSearchPlaceholder": "コンポーネントリーダーを設定\u2026",
"ComponentMembersSearchPlaceholder": "コンポーネントメンバーを変更\u2026",
"MoveToProject": "プロジェクトに移動",
"CopyToProject": "プロジェクトにコピー…",
"Duplicate": "複製",
"GotoIssues": "イシューへ移動",
"GotoActive": "アクティブなイシューへ移動",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"ComponentLeadSearchPlaceholder": "컴포넌트 리더 설정…",
"ComponentMembersSearchPlaceholder": "컴포넌트 멤버 변경…",
"MoveToProject": "프로젝트로 이동",
"CopyToProject": "프로젝트로 복사…",
"Duplicate": "복제",
"GotoIssues": "이슈로 이동",
"GotoActive": "활성 이슈로 이동",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"ComponentLeadSearchPlaceholder": "Ustaw skrót komponentu\u2026",
"ComponentMembersSearchPlaceholder": "Zmień elementy komponentu\u2026",
"MoveToProject": "Przenieś do projektu",
"CopyToProject": "Kopiuj do projektu…",
"Duplicate": "Duplikuj",

"GotoIssues": "Przejdź do zadań",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/pt-br.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"ComponentLeadSearchPlaceholder": "Definir responsável pelo componente...",
"ComponentMembersSearchPlaceholder": "Alterar membros do componente...",
"MoveToProject": "Mover para o projeto",
"CopyToProject": "Copiar para o projeto…",
"Duplicate": "Duplicar",
"GotoIssues": "Ir para as questões",
"GotoActive": "Ir para as questões ativas",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"ComponentLeadSearchPlaceholder": "Definir responsável pelo componente...",
"ComponentMembersSearchPlaceholder": "Alterar membros do componente...",
"MoveToProject": "Mover para o projeto",
"CopyToProject": "Copiar para o projeto…",
"Duplicate": "Duplicar",
"GotoIssues": "Ir para as questões",
"GotoActive": "Ir para as questões ativas",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
"ComponentLeadSearchPlaceholder": "Назначьте руководителя компонента\u2026",
"ComponentMembersSearchPlaceholder": "Изменить участников компонента\u2026",
"MoveToProject": "Изменить проект",
"CopyToProject": "Копировать в проект…",
"Duplicate": "Дублировать",

"GotoIssues": "Перейти к задачам",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
"ComponentLeadSearchPlaceholder": "Bileşen lideri belirle…",
"ComponentMembersSearchPlaceholder": "Bileşen üyelerini değiştir…",
"MoveToProject": "Projeye taşı",
"CopyToProject": "Projeye kopyala…",
"Duplicate": "Çoğalt",
"GotoIssues": "Sorunlara git",
"GotoActive": "Aktif sorunlara git",
Expand Down
1 change: 1 addition & 0 deletions plugins/tracker-assets/lang/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"ComponentLeadSearchPlaceholder": "设置组件负责人…",
"ComponentMembersSearchPlaceholder": "更改组件成员…",
"MoveToProject": "移动到项目",
"CopyToProject": "复制到项目…",
"Duplicate": "复制",

"GotoIssues": "前往问题",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<!--
// Copyright © 2026 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 { Ref, generateId, getCurrentAccount } from '@hcengineering/core'
import presentation, { Card, SpaceSelector, getClient } from '@hcengineering/presentation'
import task from '@hcengineering/task'
import { IssueTemplate, Project } from '@hcengineering/tracker'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import ProjectPresenter from '../projects/ProjectPresenter.svelte'
import IssueTemplatePresenter from './IssueTemplatePresenter.svelte'
import { makeIssueTemplateCopy } from './copy'

export let value: IssueTemplate

const dispatch = createEventDispatcher()
const client = getClient()
const me = getCurrentAccount()

let space: Ref<Project> | undefined = undefined

async function copy (): Promise<void> {
if (space === undefined) return
const project = await client.findOne(tracker.class.Project, { _id: space, members: me.uuid })
if (project === undefined) return
const taskTypes = await client.findAll(task.class.TaskType, { parent: project.type })
const data = makeIssueTemplateCopy(
value,
project._id,
taskTypes.map((it) => it._id),
generateId
)
await client.createDoc(tracker.class.IssueTemplate, project._id, data)
}
</script>

<Card
label={tracker.string.CopyToProject}
okAction={copy}
canSave={space !== undefined}
okLabel={presentation.string.Copy}
on:close={() => dispatch('close')}
on:changeContent
>
<svelte:fragment slot="header">
<SpaceSelector
_class={tracker.class.Project}
query={{ archived: false, members: me.uuid, _id: { $ne: value.space } }}
label={tracker.string.Project}
bind:space
autoSelect={false}
kind={'regular'}
size={'large'}
component={ProjectPresenter}
defaultIcon={tracker.icon.Home}
/>
</svelte:fragment>
<IssueTemplatePresenter {value} disabled />
</Card>
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//
// Copyright © 2026 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.
//

import type { IssueTemplate } from '@hcengineering/tracker'
import { makeIssueTemplateCopy } from '../copy'

function template (): IssueTemplate {
return {
_id: 'tmpl-1' as any,
_class: 'tracker:class:IssueTemplate' as any,
space: 'project-1' as any,
modifiedOn: 0,
modifiedBy: 'user' as any,
title: 'Release',
description: '<p>Steps</p>',
priority: 2,
assignee: 'person-1' as any,
component: 'component-1' as any,
milestone: 'milestone-1' as any,
estimation: 3,
labels: ['label-1' as any],
kind: 'kind-shared' as any,
comments: 5,
attachments: 2,
relations: [{ _id: 'doc-1' as any, _class: 'cls' as any }],
children: [
{
id: 'child-1' as any,
title: 'Child',
description: '',
priority: 1,
assignee: null,
component: 'component-2' as any,
milestone: 'milestone-2' as any,
estimation: 1,
labels: ['label-2' as any],
kind: 'kind-other' as any
}
]
}
}

describe('makeIssueTemplateCopy', () => {
let counter = 0
const newId = (): any => `new-${++counter}`

beforeEach(() => {
counter = 0
})

it('clears project specific references when copying to another project', () => {
const source = template()
const data = makeIssueTemplateCopy(source, 'project-2' as any, ['kind-shared' as any], newId)

expect(data).toMatchObject({
title: 'Release',
description: '<p>Steps</p>',
priority: 2,
assignee: 'person-1',
component: null,
milestone: null,
estimation: 3,
labels: ['label-1'],
kind: 'kind-shared',
comments: 0,
attachments: 0,
relations: [{ _id: 'doc-1', _class: 'cls' }]
})
expect(data.children).toEqual([
{
id: 'new-1',
title: 'Child',
description: '',
priority: 1,
assignee: null,
component: null,
milestone: null,
estimation: 1,
labels: ['label-2'],
kind: undefined
}
])
expect((data as any)._id).toBeUndefined()
expect((data as any).space).toBeUndefined()
})

it('keeps component and milestone within the same project', () => {
const data = makeIssueTemplateCopy(template(), 'project-1' as any, [], newId)
expect(data.component).toBe('component-1')
expect(data.milestone).toBe('milestone-1')
expect(data.children[0].component).toBe('component-2')
expect(data.kind).toBeUndefined()
expect(data.children[0].id).toBe('new-1')
})

it('does not share arrays with the source template', () => {
const source = template()
const data = makeIssueTemplateCopy(source, 'project-2' as any, [], newId)
expect(data.labels).not.toBe(source.labels)
expect(data.children[0].labels).not.toBe(source.children[0].labels)
expect(data.relations).not.toBe(source.relations)
})
})
Loading
Loading