mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Add kanban view (#2071)
Signed-off-by: Dvinyanin Alexandr <dvinyanin.alexandr@gmail.com>
This commit is contained in:
parent
dd915141a0
commit
619801aaca
@ -13,6 +13,7 @@ Leads:
|
|||||||
Tracker:
|
Tracker:
|
||||||
|
|
||||||
- Attachments support
|
- Attachments support
|
||||||
|
- Board view
|
||||||
|
|
||||||
## 0.6.26
|
## 0.6.26
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"@anticrm/workbench": "~0.6.1",
|
"@anticrm/workbench": "~0.6.1",
|
||||||
"@anticrm/view": "~0.6.0",
|
"@anticrm/view": "~0.6.0",
|
||||||
"@anticrm/model-presentation": "~0.6.0",
|
"@anticrm/model-presentation": "~0.6.0",
|
||||||
"@anticrm/setting": "~0.6.1"
|
"@anticrm/setting": "~0.6.1",
|
||||||
|
"@anticrm/task": "~0.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ import workbench, { createNavigateAction } from '@anticrm/model-workbench'
|
|||||||
import notification from '@anticrm/notification'
|
import notification from '@anticrm/notification'
|
||||||
import { Asset, IntlString } from '@anticrm/platform'
|
import { Asset, IntlString } from '@anticrm/platform'
|
||||||
import setting from '@anticrm/setting'
|
import setting from '@anticrm/setting'
|
||||||
|
import task from '@anticrm/task'
|
||||||
import {
|
import {
|
||||||
Document,
|
Document,
|
||||||
Issue,
|
Issue,
|
||||||
@ -307,6 +308,23 @@ export function createModel (builder: Builder): void {
|
|||||||
tracker.viewlet.List
|
tracker.viewlet.List
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||||
|
attachTo: tracker.class.Issue,
|
||||||
|
descriptor: tracker.viewlet.Kanban,
|
||||||
|
config: []
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
view.class.ViewletDescriptor,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
label: tracker.string.Board,
|
||||||
|
icon: task.icon.Kanban,
|
||||||
|
component: tracker.component.KanbanView
|
||||||
|
},
|
||||||
|
tracker.viewlet.Kanban
|
||||||
|
)
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
tracker.class.IssueStatusCategory,
|
tracker.class.IssueStatusCategory,
|
||||||
core.space.Model,
|
core.space.Model,
|
||||||
@ -469,12 +487,6 @@ export function createModel (builder: Builder): void {
|
|||||||
// icon: tracker.icon.TrackerApplication,
|
// icon: tracker.icon.TrackerApplication,
|
||||||
component: tracker.component.Backlog
|
component: tracker.component.Backlog
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: boardId,
|
|
||||||
label: tracker.string.Board,
|
|
||||||
// icon: tracker.icon.TrackerApplication,
|
|
||||||
component: tracker.component.Board
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: projectsId,
|
id: projectsId,
|
||||||
label: tracker.string.Projects,
|
label: tracker.string.Projects,
|
||||||
|
@ -46,7 +46,8 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
Tracker: '' as Ref<Application>
|
Tracker: '' as Ref<Application>
|
||||||
},
|
},
|
||||||
viewlet: {
|
viewlet: {
|
||||||
List: '' as Ref<ViewletDescriptor>
|
List: '' as Ref<ViewletDescriptor>,
|
||||||
|
Kanban: '' as Ref<ViewletDescriptor>
|
||||||
},
|
},
|
||||||
completion: {
|
completion: {
|
||||||
IssueQuery: '' as Resource<ObjectSearchFactory>,
|
IssueQuery: '' as Resource<ObjectSearchFactory>,
|
||||||
|
@ -0,0 +1,187 @@
|
|||||||
|
<!--
|
||||||
|
// 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 contact from '@anticrm/contact'
|
||||||
|
import { Class, Doc, FindOptions, Ref, WithLookup } from '@anticrm/core'
|
||||||
|
import { Kanban } from '@anticrm/kanban'
|
||||||
|
import { getClient } from '@anticrm/presentation'
|
||||||
|
import { Issue, IssuesGrouping, Team, ViewOptions } from '@anticrm/tracker'
|
||||||
|
import { Button, Icon, IconAdd, 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 CreateIssue from '../CreateIssue.svelte'
|
||||||
|
import AssigneePresenter from './AssigneePresenter.svelte'
|
||||||
|
import IssuePresenter from './IssuePresenter.svelte'
|
||||||
|
import PriorityEditor from './PriorityEditor.svelte'
|
||||||
|
|
||||||
|
export let currentSpace: Ref<Team>
|
||||||
|
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
||||||
|
export let viewOptions: ViewOptions
|
||||||
|
export let query = {}
|
||||||
|
|
||||||
|
$: ({ groupBy, shouldShowEmptyGroups, shouldShowSubIssues } = viewOptions)
|
||||||
|
$: resultQuery = {
|
||||||
|
...(shouldShowSubIssues ? {} : { attachedTo: tracker.ids.NoParent }),
|
||||||
|
space: currentSpace,
|
||||||
|
...query
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
function toIssue (object: any): WithLookup<Issue> {
|
||||||
|
return object as WithLookup<Issue>
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: FindOptions<Issue> = {
|
||||||
|
lookup: {
|
||||||
|
assignee: contact.class.Employee,
|
||||||
|
space: tracker.class.Team
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let kanbanUI: Kanban
|
||||||
|
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
||||||
|
kanbanUI.select(offset, of, dir)
|
||||||
|
})
|
||||||
|
onMount(() => {
|
||||||
|
;(document.activeElement as HTMLElement)?.blur()
|
||||||
|
})
|
||||||
|
|
||||||
|
const showMenu = async (ev: MouseEvent, items: Doc[]): Promise<void> => {
|
||||||
|
ev.preventDefault()
|
||||||
|
showPopup(
|
||||||
|
Menu,
|
||||||
|
{ object: items, baseMenuClass },
|
||||||
|
{
|
||||||
|
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// selection = undefined
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await getKanbanStatuses(client, groupBy, resultQuery, shouldShowEmptyGroups) then states}
|
||||||
|
<ActionContext
|
||||||
|
context={{
|
||||||
|
mode: 'browser'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Kanban
|
||||||
|
bind:this={kanbanUI}
|
||||||
|
_class={tracker.class.Issue}
|
||||||
|
space={currentSpace}
|
||||||
|
search=""
|
||||||
|
{states}
|
||||||
|
{options}
|
||||||
|
query={resultQuery}
|
||||||
|
fieldName={groupBy}
|
||||||
|
rankFieldName={'rank'}
|
||||||
|
on:content={(evt) => {
|
||||||
|
listProvider.update(evt.detail)
|
||||||
|
}}
|
||||||
|
on:obj-focus={(evt) => {
|
||||||
|
listProvider.updateFocus(evt.detail)
|
||||||
|
}}
|
||||||
|
selection={listProvider.current($focusStore)}
|
||||||
|
checked={$selectionStore ?? []}
|
||||||
|
on:check={(evt) => {
|
||||||
|
listProvider.updateSelection(evt.detail.docs, evt.detail.value)
|
||||||
|
}}
|
||||||
|
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="header" let:state let:count>
|
||||||
|
<div class="header flex-col">
|
||||||
|
<div class="flex-between label font-medium w-full h-full">
|
||||||
|
<div class="flex-row-center gap-2">
|
||||||
|
<Icon icon={state.icon} size={'small'} />
|
||||||
|
<span class="lines-limit-2 ml-2">{state.title}</span>
|
||||||
|
<span class="counter ml-2 text-md">{count}</span>
|
||||||
|
</div>
|
||||||
|
{#if groupBy === IssuesGrouping.Status}
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<Tooltip label={tracker.string.AddIssueTooltip} direction={'left'}>
|
||||||
|
<Button
|
||||||
|
icon={IconAdd}
|
||||||
|
kind={'transparent'}
|
||||||
|
on:click={() => {
|
||||||
|
showPopup(CreateIssue, { space: currentSpace, status: state._id }, 'top')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="card" let:object>
|
||||||
|
{@const issue = toIssue(object)}
|
||||||
|
<div class="tracker-card">
|
||||||
|
<div class="flex-col mr-6">
|
||||||
|
<IssuePresenter value={issue} />
|
||||||
|
<span class="fs-bold caption-color mt-1 lines-limit-2">
|
||||||
|
{object.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="abs-rt-content">
|
||||||
|
<AssigneePresenter
|
||||||
|
value={issue?.$lookup?.assignee}
|
||||||
|
issueId={issue._id}
|
||||||
|
{currentSpace}
|
||||||
|
isEditable={true}
|
||||||
|
defaultClass={contact.class.Employee}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="buttons-group xsmall-gap mt-10px">
|
||||||
|
<PriorityEditor
|
||||||
|
value={issue}
|
||||||
|
isEditable={true}
|
||||||
|
kind={'link-bordered'}
|
||||||
|
size={'inline'}
|
||||||
|
justify={'center'}
|
||||||
|
width={''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
</Kanban>
|
||||||
|
{/await}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.header {
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--caption-color);
|
||||||
|
.counter {
|
||||||
|
color: rgba(var(--caption-color), 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tracker-card {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
min-height: 6.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -94,7 +94,7 @@
|
|||||||
<div class="value">
|
<div class="value">
|
||||||
<MiniToggle bind:on={shouldShowSubIssues} on:change={updateOptions} />
|
<MiniToggle bind:on={shouldShowSubIssues} on:change={updateOptions} />
|
||||||
</div>
|
</div>
|
||||||
{#if _groupBy === IssuesGrouping.Status || _groupBy === IssuesGrouping.Priority}
|
{#if _groupBy === IssuesGrouping.Status}
|
||||||
<span class="label"><Label label={tracker.string.ShowEmptyGroups} /></span>
|
<span class="label"><Label label={tracker.string.ShowEmptyGroups} /></span>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<MiniToggle bind:on={_shouldShowEmptyGroups} on:change={updateOptions} />
|
<MiniToggle bind:on={_shouldShowEmptyGroups} on:change={updateOptions} />
|
||||||
|
@ -54,6 +54,7 @@ import TargetDatePresenter from './components/projects/TargetDatePresenter.svelt
|
|||||||
import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
|
import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
|
||||||
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
||||||
import Views from './components/views/Views.svelte'
|
import Views from './components/views/Views.svelte'
|
||||||
|
import KanbanView from './components/issues/KanbanView.svelte'
|
||||||
import tracker from './plugin'
|
import tracker from './plugin'
|
||||||
import { getIssueId } from './utils'
|
import { getIssueId } from './utils'
|
||||||
|
|
||||||
@ -76,7 +77,6 @@ export async function queryIssue<D extends Issue> (
|
|||||||
)
|
)
|
||||||
).map((e) => [e._id, e])
|
).map((e) => [e._id, e])
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const currentTeam of teams) {
|
for (const currentTeam of teams) {
|
||||||
const nids: number[] = []
|
const nids: number[] = []
|
||||||
for (let n = 0; n < currentTeam.sequence; n++) {
|
for (let n = 0; n < currentTeam.sequence; n++) {
|
||||||
@ -143,6 +143,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
EditProject,
|
EditProject,
|
||||||
IssuesView,
|
IssuesView,
|
||||||
ListView,
|
ListView,
|
||||||
|
KanbanView,
|
||||||
IssuePreview
|
IssuePreview
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
|
@ -194,6 +194,7 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
EditProject: '' as AnyComponent,
|
EditProject: '' as AnyComponent,
|
||||||
IssuesView: '' as AnyComponent,
|
IssuesView: '' as AnyComponent,
|
||||||
ListView: '' as AnyComponent,
|
ListView: '' as AnyComponent,
|
||||||
|
KanbanView: '' as AnyComponent,
|
||||||
IssuePreview: '' as AnyComponent
|
IssuePreview: '' as AnyComponent
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
|
@ -13,9 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Employee, formatName } from '@anticrm/contact'
|
import contact, { Employee, formatName } from '@anticrm/contact'
|
||||||
import { DocumentQuery, Ref, SortingOrder } from '@anticrm/core'
|
import { DocumentQuery, Ref, SortingOrder, TxOperations } from '@anticrm/core'
|
||||||
import type { Asset, IntlString } from '@anticrm/platform'
|
import { Asset, IntlString, translate } from '@anticrm/platform'
|
||||||
import {
|
import {
|
||||||
IssuePriority,
|
IssuePriority,
|
||||||
Team,
|
Team,
|
||||||
@ -27,6 +27,7 @@ import {
|
|||||||
IssueStatus
|
IssueStatus
|
||||||
} from '@anticrm/tracker'
|
} from '@anticrm/tracker'
|
||||||
import { AnyComponent, AnySvelteComponent, getMillisecondsInMonth, MILLISECONDS_IN_WEEK } from '@anticrm/ui'
|
import { AnyComponent, AnySvelteComponent, getMillisecondsInMonth, MILLISECONDS_IN_WEEK } from '@anticrm/ui'
|
||||||
|
import { TypeState } from '@anticrm/kanban'
|
||||||
import tracker from './plugin'
|
import tracker from './plugin'
|
||||||
|
|
||||||
export interface NavigationItem {
|
export interface NavigationItem {
|
||||||
@ -392,3 +393,112 @@ export function getCategories (
|
|||||||
export function getIssueId (team: Team, issue: Issue): string {
|
export function getIssueId (team: Team, issue: Issue): string {
|
||||||
return `${team.identifier}-${issue.number}`
|
return `${team.identifier}-${issue.number}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getKanbanStatuses (
|
||||||
|
client: TxOperations,
|
||||||
|
groupBy: IssuesGrouping,
|
||||||
|
issueQuery: DocumentQuery<Issue>,
|
||||||
|
shouldShowEmptyGroups: boolean
|
||||||
|
): Promise<TypeState[]> {
|
||||||
|
if (groupBy === IssuesGrouping.Status && shouldShowEmptyGroups) {
|
||||||
|
return (
|
||||||
|
await client.findAll(
|
||||||
|
tracker.class.IssueStatus,
|
||||||
|
{ attachedTo: issueQuery.space },
|
||||||
|
{
|
||||||
|
lookup: { category: tracker.class.IssueStatusCategory },
|
||||||
|
sort: { rank: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).map((status) => ({
|
||||||
|
_id: status._id,
|
||||||
|
title: status.name,
|
||||||
|
color: status.color ?? status.$lookup?.category?.color ?? 0,
|
||||||
|
icon: status.$lookup?.category?.icon ?? undefined
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if (groupBy === IssuesGrouping.NoGrouping) return []
|
||||||
|
if (groupBy === IssuesGrouping.Priority) {
|
||||||
|
const issues = await client.findAll(tracker.class.Issue, issueQuery, {
|
||||||
|
sort: { priority: SortingOrder.Ascending }
|
||||||
|
})
|
||||||
|
const states = issues.reduce<TypeState[]>((result, issue) => {
|
||||||
|
const { priority } = issue
|
||||||
|
if (result.find(({ _id }) => _id === priority) !== undefined) return result
|
||||||
|
return [
|
||||||
|
...result,
|
||||||
|
{
|
||||||
|
_id: priority,
|
||||||
|
title: issuePriorities[priority].label,
|
||||||
|
color: 0,
|
||||||
|
icon: issuePriorities[priority].icon
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [])
|
||||||
|
await Promise.all(
|
||||||
|
states.map(async (state) => {
|
||||||
|
state.title = await translate(state.title as IntlString, {})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return states
|
||||||
|
}
|
||||||
|
if (groupBy === IssuesGrouping.Status) {
|
||||||
|
const issues = await client.findAll(tracker.class.Issue, issueQuery, {
|
||||||
|
lookup: { status: [tracker.class.IssueStatus, { category: tracker.class.IssueStatusCategory }] },
|
||||||
|
sort: { '$lookup.status.rank': SortingOrder.Ascending }
|
||||||
|
})
|
||||||
|
return issues.reduce<TypeState[]>((result, issue) => {
|
||||||
|
const status = issue.$lookup?.status
|
||||||
|
if (status === undefined || result.find(({ _id }) => _id === status._id) !== undefined) return result
|
||||||
|
const icon = '$lookup' in status ? status.$lookup?.category?.icon : undefined
|
||||||
|
return [
|
||||||
|
...result,
|
||||||
|
{
|
||||||
|
_id: status._id,
|
||||||
|
title: status.name,
|
||||||
|
color: status.color ?? 0,
|
||||||
|
icon
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
if (groupBy === IssuesGrouping.Assignee) {
|
||||||
|
const issues = await client.findAll(tracker.class.Issue, issueQuery, {
|
||||||
|
lookup: { assignee: contact.class.Employee },
|
||||||
|
sort: { '$lookup.assignee.name': SortingOrder.Ascending }
|
||||||
|
})
|
||||||
|
const noAssignee = await translate(tracker.string.NoAssignee, {})
|
||||||
|
return issues.reduce<TypeState[]>((result, issue) => {
|
||||||
|
if (result.find(({ _id }) => _id === issue.assignee) !== undefined) return result
|
||||||
|
return [
|
||||||
|
...result,
|
||||||
|
{
|
||||||
|
_id: issue.assignee,
|
||||||
|
title: issue.$lookup?.assignee?.name ?? noAssignee,
|
||||||
|
color: 0,
|
||||||
|
icon: undefined
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
if (groupBy === IssuesGrouping.Project) {
|
||||||
|
const issues = await client.findAll(tracker.class.Issue, issueQuery, {
|
||||||
|
lookup: { project: tracker.class.Project },
|
||||||
|
sort: { '$lookup.project.label': SortingOrder.Ascending }
|
||||||
|
})
|
||||||
|
const noProject = await translate(tracker.string.NoProject, {})
|
||||||
|
return issues.reduce<TypeState[]>((result, issue) => {
|
||||||
|
if (result.find(({ _id }) => _id === issue.project) !== undefined) return result
|
||||||
|
return [
|
||||||
|
...result,
|
||||||
|
{
|
||||||
|
_id: issue.project,
|
||||||
|
title: issue.$lookup?.project?.label ?? noProject,
|
||||||
|
color: 0,
|
||||||
|
icon: undefined
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { PlatformSetting, PlatformURI } from './utils'
|
import { generateId, PlatformSetting, PlatformURI } from './utils'
|
||||||
test.use({
|
test.use({
|
||||||
storageState: PlatformSetting
|
storageState: PlatformSetting
|
||||||
})
|
})
|
||||||
@ -32,3 +32,21 @@ test('create-issue-and-sub-issue', async ({ page }) => {
|
|||||||
await page.click('button:has-text("Save")')
|
await page.click('button:has-text("Save")')
|
||||||
await page.click('span.name:text("sub-issue")')
|
await page.click('span.name:text("sub-issue")')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('use-kanban', async ({ page }) => {
|
||||||
|
await page.goto(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp`)
|
||||||
|
await page.click('[id="app-tracker\\:string\\:TrackerApplication"]')
|
||||||
|
await expect(page).toHaveURL(`${PlatformURI}/workbench%3Acomponent%3AWorkbenchApp/tracker%3Aapp%3ATracker`)
|
||||||
|
|
||||||
|
const issueName = 'issue-' + generateId(5)
|
||||||
|
await page.click('button:has-text("New issue")')
|
||||||
|
await page.click('[placeholder="Issue\\ title"]')
|
||||||
|
await page.fill('[placeholder="Issue\\ title"]', issueName)
|
||||||
|
await page.click('button:has-text("Backlog")')
|
||||||
|
await page.click('button:has-text("In Progress")')
|
||||||
|
await page.click('button:has-text("Save issue")')
|
||||||
|
|
||||||
|
await page.locator('text="Issues"').click()
|
||||||
|
await page.click('[name="tooltip-tracker:string:Board"]')
|
||||||
|
await expect(page.locator('.panel-container:has-text("In Progress")')).toContainText(issueName)
|
||||||
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user