mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-24 17:30:03 +00:00
List empty groups (#2600)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
994a356ea5
commit
8e2ddd6760
@ -521,6 +521,14 @@ export function createModel (builder: Builder): void {
|
|||||||
actionTartget: 'query',
|
actionTartget: 'query',
|
||||||
action: tracker.function.SubIssueQuery,
|
action: tracker.function.SubIssueQuery,
|
||||||
label: tracker.string.SubIssues
|
label: tracker.string.SubIssues
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'shouldShowAll',
|
||||||
|
type: 'toggle',
|
||||||
|
defaultValue: false,
|
||||||
|
actionTartget: 'category',
|
||||||
|
action: view.function.ShowEmptyGroups,
|
||||||
|
label: view.string.ShowEmptyGroups
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -596,6 +604,7 @@ export function createModel (builder: Builder): void {
|
|||||||
['dueDate', SortingOrder.Descending],
|
['dueDate', SortingOrder.Descending],
|
||||||
['rank', SortingOrder.Ascending]
|
['rank', SortingOrder.Ascending]
|
||||||
],
|
],
|
||||||
|
groupDepth: 1,
|
||||||
other: []
|
other: []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,7 +705,10 @@ export function createModel (builder: Builder): void {
|
|||||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||||
attachTo: tracker.class.Issue,
|
attachTo: tracker.class.Issue,
|
||||||
descriptor: tracker.viewlet.Kanban,
|
descriptor: tracker.viewlet.Kanban,
|
||||||
viewOptions: issuesOptions,
|
viewOptions: {
|
||||||
|
...issuesOptions,
|
||||||
|
groupDepth: 1
|
||||||
|
},
|
||||||
config: []
|
config: []
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -876,6 +888,22 @@ export function createModel (builder: Builder): void {
|
|||||||
fields: ['assignee']
|
fields: ['assignee']
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.IssueStatus, core.class.Class, view.mixin.AllValuesFunc, {
|
||||||
|
func: tracker.function.GetAllStatuses
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AllValuesFunc, {
|
||||||
|
func: tracker.function.GetAllPriority
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.AllValuesFunc, {
|
||||||
|
func: tracker.function.GetAllProjects
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.AllValuesFunc, {
|
||||||
|
func: tracker.function.GetAllSprints
|
||||||
|
})
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
workbench.class.Application,
|
workbench.class.Application,
|
||||||
core.space.Model,
|
core.space.Model,
|
||||||
@ -933,13 +961,13 @@ export function createModel (builder: Builder): void {
|
|||||||
{
|
{
|
||||||
id: activeId,
|
id: activeId,
|
||||||
label: tracker.string.Active,
|
label: tracker.string.Active,
|
||||||
// icon: tracker.icon.TrackerApplication,
|
icon: tracker.icon.CategoryStarted,
|
||||||
component: tracker.component.Active
|
component: tracker.component.Active
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: backlogId,
|
id: backlogId,
|
||||||
label: tracker.string.Backlog,
|
label: tracker.string.Backlog,
|
||||||
// icon: tracker.icon.TrackerApplication,
|
icon: tracker.icon.CategoryBacklog,
|
||||||
component: tracker.component.Backlog
|
component: tracker.component.Backlog
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -62,7 +62,8 @@ import type {
|
|||||||
ViewletDescriptor,
|
ViewletDescriptor,
|
||||||
ViewletPreference,
|
ViewletPreference,
|
||||||
ViewOptionsModel,
|
ViewOptionsModel,
|
||||||
ViewOptions
|
ViewOptions,
|
||||||
|
AllValuesFunc
|
||||||
} from '@hcengineering/view'
|
} from '@hcengineering/view'
|
||||||
import view from './plugin'
|
import view from './plugin'
|
||||||
|
|
||||||
@ -215,6 +216,11 @@ export class TSortFuncs extends TClass implements ClassSortFuncs {
|
|||||||
func!: SortFunc
|
func!: SortFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mixin(view.mixin.AllValuesFunc, core.class.Class)
|
||||||
|
export class TAllValuesFunc extends TClass implements AllValuesFunc {
|
||||||
|
func!: Resource<(space: Ref<Space> | undefined) => Promise<any[]>>
|
||||||
|
}
|
||||||
|
|
||||||
@Model(view.class.ViewletPreference, preference.class.Preference)
|
@Model(view.class.ViewletPreference, preference.class.Preference)
|
||||||
export class TViewletPreference extends TPreference implements ViewletPreference {
|
export class TViewletPreference extends TPreference implements ViewletPreference {
|
||||||
attachedTo!: Ref<Viewlet>
|
attachedTo!: Ref<Viewlet>
|
||||||
@ -339,7 +345,8 @@ export function createModel (builder: Builder): void {
|
|||||||
TLinkPresenter,
|
TLinkPresenter,
|
||||||
TArrayEditor,
|
TArrayEditor,
|
||||||
TInlineAttributEditor,
|
TInlineAttributEditor,
|
||||||
TFilteredView
|
TFilteredView,
|
||||||
|
TAllValuesFunc
|
||||||
)
|
)
|
||||||
|
|
||||||
classPresenter(
|
classPresenter(
|
||||||
|
@ -13,10 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { ObjQueryType } from '@hcengineering/core'
|
import { IntlString, mergeIds } from '@hcengineering/platform'
|
||||||
import { IntlString, mergeIds, Resource } from '@hcengineering/platform'
|
|
||||||
import type { AnyComponent } from '@hcengineering/ui'
|
import type { AnyComponent } from '@hcengineering/ui'
|
||||||
import { Filter, ViewAction, viewId } from '@hcengineering/view'
|
import { FilterFunction, ViewAction, ViewCategoryAction, viewId } from '@hcengineering/view'
|
||||||
import view from '@hcengineering/view-resources/src/plugin'
|
import view from '@hcengineering/view-resources/src/plugin'
|
||||||
|
|
||||||
export default mergeIds(viewId, view, {
|
export default mergeIds(viewId, view, {
|
||||||
@ -93,13 +92,14 @@ export default mergeIds(viewId, view, {
|
|||||||
MarkdownFormatting: '' as IntlString
|
MarkdownFormatting: '' as IntlString
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
FilterObjectInResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterObjectInResult: '' as FilterFunction,
|
||||||
FilterObjectNinResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterObjectNinResult: '' as FilterFunction,
|
||||||
FilterValueInResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterValueInResult: '' as FilterFunction,
|
||||||
FilterValueNinResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterValueNinResult: '' as FilterFunction,
|
||||||
FilterBeforeResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterBeforeResult: '' as FilterFunction,
|
||||||
FilterAfterResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterAfterResult: '' as FilterFunction,
|
||||||
FilterNestedMatchResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterNestedMatchResult: '' as FilterFunction,
|
||||||
FilterNestedDontMatchResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>
|
FilterNestedDontMatchResult: '' as FilterFunction,
|
||||||
|
ShowEmptyGroups: '' as ViewCategoryAction
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Client, Doc, ObjQueryType, Ref, Space } from '@hcengineering/core'
|
import { Client, Doc, Ref, Space } from '@hcengineering/core'
|
||||||
import type { IntlString, Resource, StatusCode } from '@hcengineering/platform'
|
import type { IntlString, Resource, StatusCode } from '@hcengineering/platform'
|
||||||
import { mergeIds } from '@hcengineering/platform'
|
import { mergeIds } from '@hcengineering/platform'
|
||||||
import recruit, { recruitId } from '@hcengineering/recruit'
|
import recruit, { recruitId } from '@hcengineering/recruit'
|
||||||
import { TagCategory } from '@hcengineering/tags'
|
import { TagCategory } from '@hcengineering/tags'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
import { Filter, FilterMode } from '@hcengineering/view'
|
import { FilterFunction, FilterMode } from '@hcengineering/view'
|
||||||
|
|
||||||
export default mergeIds(recruitId, recruit, {
|
export default mergeIds(recruitId, recruit, {
|
||||||
status: {
|
status: {
|
||||||
@ -137,9 +137,9 @@ export default mergeIds(recruitId, recruit, {
|
|||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
ApplicationTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>,
|
ApplicationTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>) => Promise<string>>,
|
||||||
HasActiveApplicant: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
HasActiveApplicant: '' as FilterFunction,
|
||||||
HasNoActiveApplicant: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
HasNoActiveApplicant: '' as FilterFunction,
|
||||||
NoneApplications: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>
|
NoneApplications: '' as FilterFunction
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
HasActive: '' as Ref<FilterMode>,
|
HasActive: '' as Ref<FilterMode>,
|
||||||
|
@ -11,11 +11,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { ObjQueryType } from '@hcengineering/core'
|
|
||||||
import { IntlString, mergeIds, Resource } from '@hcengineering/platform'
|
import { IntlString, mergeIds, Resource } from '@hcengineering/platform'
|
||||||
import tags, { tagsId } from '@hcengineering/tags'
|
import tags, { tagsId } from '@hcengineering/tags'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
import { Filter } from '@hcengineering/view'
|
import { FilterFunction } from '@hcengineering/view'
|
||||||
|
|
||||||
export default mergeIds(tagsId, tags, {
|
export default mergeIds(tagsId, tags, {
|
||||||
component: {
|
component: {
|
||||||
@ -53,8 +52,8 @@ export default mergeIds(tagsId, tags, {
|
|||||||
Initial: '' as IntlString
|
Initial: '' as IntlString
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
FilterTagsInResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterTagsInResult: '' as FilterFunction,
|
||||||
FilterTagsNinResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
|
FilterTagsNinResult: '' as FilterFunction,
|
||||||
CreateTagElement: '' as Resource<(props?: Record<string, any>) => Promise<void>>
|
CreateTagElement: '' as Resource<(props?: Record<string, any>) => Promise<void>>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -118,7 +118,6 @@
|
|||||||
"Grouping": "Grouping",
|
"Grouping": "Grouping",
|
||||||
"Ordering": "Ordering",
|
"Ordering": "Ordering",
|
||||||
"CompletedIssues": "Completed issues",
|
"CompletedIssues": "Completed issues",
|
||||||
"ShowEmptyGroups": "Show empty groups",
|
|
||||||
"NoGrouping": "No grouping",
|
"NoGrouping": "No grouping",
|
||||||
"NoAssignee": "No assignee",
|
"NoAssignee": "No assignee",
|
||||||
"LastUpdated": "Last updated",
|
"LastUpdated": "Last updated",
|
||||||
|
@ -118,7 +118,6 @@
|
|||||||
"Grouping": "Группировка",
|
"Grouping": "Группировка",
|
||||||
"Ordering": "Сортировка",
|
"Ordering": "Сортировка",
|
||||||
"CompletedIssues": "Завершённые задачи",
|
"CompletedIssues": "Завершённые задачи",
|
||||||
"ShowEmptyGroups": "Показывать пустые группы",
|
|
||||||
"NoGrouping": "Нет группировки",
|
"NoGrouping": "Нет группировки",
|
||||||
"NoAssignee": "Нет исполнителя",
|
"NoAssignee": "Нет исполнителя",
|
||||||
"LastUpdated": "Последнее обновление",
|
"LastUpdated": "Последнее обновление",
|
||||||
|
@ -13,25 +13,26 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact from '@hcengineering/contact'
|
import contact, { Employee } from '@hcengineering/contact'
|
||||||
import { Class, Doc, DocumentQuery, Lookup, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
|
import { Class, Doc, DocumentQuery, IdMap, Lookup, Ref, toIdMap, WithLookup } from '@hcengineering/core'
|
||||||
import { Kanban, TypeState } from '@hcengineering/kanban'
|
import { Kanban, TypeState } from '@hcengineering/kanban'
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import tags from '@hcengineering/tags'
|
import tags from '@hcengineering/tags'
|
||||||
import { Issue, IssuesGrouping, IssuesOrdering, IssueStatus, Team } from '@hcengineering/tracker'
|
import { Issue, IssuesGrouping, IssuesOrdering, IssueStatus, Project, Sprint, Team } from '@hcengineering/tracker'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Component,
|
Component,
|
||||||
getEventPositionElement,
|
getEventPositionElement,
|
||||||
|
Icon,
|
||||||
IconAdd,
|
IconAdd,
|
||||||
Loading,
|
Loading,
|
||||||
showPanel,
|
showPanel,
|
||||||
showPopup,
|
showPopup,
|
||||||
tooltip
|
tooltip
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { ViewOptionModel, ViewOptions, ViewQueryOption } from '@hcengineering/view'
|
import { CategoryOption, ViewOptionModel, ViewOptions, ViewQueryOption } from '@hcengineering/view'
|
||||||
import {
|
import {
|
||||||
focusStore,
|
focusStore,
|
||||||
ListSelectionProvider,
|
ListSelectionProvider,
|
||||||
@ -41,9 +42,10 @@
|
|||||||
} from '@hcengineering/view-resources'
|
} from '@hcengineering/view-resources'
|
||||||
import ActionContext from '@hcengineering/view-resources/src/components/ActionContext.svelte'
|
import ActionContext from '@hcengineering/view-resources/src/components/ActionContext.svelte'
|
||||||
import Menu from '@hcengineering/view-resources/src/components/Menu.svelte'
|
import Menu from '@hcengineering/view-resources/src/components/Menu.svelte'
|
||||||
|
import { getCategories } from '@hcengineering/view-resources/src/utils'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { getIssueStatusStates, getKanbanStatuses, getPriorityStates, issuesGroupBySorting } from '../../utils'
|
import { issuesGroupBySorting, mapKanbanCategories } from '../../utils'
|
||||||
import CreateIssue from '../CreateIssue.svelte'
|
import CreateIssue from '../CreateIssue.svelte'
|
||||||
import ProjectEditor from '../projects/ProjectEditor.svelte'
|
import ProjectEditor from '../projects/ProjectEditor.svelte'
|
||||||
import AssigneePresenter from './AssigneePresenter.svelte'
|
import AssigneePresenter from './AssigneePresenter.svelte'
|
||||||
@ -68,7 +70,6 @@
|
|||||||
$: dontUpdateRank = orderBy[0] !== IssuesOrdering.Manual
|
$: dontUpdateRank = orderBy[0] !== IssuesOrdering.Manual
|
||||||
|
|
||||||
const spaceQuery = createQuery()
|
const spaceQuery = createQuery()
|
||||||
const statusesQuery = createQuery()
|
|
||||||
|
|
||||||
let currentTeam: Team | undefined
|
let currentTeam: Team | undefined
|
||||||
$: spaceQuery.query(tracker.class.Team, { _id: currentSpace }, (res) => {
|
$: spaceQuery.query(tracker.class.Team, { _id: currentSpace }, (res) => {
|
||||||
@ -97,20 +98,6 @@
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
let issueStatuses: WithLookup<IssueStatus>[] | undefined
|
|
||||||
$: issueStatusStates = getIssueStatusStates(issueStatuses)
|
|
||||||
$: statusesQuery.query(
|
|
||||||
tracker.class.IssueStatus,
|
|
||||||
{ attachedTo: currentSpace },
|
|
||||||
(is) => {
|
|
||||||
issueStatuses = is
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lookup: { category: tracker.class.IssueStatusCategory },
|
|
||||||
sort: { rank: SortingOrder.Ascending }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function toIssue (object: any): WithLookup<Issue> {
|
function toIssue (object: any): WithLookup<Issue> {
|
||||||
return object as WithLookup<Issue>
|
return object as WithLookup<Issue>
|
||||||
}
|
}
|
||||||
@ -138,9 +125,9 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const issuesQuery = createQuery()
|
const issuesQuery = createQuery()
|
||||||
let issueStates: TypeState[] = []
|
let issues: Issue[] = []
|
||||||
const lookupIssue: Lookup<Issue> = {
|
const lookupIssue: Lookup<Issue> = {
|
||||||
status: [tracker.class.IssueStatus, { category: tracker.class.IssueStatusCategory }],
|
status: tracker.class.IssueStatus,
|
||||||
project: tracker.class.Project,
|
project: tracker.class.Project,
|
||||||
sprint: tracker.class.Sprint,
|
sprint: tracker.class.Sprint,
|
||||||
assignee: contact.class.Employee
|
assignee: contact.class.Employee
|
||||||
@ -148,8 +135,8 @@
|
|||||||
$: issuesQuery.query(
|
$: issuesQuery.query(
|
||||||
tracker.class.Issue,
|
tracker.class.Issue,
|
||||||
resultQuery,
|
resultQuery,
|
||||||
async (result) => {
|
(result) => {
|
||||||
issueStates = await getKanbanStatuses(groupBy, result)
|
issues = result
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lookup: lookupIssue,
|
lookup: lookupIssue,
|
||||||
@ -157,27 +144,107 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
let priorityStates: TypeState[] = []
|
const assigneeQuery = createQuery()
|
||||||
getPriorityStates().then((states) => {
|
let assignee: Employee[] = []
|
||||||
priorityStates = states
|
$: assigneeQuery.query(contact.class.Employee, {}, (result) => {
|
||||||
|
assignee = result
|
||||||
})
|
})
|
||||||
function getIssueStates (
|
|
||||||
groupBy: IssuesGrouping,
|
const statusesQuery = createQuery()
|
||||||
states: TypeState[],
|
let statuses: WithLookup<IssueStatus>[] = []
|
||||||
statusStates: TypeState[],
|
let statusesMap: IdMap<IssueStatus> = new Map()
|
||||||
priorityStates: TypeState[]
|
$: statusesQuery.query(
|
||||||
|
tracker.class.IssueStatus,
|
||||||
|
{
|
||||||
|
space: currentSpace
|
||||||
|
},
|
||||||
|
(result) => {
|
||||||
|
statuses = result
|
||||||
|
statusesMap = toIdMap(result)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: { category: tracker.class.IssueStatusCategory }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const projectsQuery = createQuery()
|
||||||
|
let projects: Project[] = []
|
||||||
|
$: projectsQuery.query(
|
||||||
|
tracker.class.Project,
|
||||||
|
{
|
||||||
|
space: currentSpace
|
||||||
|
},
|
||||||
|
(result) => {
|
||||||
|
projects = result
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const sprintsQuery = createQuery()
|
||||||
|
let sprints: Sprint[] = []
|
||||||
|
$: sprintsQuery.query(
|
||||||
|
tracker.class.Sprint,
|
||||||
|
{
|
||||||
|
space: currentSpace
|
||||||
|
},
|
||||||
|
(result) => {
|
||||||
|
sprints = result
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let states: TypeState[]
|
||||||
|
|
||||||
|
$: updateCategories(
|
||||||
|
tracker.class.Issue,
|
||||||
|
issues,
|
||||||
|
groupBy,
|
||||||
|
viewOptions,
|
||||||
|
viewOptionsConfig,
|
||||||
|
statuses,
|
||||||
|
projects,
|
||||||
|
sprints,
|
||||||
|
assignee
|
||||||
|
)
|
||||||
|
|
||||||
|
async function updateCategories (
|
||||||
|
_class: Ref<Class<Doc>>,
|
||||||
|
docs: Doc[],
|
||||||
|
groupByKey: string,
|
||||||
|
viewOptions: ViewOptions,
|
||||||
|
viewOptionsModel: ViewOptionModel[] | undefined,
|
||||||
|
statuses: WithLookup<IssueStatus>[],
|
||||||
|
projects: Project[],
|
||||||
|
sprints: Sprint[],
|
||||||
|
assignee: Employee[]
|
||||||
) {
|
) {
|
||||||
if (states.length > 0) return states
|
let categories = await getCategories(client, _class, docs, groupByKey)
|
||||||
if (groupBy === IssuesGrouping.Status) return statusStates
|
for (const viewOption of viewOptionsModel ?? []) {
|
||||||
if (groupBy === IssuesGrouping.Priority) return priorityStates
|
if (viewOption.actionTartget !== 'category') continue
|
||||||
return []
|
const categoryFunc = viewOption as CategoryOption
|
||||||
|
if (viewOptions[viewOption.key] ?? viewOption.defaultValue) {
|
||||||
|
const f = await getResource(categoryFunc.action)
|
||||||
|
const res = await f(_class, space, groupByKey)
|
||||||
|
if (res !== undefined) {
|
||||||
|
for (const category of categories) {
|
||||||
|
if (!res.includes(category)) {
|
||||||
|
res.push(category)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
categories = res
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const indexes = new Map(categories.map((p, i) => [p, i]))
|
||||||
|
const res = await mapKanbanCategories(groupByKey, categories, statuses, projects, sprints, assignee)
|
||||||
|
res.sort((a, b) => {
|
||||||
|
const aIndex = indexes.get(a._id ?? undefined) ?? -1
|
||||||
|
const bIndex = indexes.get(b._id ?? undefined) ?? -1
|
||||||
|
return aIndex - bIndex
|
||||||
|
})
|
||||||
|
states = res
|
||||||
}
|
}
|
||||||
$: states = getIssueStates(groupBy, issueStates, issueStatusStates, priorityStates)
|
|
||||||
|
|
||||||
const fullFilled: { [key: string]: boolean } = {}
|
const fullFilled: { [key: string]: boolean } = {}
|
||||||
const getState = (state: any): WithLookup<IssueStatus> | undefined => {
|
|
||||||
return issueStatuses?.filter((is) => is._id === state._id)[0]
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !states?.length}
|
{#if !states?.length}
|
||||||
@ -211,11 +278,17 @@
|
|||||||
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
|
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="header" let:state let:count>
|
<svelte:fragment slot="header" let:state let:count>
|
||||||
{@const stateWLU = getState(state)}
|
{@const status = statusesMap.get(state._id)}
|
||||||
<div class="header flex-col">
|
<div class="header flex-col">
|
||||||
<div class="flex-between label font-medium w-full h-full">
|
<div class="flex-between label font-medium w-full h-full">
|
||||||
<div class="flex-row-center gap-2">
|
<div class="flex-row-center gap-2">
|
||||||
{#if stateWLU !== undefined}<IssueStatusIcon value={stateWLU} size={'small'} />{/if}
|
{#if state.icon}
|
||||||
|
{#if groupBy === 'status' && status}
|
||||||
|
<IssueStatusIcon value={status} size="small" />
|
||||||
|
{:else}
|
||||||
|
<Icon icon={state.icon} size="small" />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
<span class="lines-limit-2 ml-2">{state.title}</span>
|
<span class="lines-limit-2 ml-2">{state.title}</span>
|
||||||
<span class="counter ml-2 text-md">{count}</span>
|
<span class="counter ml-2 text-md">{count}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,7 +106,17 @@ import IssueTemplates from './components/templates/IssueTemplates.svelte'
|
|||||||
import EditIssueTemplate from './components/templates/EditIssueTemplate.svelte'
|
import EditIssueTemplate from './components/templates/EditIssueTemplate.svelte'
|
||||||
import TemplateEstimationEditor from './components/templates/EstimationEditor.svelte'
|
import TemplateEstimationEditor from './components/templates/EstimationEditor.svelte'
|
||||||
import MoveAndDeleteSprintPopup from './components/sprints/MoveAndDeleteSprintPopup.svelte'
|
import MoveAndDeleteSprintPopup from './components/sprints/MoveAndDeleteSprintPopup.svelte'
|
||||||
import { moveIssuesToAnotherSprint, issueStatusSort, issuePrioritySort, sprintSort, subIssueQuery } from './utils'
|
import {
|
||||||
|
moveIssuesToAnotherSprint,
|
||||||
|
issueStatusSort,
|
||||||
|
issuePrioritySort,
|
||||||
|
sprintSort,
|
||||||
|
subIssueQuery,
|
||||||
|
getAllStatuses,
|
||||||
|
getAllPriority,
|
||||||
|
getAllProjects,
|
||||||
|
getAllSprints
|
||||||
|
} from './utils'
|
||||||
import { deleteObject } from '@hcengineering/view-resources/src/utils'
|
import { deleteObject } from '@hcengineering/view-resources/src/utils'
|
||||||
|
|
||||||
import CreateTeam from './components/teams/CreateTeam.svelte'
|
import CreateTeam from './components/teams/CreateTeam.svelte'
|
||||||
@ -384,7 +394,11 @@ export default async (): Promise<Resources> => ({
|
|||||||
IssueStatusSort: issueStatusSort,
|
IssueStatusSort: issueStatusSort,
|
||||||
IssuePrioritySort: issuePrioritySort,
|
IssuePrioritySort: issuePrioritySort,
|
||||||
SprintSort: sprintSort,
|
SprintSort: sprintSort,
|
||||||
SubIssueQuery: subIssueQuery
|
SubIssueQuery: subIssueQuery,
|
||||||
|
GetAllStatuses: getAllStatuses,
|
||||||
|
GetAllPriority: getAllPriority,
|
||||||
|
GetAllProjects: getAllProjects,
|
||||||
|
GetAllSprints: getAllSprints
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
EditWorkflowStatuses: editWorkflowStatuses,
|
EditWorkflowStatuses: editWorkflowStatuses,
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import { Client, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
import { Client, Doc, Ref, Space } from '@hcengineering/core'
|
||||||
import type { IntlString, Metadata, Resource } from '@hcengineering/platform'
|
import type { IntlString, Metadata, Resource } from '@hcengineering/platform'
|
||||||
import { mergeIds } from '@hcengineering/platform'
|
import { mergeIds } from '@hcengineering/platform'
|
||||||
import { IssueDraft } from '@hcengineering/tracker'
|
import { IssueDraft, IssuePriority, IssueStatus, Project, Sprint } from '@hcengineering/tracker'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
import { SortFunc, Viewlet } from '@hcengineering/view'
|
import { SortFunc, Viewlet, ViewQueryAction } from '@hcengineering/view'
|
||||||
import tracker, { trackerId } from '../../tracker/lib'
|
import tracker, { trackerId } from '../../tracker/lib'
|
||||||
|
|
||||||
export default mergeIds(trackerId, tracker, {
|
export default mergeIds(trackerId, tracker, {
|
||||||
@ -143,7 +143,6 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
Grouping: '' as IntlString,
|
Grouping: '' as IntlString,
|
||||||
Ordering: '' as IntlString,
|
Ordering: '' as IntlString,
|
||||||
CompletedIssues: '' as IntlString,
|
CompletedIssues: '' as IntlString,
|
||||||
ShowEmptyGroups: '' as IntlString,
|
|
||||||
NoGrouping: '' as IntlString,
|
NoGrouping: '' as IntlString,
|
||||||
NoAssignee: '' as IntlString,
|
NoAssignee: '' as IntlString,
|
||||||
LastUpdated: '' as IntlString,
|
LastUpdated: '' as IntlString,
|
||||||
@ -377,6 +376,10 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
IssueStatusSort: '' as SortFunc,
|
IssueStatusSort: '' as SortFunc,
|
||||||
IssuePrioritySort: '' as SortFunc,
|
IssuePrioritySort: '' as SortFunc,
|
||||||
SprintSort: '' as SortFunc,
|
SprintSort: '' as SortFunc,
|
||||||
SubIssueQuery: '' as Resource<(value: any, query: DocumentQuery<Doc>) => DocumentQuery<Doc>>
|
SubIssueQuery: '' as ViewQueryAction,
|
||||||
|
GetAllStatuses: '' as Resource<(space: Ref<Space> | undefined) => Promise<Array<Ref<IssueStatus>>>>,
|
||||||
|
GetAllPriority: '' as Resource<(space: Ref<Space> | undefined) => Promise<IssuePriority[]>>,
|
||||||
|
GetAllProjects: '' as Resource<(space: Ref<Space> | undefined) => Promise<Array<Ref<Project>>>>,
|
||||||
|
GetAllSprints: '' as Resource<(space: Ref<Space> | undefined) => Promise<Array<Ref<Sprint>>>>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -13,13 +13,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Employee } from '@hcengineering/contact'
|
import { Employee, formatName } from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
AttachedData,
|
AttachedData,
|
||||||
Doc,
|
Doc,
|
||||||
DocumentQuery,
|
DocumentQuery,
|
||||||
Ref,
|
Ref,
|
||||||
SortingOrder,
|
SortingOrder,
|
||||||
|
Space,
|
||||||
toIdMap,
|
toIdMap,
|
||||||
TxCollectionCUD,
|
TxCollectionCUD,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
@ -37,6 +38,7 @@ import {
|
|||||||
IssuesOrdering,
|
IssuesOrdering,
|
||||||
IssueStatus,
|
IssueStatus,
|
||||||
IssueTemplateData,
|
IssueTemplateData,
|
||||||
|
Project,
|
||||||
ProjectStatus,
|
ProjectStatus,
|
||||||
Sprint,
|
Sprint,
|
||||||
SprintStatus,
|
SprintStatus,
|
||||||
@ -51,7 +53,6 @@ import {
|
|||||||
isWeekend,
|
isWeekend,
|
||||||
MILLISECONDS_IN_WEEK
|
MILLISECONDS_IN_WEEK
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { ViewOptionModel } from '@hcengineering/view'
|
|
||||||
import { ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
|
import { ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
|
||||||
import tracker from './plugin'
|
import tracker from './plugin'
|
||||||
import { defaultPriorities, defaultProjectStatuses, defaultSprintStatuses, issuePriorities } from './types'
|
import { defaultPriorities, defaultProjectStatuses, defaultSprintStatuses, issuePriorities } from './types'
|
||||||
@ -360,154 +361,108 @@ export async function sprintSort (value: Array<Ref<Sprint>>): Promise<Array<Ref<
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getKanbanStatuses (
|
export async function mapKanbanCategories (
|
||||||
groupBy: IssuesGrouping,
|
groupBy: string,
|
||||||
issues: Array<WithLookup<Issue>>
|
categories: any[],
|
||||||
|
statuses: Array<WithLookup<IssueStatus>>,
|
||||||
|
projects: Project[],
|
||||||
|
sprints: Sprint[],
|
||||||
|
assignee: Employee[]
|
||||||
): Promise<TypeState[]> {
|
): Promise<TypeState[]> {
|
||||||
if (groupBy === IssuesGrouping.NoGrouping) {
|
if (groupBy === IssuesGrouping.NoGrouping) {
|
||||||
return [{ _id: undefined, color: UNSET_COLOR, title: await translate(tracker.string.NoGrouping, {}) }]
|
return [{ _id: undefined, color: UNSET_COLOR, title: await translate(tracker.string.NoGrouping, {}) }]
|
||||||
}
|
}
|
||||||
if (groupBy === IssuesGrouping.Priority) {
|
if (groupBy === IssuesGrouping.Priority) {
|
||||||
const states = issues.reduce<TypeState[]>((result, issue) => {
|
const res: TypeState[] = []
|
||||||
const { priority } = issue
|
for (const priority of categories) {
|
||||||
if (result.find(({ _id }) => _id === priority) !== undefined) return result
|
const title = await translate((issuePriorities as any)[priority].label, {})
|
||||||
return [
|
res.push({
|
||||||
...result,
|
_id: priority,
|
||||||
{
|
title,
|
||||||
_id: priority,
|
color: UNSET_COLOR,
|
||||||
title: issuePriorities[priority].label,
|
icon: (issuePriorities as any)[priority].icon
|
||||||
color: UNSET_COLOR,
|
|
||||||
icon: issuePriorities[priority].icon
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}, [])
|
|
||||||
await Promise.all(
|
|
||||||
states.map(async (state) => {
|
|
||||||
state.title = await translate(state.title as IntlString, {})
|
|
||||||
})
|
})
|
||||||
)
|
}
|
||||||
return states
|
return res
|
||||||
}
|
}
|
||||||
if (groupBy === IssuesGrouping.Status) {
|
if (groupBy === IssuesGrouping.Status) {
|
||||||
return issues.reduce<TypeState[]>((result, issue) => {
|
return statuses
|
||||||
const status = issue.$lookup?.status
|
.filter((p) => categories.includes(p._id))
|
||||||
if (status === undefined || result.find(({ _id }) => _id === status._id) !== undefined) return result
|
.map((status) => {
|
||||||
const category = '$lookup' in status ? status.$lookup?.category : undefined
|
const category = '$lookup' in status ? status.$lookup?.category : undefined
|
||||||
return [
|
return {
|
||||||
...result,
|
|
||||||
{
|
|
||||||
_id: status._id,
|
_id: status._id,
|
||||||
title: status.name,
|
title: status.name,
|
||||||
icon: category?.icon,
|
icon: category?.icon,
|
||||||
color: status.color ?? category?.color ?? UNSET_COLOR
|
color: status.color ?? category?.color ?? UNSET_COLOR
|
||||||
}
|
}
|
||||||
]
|
})
|
||||||
}, [])
|
|
||||||
}
|
}
|
||||||
if (groupBy === IssuesGrouping.Assignee) {
|
if (groupBy === IssuesGrouping.Assignee) {
|
||||||
const noAssignee = await translate(tracker.string.NoAssignee, {})
|
const noAssignee = await translate(tracker.string.NoAssignee, {})
|
||||||
return issues.reduce<TypeState[]>((result, issue) => {
|
const res: TypeState[] = assignee
|
||||||
if (result.find(({ _id }) => _id === issue.assignee) !== undefined) return result
|
.filter((p) => categories.includes(p._id))
|
||||||
return [
|
.map((employee) => {
|
||||||
...result,
|
return {
|
||||||
{
|
_id: employee._id,
|
||||||
_id: issue.assignee,
|
title: formatName(employee.name),
|
||||||
title: issue.$lookup?.assignee?.name ?? noAssignee,
|
|
||||||
color: UNSET_COLOR,
|
color: UNSET_COLOR,
|
||||||
icon: undefined
|
icon: undefined
|
||||||
}
|
}
|
||||||
]
|
})
|
||||||
}, [])
|
if (categories.includes(undefined)) {
|
||||||
|
res.push({
|
||||||
|
_id: null,
|
||||||
|
title: noAssignee,
|
||||||
|
color: UNSET_COLOR,
|
||||||
|
icon: undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
if (groupBy === IssuesGrouping.Project) {
|
if (groupBy === IssuesGrouping.Project) {
|
||||||
const noProject = await translate(tracker.string.NoProject, {})
|
const noProject = await translate(tracker.string.NoProject, {})
|
||||||
return issues.reduce<TypeState[]>((result, issue) => {
|
const res: TypeState[] = projects
|
||||||
if (result.find(({ _id }) => _id === issue.project) !== undefined) return result
|
.filter((p) => categories.includes(p._id))
|
||||||
return [
|
.map((project) => ({
|
||||||
...result,
|
_id: project._id,
|
||||||
{
|
title: project.label,
|
||||||
_id: issue.project,
|
color: UNSET_COLOR,
|
||||||
title: issue.$lookup?.project?.label ?? noProject,
|
icon: undefined
|
||||||
color: UNSET_COLOR,
|
}))
|
||||||
icon: undefined
|
if (categories.includes(undefined)) {
|
||||||
}
|
res.push({
|
||||||
]
|
_id: null,
|
||||||
}, [])
|
title: noProject,
|
||||||
|
color: UNSET_COLOR,
|
||||||
|
icon: undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
if (groupBy === IssuesGrouping.Sprint) {
|
if (groupBy === IssuesGrouping.Sprint) {
|
||||||
const noSprint = await translate(tracker.string.NoSprint, {})
|
const noSprint = await translate(tracker.string.NoSprint, {})
|
||||||
return issues.reduce<TypeState[]>((result, issue) => {
|
const res: TypeState[] = sprints
|
||||||
if (result.find(({ _id }) => _id === issue.sprint) !== undefined) return result
|
.filter((p) => categories.includes(p._id))
|
||||||
return [
|
.map((sprint) => ({
|
||||||
...result,
|
_id: sprint._id,
|
||||||
{
|
title: sprint.label,
|
||||||
_id: issue.sprint,
|
color: UNSET_COLOR,
|
||||||
title: issue.$lookup?.sprint?.label ?? noSprint,
|
icon: undefined
|
||||||
color: UNSET_COLOR,
|
}))
|
||||||
icon: undefined
|
if (categories.includes(undefined)) {
|
||||||
}
|
res.push({
|
||||||
]
|
_id: null,
|
||||||
}, [])
|
title: noSprint,
|
||||||
|
color: UNSET_COLOR,
|
||||||
|
icon: undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getIssueStatusStates (issueStatuses: Array<WithLookup<IssueStatus>> = []): TypeState[] {
|
|
||||||
return issueStatuses.map((status) => ({
|
|
||||||
_id: status._id,
|
|
||||||
title: status.name,
|
|
||||||
color: status.color ?? status.$lookup?.category?.color ?? UNSET_COLOR,
|
|
||||||
icon: status.$lookup?.category?.icon ?? undefined
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPriorityStates (): Promise<TypeState[]> {
|
|
||||||
return await Promise.all(
|
|
||||||
defaultPriorities.map(async (priority) => ({
|
|
||||||
_id: priority,
|
|
||||||
title: await translate(issuePriorities[priority].label, {}),
|
|
||||||
color: UNSET_COLOR,
|
|
||||||
icon: issuePriorities[priority].icon
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDefaultViewOptionsTemplatesConfig (): ViewOptionModel[] {
|
|
||||||
const groupByCategory: ViewOptionModel = {
|
|
||||||
key: 'groupBy',
|
|
||||||
label: tracker.string.Grouping,
|
|
||||||
defaultValue: 'project',
|
|
||||||
values: [
|
|
||||||
{ id: 'assignee', label: tracker.string.Assignee },
|
|
||||||
{ id: 'priority', label: tracker.string.Priority },
|
|
||||||
{ id: 'project', label: tracker.string.Project },
|
|
||||||
{ id: 'sprint', label: tracker.string.Sprint },
|
|
||||||
{ id: '#no_category', label: tracker.string.NoGrouping }
|
|
||||||
],
|
|
||||||
type: 'dropdown'
|
|
||||||
}
|
|
||||||
const orderByCategory: ViewOptionModel = {
|
|
||||||
key: 'orderBy',
|
|
||||||
label: tracker.string.Ordering,
|
|
||||||
defaultValue: 'priority',
|
|
||||||
values: [
|
|
||||||
{ id: 'modifiedOn', label: tracker.string.LastUpdated },
|
|
||||||
{ id: 'priority', label: tracker.string.Priority },
|
|
||||||
{ id: 'dueDate', label: tracker.string.DueDate }
|
|
||||||
],
|
|
||||||
type: 'dropdown'
|
|
||||||
}
|
|
||||||
const showEmptyGroups: ViewOptionModel = {
|
|
||||||
key: 'shouldShowEmptyGroups',
|
|
||||||
label: tracker.string.ShowEmptyGroups,
|
|
||||||
defaultValue: false,
|
|
||||||
type: 'toggle'
|
|
||||||
}
|
|
||||||
const result: ViewOptionModel[] = [groupByCategory, orderByCategory]
|
|
||||||
result.push(showEmptyGroups)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -591,6 +546,43 @@ export function subIssueQuery (value: boolean, query: DocumentQuery<Issue>): Doc
|
|||||||
return value ? query : { ...query, attachedTo: tracker.ids.NoParent }
|
return value ? query : { ...query, attachedTo: tracker.ids.NoParent }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAllStatuses (space: Ref<Space> | undefined): Promise<Array<Ref<IssueStatus>> | undefined> {
|
||||||
|
if (space === undefined) return
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const query = createQuery(true)
|
||||||
|
query.query(tracker.class.IssueStatus, { space }, (res) => {
|
||||||
|
resolve(res.map((p) => p._id))
|
||||||
|
query.unsubscribe()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllPriority (space: Ref<Space> | undefined): Promise<IssuePriority[] | undefined> {
|
||||||
|
return defaultPriorities
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllProjects (space: Ref<Team> | undefined): Promise<Array<Ref<Project>> | undefined> {
|
||||||
|
if (space === undefined) return
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const query = createQuery(true)
|
||||||
|
query.query(tracker.class.Project, { space }, (res) => {
|
||||||
|
resolve(res.map((p) => p._id))
|
||||||
|
query.unsubscribe()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllSprints (space: Ref<Team> | undefined): Promise<Array<Ref<Sprint>> | undefined> {
|
||||||
|
if (space === undefined) return
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const query = createQuery(true)
|
||||||
|
query.query(tracker.class.Sprint, { space }, (res) => {
|
||||||
|
resolve(res.map((p) => p._id))
|
||||||
|
query.unsubscribe()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function subIssueListProvider (subIssues: Issue[], target: Ref<Issue>): void {
|
export function subIssueListProvider (subIssues: Issue[], target: Ref<Issue>): void {
|
||||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
||||||
if (dir === 'vertical') {
|
if (dir === 'vertical') {
|
||||||
|
@ -387,17 +387,6 @@ export interface Scrum extends Doc {
|
|||||||
attachments?: number
|
attachments?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface ViewOptions {
|
|
||||||
groupBy: IssuesGrouping
|
|
||||||
orderBy: IssuesOrdering
|
|
||||||
completedIssuesPeriod: IssuesDateModificationPeriod
|
|
||||||
shouldShowEmptyGroups: boolean
|
|
||||||
shouldShowSubIssues: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
"Then": "Then",
|
"Then": "Then",
|
||||||
"ShowPreviewOnClick": "Please click to show document index preview...",
|
"ShowPreviewOnClick": "Please click to show document index preview...",
|
||||||
"Showed": "Showed",
|
"Showed": "Showed",
|
||||||
"Total": "Total"
|
"Total": "Total",
|
||||||
|
"ShowEmptyGroups": "Show empty groups"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
"Then": "Затем",
|
"Then": "Затем",
|
||||||
"ShowPreviewOnClick": "Пожалуйста нажмите чтобы увидеть предпросмотр...",
|
"ShowPreviewOnClick": "Пожалуйста нажмите чтобы увидеть предпросмотр...",
|
||||||
"Showed": "Показано",
|
"Showed": "Показано",
|
||||||
"Total": "Всего"
|
"Total": "Всего",
|
||||||
|
"ShowEmptyGroups": "Показывать пустые группы"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const changeStatus = async (newStatus: any) => {
|
const changeStatus = async (newStatus: any) => {
|
||||||
console.log('CHANGE VALUE', newStatus)
|
|
||||||
if (newStatus === '#null') {
|
if (newStatus === '#null') {
|
||||||
newStatus = null
|
newStatus = null
|
||||||
return
|
return
|
||||||
@ -120,7 +119,6 @@
|
|||||||
<SelectPopup
|
<SelectPopup
|
||||||
value={valuesToShow}
|
value={valuesToShow}
|
||||||
on:close={(evt) => {
|
on:close={(evt) => {
|
||||||
console.log(evt)
|
|
||||||
changeStatus(evt.detail)
|
changeStatus(evt.detail)
|
||||||
}}
|
}}
|
||||||
placeholder={placeholder ?? view.string.Filter}
|
placeholder={placeholder ?? view.string.Filter}
|
||||||
@ -138,7 +136,6 @@
|
|||||||
allowDeselect={true}
|
allowDeselect={true}
|
||||||
selected={current}
|
selected={current}
|
||||||
on:close={(evt) => {
|
on:close={(evt) => {
|
||||||
console.log(evt)
|
|
||||||
changeStatus(evt.detail === null ? null : evt.detail?._id)
|
changeStatus(evt.detail === null ? null : evt.detail?._id)
|
||||||
}}
|
}}
|
||||||
placeholder={placeholder ?? view.string.Filter}
|
placeholder={placeholder ?? view.string.Filter}
|
||||||
|
@ -34,16 +34,11 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$: groups =
|
|
||||||
viewOptions.groupBy[viewOptions.groupBy.length - 1] === noCategory
|
|
||||||
? viewOptions.groupBy
|
|
||||||
: [...viewOptions.groupBy, noCategory]
|
|
||||||
|
|
||||||
function selectGrouping (value: string, i: number) {
|
function selectGrouping (value: string, i: number) {
|
||||||
viewOptions.groupBy[i] = value
|
viewOptions.groupBy[i] = value
|
||||||
if (value === noCategory) {
|
if (value === noCategory) {
|
||||||
viewOptions.groupBy.length = i
|
viewOptions.groupBy.length = i + 1
|
||||||
} else {
|
} else if (config.groupDepth === undefined || config.groupDepth > viewOptions.groupBy.length) {
|
||||||
viewOptions.groupBy.length = i + 1
|
viewOptions.groupBy.length = i + 1
|
||||||
viewOptions.groupBy[i + 1] = noCategory
|
viewOptions.groupBy[i + 1] = noCategory
|
||||||
}
|
}
|
||||||
@ -62,7 +57,7 @@
|
|||||||
|
|
||||||
<div class="antiCard">
|
<div class="antiCard">
|
||||||
<div class="antiCard-group grid">
|
<div class="antiCard-group grid">
|
||||||
{#each groups as group, i}
|
{#each viewOptions.groupBy as group, i}
|
||||||
<span class="label"><Label label={i === 0 ? view.string.Grouping : view.string.Then} /></span>
|
<span class="label"><Label label={i === 0 ? view.string.Grouping : view.string.Then} /></span>
|
||||||
<div class="value grouping">
|
<div class="value grouping">
|
||||||
<DropdownLabelsIntl
|
<DropdownLabelsIntl
|
||||||
@ -93,12 +88,12 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#each config.other as model}
|
{#each config.other.filter((p) => !p.hidden?.(viewOptions)) as model}
|
||||||
<span class="label"><Label label={model.label} /></span>
|
<span class="label"><Label label={model.label} /></span>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{#if isToggleType(model)}
|
{#if isToggleType(model)}
|
||||||
<MiniToggle
|
<MiniToggle
|
||||||
on={viewOptions[model.key]}
|
on={viewOptions[model.key] ?? model.defaultValue}
|
||||||
on:change={() => {
|
on:change={() => {
|
||||||
viewOptions[model.key] = !viewOptions[model.key]
|
viewOptions[model.key] = !viewOptions[model.key]
|
||||||
dispatch('update', { key: model.key, value: viewOptions[model.key] })
|
dispatch('update', { key: model.key, value: viewOptions[model.key] })
|
||||||
@ -109,7 +104,7 @@
|
|||||||
<DropdownLabelsIntl
|
<DropdownLabelsIntl
|
||||||
label={model.label}
|
label={model.label}
|
||||||
{items}
|
{items}
|
||||||
selected={viewOptions[model.key]}
|
selected={viewOptions[model.key] ?? model.defaultValue}
|
||||||
width="10rem"
|
width="10rem"
|
||||||
justify="left"
|
justify="left"
|
||||||
on:selected={(e) => {
|
on:selected={(e) => {
|
||||||
|
@ -347,8 +347,6 @@
|
|||||||
},
|
},
|
||||||
getEventPositionElement(e),
|
getEventPositionElement(e),
|
||||||
(val) => {
|
(val) => {
|
||||||
console.log('val')
|
|
||||||
console.log(val)
|
|
||||||
if (val !== undefined) {
|
if (val !== undefined) {
|
||||||
const value = classes.get(_class)?.find((it) => it.value === val)
|
const value = classes.get(_class)?.find((it) => it.value === val)
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -58,7 +58,6 @@
|
|||||||
const attr = hieararchy.getAttribute(filter.key._class, filter.key.key)
|
const attr = hieararchy.getAttribute(filter.key._class, filter.key.key)
|
||||||
if (hieararchy.isMixin(attr.attributeOf)) {
|
if (hieararchy.isMixin(attr.attributeOf)) {
|
||||||
prefix = attr.attributeOf + '.'
|
prefix = attr.attributeOf + '.'
|
||||||
console.log('prefix', prefix)
|
|
||||||
}
|
}
|
||||||
objectsPromise = client.findAll(
|
objectsPromise = client.findAll(
|
||||||
_class,
|
_class,
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Class, Doc, Lookup, Ref, Space } from '@hcengineering/core'
|
import { Class, Doc, Lookup, Ref, Space } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { getResource, IntlString } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
import { AttributeModel, BuildModelKey, ViewOptions } from '@hcengineering/view'
|
import { AttributeModel, BuildModelKey, CategoryOption, ViewOptionModel, ViewOptions } from '@hcengineering/view'
|
||||||
import { buildModel, getCategories, getPresenter, groupBy, getAdditionalHeader } from '../../utils'
|
import { buildModel, getAdditionalHeader, getCategories, getPresenter, groupBy } from '../../utils'
|
||||||
import { noCategory } from '../../viewOptions'
|
import { noCategory } from '../../viewOptions'
|
||||||
import ListCategory from './ListCategory.svelte'
|
import ListCategory from './ListCategory.svelte'
|
||||||
|
|
||||||
@ -42,13 +42,41 @@
|
|||||||
export let initIndex = 0
|
export let initIndex = 0
|
||||||
export let newObjectProps: Record<string, any>
|
export let newObjectProps: Record<string, any>
|
||||||
export let docByIndex: Map<number, Doc>
|
export let docByIndex: Map<number, Doc>
|
||||||
|
export let viewOptionsConfig: ViewOptionModel[] | undefined
|
||||||
|
|
||||||
$: groupByKey = viewOptions.groupBy[level] ?? noCategory
|
$: groupByKey = viewOptions.groupBy[level] ?? noCategory
|
||||||
$: groupedDocs = groupBy(docs, groupByKey)
|
$: groupedDocs = groupBy(docs, groupByKey)
|
||||||
let categories: any[] = []
|
let categories: any[] = []
|
||||||
$: getCategories(client, _class, docs, groupByKey).then((p) => {
|
$: updateCategories(_class, docs, groupByKey, viewOptions, viewOptionsConfig)
|
||||||
categories = p
|
|
||||||
})
|
async function updateCategories (
|
||||||
|
_class: Ref<Class<Doc>>,
|
||||||
|
docs: Doc[],
|
||||||
|
groupByKey: string,
|
||||||
|
viewOptions: ViewOptions,
|
||||||
|
viewOptionsModel: ViewOptionModel[] | undefined
|
||||||
|
) {
|
||||||
|
categories = await getCategories(client, _class, docs, groupByKey)
|
||||||
|
if (level === 0) {
|
||||||
|
for (const viewOption of viewOptionsModel ?? []) {
|
||||||
|
if (viewOption.actionTartget !== 'category') continue
|
||||||
|
const categoryFunc = viewOption as CategoryOption
|
||||||
|
if (viewOptions[viewOption.key] ?? viewOption.defaultValue) {
|
||||||
|
const f = await getResource(categoryFunc.action)
|
||||||
|
const res = await f(_class, space, groupByKey)
|
||||||
|
if (res !== undefined) {
|
||||||
|
for (const category of categories) {
|
||||||
|
if (!res.includes(category)) {
|
||||||
|
res.push(category)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
categories = res
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
showPopup,
|
showPopup,
|
||||||
Spinner
|
Spinner
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { AttributeModel, BuildModelKey, ViewOptions } from '@hcengineering/view'
|
import { AttributeModel, BuildModelKey, ViewOptionModel, ViewOptions } from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { FocusSelection, focusStore } from '../../selection'
|
import { FocusSelection, focusStore } from '../../selection'
|
||||||
import Menu from '../Menu.svelte'
|
import Menu from '../Menu.svelte'
|
||||||
@ -57,6 +57,7 @@
|
|||||||
export let viewOptions: ViewOptions
|
export let viewOptions: ViewOptions
|
||||||
export let newObjectProps: Record<string, any>
|
export let newObjectProps: Record<string, any>
|
||||||
export let docByIndex: Map<number, Doc>
|
export let docByIndex: Map<number, Doc>
|
||||||
|
export let viewOptionsConfig: ViewOptionModel[] | undefined
|
||||||
|
|
||||||
$: lastLevel = level + 1 >= viewOptions.groupBy.length
|
$: lastLevel = level + 1 >= viewOptions.groupBy.length
|
||||||
|
|
||||||
@ -155,6 +156,7 @@
|
|||||||
level={level + 1}
|
level={level + 1}
|
||||||
{initIndex}
|
{initIndex}
|
||||||
{docByIndex}
|
{docByIndex}
|
||||||
|
{viewOptionsConfig}
|
||||||
on:check
|
on:check
|
||||||
on:uncheckAll
|
on:uncheckAll
|
||||||
on:row-focus
|
on:row-focus
|
||||||
|
@ -78,6 +78,7 @@ import {
|
|||||||
} from './filter'
|
} from './filter'
|
||||||
|
|
||||||
import { IndexedDocumentPreview } from '@hcengineering/presentation'
|
import { IndexedDocumentPreview } from '@hcengineering/presentation'
|
||||||
|
import { showEmptyGroups } from './viewOptions'
|
||||||
|
|
||||||
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
|
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
|
||||||
return getEventPopupPositionElement(e)
|
return getEventPopupPositionElement(e)
|
||||||
@ -192,6 +193,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
FilterBeforeResult: beforeResult,
|
FilterBeforeResult: beforeResult,
|
||||||
FilterAfterResult: afterResult,
|
FilterAfterResult: afterResult,
|
||||||
FilterNestedMatchResult: nestedMatchResult,
|
FilterNestedMatchResult: nestedMatchResult,
|
||||||
FilterNestedDontMatchResult: nestedDontMatchResult
|
FilterNestedDontMatchResult: nestedDontMatchResult,
|
||||||
|
ShowEmptyGroups: showEmptyGroups
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -64,6 +64,7 @@ export default mergeIds(viewId, view, {
|
|||||||
Then: '' as IntlString,
|
Then: '' as IntlString,
|
||||||
ShowPreviewOnClick: '' as IntlString,
|
ShowPreviewOnClick: '' as IntlString,
|
||||||
Showed: '' as IntlString,
|
Showed: '' as IntlString,
|
||||||
|
ShowEmptyGroups: '' as IntlString,
|
||||||
Total: '' as IntlString
|
Total: '' as IntlString
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { SortingOrder } from '@hcengineering/core'
|
import { Class, Doc, Ref, SortingOrder, Space } from '@hcengineering/core'
|
||||||
|
import { getResource } from '@hcengineering/platform'
|
||||||
|
import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||||
import { getCurrentLocation, locationToUrl } from '@hcengineering/ui'
|
import { getCurrentLocation, locationToUrl } from '@hcengineering/ui'
|
||||||
import {
|
import {
|
||||||
DropdownViewOption,
|
DropdownViewOption,
|
||||||
@ -8,6 +10,7 @@ import {
|
|||||||
ViewOptions,
|
ViewOptions,
|
||||||
ViewOptionsModel
|
ViewOptionsModel
|
||||||
} from '@hcengineering/view'
|
} from '@hcengineering/view'
|
||||||
|
import view from './plugin'
|
||||||
|
|
||||||
export const noCategory = '#no_category'
|
export const noCategory = '#no_category'
|
||||||
|
|
||||||
@ -82,3 +85,28 @@ export function migrateViewOpttions (): void {
|
|||||||
localStorage.setItem(key, JSON.stringify(res))
|
localStorage.setItem(key, JSON.stringify(res))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function showEmptyGroups (
|
||||||
|
_class: Ref<Class<Doc>>,
|
||||||
|
space: Ref<Space> | undefined,
|
||||||
|
key: string
|
||||||
|
): Promise<any[] | undefined> {
|
||||||
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
const attr = hierarchy.getAttribute(_class, key)
|
||||||
|
if (attr === undefined) return
|
||||||
|
const { attrClass } = getAttributePresenterClass(hierarchy, attr)
|
||||||
|
const attributeClass = hierarchy.getClass(attrClass)
|
||||||
|
const mixin = hierarchy.as(attributeClass, view.mixin.AllValuesFunc)
|
||||||
|
if (mixin.func !== undefined) {
|
||||||
|
const f = await getResource(mixin.func)
|
||||||
|
const res = await f(space)
|
||||||
|
if (res !== undefined) {
|
||||||
|
const sortFunc = hierarchy.as(attributeClass, view.mixin.SortFuncs)
|
||||||
|
if (sortFunc?.func === undefined) return res
|
||||||
|
const f = await getResource(sortFunc.func)
|
||||||
|
|
||||||
|
return await f(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -59,9 +59,14 @@ export interface KeyFilter {
|
|||||||
export interface FilterMode extends Doc {
|
export interface FilterMode extends Doc {
|
||||||
label: IntlString
|
label: IntlString
|
||||||
disableValueSelector?: boolean
|
disableValueSelector?: boolean
|
||||||
result: Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>
|
result: FilterFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type FilterFunction = Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -238,6 +243,13 @@ export interface ClassSortFuncs extends Class<Doc> {
|
|||||||
func: SortFunc
|
func: SortFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface AllValuesFunc extends Class<Doc> {
|
||||||
|
func: Resource<(space: Ref<Space> | undefined) => Promise<any[]>>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -468,16 +480,36 @@ export interface ViewOption {
|
|||||||
defaultValue: any
|
defaultValue: any
|
||||||
label: IntlString
|
label: IntlString
|
||||||
hidden?: (viewOptions: ViewOptions) => boolean
|
hidden?: (viewOptions: ViewOptions) => boolean
|
||||||
actionTartget?: 'query'
|
actionTartget?: 'query' | 'category'
|
||||||
action?: Resource<(value: any, ...params: any) => any>
|
action?: Resource<(value: any, ...params: any) => any>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type ViewCategoryAction = Resource<
|
||||||
|
(_class: Ref<Class<Doc>>, space: Ref<Space> | undefined, key: string) => Promise<any[] | undefined>
|
||||||
|
>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface CategoryOption extends ViewOption {
|
||||||
|
actionTartget: 'category'
|
||||||
|
action: ViewCategoryAction
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type ViewQueryAction = Resource<(value: any, query: DocumentQuery<Doc>) => DocumentQuery<Doc>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface ViewQueryOption extends ViewOption {
|
export interface ViewQueryOption extends ViewOption {
|
||||||
actionTartget: 'query'
|
actionTartget: 'query'
|
||||||
action: Resource<(value: any, query: DocumentQuery<Doc>) => DocumentQuery<Doc>>
|
action: ViewQueryAction
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -514,6 +546,7 @@ export interface ViewOptionsModel {
|
|||||||
groupBy: string[]
|
groupBy: string[]
|
||||||
orderBy: OrderOption[]
|
orderBy: OrderOption[]
|
||||||
other: ViewOptionModel[]
|
other: ViewOptionModel[]
|
||||||
|
groupDepth?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -542,7 +575,8 @@ const view = plugin(viewId, {
|
|||||||
IgnoreActions: '' as Ref<Mixin<IgnoreActions>>,
|
IgnoreActions: '' as Ref<Mixin<IgnoreActions>>,
|
||||||
PreviewPresenter: '' as Ref<Mixin<PreviewPresenter>>,
|
PreviewPresenter: '' as Ref<Mixin<PreviewPresenter>>,
|
||||||
ListHeaderExtra: '' as Ref<Mixin<ListHeaderExtra>>,
|
ListHeaderExtra: '' as Ref<Mixin<ListHeaderExtra>>,
|
||||||
SortFuncs: '' as Ref<Mixin<ClassSortFuncs>>
|
SortFuncs: '' as Ref<Mixin<ClassSortFuncs>>,
|
||||||
|
AllValuesFunc: '' as Ref<Mixin<AllValuesFunc>>
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
ViewletPreference: '' as Ref<Class<ViewletPreference>>,
|
ViewletPreference: '' as Ref<Class<ViewletPreference>>,
|
||||||
|
@ -133,7 +133,6 @@
|
|||||||
on:select={(result) => {
|
on:select={(result) => {
|
||||||
if (result.detail !== undefined) {
|
if (result.detail !== undefined) {
|
||||||
viewlet = viewlets.find((vl) => vl._id === result.detail.id)
|
viewlet = viewlets.find((vl) => vl._id === result.detail.id)
|
||||||
console.log('set viewlet by space headed')
|
|
||||||
if (viewlet) setActiveViewletId(viewlet._id)
|
if (viewlet) setActiveViewletId(viewlet._id)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -165,8 +165,9 @@ test.describe('tracker layout tests', () => {
|
|||||||
} else {
|
} else {
|
||||||
orderedIssueNames = issuesProps.map((props) => props.name).reverse()
|
orderedIssueNames = issuesProps.map((props) => props.name).reverse()
|
||||||
}
|
}
|
||||||
await setViewOrder(page, order)
|
|
||||||
await page.click(ViewletSelectors.Board)
|
await page.click(ViewletSelectors.Board)
|
||||||
|
await setViewGroup(page, 'No grouping')
|
||||||
|
await setViewOrder(page, order)
|
||||||
await expect(locator).toContainText(orderedIssueNames)
|
await expect(locator).toContainText(orderedIssueNames)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user