mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
TSK-960: move for issues (#2846)
* TSK-960: move for issues Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me> * TSK-960: remove validation and fix labels Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me> * TSK-960: apply changes in one transaction and get rid of text constant Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me> --------- Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
parent
85e8ddba72
commit
eb8bd6634c
@ -1598,7 +1598,7 @@ export function createModel (builder: Builder): void {
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.Move,
|
||||
action: tracker.actionImpl.Move,
|
||||
label: tracker.string.MoveToProject,
|
||||
icon: view.icon.Move,
|
||||
keyBinding: [],
|
||||
|
@ -58,6 +58,7 @@ export default mergeIds(trackerId, tracker, {
|
||||
IssueCategory: '' as Ref<ObjectSearchCategory>
|
||||
},
|
||||
actionImpl: {
|
||||
Move: '' as ViewAction,
|
||||
CopyToClipboard: '' as ViewAction,
|
||||
EditWorkflowStatuses: '' as ViewAction,
|
||||
EditProject: '' as ViewAction,
|
||||
|
@ -157,6 +157,8 @@
|
||||
"Assigned": "Assigned",
|
||||
"Created": "Created",
|
||||
"Subscribed": "Subscribed",
|
||||
"MoveIssues": "Move issues",
|
||||
"MoveIssuesDescription": "Select the project you want to move issues to.",
|
||||
|
||||
"Relations": "Relations",
|
||||
"RemoveRelation": "Remove relation...",
|
||||
|
@ -157,6 +157,8 @@
|
||||
"Assigned": "Назначенные",
|
||||
"Created": "{value, plural, =1 {Создана} other {Созданные}}",
|
||||
"Subscribed": "Отслеживаемые",
|
||||
"MoveIssues": "Переместить задачи",
|
||||
"MoveIssuesDescription": "Выберите проект, в который вы хотите переместить задачи.",
|
||||
|
||||
"Relations": "Зависимости",
|
||||
"RemoveRelation": "Удалить зависимость...",
|
||||
|
121
plugins/tracker-resources/src/components/issues/Move.svelte
Normal file
121
plugins/tracker-resources/src/components/issues/Move.svelte
Normal file
@ -0,0 +1,121 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2023 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 { getClient } from '@hcengineering/presentation'
|
||||
import { Button, Label } from '@hcengineering/ui'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { SpaceSelect } from '@hcengineering/presentation'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import view from '@hcengineering/view'
|
||||
import ui from '@hcengineering/ui'
|
||||
import tracker from '../../plugin'
|
||||
import { Issue, Project } from '@hcengineering/tracker'
|
||||
import { moveIssueToSpace } from '../../utils'
|
||||
|
||||
export let selected: Issue | Issue[]
|
||||
$: docs = Array.isArray(selected) ? selected : [selected]
|
||||
|
||||
let currentSpace: Project | undefined
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
const hierarchy = client.getHierarchy()
|
||||
let space: Ref<Project>
|
||||
|
||||
$: _class = hierarchy.getClass(tracker.class.Project).label
|
||||
$: {
|
||||
const doc = docs[0]
|
||||
if (space === undefined) space = doc.space
|
||||
}
|
||||
|
||||
const moveAll = async () => {
|
||||
const spaceObject = await client.findOne(tracker.class.Project, { _id: space })
|
||||
if (spaceObject === undefined) {
|
||||
throw new Error('Move: state not found')
|
||||
}
|
||||
await moveIssueToSpace(client, docs, space, {
|
||||
status: spaceObject.defaultIssueStatus
|
||||
})
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
async function getSpace (): Promise<void> {
|
||||
client.findOne(tracker.class.Project, { _id: space }).then((res) => (currentSpace = res))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="overflow-label fs-title">
|
||||
<Label label={tracker.string.MoveIssues} />
|
||||
</div>
|
||||
<div class="content-accent-color mt-4 mb-4">
|
||||
<Label label={tracker.string.MoveIssuesDescription} />
|
||||
</div>
|
||||
<div class="spaceSelect">
|
||||
{#await getSpace() then}
|
||||
{#if currentSpace && _class}
|
||||
<SpaceSelect _class={currentSpace._class} label={_class} bind:value={space} />
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<Button
|
||||
label={view.string.Move}
|
||||
size={'small'}
|
||||
disabled={space === currentSpace?._id}
|
||||
kind={'primary'}
|
||||
on:click={moveAll}
|
||||
/>
|
||||
<Button
|
||||
size={'small'}
|
||||
label={ui.string.Cancel}
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem 1.75rem 1.75rem;
|
||||
width: 25rem;
|
||||
max-width: 40rem;
|
||||
background: var(--popup-bg-color);
|
||||
border-radius: 1.25rem;
|
||||
user-select: none;
|
||||
box-shadow: var(--popup-shadow);
|
||||
|
||||
.spaceSelect {
|
||||
padding: 0.75rem;
|
||||
background-color: var(--body-color);
|
||||
border: 1px solid var(--popup-divider);
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
direction: rtl;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -132,6 +132,7 @@ import IssueStatistics from './components/sprints/IssueStatistics.svelte'
|
||||
import SprintRefPresenter from './components/sprints/SprintRefPresenter.svelte'
|
||||
import CreateProject from './components/projects/CreateProject.svelte'
|
||||
import ProjectPresenter from './components/projects/ProjectPresenter.svelte'
|
||||
import MoveIssues from './components/issues/Move.svelte'
|
||||
|
||||
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
|
||||
|
||||
@ -190,6 +191,10 @@ export async function queryIssue<D extends Issue> (
|
||||
}))
|
||||
}
|
||||
|
||||
async function move (issues: Issue | Issue[]): Promise<void> {
|
||||
showPopup(MoveIssues, { selected: issues })
|
||||
}
|
||||
|
||||
async function editWorkflowStatuses (project: Project | undefined): Promise<void> {
|
||||
if (project !== undefined) {
|
||||
showPopup(Statuses, { projectId: project._id, projectClass: project._class }, 'float')
|
||||
@ -436,6 +441,7 @@ export default async (): Promise<Resources> => ({
|
||||
GetAllSprints: getAllSprints
|
||||
},
|
||||
actionImpl: {
|
||||
Move: move,
|
||||
EditWorkflowStatuses: editWorkflowStatuses,
|
||||
EditProject: editProject,
|
||||
DeleteSprint: deleteSprint,
|
||||
|
@ -170,6 +170,8 @@ export default mergeIds(trackerId, tracker, {
|
||||
NumberLabels: '' as IntlString,
|
||||
MoveToProject: '' as IntlString,
|
||||
Duplicate: '' as IntlString,
|
||||
MoveIssues: '' as IntlString,
|
||||
MoveIssuesDescription: '' as IntlString,
|
||||
|
||||
TypeIssuePriority: '' as IntlString,
|
||||
IssueTitlePlaceholder: '' as IntlString,
|
||||
|
@ -15,9 +15,13 @@
|
||||
|
||||
import { Employee, getName } from '@hcengineering/contact'
|
||||
import core, {
|
||||
ApplyOperations,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Collection,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
DocumentUpdate,
|
||||
IdMap,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
@ -58,6 +62,7 @@ import { CategoryQuery, ListSelectionProvider, SelectDirection } from '@hcengine
|
||||
import { writable } from 'svelte/store'
|
||||
import tracker from './plugin'
|
||||
import { defaultComponentStatuses, defaultPriorities, defaultSprintStatuses, issuePriorities } from './types'
|
||||
import { calcRank } from '@hcengineering/task'
|
||||
|
||||
export * from './types'
|
||||
|
||||
@ -685,6 +690,78 @@ export interface StatusStore {
|
||||
byId: IdMap<WithLookup<IssueStatus>>
|
||||
version: number
|
||||
}
|
||||
|
||||
async function updateIssuesOnMove (
|
||||
client: TxOperations,
|
||||
applyOps: ApplyOperations,
|
||||
doc: Doc,
|
||||
space: Ref<Project>,
|
||||
extra?: DocumentUpdate<any>
|
||||
): Promise<void> {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const attributes = hierarchy.getAllAttributes(doc._class)
|
||||
for (const attribute of attributes.values()) {
|
||||
if (hierarchy.isDerived(attribute.type._class, core.class.Collection)) {
|
||||
const collection = attribute.type as Collection<AttachedDoc>
|
||||
const allAttached = await client.findAll(collection.of, { attachedTo: doc._id })
|
||||
for (const attached of allAttached) {
|
||||
// Do not use extra for childs.
|
||||
if (hierarchy.isDerived(collection.of, tracker.class.Issue)) {
|
||||
const lastOne = await client.findOne(tracker.class.Issue, {}, { sort: { rank: SortingOrder.Descending } })
|
||||
const incResult = await client.updateDoc(
|
||||
tracker.class.Project,
|
||||
core.space.Space,
|
||||
space,
|
||||
{
|
||||
$inc: { sequence: 1 }
|
||||
},
|
||||
true
|
||||
)
|
||||
await updateIssuesOnMove(client, applyOps, attached, space, {
|
||||
...extra,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
number: (incResult as any).object.sequence
|
||||
})
|
||||
} else await updateIssuesOnMove(client, applyOps, attached, space)
|
||||
}
|
||||
}
|
||||
}
|
||||
await applyOps.update(doc, {
|
||||
space,
|
||||
...extra
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function moveIssueToSpace (
|
||||
client: TxOperations,
|
||||
docs: Doc[],
|
||||
space: Ref<Project>,
|
||||
extra?: DocumentUpdate<any>
|
||||
): Promise<void> {
|
||||
const applyOps = client.apply(docs[0]._id)
|
||||
for (const doc of docs) {
|
||||
const lastOne = await client.findOne(tracker.class.Issue, {}, { sort: { rank: SortingOrder.Descending } })
|
||||
const incResult = await client.updateDoc(
|
||||
tracker.class.Project,
|
||||
core.space.Space,
|
||||
space,
|
||||
{
|
||||
$inc: { sequence: 1 }
|
||||
},
|
||||
true
|
||||
)
|
||||
await updateIssuesOnMove(client, applyOps, doc, space, {
|
||||
...extra,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
number: (incResult as any).object.sequence
|
||||
})
|
||||
}
|
||||
await applyOps.commit()
|
||||
}
|
||||
|
||||
// Issue status live query
|
||||
export const statusStore = writable<StatusStore>({ statuses: [], byId: new Map(), version: 0 })
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"string": {
|
||||
"MoveClass": "Переместить {class}",
|
||||
"SelectToMove": "Выберите {classLabel} который вы хотите переместить в {class}.",
|
||||
"SelectToMove": "Выберите {classLabel}, в который вы хотите переместить {class}.",
|
||||
"Delete": "Удалить",
|
||||
"Move": "Переместить",
|
||||
"Cancel": "Отменть",
|
||||
|
@ -28,9 +28,6 @@ export default mergeIds(viewId, view, {
|
||||
ProxyPresenter: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
MoveClass: '' as IntlString,
|
||||
SelectToMove: '' as IntlString,
|
||||
Cancel: '' as IntlString,
|
||||
LabelYes: '' as IntlString,
|
||||
LabelNo: '' as IntlString,
|
||||
ChooseAColor: '' as IntlString,
|
||||
|
@ -654,6 +654,9 @@ const view = plugin(viewId, {
|
||||
NewFilteredView: '' as IntlString,
|
||||
FilteredViewName: '' as IntlString,
|
||||
Move: '' as IntlString,
|
||||
MoveClass: '' as IntlString,
|
||||
SelectToMove: '' as IntlString,
|
||||
Cancel: '' as IntlString,
|
||||
List: '' as IntlString,
|
||||
Timeline: '' as IntlString
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user