TSK-1032: add confirmation dialog for projects, fix sprint deleting and allow deleting for Owner or creator only (#2964)

Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
Vyacheslav Tumanov 2023-04-17 11:08:41 +05:00 committed by GitHub
parent 6bdb599e35
commit e4a53a1b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 51 additions and 59 deletions

View File

@ -36,8 +36,4 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, { builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTracker.trigger.OnComponentRemove trigger: serverTracker.trigger.OnComponentRemove
}) })
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTracker.trigger.OnProjectDelete
})
} }

View File

@ -594,7 +594,12 @@ export function createModel (builder: Builder): void {
presenter: tracker.component.AssigneePresenter, presenter: tracker.component.AssigneePresenter,
props: { defaultClass: contact.class.Employee, shouldShowLabel: false } props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
} }
] ],
options: {
lookup: {
space: tracker.class.Project
}
}
}, },
tracker.viewlet.IssueList tracker.viewlet.IssueList
) )
@ -1137,8 +1142,8 @@ export function createModel (builder: Builder): void {
builder, builder,
{ {
action: tracker.actionImpl.DeleteProject, action: tracker.actionImpl.DeleteProject,
label: tracker.string.DeleteProject, label: workbench.string.Archive,
icon: view.icon.Delete, icon: view.icon.Archive,
input: 'focus', input: 'focus',
category: tracker.category.Tracker, category: tracker.category.Tracker,
target: tracker.class.Project, target: tracker.class.Project,

View File

@ -180,8 +180,9 @@
"EditWorkflowStatuses": "Edit issue statuses", "EditWorkflowStatuses": "Edit issue statuses",
"EditProject": "Edit project", "EditProject": "Edit project",
"DeleteProject": "Delete project", "DeleteProject": "Delete project",
"DeleteProjectName": "Delete project {name}?", "ArchiveProjectName": "Archive project {name}?",
"ProjectHasIssues": "There are existing issues in this project, are you sure that you want to delete? Both the project and the issues will be deleted.", "ArchiveProjectConfirm": "Do you want to archive this project?",
"ProjectHasIssues": "There are existing issues in this project, are you sure that you want to archive?",
"ManageWorkflowStatuses": "Manage issue statuses within project", "ManageWorkflowStatuses": "Manage issue statuses within project",
"AddWorkflowStatus": "Add issue status", "AddWorkflowStatus": "Add issue status",
"EditWorkflowStatus": "Edit issue status", "EditWorkflowStatus": "Edit issue status",

View File

@ -180,8 +180,9 @@
"EditWorkflowStatuses": "Редактировать статусы задач", "EditWorkflowStatuses": "Редактировать статусы задач",
"EditProject": "Редактировать проект", "EditProject": "Редактировать проект",
"DeleteProject": "Удалить проект", "DeleteProject": "Удалить проект",
"DeleteProjectName": "Удалить проект {name}?", "ArchiveProjectName": "Архивировать проект {name}?",
"ProjectHasIssues": "Для данного проекта существуют задачи, уверены, что хотите удалить? Задачи и проект будут удалены.", "ArchiveProjectConfirm": "Вы действительно хотите заархивировать этот проект?",
"ProjectHasIssues": "Для данного проекта существуют задачи, уверены, что хотите заархивировать?",
"ManageWorkflowStatuses": "Управлять статусами задач для команды", "ManageWorkflowStatuses": "Управлять статусами задач для команды",
"AddWorkflowStatus": "Добавить статус задачи", "AddWorkflowStatus": "Добавить статус задачи",
"EditWorkflowStatus": "Редактировать статус задачи", "EditWorkflowStatus": "Редактировать статус задачи",

View File

@ -54,7 +54,7 @@
} }
function getQuery (mode: string, queries: { [key: string]: DocumentQuery<Issue> }) { function getQuery (mode: string, queries: { [key: string]: DocumentQuery<Issue> }) {
return queries[mode] return { ...queries[mode], '$lookup.space.archived': false }
} }
$: query = getQuery(mode, { assigned, created, subscribed }) $: query = getQuery(mode, { assigned, created, subscribed })
</script> </script>

View File

@ -119,7 +119,6 @@ import {
issuePrioritySort, issuePrioritySort,
issueStatusSort, issueStatusSort,
moveIssuesToAnotherSprint, moveIssuesToAnotherSprint,
removeProject,
sprintSort, sprintSort,
subIssueQuery subIssueQuery
} from './utils' } from './utils'
@ -218,19 +217,32 @@ async function deleteProject (project: Project | undefined): Promise<void> {
showPopup( showPopup(
MessageBox, MessageBox,
{ {
label: tracker.string.DeleteProjectName, label: tracker.string.ArchiveProjectName,
labelProps: { name: project.name }, labelProps: { name: project.name },
message: tracker.string.ProjectHasIssues message: tracker.string.ProjectHasIssues
}, },
undefined, undefined,
(result?: boolean) => { (result?: boolean) => {
if (result === true) { if (result === true) {
void removeProject(project) void client.update(project, { archived: true })
} }
} }
) )
} else { } else {
await removeProject(project) showPopup(
MessageBox,
{
label: tracker.string.ArchiveProjectName,
labelProps: { name: project.name },
message: tracker.string.ArchiveProjectConfirm
},
undefined,
(result?: boolean) => {
if (result === true) {
void client.update(project, { archived: true })
}
}
)
} }
} }
} }
@ -260,22 +272,25 @@ async function moveAndDeleteSprints (client: TxOperations, oldSprints: Sprint[],
) )
} }
async function deleteSprint (sprints: Sprint[]): Promise<void> { async function deleteSprint (sprints: Sprint | Sprint[]): Promise<void> {
const client = getClient() const client = getClient()
const sprintArray = Array.isArray(sprints) ? sprints : [sprints]
// Check if available to move issues to another sprint // Check if available to move issues to another sprint
const firstSearchedSprint = await client.findOne(tracker.class.Sprint, { _id: { $nin: sprints.map((p) => p._id) } }) const firstSearchedSprint = await client.findOne(tracker.class.Sprint, {
_id: { $nin: sprintArray.map((p) => p._id) }
})
if (firstSearchedSprint !== undefined) { if (firstSearchedSprint !== undefined) {
showPopup( showPopup(
MoveAndDeleteSprintPopup, MoveAndDeleteSprintPopup,
{ {
sprints, sprintArray,
moveAndDeleteSprint: async (selectedSprint?: Sprint) => moveAndDeleteSprint: async (selectedSprint?: Sprint) =>
await moveAndDeleteSprints(client, sprints, selectedSprint) await moveAndDeleteSprints(client, sprintArray, selectedSprint)
}, },
'top' 'top'
) )
} else { } else {
await moveAndDeleteSprints(client, sprints) await moveAndDeleteSprints(client, sprintArray)
} }
} }

View File

@ -98,7 +98,8 @@ export default mergeIds(trackerId, tracker, {
EditWorkflowStatuses: '' as IntlString, EditWorkflowStatuses: '' as IntlString,
EditProject: '' as IntlString, EditProject: '' as IntlString,
DeleteProject: '' as IntlString, DeleteProject: '' as IntlString,
DeleteProjectName: '' as IntlString, ArchiveProjectName: '' as IntlString,
ArchiveProjectConfirm: '' as IntlString,
ProjectHasIssues: '' as IntlString, ProjectHasIssues: '' as IntlString,
ManageWorkflowStatuses: '' as IntlString, ManageWorkflowStatuses: '' as IntlString,
AddWorkflowStatus: '' as IntlString, AddWorkflowStatus: '' as IntlString,

View File

@ -33,7 +33,7 @@ import core, {
TxUpdateDoc TxUpdateDoc
} from '@hcengineering/core' } from '@hcengineering/core'
import { Asset, IntlString } from '@hcengineering/platform' import { Asset, IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import { calcRank } from '@hcengineering/task' import { calcRank } from '@hcengineering/task'
import { import {
ComponentStatus, ComponentStatus,
@ -550,11 +550,6 @@ export async function getPreviousAssignees (issue: Issue): Promise<Array<Ref<Emp
}) })
} }
export async function removeProject (project: Project): Promise<void> {
const client = getClient()
await client.removeDoc(tracker.class.Project, core.space.Space, project._id)
}
async function updateIssuesOnMove ( async function updateIssuesOnMove (
client: TxOperations, client: TxOperations,
applyOps: ApplyOperations, applyOps: ApplyOperations,

View File

@ -15,6 +15,7 @@
// //
import core, { import core, {
AccountRole,
AttachedDoc, AttachedDoc,
CategoryType, CategoryType,
Class, Class,
@ -22,6 +23,7 @@ import core, {
Collection, Collection,
Doc, Doc,
DocumentUpdate, DocumentUpdate,
getCurrentAccount,
getObjectValue, getObjectValue,
Hierarchy, Hierarchy,
Lookup, Lookup,
@ -298,6 +300,8 @@ export async function buildModel (options: BuildModelOptions): Promise<Attribute
} }
export async function deleteObject (client: TxOperations, object: Doc): Promise<void> { export async function deleteObject (client: TxOperations, object: Doc): Promise<void> {
const currentAcc = getCurrentAccount()
if (currentAcc.role !== AccountRole.Owner && object.createdBy !== currentAcc._id) return
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) { if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
const adoc = object as AttachedDoc const adoc = object as AttachedDoc
await client await client
@ -309,6 +313,8 @@ export async function deleteObject (client: TxOperations, object: Doc): Promise<
} }
export async function deleteObjects (client: TxOperations, objects: Doc[]): Promise<void> { export async function deleteObjects (client: TxOperations, objects: Doc[]): Promise<void> {
const currentAcc = getCurrentAccount()
if (currentAcc.role !== AccountRole.Owner && objects.some((p) => p.createdBy !== currentAcc._id)) return
const ops = client.apply('delete') const ops = client.apply('delete')
for (const object of objects) { for (const object of objects) {
if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) { if (client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {

View File

@ -33,7 +33,7 @@ import core, {
import { getMetadata } from '@hcengineering/platform' import { getMetadata } from '@hcengineering/platform'
import serverCore, { TriggerControl } from '@hcengineering/server-core' import serverCore, { TriggerControl } from '@hcengineering/server-core'
import { addAssigneeNotification } from '@hcengineering/server-task-resources' import { addAssigneeNotification } from '@hcengineering/server-task-resources'
import tracker, { Component, Issue, IssueParentInfo, Project, TimeSpendReport, trackerId } from '@hcengineering/tracker' import tracker, { Component, Issue, IssueParentInfo, TimeSpendReport, trackerId } from '@hcengineering/tracker'
import { workbenchId } from '@hcengineering/workbench' import { workbenchId } from '@hcengineering/workbench'
async function updateSubIssues ( async function updateSubIssues (
@ -84,32 +84,6 @@ export async function addTrackerAssigneeNotification (
await addAssigneeNotification(control, res, issue, assignee, ptx) await addAssigneeNotification(control, res, issue, assignee, ptx)
} }
/**
* @public
*/
export async function OnProjectDelete (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = TxProcessor.extractTx(tx)
if (actualTx._class !== core.class.TxRemoveDoc) {
return []
}
const ctx = actualTx as TxRemoveDoc<Project>
if (ctx.objectClass !== tracker.class.Project) {
return []
}
const issues = await control.findAll(tracker.class.Issue, {
space: ctx.objectId
})
const res: Tx[] = []
issues.forEach((issue) => {
res.push(control.txFactory.createTxRemoveDoc(issue._class, issue.space, issue._id))
})
return res
}
/** /**
* @public * @public
*/ */
@ -216,8 +190,7 @@ export default async () => ({
}, },
trigger: { trigger: {
OnIssueUpdate, OnIssueUpdate,
OnComponentRemove, OnComponentRemove
OnProjectDelete
} }
}) })

View File

@ -33,7 +33,6 @@ export default plugin(serverTrackerId, {
}, },
trigger: { trigger: {
OnIssueUpdate: '' as Resource<TriggerFunc>, OnIssueUpdate: '' as Resource<TriggerFunc>,
OnComponentRemove: '' as Resource<TriggerFunc>, OnComponentRemove: '' as Resource<TriggerFunc>
OnProjectDelete: '' as Resource<TriggerFunc>
} }
}) })