mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-29 11:43:49 +00:00
TSK-399: Allow to delete sprints (#2386)
Signed-off-by: Anton Brechka <anton.brechka@xored.com>
This commit is contained in:
parent
4878dae539
commit
3ae814cf21
@ -1286,6 +1286,25 @@ export function createModel (builder: Builder): void {
|
||||
tracker.action.Duplicate
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: tracker.actionImpl.DeleteSprint,
|
||||
label: view.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
keyBinding: ['Meta + Backspace', 'Ctrl + Backspace'],
|
||||
category: tracker.category.Tracker,
|
||||
input: 'any',
|
||||
target: tracker.class.Sprint,
|
||||
context: { mode: ['context', 'browser'], group: 'tools' }
|
||||
},
|
||||
tracker.action.DeleteSprint
|
||||
)
|
||||
|
||||
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [view.action.Delete]
|
||||
})
|
||||
|
||||
classPresenter(
|
||||
builder,
|
||||
tracker.class.TypeReportedTime,
|
||||
|
@ -55,9 +55,11 @@ export default mergeIds(trackerId, tracker, {
|
||||
},
|
||||
actionImpl: {
|
||||
CopyToClipboard: '' as ViewAction,
|
||||
EditWorkflowStatuses: '' as ViewAction
|
||||
EditWorkflowStatuses: '' as ViewAction,
|
||||
DeleteSprint: '' as ViewAction
|
||||
},
|
||||
action: {
|
||||
NewRelatedIssue: '' as Ref<Action<Doc, Record<string, any>>>
|
||||
NewRelatedIssue: '' as Ref<Action<Doc, Record<string, any>>>,
|
||||
DeleteSprint: '' as Ref<Action<Doc, Record<string, any>>>
|
||||
}
|
||||
})
|
||||
|
@ -41,6 +41,7 @@
|
||||
export let docQuery: DocumentQuery<Doc> | undefined = undefined
|
||||
|
||||
export let multiSelect: boolean = false
|
||||
export let closeAfterSelect: boolean = true
|
||||
export let allowDeselect: boolean = false
|
||||
export let titleDeselect: IntlString | undefined = undefined
|
||||
export let placeholder: IntlString = presentation.string.Search
|
||||
@ -110,7 +111,7 @@
|
||||
} else {
|
||||
selected = person._id
|
||||
}
|
||||
dispatch('close', selected !== undefined ? person : undefined)
|
||||
dispatch(closeAfterSelect ? 'close' : 'update', selected !== undefined ? person : undefined)
|
||||
} else {
|
||||
checkSelected(person, objects)
|
||||
}
|
||||
|
@ -105,8 +105,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="editbox-container"
|
||||
class:w-full={focusable}
|
||||
class="editbox-container w-full"
|
||||
on:click={() => {
|
||||
input.focus()
|
||||
}}
|
||||
|
@ -209,6 +209,9 @@
|
||||
"NewSprint": "New Sprint",
|
||||
"CreateSprint": "Create",
|
||||
|
||||
"MoveAndDeleteSprint": "Move Issues to {newSprint} and Delete {deleteSprint}",
|
||||
"MoveAndDeleteSprintConfirm": "Do you want to delete sprint and move issues to another sprint?",
|
||||
|
||||
"Estimation": "Estimation",
|
||||
"ReportedTime": "Reported Time",
|
||||
"TimeSpendReports": "Time spend reports",
|
||||
|
@ -209,6 +209,9 @@
|
||||
"NewSprint": "Новый Спринт",
|
||||
"CreateSprint": "Создать",
|
||||
|
||||
"MoveAndDeleteSprint": "Переместить Задачи в {newSprint} и Удалить {deleteSprint}",
|
||||
"MoveAndDeleteSprintConfirm": "Вы действительно хотите удалить спринт и перенести задачи в другой спринт?",
|
||||
|
||||
"Estimation": "Оценка",
|
||||
"ReportedTime": "Использовано",
|
||||
"TimeSpendReports": "Отчеты по времени",
|
||||
|
@ -2,8 +2,8 @@
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
import { Button, DatePresenter, EditBox, Icon, Label, showPopup } from '@hcengineering/ui'
|
||||
import { DocAttributeBar } from '@hcengineering/view-resources'
|
||||
import { Button, DatePresenter, EditBox, Icon, IconMoreH, Label, showPopup } from '@hcengineering/ui'
|
||||
import { ContextMenu, DocAttributeBar } from '@hcengineering/view-resources'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { activeSprint } from '../../issues'
|
||||
import tracker from '../../plugin'
|
||||
@ -28,6 +28,12 @@
|
||||
})
|
||||
}
|
||||
|
||||
function showMenu (ev?: Event): void {
|
||||
if (sprint) {
|
||||
showPopup(ContextMenu, { object: sprint }, (ev as MouseEvent).target as HTMLElement)
|
||||
}
|
||||
}
|
||||
|
||||
$: $activeSprint = sprint?._id
|
||||
|
||||
onDestroy(() => {
|
||||
@ -71,6 +77,7 @@
|
||||
{#if sprint?.capacity}
|
||||
<Label label={tracker.string.CapacityValue} params={{ value: sprint?.capacity }} />
|
||||
{/if}
|
||||
<Button icon={IconMoreH} kind={'transparent'} size={'medium'} on:click={showMenu} />
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
@ -0,0 +1,51 @@
|
||||
<!--
|
||||
//
|
||||
// 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 tracker from '../../plugin'
|
||||
import { Card } from '@hcengineering/presentation'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import SprintPopup from './SprintPopup.svelte'
|
||||
import { Sprint } from '@hcengineering/tracker'
|
||||
|
||||
export let sprint: Sprint
|
||||
export let moveAndDeleteSprint: (selectedSprint?: Sprint) => Promise<void>
|
||||
|
||||
let selectedSprint: Sprint | undefined
|
||||
let noSprintLabel: string
|
||||
|
||||
$: translate(tracker.string.NoSprint, {}).then((label) => (noSprintLabel = label))
|
||||
$: selectedSprintLabel = selectedSprint?.label ?? noSprintLabel
|
||||
</script>
|
||||
|
||||
<Card
|
||||
canSave
|
||||
label={tracker.string.MoveAndDeleteSprint}
|
||||
labelProps={{ newSprint: selectedSprintLabel, deleteSprint: sprint.label }}
|
||||
okLabel={tracker.string.Delete}
|
||||
okAction={() => moveAndDeleteSprint(selectedSprint)}
|
||||
on:close
|
||||
>
|
||||
<SprintPopup
|
||||
_class={tracker.class.Sprint}
|
||||
ignoreSprints={[sprint]}
|
||||
allowDeselect
|
||||
closeAfterSelect={false}
|
||||
shadows={false}
|
||||
width="full"
|
||||
on:update={({ detail }) => (selectedSprint = detail)}
|
||||
/>
|
||||
</Card>
|
@ -20,11 +20,16 @@
|
||||
import { sprintStatusAssets } from '../../utils'
|
||||
import SprintTitlePresenter from './SprintTitlePresenter.svelte'
|
||||
export let _class: Ref<Class<Sprint>>
|
||||
export let selected: Ref<Sprint> | undefined
|
||||
export let selected: Ref<Sprint> | undefined = undefined
|
||||
export let sprintQuery: DocumentQuery<Sprint> = {}
|
||||
export let create: ObjectCreate | undefined = undefined
|
||||
export let allowDeselect = false
|
||||
export let closeAfterSelect: boolean = true
|
||||
export let shadows = true
|
||||
export let width: 'medium' | 'large' | 'full' = 'medium'
|
||||
export let ignoreSprints: Sprint[] | undefined = undefined
|
||||
|
||||
$: ignoreObjects = ignoreSprints?.filter((s) => '_id' in s).map((s) => s._id)
|
||||
$: _create =
|
||||
create !== undefined
|
||||
? {
|
||||
@ -38,11 +43,14 @@
|
||||
<ObjectPopup
|
||||
{_class}
|
||||
{selected}
|
||||
{ignoreObjects}
|
||||
bind:docQuery={sprintQuery}
|
||||
searchField={'label'}
|
||||
multiSelect={false}
|
||||
{allowDeselect}
|
||||
shadows={true}
|
||||
{closeAfterSelect}
|
||||
{shadows}
|
||||
{width}
|
||||
create={_create}
|
||||
on:update
|
||||
on:close
|
||||
|
@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Class, Client, DocumentQuery, Ref, RelatedDocument } from '@hcengineering/core'
|
||||
import { Resources } from '@hcengineering/platform'
|
||||
import { ObjectSearchResult } from '@hcengineering/presentation'
|
||||
import { Issue, Team } from '@hcengineering/tracker'
|
||||
import { Class, Client, DocumentQuery, Ref, RelatedDocument, TxOperations } from '@hcengineering/core'
|
||||
import { Resources, translate } from '@hcengineering/platform'
|
||||
import { getClient, MessageBox, ObjectSearchResult } from '@hcengineering/presentation'
|
||||
import { Issue, Sprint, Team } from '@hcengineering/tracker'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import CreateIssue from './components/CreateIssue.svelte'
|
||||
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
||||
@ -93,6 +93,9 @@ import IssueTemplates from './components/templates/IssueTemplates.svelte'
|
||||
|
||||
import EditIssueTemplate from './components/templates/EditIssueTemplate.svelte'
|
||||
import TemplateEstimationEditor from './components/templates/EstimationEditor.svelte'
|
||||
import MoveAndDeleteSprintPopup from './components/sprints/MoveAndDeleteSprintPopup.svelte'
|
||||
import { moveIssuesToAnotherSprint } from './utils'
|
||||
import { deleteObject } from '@hcengineering/view-resources/src/utils'
|
||||
|
||||
import CreateTeam from './components/teams/CreateTeam.svelte'
|
||||
import TeamPresenter from './components/teams/TeamPresenter.svelte'
|
||||
@ -160,6 +163,48 @@ async function editWorkflowStatuses (team: Team | undefined): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function moveAndDeleteSprint (client: TxOperations, oldSprint: Sprint, newSprint?: Sprint): Promise<void> {
|
||||
const noSprintLabel = await translate(tracker.string.NoSprint, {})
|
||||
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
label: tracker.string.MoveAndDeleteSprint,
|
||||
message: tracker.string.MoveAndDeleteSprintConfirm,
|
||||
labelProps: { newSprint: newSprint?.label ?? noSprintLabel, deleteSprint: oldSprint.label }
|
||||
},
|
||||
undefined,
|
||||
(result?: boolean) => {
|
||||
if (result === true) {
|
||||
void moveIssuesToAnotherSprint(client, oldSprint, newSprint).then((succes) => {
|
||||
if (succes) {
|
||||
void deleteObject(client, oldSprint)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function deleteSprint (sprint: Sprint): Promise<void> {
|
||||
const client = getClient()
|
||||
// Check if available to move issues to another sprint
|
||||
const firstSearchedSprint = await client.findOne(tracker.class.Sprint, { _id: { $nin: [sprint._id] } })
|
||||
if (firstSearchedSprint !== undefined) {
|
||||
showPopup(
|
||||
MoveAndDeleteSprintPopup,
|
||||
{
|
||||
sprint,
|
||||
moveAndDeleteSprint: async (selectedSprint?: Sprint) =>
|
||||
await moveAndDeleteSprint(client, sprint, selectedSprint)
|
||||
},
|
||||
'top'
|
||||
)
|
||||
} else {
|
||||
await moveAndDeleteSprint(client, sprint)
|
||||
}
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
NopeComponent,
|
||||
@ -234,7 +279,8 @@ export default async (): Promise<Resources> => ({
|
||||
GetIssueTitle: issueTitleProvider
|
||||
},
|
||||
actionImpl: {
|
||||
EditWorkflowStatuses: editWorkflowStatuses
|
||||
EditWorkflowStatuses: editWorkflowStatuses,
|
||||
DeleteSprint: deleteSprint
|
||||
},
|
||||
resolver: {
|
||||
Location: resolveLocation
|
||||
|
@ -228,6 +228,9 @@ export default mergeIds(trackerId, tracker, {
|
||||
MoveToSprint: '' as IntlString,
|
||||
NoSprint: '' as IntlString,
|
||||
|
||||
MoveAndDeleteSprint: '' as IntlString,
|
||||
MoveAndDeleteSprintConfirm: '' as IntlString,
|
||||
|
||||
Estimation: '' as IntlString,
|
||||
ReportedTime: '' as IntlString,
|
||||
TimeSpendReport: '' as IntlString,
|
||||
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import { Employee, formatName } from '@hcengineering/contact'
|
||||
import { DocumentQuery, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
|
||||
import { DocumentQuery, Ref, SortingOrder, TxOperations, WithLookup } from '@hcengineering/core'
|
||||
import { TypeState } from '@hcengineering/kanban'
|
||||
import { Asset, IntlString, translate } from '@hcengineering/platform'
|
||||
import {
|
||||
@ -614,6 +614,34 @@ export function getDayOfSprint (startDate: number, now: number): number {
|
||||
return ds.filter((it) => !isWeekend(new Date(new Date(stTime).setDate(it)))).length
|
||||
}
|
||||
|
||||
export async function moveIssuesToAnotherSprint (
|
||||
client: TxOperations,
|
||||
oldSprint: Sprint,
|
||||
newSprint: Sprint | undefined
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
// Find all Issues by Sprint
|
||||
const movedIssues = await client.findAll(tracker.class.Issue, { sprint: oldSprint._id })
|
||||
|
||||
// Update Issues by new Sprint
|
||||
const awaitedUpdates = []
|
||||
for (const issue of movedIssues) {
|
||||
awaitedUpdates.push(client.update(issue, { sprint: newSprint?._id ?? undefined }))
|
||||
}
|
||||
await Promise.all(awaitedUpdates)
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error happened while moving issues between sprints from ${oldSprint.label} to ${
|
||||
newSprint?.label ?? 'No Sprint'
|
||||
}: `,
|
||||
error
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user