Rank order (#2150)

Signed-off-by: Dvinyanin Alexandr <dvinyanin.alexandr@gmail.com>
This commit is contained in:
Alex 2022-06-28 13:53:39 +07:00 committed by GitHub
parent cade773b95
commit 1e21c85379
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 86 additions and 42 deletions

View File

@ -6,6 +6,9 @@ HR:
- Allow to change assignee in Kanban
Tracker:
- Manual issues ordering
## 0.6.29
Platform:

View File

@ -494,7 +494,7 @@ export function createModel (builder: Builder): void {
id: issuesId,
label: tracker.string.Issues,
icon: tracker.icon.Issues,
component: tracker.component.IssuesView,
component: tracker.component.Issues,
componentProps: {
title: tracker.string.Issues
}

View File

@ -27,7 +27,7 @@
export let states: TypeState[] = []
export let query: DocumentQuery<Item> = {}
export let fieldName: string
export let rankFieldName: string
export let rankFieldName: string | undefined
export let selection: number | undefined = undefined
export let checked: Doc[] = []
@ -57,7 +57,10 @@
dragItem?: Item // required for svelte to properly recalculate state.
): ExtItem[] {
const stateCards = objects.filter((it) => (it as any)[fieldName] === state._id)
stateCards.sort((a, b) => (a as any)[rankFieldName]?.localeCompare((b as any)[rankFieldName]))
if (rankFieldName !== undefined) {
const sortField = rankFieldName
stateCards.sort((a, b) => (a as any)[sortField]?.localeCompare((b as any)[sortField]))
}
return stateCards.map((it, idx, arr) => ({
it,
prev: arr[idx - 1],
@ -96,14 +99,13 @@
}
}
const dragCardRank = (dragCard as any)[rankFieldName]
if (dragCardInitialRank !== (dragCard as any)[rankFieldName]) {
if (rankFieldName !== undefined && dragCardInitialRank !== (dragCard as any)[rankFieldName]) {
const dragCardRank = (dragCard as any)[rankFieldName]
updates = {
...updates,
[rankFieldName]: dragCardRank
}
}
if (Object.keys(updates).length > 0) {
await updateItem(dragCard, updates)
}
@ -113,7 +115,7 @@
const client = getClient()
let dragCard: Item | undefined
let dragCardInitialRank: string
let dragCardInitialRank: string | undefined
let dragCardInitialState: StateType
let isDragging = false
@ -144,23 +146,26 @@
if (card !== undefined && card[fieldName] !== state._id) {
card[fieldName] = state._id
const objs = getStateObjects(objects, state)
card[rankFieldName] = calcRank(objs[objs.length - 1]?.it, undefined)
if (rankFieldName !== undefined) card[rankFieldName] = calcRank(objs[objs.length - 1]?.it, undefined)
}
}
function cardDragOver (evt: CardDragEvent, object: ExtItem): void {
if (dragCard !== undefined) {
;(dragCard as any)[rankFieldName] = doCalcRank(object, evt)
;(dragCard as any)[fieldName] = (dragCard as any)[fieldName]
if (rankFieldName !== undefined) {
;(dragCard as any)[rankFieldName] = doCalcRank(object, evt)
}
}
}
function cardDrop (evt: CardDragEvent, object: ExtItem): void {
if (dragCard !== undefined) {
if (dragCard !== undefined && rankFieldName !== undefined) {
;(dragCard as any)[rankFieldName] = doCalcRank(object, evt)
}
isDragging = false
}
function onDragStart (object: ExtItem, state: TypeState): void {
dragCardInitialState = state._id
dragCardInitialRank = (object.it as any)[rankFieldName]
dragCardInitialRank = rankFieldName === undefined ? undefined : (object.it as any)[rankFieldName]
dragCard = object.it
isDragging = true
dispatch('obj-focus', object.it)

View File

@ -106,6 +106,7 @@
"NoAssignee": "No assignee",
"LastUpdated": "Last updated",
"DueDate": "Due date",
"Manual": "Manual",
"All": "All",
"PastWeek": "Past week",
"PastMonth": "Past month",

View File

@ -106,6 +106,7 @@
"NoAssignee": "Нет исполнителя",
"LastUpdated": "Последнее обновление",
"DueDate": "Срок",
"Manual": "Пользовательский",
"All": "Все",
"PastWeek": "Предыдущая неделя",
"PastMonth": "Предыдущий месяц",

View File

@ -25,7 +25,10 @@
let query: DocumentQuery<Issue>
$: statusQuery.query(
tracker.class.IssueStatus,
{ category: { $in: [tracker.issueStatusCategory.Unstarted, tracker.issueStatusCategory.Started] } },
{
category: { $in: [tracker.issueStatusCategory.Unstarted, tracker.issueStatusCategory.Started] },
space: currentSpace
},
(result) => {
query = { status: { $in: result.map(({ _id }) => _id) }, space: currentSpace }
}

View File

@ -23,9 +23,13 @@
const statusQuery = createQuery()
let query: DocumentQuery<Issue> = {}
$: statusQuery.query(tracker.class.IssueStatus, { category: tracker.issueStatusCategory.Backlog }, (result) => {
query = { status: { $in: result.map(({ _id }) => _id) }, space: currentSpace }
})
$: statusQuery.query(
tracker.class.IssueStatus,
{ category: tracker.issueStatusCategory.Backlog, space: currentSpace },
(result) => {
query = { status: { $in: result.map(({ _id }) => _id) }, space: currentSpace }
}
)
</script>
<IssuesView {query} title={tracker.string.BacklogIssues} />

View File

@ -0,0 +1,26 @@
<!--
// 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 { Ref } from '@anticrm/core'
import { Team } from '@anticrm/tracker'
import tracker from '../../plugin'
import IssuesView from './IssuesView.svelte'
export let currentSpace: Ref<Team>
$: query = { space: currentSpace }
</script>
<IssuesView {query} title={tracker.string.ActiveIssues} />

View File

@ -14,18 +14,18 @@
-->
<script lang="ts">
import contact from '@anticrm/contact'
import { Class, Doc, DocumentQuery, FindOptions, Ref, SortingOrder, WithLookup } from '@anticrm/core'
import { Kanban, TypeState } from '@anticrm/kanban'
import { Class, Doc, DocumentQuery, Lookup, Ref, SortingOrder, WithLookup } from '@anticrm/core'
import { Kanban } from '@anticrm/kanban'
import notification from '@anticrm/notification'
import { createQuery, getClient } from '@anticrm/presentation'
import { Issue, IssuesGrouping, IssueStatus, Team, ViewOptions } from '@anticrm/tracker'
import { Issue, IssuesGrouping, IssuesOrdering, IssueStatus, Team, ViewOptions } from '@anticrm/tracker'
import { Button, Component, Icon, IconAdd, showPanel, showPopup, Tooltip } from '@anticrm/ui'
import { focusStore, ListSelectionProvider, SelectDirection, selectionStore } from '@anticrm/view-resources'
import ActionContext from '@anticrm/view-resources/src/components/ActionContext.svelte'
import Menu from '@anticrm/view-resources/src/components/Menu.svelte'
import { onMount } from 'svelte'
import tracker from '../../plugin'
import { getKanbanStatuses } from '../../utils'
import { getKanbanStatuses, issuesSortOrderMap } from '../../utils'
import CreateIssue from '../CreateIssue.svelte'
import ProjectEditor from '../projects/ProjectEditor.svelte'
import AssigneePresenter from './AssigneePresenter.svelte'
@ -40,7 +40,9 @@
export let query: DocumentQuery<Issue> = {}
$: currentSpace = typeof query.space === 'string' ? query.space : tracker.team.DefaultTeam
$: ({ groupBy, shouldShowEmptyGroups, shouldShowSubIssues } = viewOptions)
$: ({ groupBy, orderBy, shouldShowEmptyGroups, shouldShowSubIssues } = viewOptions)
$: sort = { [orderBy]: issuesSortOrderMap[orderBy] }
$: rankFieldName = orderBy === IssuesOrdering.Manual ? orderBy : undefined
$: resultQuery = {
...(shouldShowSubIssues ? {} : { attachedTo: tracker.ids.NoParent }),
space: currentSpace,
@ -57,17 +59,10 @@
})
let issueStatuses: WithLookup<IssueStatus>[] | undefined
let states: TypeState[] | undefined
$: statusesQuery.query(
tracker.class.IssueStatus,
{ attachedTo: currentSpace },
(is) => {
states = is.map((status) => ({
_id: status._id,
title: status.name,
color: status.color ?? status.$lookup?.category?.color ?? 0,
icon: status.$lookup?.category?.icon ?? undefined
}))
issueStatuses = is
},
{
@ -80,13 +75,11 @@
return object as WithLookup<Issue>
}
const options: FindOptions<Issue> = {
lookup: {
assignee: contact.class.Employee,
space: tracker.class.Team,
_id: {
subIssues: tracker.class.Issue
}
const lookup: Lookup<Issue> = {
assignee: contact.class.Employee,
space: tracker.class.Team,
_id: {
subIssues: tracker.class.Issue
}
}
@ -124,10 +117,10 @@
_class={tracker.class.Issue}
search=""
{states}
{options}
options={{ sort, lookup }}
query={resultQuery}
fieldName={groupBy}
rankFieldName={'rank'}
{rankFieldName}
on:content={(evt) => {
listProvider.update(evt.detail)
}}

View File

@ -18,6 +18,7 @@ import { Resources } from '@anticrm/platform'
import { ObjectSearchResult } from '@anticrm/presentation'
import { Issue, Team } from '@anticrm/tracker'
import Inbox from './components/inbox/Inbox.svelte'
import Issues from './components/issues/Issues.svelte'
import Active from './components/issues/Active.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
import Backlog from './components/issues/Backlog.svelte'
@ -111,6 +112,7 @@ export async function queryIssue<D extends Issue> (
export default async (): Promise<Resources> => ({
component: {
NopeComponent,
Issues,
Active,
Backlog,
Inbox,

View File

@ -122,6 +122,7 @@ export default mergeIds(trackerId, tracker, {
NoAssignee: '' as IntlString,
LastUpdated: '' as IntlString,
DueDate: '' as IntlString,
Manual: '' as IntlString,
All: '' as IntlString,
PastWeek: '' as IntlString,
PastMonth: '' as IntlString,
@ -179,6 +180,7 @@ export default mergeIds(trackerId, tracker, {
Inbox: '' as AnyComponent,
MyIssues: '' as AnyComponent,
Views: '' as AnyComponent,
Issues: '' as AnyComponent,
Active: '' as AnyComponent,
Backlog: '' as AnyComponent,
Projects: '' as AnyComponent,

View File

@ -43,7 +43,8 @@ export const issuesOrderByOptions: Record<IssuesOrdering, IntlString> = {
[IssuesOrdering.Status]: tracker.string.Status,
[IssuesOrdering.Priority]: tracker.string.Priority,
[IssuesOrdering.LastUpdated]: tracker.string.LastUpdated,
[IssuesOrdering.DueDate]: tracker.string.DueDate
[IssuesOrdering.DueDate]: tracker.string.DueDate,
[IssuesOrdering.Manual]: tracker.string.Manual
}
export const issuesDateModificationPeriodOptions: Record<IssuesDateModificationPeriod, IntlString> = {

View File

@ -54,7 +54,7 @@ export interface Selection {
}
export type IssuesGroupByKeys = keyof Pick<Issue, 'status' | 'priority' | 'assignee' | 'project'>
export type IssuesOrderByKeys = keyof Pick<Issue, 'status' | 'priority' | 'modifiedOn' | 'dueDate'>
export type IssuesOrderByKeys = keyof Pick<Issue, 'status' | 'priority' | 'modifiedOn' | 'dueDate' | 'rank'>
export const issuesGroupKeyMap: Record<IssuesGrouping, IssuesGroupByKeys | undefined> = {
[IssuesGrouping.Status]: 'status',
@ -68,14 +68,16 @@ export const issuesOrderKeyMap: Record<IssuesOrdering, IssuesOrderByKeys> = {
[IssuesOrdering.Status]: 'status',
[IssuesOrdering.Priority]: 'priority',
[IssuesOrdering.LastUpdated]: 'modifiedOn',
[IssuesOrdering.DueDate]: 'dueDate'
[IssuesOrdering.DueDate]: 'dueDate',
[IssuesOrdering.Manual]: 'rank'
}
export const issuesSortOrderMap: Record<IssuesOrderByKeys, SortingOrder> = {
status: SortingOrder.Ascending,
priority: SortingOrder.Ascending,
modifiedOn: SortingOrder.Descending,
dueDate: SortingOrder.Descending
dueDate: SortingOrder.Descending,
rank: SortingOrder.Ascending
}
export const issuesGroupEditorMap: Record<'status' | 'priority' | 'project', AnyComponent | undefined> = {

View File

@ -82,8 +82,9 @@ export enum IssuesGrouping {
export enum IssuesOrdering {
Status = 'status',
Priority = 'priority',
LastUpdated = 'lastUpdated',
DueDate = 'dueDate'
LastUpdated = 'modifiedOn',
DueDate = 'dueDate',
Manual = 'rank'
}
/**