mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-31 12:19:47 +02:00
UBERF-8584: Add test runs (#7235)
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 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.
|
||||
//
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
|
||||
import { TDefaultProjectTypeData } from './types'
|
||||
import testManagement from './plugin'
|
||||
|
||||
export function defineDefaultSpace (builder: Builder): void {
|
||||
defineDefaultProject(builder)
|
||||
}
|
||||
|
||||
function defineDefaultProject (builder: Builder): void {
|
||||
builder.createModel(TDefaultProjectTypeData)
|
||||
|
||||
builder.createDoc(
|
||||
core.class.SpaceTypeDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
name: testManagement.string.TestProject,
|
||||
description: testManagement.string.FullDescription,
|
||||
icon: testManagement.icon.TestProject,
|
||||
baseClass: testManagement.class.TestProject,
|
||||
availablePermissions: [
|
||||
core.permission.UpdateSpace,
|
||||
core.permission.ArchiveSpace,
|
||||
core.permission.ForbidDeleteObject
|
||||
]
|
||||
},
|
||||
testManagement.descriptors.ProjectType
|
||||
)
|
||||
|
||||
builder.createDoc(core.class.SpaceType, core.space.Model, {
|
||||
name: 'Default project type',
|
||||
descriptor: testManagement.descriptors.ProjectType,
|
||||
roles: 0,
|
||||
targetClass: testManagement.mixin.DefaultProjectTypeData
|
||||
})
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
import activity from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import core from '@hcengineering/model-core'
|
||||
import { SortingOrder } from '@hcengineering/core'
|
||||
import { SortingOrder, type FindOptions } from '@hcengineering/core'
|
||||
|
||||
import { type Builder } from '@hcengineering/model'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
@@ -25,7 +25,7 @@ import print from '@hcengineering/model-print'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
import { type ViewOptionsModel } from '@hcengineering/view'
|
||||
|
||||
import { testManagementId } from '@hcengineering/test-management'
|
||||
import { testManagementId, type TestResult } from '@hcengineering/test-management'
|
||||
|
||||
import {
|
||||
DOMAIN_TEST_MANAGEMENT,
|
||||
@@ -37,8 +37,8 @@ import {
|
||||
TTestCase,
|
||||
TDefaultProjectTypeData,
|
||||
TTestRun,
|
||||
TTypeTestRunResult,
|
||||
TTestRunItem
|
||||
TTypeTestRunStatus,
|
||||
TTestResult
|
||||
} from './types'
|
||||
|
||||
import testManagement from './plugin'
|
||||
@@ -55,7 +55,6 @@ function defineApplication (builder: Builder): void {
|
||||
icon: testManagement.icon.TestManagementApplication,
|
||||
alias: testManagementId,
|
||||
hidden: false,
|
||||
locationResolver: testManagement.resolver.Location,
|
||||
navigatorModel: {
|
||||
spaces: [
|
||||
{
|
||||
@@ -85,6 +84,7 @@ function defineApplication (builder: Builder): void {
|
||||
mainComponentLabel: testManagement.string.TestCases,
|
||||
mainComponentIcon: testManagement.icon.TestCases,
|
||||
createComponent: testManagement.component.CreateTestSuite,
|
||||
mainHeaderComponent: testManagement.component.RunSelectedTestsButton,
|
||||
navigationComponentProps: {
|
||||
_class: testManagement.class.TestSuite,
|
||||
icon: testManagement.icon.TestSuites,
|
||||
@@ -100,21 +100,34 @@ function defineApplication (builder: Builder): void {
|
||||
},
|
||||
syncWithLocationQuery: true
|
||||
}
|
||||
}
|
||||
/* TODO: UBERF-8584
|
||||
},
|
||||
{
|
||||
id: opt.testRunsId,
|
||||
id: 'testRuns',
|
||||
label: testManagement.string.TestRuns,
|
||||
icon: testManagement.icon.TestRuns,
|
||||
component: workbench.component.SpecialView,
|
||||
componentProps: {
|
||||
_class: testManagement.class.TestRun,
|
||||
_class: testManagement.class.TestResult,
|
||||
icon: testManagement.icon.TestRuns,
|
||||
title: testManagement.string.TestRuns,
|
||||
createLabel: testManagement.string.NewTestRun,
|
||||
createComponent: testManagement.component.CreateTestRun
|
||||
label: testManagement.string.TestRuns
|
||||
},
|
||||
navigationModel: {
|
||||
navigationComponent: view.component.FoldersBrowser,
|
||||
navigationComponentLabel: testManagement.string.TestRun,
|
||||
navigationComponentIcon: testManagement.icon.TestRuns,
|
||||
mainComponentLabel: testManagement.string.TestResults,
|
||||
mainComponentIcon: testManagement.icon.TestResult,
|
||||
navigationComponentProps: {
|
||||
_class: testManagement.class.TestRun,
|
||||
icon: testManagement.icon.TestRuns,
|
||||
title: testManagement.string.TestSuites,
|
||||
titleKey: 'name',
|
||||
getFolderLink: testManagement.function.GetTestRunLink,
|
||||
plainList: true
|
||||
},
|
||||
syncWithLocationQuery: true
|
||||
}
|
||||
} */
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -135,8 +148,8 @@ export function createModel (builder: Builder): void {
|
||||
TTestCase,
|
||||
TDefaultProjectTypeData,
|
||||
TTestRun,
|
||||
TTestRunItem,
|
||||
TTypeTestRunResult
|
||||
TTypeTestRunStatus,
|
||||
TTestResult
|
||||
)
|
||||
|
||||
builder.mixin(testManagement.class.TestProject, core.class.Class, activity.mixin.ActivityDoc, {})
|
||||
@@ -204,7 +217,7 @@ function defineSpaceType (builder: Builder): void {
|
||||
core.class.SpaceType,
|
||||
core.space.Model,
|
||||
{
|
||||
name: 'Default project type',
|
||||
name: 'Default Test Management',
|
||||
descriptor: testManagement.descriptors.ProjectType,
|
||||
roles: 0,
|
||||
targetClass: testManagement.mixin.DefaultProjectTypeData
|
||||
@@ -250,13 +263,7 @@ function defineTestSuite (builder: Builder): void {
|
||||
// Actions
|
||||
|
||||
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [
|
||||
view.action.Open,
|
||||
view.action.OpenInNewTab,
|
||||
print.action.Print,
|
||||
tracker.action.EditRelatedTargets,
|
||||
tracker.action.NewRelatedIssue
|
||||
]
|
||||
actions: [print.action.Print, tracker.action.EditRelatedTargets]
|
||||
})
|
||||
|
||||
createAction(
|
||||
@@ -276,6 +283,24 @@ function defineTestSuite (builder: Builder): void {
|
||||
},
|
||||
testManagement.action.CreateChildTestSuite
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: testManagement.actionImpl.RunSelectedTests,
|
||||
label: testManagement.string.CreateTestRun,
|
||||
icon: testManagement.icon.TestRuns,
|
||||
category: testManagement.category.TestCase,
|
||||
input: 'selection',
|
||||
target: testManagement.class.TestCase,
|
||||
context: {
|
||||
mode: ['context'],
|
||||
application: testManagement.app.TestManagement,
|
||||
group: 'create'
|
||||
}
|
||||
},
|
||||
testManagement.action.RunSelectedTests
|
||||
)
|
||||
}
|
||||
|
||||
function defineTestCase (builder: Builder): void {
|
||||
@@ -306,6 +331,11 @@ function defineTestCase (builder: Builder): void {
|
||||
presenter: testManagement.component.TestSuiteRefPresenter
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: ['priority', 'status'],
|
||||
ignoreKeys: ['createdBy', 'modifiedBy', 'createdOn', 'modifiedOn']
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
@@ -350,6 +380,7 @@ function defineTestCase (builder: Builder): void {
|
||||
hiddenKeys: ['title']
|
||||
},
|
||||
config: [
|
||||
{ key: '', displayProps: { fixed: 'left' } },
|
||||
{
|
||||
key: 'status',
|
||||
props: { kind: 'list', size: 'small', shouldShowName: false },
|
||||
@@ -368,21 +399,6 @@ function defineTestCase (builder: Builder): void {
|
||||
},
|
||||
testManagement.viewlet.ListTestCase
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: testManagement.class.TestCase,
|
||||
descriptor: view.viewlet.Table,
|
||||
config: ['', 'assignee', 'modifiedOn'],
|
||||
configOptions: {
|
||||
sortable: true
|
||||
},
|
||||
variant: 'short'
|
||||
},
|
||||
testManagement.viewlet.SuiteTestCases
|
||||
)
|
||||
}
|
||||
|
||||
function defineTestRun (builder: Builder): void {
|
||||
@@ -393,10 +409,6 @@ function defineTestRun (builder: Builder): void {
|
||||
components: { input: { component: chunter.component.ChatMessageInput } }
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: testManagement.component.EditTestRun
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectPanel, {
|
||||
component: testManagement.component.EditTestRun
|
||||
})
|
||||
@@ -405,18 +417,111 @@ function defineTestRun (builder: Builder): void {
|
||||
presenter: testManagement.component.TestRunPresenter
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectIcon, {
|
||||
component: testManagement.component.TestResultStatusPresenter
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestResult, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: testManagement.component.TestResultPresenter
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestResult, core.class.Class, activity.mixin.ActivityDoc, {})
|
||||
|
||||
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
|
||||
ofClass: testManagement.class.TestResult,
|
||||
components: { input: { component: chunter.component.ChatMessageInput } }
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestResult, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: testManagement.component.EditTestResult
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestResult, core.class.Class, view.mixin.ObjectEditorHeader, {
|
||||
editor: testManagement.component.TestResultHeader
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestResult, core.class.Class, view.mixin.ObjectPanel, {
|
||||
component: testManagement.component.EditTestResult
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestResult, core.class.Class, view.mixin.ObjectPanelFooter, {
|
||||
editor: testManagement.component.TestResultFooter
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TestResult, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: ['assignee', 'status', 'testSuite'],
|
||||
ignoreKeys: ['createdBy', 'modifiedBy', 'createdOn', 'modifiedOn']
|
||||
})
|
||||
|
||||
const viewOptions: ViewOptionsModel = {
|
||||
groupBy: ['testSuite'],
|
||||
orderBy: [
|
||||
['status', SortingOrder.Ascending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending]
|
||||
],
|
||||
other: [
|
||||
{
|
||||
key: 'shouldShowAll',
|
||||
type: 'toggle',
|
||||
defaultValue: false,
|
||||
actionTarget: 'category',
|
||||
action: view.function.ShowEmptyGroups,
|
||||
label: view.string.ShowEmptyGroups
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: testManagement.class.TestRun,
|
||||
attachTo: testManagement.class.TestResult,
|
||||
descriptor: view.viewlet.List,
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: ['title', 'status', 'modifiedOn']
|
||||
},
|
||||
config: [
|
||||
{ key: '', displayProps: { fixed: 'left' } },
|
||||
{
|
||||
key: 'status',
|
||||
props: { kind: 'list', size: 'small', shouldShowName: false }
|
||||
},
|
||||
{
|
||||
key: 'assignee',
|
||||
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' },
|
||||
displayProps: { key: 'assignee', fixed: 'right' }
|
||||
}
|
||||
],
|
||||
viewOptions,
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
options: {
|
||||
lookup: {
|
||||
testCase: testManagement.class.TestCase
|
||||
}
|
||||
} as FindOptions<TestResult>
|
||||
},
|
||||
testManagement.viewlet.TestResultList
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: testManagement.class.TestResult,
|
||||
descriptor: view.viewlet.Table,
|
||||
config: [''],
|
||||
config: ['', 'testSuite', 'status', 'assignee'],
|
||||
configOptions: {
|
||||
strict: true
|
||||
}
|
||||
},
|
||||
options: {
|
||||
lookup: {
|
||||
testCase: testManagement.class.TestCase
|
||||
}
|
||||
} as FindOptions<TestResult>
|
||||
},
|
||||
testManagement.viewlet.TableTestRun
|
||||
testManagement.viewlet.TableTestResult
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,23 +15,15 @@
|
||||
|
||||
import { testManagementId } from '@hcengineering/test-management'
|
||||
import testManganement from '@hcengineering/test-management-resources/src/plugin'
|
||||
import type { Doc, Ref } from '@hcengineering/core'
|
||||
import type { Ref } from '@hcengineering/core'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import type { Action, ActionCategory, ViewAction } from '@hcengineering/view'
|
||||
import type { ActionCategory } from '@hcengineering/view'
|
||||
|
||||
export default mergeIds(testManagementId, testManganement, {
|
||||
action: {
|
||||
DeleteTestCase: '' as Ref<Action<Doc, any>>,
|
||||
CreateChildTestSuite: '' as Ref<Action>,
|
||||
EditTestSuite: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
CreateChildTestSuite: '' as ViewAction,
|
||||
EditTestSuite: '' as ViewAction
|
||||
},
|
||||
category: {
|
||||
TestSuite: '' as Ref<ActionCategory>
|
||||
TestSuite: '' as Ref<ActionCategory>,
|
||||
TestCase: '' as Ref<ActionCategory>
|
||||
},
|
||||
component: {
|
||||
CreateTestCase: '' as AnyComponent,
|
||||
@@ -44,6 +36,10 @@ export default mergeIds(testManagementId, testManganement, {
|
||||
CreateTestRun: '' as AnyComponent,
|
||||
TestRunPresenter: '' as AnyComponent,
|
||||
EditTestRun: '' as AnyComponent,
|
||||
TestSuiteRefPresenter: '' as AnyComponent
|
||||
TestSuiteRefPresenter: '' as AnyComponent,
|
||||
RunSelectedTestsButton: '' as AnyComponent,
|
||||
TestResultPresenter: '' as AnyComponent,
|
||||
EditTestResult: '' as AnyComponent,
|
||||
TestResultFooter: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
|
||||
@@ -53,4 +53,12 @@ export function definePresenters (builder: Builder): void {
|
||||
builder.mixin(testManagement.class.TypeTestCaseStatus, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: testManagement.component.TestCaseStatusPresenter
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TypeTestRunStatus, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: testManagement.component.TestResultStatusPresenter
|
||||
})
|
||||
|
||||
builder.mixin(testManagement.class.TypeTestRunStatus, core.class.Class, view.mixin.AttributeEditor, {
|
||||
inlineEditor: testManagement.component.TestResultStatusEditor
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import type {
|
||||
TestCaseStatus,
|
||||
TestProject,
|
||||
TestRun,
|
||||
TestRunResult,
|
||||
TestRunItem
|
||||
TestRunStatus,
|
||||
TestResult
|
||||
} from '@hcengineering/test-management'
|
||||
import { type Attachment } from '@hcengineering/attachment'
|
||||
import contact from '@hcengineering/contact'
|
||||
@@ -201,32 +201,65 @@ export class TTestRun extends TDoc implements TestRun {
|
||||
@Prop(TypeDate(DateRangeMode.DATETIME), testManagement.string.DueDate)
|
||||
dueDate?: Timestamp
|
||||
|
||||
@Prop(Collection(testManagement.class.TestRunItem), testManagement.string.TestRunItems, {
|
||||
shortLabel: testManagement.string.TestRunItem
|
||||
@Prop(Collection(testManagement.class.TestResult), testManagement.string.TestResult, {
|
||||
shortLabel: testManagement.string.TestResult
|
||||
})
|
||||
items?: CollectionSize<TestRunItem>
|
||||
results?: CollectionSize<TestResult>
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function TypeTestRunResult (): Type<TestRunResult> {
|
||||
return { _class: testManagement.class.TypeTestRunResult, label: testManagement.string.TestRunResult }
|
||||
export function TypeTestRunStatus (): Type<TestRunStatus> {
|
||||
return { _class: testManagement.class.TypeTestRunStatus, label: testManagement.string.TestRunStatus }
|
||||
}
|
||||
|
||||
@Model(testManagement.class.TypeTestRunResult, core.class.Type, DOMAIN_TEST_MANAGEMENT)
|
||||
@UX(testManagement.string.TestRunResult)
|
||||
export class TTypeTestRunResult extends TType {}
|
||||
@Model(testManagement.class.TypeTestRunStatus, core.class.Type, DOMAIN_TEST_MANAGEMENT)
|
||||
@UX(testManagement.string.TestRunStatus)
|
||||
export class TTypeTestRunStatus extends TType {}
|
||||
|
||||
@Model(testManagement.class.TestRunItem, core.class.AttachedDoc, DOMAIN_TEST_MANAGEMENT)
|
||||
@UX(testManagement.string.TestRunItem)
|
||||
export class TTestRunItem extends TAttachedDoc implements TestRunItem {
|
||||
@Prop(TypeRef(testManagement.class.TestRun), testManagement.string.TestRun)
|
||||
testRun!: Ref<TestRun>
|
||||
// TODO: Refactor to associations
|
||||
@Model(testManagement.class.TestResult, core.class.AttachedDoc, DOMAIN_TEST_MANAGEMENT)
|
||||
@UX(testManagement.string.TestResult)
|
||||
export class TTestResult extends TAttachedDoc implements TestResult {
|
||||
@Prop(TypeRef(testManagement.class.TestRun), core.string.AttachedTo)
|
||||
@Index(IndexKind.Indexed)
|
||||
declare attachedTo: Ref<TestRun>
|
||||
|
||||
@Prop(TypeRef(testManagement.class.TestRun), core.string.AttachedToClass)
|
||||
@Index(IndexKind.Indexed)
|
||||
@Hidden()
|
||||
declare attachedToClass: Ref<Class<TestRun>>
|
||||
|
||||
@Prop(TypeRef(testManagement.class.TestProject), core.string.Space)
|
||||
@Index(IndexKind.Indexed)
|
||||
@Hidden()
|
||||
declare space: Ref<TestProject>
|
||||
|
||||
@Prop(TypeString(), core.string.Collection)
|
||||
@Hidden()
|
||||
override collection: 'results' = 'results'
|
||||
|
||||
@Prop(TypeString(), testManagement.string.TestRunName)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(TypeCollaborativeDoc(), testManagement.string.FullDescription)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: CollaborativeDoc
|
||||
|
||||
@Prop(TypeRef(testManagement.class.TestCase), testManagement.string.TestCase)
|
||||
testCase!: Ref<TestCase>
|
||||
|
||||
@Prop(TypeTestRunResult(), testManagement.string.TestRunResult)
|
||||
result?: TestRunResult
|
||||
@Prop(TypeRef(testManagement.class.TestSuite), testManagement.string.TestSuite)
|
||||
testSuite?: Ref<TestSuite>
|
||||
|
||||
@Prop(TypeTestRunStatus(), testManagement.string.TestRunStatus)
|
||||
status?: TestRunStatus
|
||||
|
||||
@Prop(TypeRef(contact.mixin.Employee), testManagement.string.TestAssignee)
|
||||
assignee?: Ref<Employee>
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: CollectionSize<Attachment>
|
||||
|
||||
@Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments)
|
||||
comments?: number
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
export let position: 'start' | 'middle' | 'end' | undefined = undefined
|
||||
export let selected = false
|
||||
export let color = 'var(--theme-bg-color)'
|
||||
export let noGap = false
|
||||
export let fontColor: string | undefined = undefined
|
||||
|
||||
let clientWidth = 0
|
||||
|
||||
@@ -43,6 +45,7 @@
|
||||
<div
|
||||
class="asb-bar"
|
||||
class:selected
|
||||
class:noGap
|
||||
class:cursor-pointer={!selected && !$$slots.default}
|
||||
class:cursor-default={selected || $$slots.default}
|
||||
{title}
|
||||
@@ -91,7 +94,7 @@
|
||||
{/if}
|
||||
</svg>
|
||||
<div class="asb-label__container" class:selected class:disabled={!$$slots.default}>
|
||||
<div class="overflow-label">
|
||||
<div class="overflow-label" style={fontColor ? `color: ${fontColor};` : ''}>
|
||||
{#if $$slots.default}
|
||||
<slot />
|
||||
{:else}
|
||||
|
||||
@@ -1493,6 +1493,10 @@
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
|
||||
&.noGap {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
&__back {
|
||||
padding: 1px 0.5px;
|
||||
height: calc(1.5rem + 2px);
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
<symbol id="testManagementApplication" viewBox="0 0 24 24">
|
||||
<path d="M17.2,7.8h3.6c1.1,0,2-0.9,2-2V4.2c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0H9.8V4c0-1.5-1.2-2.8-2.8-2.8H4 C2.5,1.2,1.2,2.5,1.2,4v2c0,1.5,1.2,2.8,2.8,2.8h3c1.5,0,2.8-1.2,2.8-2.8V5.8h2V18c0,1.5,1.2,2.8,2.8,2.8h0.8v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-0.8c-0.7,0-1.2-0.6-1.2-1.2v-4.8h2v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-2v-6h2v0C15.2,6.9,16.1,7.8,17.2,7.8z M8.2,6 c0,0.7-0.6,1.2-1.2,1.2H4C3.3,7.2,2.8,6.7,2.8,6V4c0-0.7,0.6-1.2,1.2-1.2h3c0.7,0,1.2,0.6,1.2,1.2V6z M16.8,19.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V19.2z M16.8,11.7 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V11.7z M16.8,4.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V4.2z" />
|
||||
</symbol>
|
||||
<symbol id="testCase" viewBox="0 0 24 24">
|
||||
<path xmlns:default="http://www.w3.org/2000/svg" d="M3 9H1v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2H3V9zm15-4V3c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H5v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2V5h-5zm-6-2h4v2h-4V3zm0 12V8l5.5 3-5.5 4z" vector-effect="non-scaling-stroke"/>
|
||||
<symbol id="testCase" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 4C8.89543 4 8 4.89543 8 6V26C8 27.1046 8.89543 28 10 28H22C23.1046 28 24 27.1046 24 26V12H20C17.7909 12 16 10.2091 16 8V4H10ZM18 4.41421V8C18 9.10457 18.8954 10 20 10H23.5858L18 4.41421ZM6 6C6 3.79086 7.79086 2 10 2H17.5858C18.1162 2 18.6249 2.21071 19 2.58579L25.4142 9C25.7893 9.37507 26 9.88378 26 10.4142V26C26 28.2091 24.2091 30 22 30H10C7.79086 30 6 28.2091 6 26V6ZM10 17C10 16.4477 10.4477 16 11 16H15C15.5523 16 16 16.4477 16 17C16 17.5523 15.5523 18 15 18H11C10.4477 18 10 17.5523 10 17ZM10 21C10 20.4477 10.4477 20 11 20H21C21.5523 20 22 20.4477 22 21C22 21.5523 21.5523 22 21 22H11C10.4477 22 10 21.5523 10 21Z" />
|
||||
</symbol>
|
||||
<symbol id="testRun" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10 1.24988C8.26942 1.24988 6.57769 1.76306 5.13876 2.72452C3.69983 3.68598 2.57832 5.05254 1.91606 6.6514C1.25379 8.25025 1.08051 10.0096 1.41813 11.7069C1.75575 13.4043 2.58911 14.9634 3.81282 16.1871C5.03653 17.4108 6.59563 18.2441 8.29296 18.5817C9.9903 18.9194 11.7496 18.7461 13.3485 18.0838C14.9473 17.4216 16.3139 16.3 17.2754 14.8611C18.2368 13.4222 18.75 11.7305 18.75 9.99988C18.75 7.67923 17.8281 5.45364 16.1872 3.81269C14.5462 2.17175 12.3206 1.24988 10 1.24988ZM10 17.4999C8.51664 17.4999 7.0666 17.06 5.83323 16.2359C4.59986 15.4118 3.63856 14.2404 3.07091 12.87C2.50325 11.4996 2.35473 9.99156 2.64411 8.5367C2.9335 7.08184 3.64781 5.74547 4.6967 4.69658C5.7456 3.64768 7.08197 2.93338 8.53683 2.64399C9.99168 2.3546 11.4997 2.50312 12.8701 3.07078C14.2406 3.63844 15.4119 4.59973 16.236 5.8331C17.0601 7.06647 17.5 8.51652 17.5 9.99988C17.5 11.989 16.7098 13.8967 15.3033 15.3032C13.8968 16.7097 11.9891 17.4999 10 17.4999Z" fill="currentColor"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1919 7.05809C14.436 7.30217 14.436 7.6979 14.1919 7.94197L9.19194 12.942C8.94786 13.1861 8.55214 13.1861 8.30806 12.942L5.80806 10.442C5.56398 10.1979 5.56398 9.80217 5.80806 9.55809C6.05214 9.31401 6.44786 9.31401 6.69194 9.55809L8.75 11.6161L13.3081 7.05809C13.5521 6.81401 13.9479 6.81401 14.1919 7.05809Z" fill="currentColor"/>
|
||||
<symbol id="testRun" viewBox="0 0 24 24">
|
||||
<path xmlns:default="http://www.w3.org/2000/svg" d="M3 9H1v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2H3V9zm15-4V3c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H5v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2V5h-5zm-6-2h4v2h-4V3zm0 12V8l5.5 3-5.5 4z" vector-effect="non-scaling-stroke"/>
|
||||
</symbol>
|
||||
<symbol id="testSuite" viewBox="0 0 16 16">
|
||||
<path d="M14.5,6.7c0-2,0-3-0.7-3.8C13,2.2,12,2.2,10,2.2H6c-0.2,0-0.4,0-0.6,0l-0.1,0v0c-1.5,0-2.5,0.1-3.1,0.7 C1.5,3.6,1.5,4.7,1.5,6.7v2.7c0,2,0,3,0.7,3.8C3,13.8,4,13.8,6,13.8h4c2,0,3,0,3.8-0.7c0.5-0.5,0.7-1.3,0.7-2.4h0V10 c0-0.1,0-0.3,0-0.4c0-0.1,0-0.2,0-0.3V6.7z M13.1,3.6c0.4,0.4,0.4,1.3,0.4,3.1v0c-0.1-0.1-0.2-0.2-0.3-0.2c-0.6-0.3-1.2-0.3-2.5-0.3 c-0.7,0-1.1,0-1.3-0.1C9.2,6,9,5.9,8.9,5.8C8.7,5.6,8.5,5.3,8.2,4.6L7.6,3.5C7.6,3.3,7.5,3.3,7.4,3.2H10C11.7,3.2,12.6,3.2,13.1,3.6z M10,12.8H6c-1.7,0-2.6,0-3.1-0.4C2.5,12,2.5,11.1,2.5,9.3V6.7c0-1.7,0-2.6,0.4-3.1c0.4-0.4,1.1-0.4,2.5-0.4l0.1,0 c0.5,0,1,0.3,1.2,0.8l0.6,1.1c0.4,0.7,0.6,1.1,0.9,1.4C8.4,6.7,8.7,6.9,9,7c0.4,0.2,0.9,0.2,1.7,0.2c1.1,0,1.7,0,2,0.2 c0.2,0.1,0.4,0.3,0.6,0.6c0.2,0.3,0.2,0.9,0.2,1.9c0,1.4,0,2.2-0.4,2.6C12.6,12.8,11.7,12.8,10,12.8z"/>
|
||||
@@ -52,4 +51,21 @@
|
||||
<symbol id="project" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 6C13.4477 6 13 6.44772 13 7V8H19V7C19 6.44772 18.5523 6 18 6H14ZM21 8V7C21 5.34315 19.6569 4 18 4H14C12.3431 4 11 5.34315 11 7V8H6C3.79086 8 2 9.79086 2 12V24C2 26.2091 3.79086 28 6 28H26C28.2091 28 30 26.2091 30 24V12C30 9.79086 28.2091 8 26 8H21ZM6 10C4.89543 10 4 10.8954 4 12V16H28V12C28 10.8954 27.1046 10 26 10H6ZM28 18H17V19C17 19.5523 16.5523 20 16 20C15.4477 20 15 19.5523 15 19V18H4V24C4 25.1046 4.89543 26 6 26H26C27.1046 26 28 25.1046 28 24V18Z" />
|
||||
</symbol>
|
||||
<symbol id="testResult" viewBox="0 0 32 32">
|
||||
<path d="M8 4.00012H22C23.1046 4.00012 24 4.89555 24 6.00012V19.0001C24 19.5524 24.4477 20.0001 25 20.0001C25.5523 20.0001 26 19.5524 26 19.0001V6.00012C26 3.79098 24.2091 2.00012 22 2.00012H8C5.79086 2.00012 4 3.79098 4 6.00012V8.00012H3C2.44772 8.00012 2 8.44784 2 9.00012C2 9.55241 2.44772 10.0001 3 10.0001H4V15.0001H3C2.44772 15.0001 2 15.4478 2 16.0001C2 16.5524 2.44772 17.0001 3 17.0001H4V22.0001H3C2.44772 22.0001 2 22.4478 2 23.0001C2 23.5524 2.44772 24.0001 3 24.0001H4V26.0001C4 28.2093 5.79086 30.0001 8 30.0001H15C15.5523 30.0001 16 29.5524 16 29.0001C16 28.4478 15.5523 28.0001 15 28.0001H8C6.89543 28.0001 6 27.1047 6 26.0001V24.0001H7C7.55228 24.0001 8 23.5524 8 23.0001C8 22.4478 7.55228 22.0001 7 22.0001H6V17.0001H7C7.55228 17.0001 8 16.5524 8 16.0001C8 15.4478 7.55228 15.0001 7 15.0001H6V10.0001H7C7.55228 10.0001 8 9.55241 8 9.00012C8 8.44784 7.55228 8.00012 7 8.00012H6V6.00012C6 4.89555 6.89543 4.00012 8 4.00012Z" />
|
||||
<path d="M19 8.00049H11C10.4477 8.00049 10 8.4482 10 9.00049C10 9.55277 10.4477 10.0005 11 10.0005H19C19.5523 10.0005 20 9.55277 20 9.00049C20 8.4482 19.5523 8.00049 19 8.00049Z" />
|
||||
<path d="M11 13.0005H19C19.5523 13.0005 20 13.4482 20 14.0005C20 14.5528 19.5523 15.0005 19 15.0005H11C10.4477 15.0005 10 14.5528 10 14.0005C10 13.4482 10.4477 13.0005 11 13.0005Z" />
|
||||
<path d="M11 18.0005H19C19.5523 18.0005 20 18.4482 20 19.0005C20 19.5528 19.5523 20.0005 19 20.0005H11C10.4477 20.0005 10 19.5528 10 19.0005C10 18.4482 10.4477 18.0005 11 18.0005Z" />
|
||||
<path d="M23 27.2005L20.4 24.6005L19 26.0005L23 30.0005L30 23.0005L28.6 21.6005L23 27.2005Z" />
|
||||
</symbol>
|
||||
<symbol id="testCase" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 4C8.89543 4 8 4.89543 8 6V26C8 27.1046 8.89543 28 10 28H22C23.1046 28 24 27.1046 24 26V12H20C17.7909 12 16 10.2091 16 8V4H10ZM18 4.41421V8C18 9.10457 18.8954 10 20 10H23.5858L18 4.41421ZM6 6C6 3.79086 7.79086 2 10 2H17.5858C18.1162 2 18.6249 2.21071 19 2.58579L25.4142 9C25.7893 9.37507 26 9.88378 26 10.4142V26C26 28.2091 24.2091 30 22 30H10C7.79086 30 6 28.2091 6 26V6ZM10 17C10 16.4477 10.4477 16 11 16H15C15.5523 16 16 16.4477 16 17C16 17.5523 15.5523 18 15 18H11C10.4477 18 10 17.5523 10 17ZM10 21C10 20.4477 10.4477 20 11 20H21C21.5523 20 22 20.4477 22 21C22 21.5523 21.5523 22 21 22H11C10.4477 22 10 21.5523 10 21Z" />
|
||||
</symbol>
|
||||
<symbol id="check" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 6H8C6.89543 6 6 6.89543 6 8V24C6 25.1046 6.89543 26 8 26H24C25.1046 26 26 25.1046 26 24V8C26 6.89543 25.1046 6 24 6ZM8 4C5.79086 4 4 5.79086 4 8V24C4 26.2091 5.79086 28 8 28H24C26.2091 28 28 26.2091 28 24V8C28 5.79086 26.2091 4 24 4H8Z" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.7071 10.2929C24.0976 10.6834 24.0976 11.3166 23.7071 11.7071L14.7071 20.7071C14.3166 21.0976 13.6834 21.0976 13.2929 20.7071L9.29289 16.7071C8.90237 16.3166 8.90237 15.6834 9.29289 15.2929C9.68342 14.9024 10.3166 14.9024 10.7071 15.2929L14 18.5858L22.2929 10.2929C22.6834 9.90237 23.3166 9.90237 23.7071 10.2929Z" />
|
||||
</symbol>
|
||||
<symbol id="filter" viewBox="0 0 24 24">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.3,4.2C2.4,3.9,2.7,3.8,3,3.8h18c0.3,0,0.6,0.2,0.7,0.4c0.1,0.3,0.1,0.6-0.1,0.8l-7,8.2V21c0,0.3-0.1,0.5-0.4,0.6c-0.2,0.1-0.5,0.2-0.7,0l-3.6-1.8c-0.3-0.1-0.4-0.4-0.4-0.7v-6L2.4,5C2.2,4.8,2.2,4.5,2.3,4.2z M4.6,5.2l6.1,7.2c0.1,0.1,0.2,0.3,0.2,0.5v5.8l2.1,1v-6.9c0-0.2,0.1-0.4,0.2-0.5l6.1-7.2H4.6z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
@@ -61,6 +61,25 @@
|
||||
"TestRunNamePlaceholder": "Název testovacího běhu",
|
||||
"SelectTestSuites": "Vyberte testovací sady",
|
||||
"SelectTestCases": "Vyberte testovací případy",
|
||||
"TestLibrary": "Testovací knihovna"
|
||||
"TestLibrary": "Testovací knihovna",
|
||||
"TestResult": "Výsledek",
|
||||
"TestRunResult": "Výsledek",
|
||||
"TestRunStatus": "Stav",
|
||||
"StatusNonTested": "Neprovedeno",
|
||||
"StatusBlocked": "Blokováno",
|
||||
"StatusPassed": "Prošlo",
|
||||
"StatusFailed": "Neprošlo",
|
||||
"SelectTestCase": "Vyberte testovací případ",
|
||||
"Save": "Uložit",
|
||||
"SaveAndNext": "Uložit a další",
|
||||
"DonePercent": "% Hotovo",
|
||||
"TestResults": "Výsledky",
|
||||
"TestRunName": "Název",
|
||||
"DueDate": "Termín",
|
||||
"RunAllTestCases": "Spustit všechny testovací případy",
|
||||
"RunSelectedTestCases": "Spustit vybrané testovací případy",
|
||||
"RunFilteredTestCases": "Spustit testovací případy odpovídající filtru",
|
||||
"TestCaseDescription": "Popis testovacího případu",
|
||||
"TestResultAttributes": "Výsledek"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,29 @@
|
||||
"AssignedTo": "Assigneed to",
|
||||
"PreviousAssigned": "Previously assigned",
|
||||
"NoTestCases": "There are no test cases in this test suite",
|
||||
"CreateTestRun": "Create test run",
|
||||
"CreateTestRun": "New test run",
|
||||
"TestRunNamePlaceholder": "Test run name",
|
||||
"SelectTestSuites": "Select test suites",
|
||||
"SelectTestCases": "Select test cases",
|
||||
"TestLibrary": "Test library"
|
||||
"TestLibrary": "Test library",
|
||||
"TestResult": "Result",
|
||||
"TestRunResult": "Result",
|
||||
"TestRunStatus": "Status",
|
||||
"StatusNonTested": "Untested",
|
||||
"StatusBlocked": "Blocked",
|
||||
"StatusPassed": "Passed",
|
||||
"StatusFailed": "Failed",
|
||||
"SelectTestCase": "Select test case",
|
||||
"Save": "Save",
|
||||
"SaveAndNext": "Save and next",
|
||||
"DonePercent": "% Done",
|
||||
"TestResults": "Results",
|
||||
"TestRunName": "Name",
|
||||
"DueDate": "Due date",
|
||||
"RunAllTestCases": "Run all test cases",
|
||||
"RunSelectedTestCases": "Run selected test cases",
|
||||
"RunFilteredTestCases": "Run test cases matching a filter",
|
||||
"TestCaseDescription": "Test case description",
|
||||
"TestResultAttributes": "Result"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"string": {
|
||||
"ConfigLabel": "Gestión de Pruebas",
|
||||
"ConfigDescription": "Extensión para gestionar casos de prueba",
|
||||
"TestCaseType": "Tipo",
|
||||
"TestCasePriority": "Prioridad",
|
||||
"TestCaseStatus": "Estado",
|
||||
"TestSuite": "Suite de Pruebas",
|
||||
"SuiteName": "Nombre",
|
||||
"SuiteDescription": "Descripción",
|
||||
"Suite": "Suite",
|
||||
"TestName": "Nombre",
|
||||
"TestDescription": "Descripción",
|
||||
"TestType": "Tipo",
|
||||
"TestPriority": "Prioridad",
|
||||
"TestStatus": "Estado",
|
||||
"TestEstimatedTime": "Tiempo estimado",
|
||||
"TestPreconditions": "Precondiciones",
|
||||
"TestSteps": "Pasos",
|
||||
"TestAssignee": "Asignado a",
|
||||
"TestCase": "Caso de prueba",
|
||||
"TestProject": "Proyecto",
|
||||
"TestManagementApplication": "Gestión de Pruebas",
|
||||
"AllTestCases": "Todos los casos de prueba",
|
||||
"AllProjects": "Todos los proyectos",
|
||||
"Projects": "Proyectos",
|
||||
"CreateProject": "Crear proyecto",
|
||||
"EditProject": "Editar proyecto",
|
||||
"TestCases": "Casos de prueba",
|
||||
"TestManagementDescription": "Extensión para gestionar casos de prueba",
|
||||
"CreateTestCase": "Nuevo caso de prueba",
|
||||
"FullDescription": "Descripción completa",
|
||||
"ProjectName": "Nombre del proyecto",
|
||||
"ProjectType": "Tipo de proyecto",
|
||||
"Members": "Miembros",
|
||||
"RoleLabel": "Rol",
|
||||
"ProjectMembers": "Miembros del proyecto",
|
||||
"TestSuites": "Suites de pruebas",
|
||||
"CreateTestSuite": "Crear suite de pruebas",
|
||||
"NamePlaceholder": "Nombre de la suite",
|
||||
"DescriptionPlaceholder": "Descripción (opcional)",
|
||||
"TestRuns": "Ejecuciones de prueba",
|
||||
"NewTestRun": "Nueva ejecución de prueba",
|
||||
"TestRun": "Ejecución de prueba",
|
||||
"TestNamePlaceholder": "Título del caso de prueba",
|
||||
"ChooseIcon": "Elegir ícono",
|
||||
"NoTestSuite": "No hay suite de pruebas",
|
||||
"StatusDraft": "Borrador",
|
||||
"StatusReview": "Listo para revisión",
|
||||
"StatusReviewComments": "Necesita corregir comentarios de revisión",
|
||||
"StatusApproved": "Aprobado",
|
||||
"StatusRejected": "Rechazado",
|
||||
"SetStatus": "Establecer estado",
|
||||
"Assignee": "Asignado a",
|
||||
"Unassigned": "No asignado",
|
||||
"AssignTo": "Asignar a",
|
||||
"AssignedTo": "Asignado a",
|
||||
"PreviousAssigned": "Asignado previamente",
|
||||
"NoTestCases": "No hay casos de prueba en esta suite de pruebas",
|
||||
"CreateTestRun": "Nueva ejecución de prueba",
|
||||
"TestRunNamePlaceholder": "Nombre de la ejecución de prueba",
|
||||
"SelectTestSuites": "Seleccionar suites de prueba",
|
||||
"SelectTestCases": "Seleccionar casos de prueba",
|
||||
"TestLibrary": "Biblioteca de pruebas",
|
||||
"TestResult": "Resultado",
|
||||
"TestRunResult": "Resultado",
|
||||
"TestRunStatus": "Estado",
|
||||
"StatusNonTested": "No probado",
|
||||
"StatusBlocked": "Bloqueado",
|
||||
"StatusPassed": "Aprobado",
|
||||
"StatusFailed": "Fallido",
|
||||
"SelectTestCase": "Seleccionar caso de prueba",
|
||||
"Save": "Guardar",
|
||||
"SaveAndNext": "Guardar y siguiente",
|
||||
"DonePercent": "% Completado",
|
||||
"TestResults": "Resultados",
|
||||
"TestRunName": "Nombre",
|
||||
"DueDate": "Fecha de vencimiento",
|
||||
"RunAllTestCases": "Ejecutar todos los casos de prueba",
|
||||
"RunSelectedTestCases": "Ejecutar casos de prueba seleccionados",
|
||||
"RunFilteredTestCases": "Ejecutar casos de prueba que coincidan con un filtro",
|
||||
"TestCaseDescription": "Descripción del caso de prueba",
|
||||
"TestResultAttributes": "Resultado"
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,25 @@
|
||||
"TestRunNamePlaceholder": "Nom de l'exécution du test",
|
||||
"SelectTestSuites": "Sélectionnez les suites de tests",
|
||||
"SelectTestCases": "Sélectionnez des cas de test",
|
||||
"TestLibrary": "Bibliothèque de tests"
|
||||
"TestLibrary": "Bibliothèque de tests",
|
||||
"TestResult": "Résultat",
|
||||
"TestRunResult": "Résultat",
|
||||
"TestRunStatus": "Statut",
|
||||
"StatusNonTested": "Non testé",
|
||||
"StatusBlocked": "Bloqué",
|
||||
"StatusPassed": "Réussi",
|
||||
"StatusFailed": "Échoué",
|
||||
"SelectTestCase": "Sélectionnez le cas de test",
|
||||
"Save": "Sauvegarder",
|
||||
"SaveAndNext": "Sauvegarder et suivant",
|
||||
"DonePercent": "% Fait",
|
||||
"TestResults": "Résultats",
|
||||
"TestRunName": "Nom",
|
||||
"DueDate": "Date d'échéance",
|
||||
"RunAllTestCases": "Exécuter tous les cas de test",
|
||||
"RunSelectedTestCases": "Exécuter des scénarios de test sélectionnés",
|
||||
"RunFilteredTestCases": "Exécuter des cas de test correspondant à un filtre",
|
||||
"TestCaseDescription": "Description du cas de test",
|
||||
"TestResultAttributes": "Résultat"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"string": {
|
||||
"ConfigLabel": "Gestione Test",
|
||||
"ConfigDescription": "Estensione per gestire i casi di test",
|
||||
"TestCaseType": "Tipo",
|
||||
"TestCasePriority": "Priorità",
|
||||
"TestCaseStatus": "Stato",
|
||||
"TestSuite": "Suite di Test",
|
||||
"SuiteName": "Nome",
|
||||
"SuiteDescription": "Descrizione",
|
||||
"Suite": "Suite",
|
||||
"TestName": "Nome",
|
||||
"TestDescription": "Descrizione",
|
||||
"TestType": "Tipo",
|
||||
"TestPriority": "Priorità",
|
||||
"TestStatus": "Stato",
|
||||
"TestEstimatedTime": "Tempo stimato",
|
||||
"TestPreconditions": "Precondizioni",
|
||||
"TestSteps": "Passaggi",
|
||||
"TestAssignee": "Assegnatario",
|
||||
"TestCase": "Caso di test",
|
||||
"TestProject": "Progetto",
|
||||
"TestManagementApplication": "Gestione Test",
|
||||
"AllTestCases": "Tutti i casi di test",
|
||||
"AllProjects": "Tutti i progetti",
|
||||
"Projects": "Progetti",
|
||||
"CreateProject": "Crea progetto",
|
||||
"EditProject": "Modifica progetto",
|
||||
"TestCases": "Casi di test",
|
||||
"TestManagementDescription": "Estensione per gestire i casi di test",
|
||||
"CreateTestCase": "Nuovo caso di test",
|
||||
"FullDescription": "Descrizione completa",
|
||||
"ProjectName": "Nome",
|
||||
"ProjectType": "Tipo",
|
||||
"Members": "Membri",
|
||||
"RoleLabel": "Ruolo",
|
||||
"ProjectMembers": "Membri del progetto",
|
||||
"TestSuites": "Suite di test",
|
||||
"CreateTestSuite": "Crea suite di test",
|
||||
"NamePlaceholder": "Nome della suite",
|
||||
"DescriptionPlaceholder": "Descrizione (opzionale)",
|
||||
"TestRuns": "Esecuzioni di test",
|
||||
"NewTestRun": "Nuova esecuzione di test",
|
||||
"TestRun": "Esecuzione di test",
|
||||
"TestNamePlaceholder": "Titolo del caso di test",
|
||||
"ChooseIcon": "Scegli icona",
|
||||
"NoTestSuite": "Nessuna suite di test",
|
||||
"StatusDraft": "Bozza",
|
||||
"StatusReview": "Pronto per la revisione",
|
||||
"StatusReviewComments": "Necessita di correggere i commenti della revisione",
|
||||
"StatusApproved": "Approvato",
|
||||
"StatusRejected": "Respinto",
|
||||
"SetStatus": "Imposta stato",
|
||||
"Assignee": "Assegnatario",
|
||||
"Unassigned": "Non assegnato",
|
||||
"AssignTo": "Assegna a",
|
||||
"AssignedTo": "Assegnato a",
|
||||
"PreviousAssigned": "Assegnato in precedenza",
|
||||
"NoTestCases": "Non ci sono casi di test in questa suite di test",
|
||||
"CreateTestRun": "Nuova esecuzione di test",
|
||||
"TestRunNamePlaceholder": "Nome dell'esecuzione di test",
|
||||
"SelectTestSuites": "Seleziona suite di test",
|
||||
"SelectTestCases": "Seleziona casi di test",
|
||||
"TestLibrary": "Libreria di test",
|
||||
"TestResult": "Risultato",
|
||||
"TestRunResult": "Risultato",
|
||||
"TestRunStatus": "Stato",
|
||||
"StatusNonTested": "Non testato",
|
||||
"StatusBlocked": "Bloccato",
|
||||
"StatusPassed": "Superato",
|
||||
"StatusFailed": "Fallito",
|
||||
"SelectTestCase": "Seleziona caso di test",
|
||||
"Save": "Salva",
|
||||
"SaveAndNext": "Salva e successivo",
|
||||
"DonePercent": "% Completato",
|
||||
"TestResults": "Risultati",
|
||||
"TestRunName": "Nome",
|
||||
"DueDate": "Data di scadenza",
|
||||
"RunAllTestCases": "Esegui tutti i casi di test",
|
||||
"RunSelectedTestCases": "Esegui i casi di test selezionati",
|
||||
"RunFilteredTestCases": "Esegui i casi di test che corrispondono a un filtro",
|
||||
"TestCaseDescription": "Descrizione del caso di test",
|
||||
"TestResultAttributes": "Risultato"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"string": {
|
||||
"ConfigLabel": "Gestão de Testes",
|
||||
"ConfigDescription": "Extensão para gerenciar casos de teste",
|
||||
"TestCaseType": "Tipo",
|
||||
"TestCasePriority": "Prioridade",
|
||||
"TestCaseStatus": "Status",
|
||||
"TestSuite": "Suite de Testes",
|
||||
"SuiteName": "Nome",
|
||||
"SuiteDescription": "Descrição",
|
||||
"Suite": "Suite",
|
||||
"TestName": "Nome",
|
||||
"TestDescription": "Descrição",
|
||||
"TestType": "Tipo",
|
||||
"TestPriority": "Prioridade",
|
||||
"TestStatus": "Status",
|
||||
"TestEstimatedTime": "Tempo estimado",
|
||||
"TestPreconditions": "Precondições",
|
||||
"TestSteps": "Passos",
|
||||
"TestAssignee": "Responsável",
|
||||
"TestCase": "Caso de teste",
|
||||
"TestProject": "Projeto",
|
||||
"TestManagementApplication": "Gestão de Testes",
|
||||
"AllTestCases": "Todos os casos de teste",
|
||||
"AllProjects": "Todos os projetos",
|
||||
"Projects": "Projetos",
|
||||
"CreateProject": "Criar projeto",
|
||||
"EditProject": "Editar projeto",
|
||||
"TestCases": "Casos de teste",
|
||||
"TestManagementDescription": "Extensão para gerenciar casos de teste",
|
||||
"CreateTestCase": "Novo caso de teste",
|
||||
"FullDescription": "Descrição completa",
|
||||
"ProjectName": "Nome",
|
||||
"ProjectType": "Tipo",
|
||||
"Members": "Membros",
|
||||
"RoleLabel": "Função",
|
||||
"ProjectMembers": "Membros do projeto",
|
||||
"TestSuites": "Suites de teste",
|
||||
"CreateTestSuite": "Criar suite de teste",
|
||||
"NamePlaceholder": "Nome da suite",
|
||||
"DescriptionPlaceholder": "Descrição (opcional)",
|
||||
"TestRuns": "Execuções de teste",
|
||||
"NewTestRun": "Nova execução de teste",
|
||||
"TestRun": "Execução de teste",
|
||||
"TestNamePlaceholder": "Título do caso de teste",
|
||||
"ChooseIcon": "Escolher ícone",
|
||||
"NoTestSuite": "Nenhuma suite de teste",
|
||||
"StatusDraft": "Rascunho",
|
||||
"StatusReview": "Pronto para revisão",
|
||||
"StatusReviewComments": "Precisa corrigir comentários da revisão",
|
||||
"StatusApproved": "Aprovado",
|
||||
"StatusRejected": "Rejeitado",
|
||||
"SetStatus": "Definir status",
|
||||
"Assignee": "Responsável",
|
||||
"Unassigned": "Não atribuído",
|
||||
"AssignTo": "Atribuir a",
|
||||
"AssignedTo": "Atribuído a",
|
||||
"PreviousAssigned": "Atribuído anteriormente",
|
||||
"NoTestCases": "Não há casos de teste nesta suite de teste",
|
||||
"CreateTestRun": "Nova execução de teste",
|
||||
"TestRunNamePlaceholder": "Nome da execução de teste",
|
||||
"SelectTestSuites": "Selecionar suites de teste",
|
||||
"SelectTestCases": "Selecionar casos de teste",
|
||||
"TestLibrary": "Biblioteca de testes",
|
||||
"TestResult": "Resultado",
|
||||
"TestRunResult": "Resultado",
|
||||
"TestRunStatus": "Status",
|
||||
"StatusNonTested": "Não testado",
|
||||
"StatusBlocked": "Bloqueado",
|
||||
"StatusPassed": "Aprovado",
|
||||
"StatusFailed": "Falhou",
|
||||
"SelectTestCase": "Selecionar caso de teste",
|
||||
"Save": "Salvar",
|
||||
"SaveAndNext": "Salvar e próximo",
|
||||
"DonePercent": "% Concluído",
|
||||
"TestResults": "Resultados",
|
||||
"TestRunName": "Nome",
|
||||
"DueDate": "Data de vencimento",
|
||||
"RunAllTestCases": "Executar todos os casos de teste",
|
||||
"RunSelectedTestCases": "Executar casos de teste selecionados",
|
||||
"RunFilteredTestCases": "Executar casos de teste que correspondem a um filtro",
|
||||
"TestCaseDescription": "Descrição do caso de teste",
|
||||
"TestResultAttributes": "Resultado"
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,25 @@
|
||||
"TestRunNamePlaceholder": "Название тест плана",
|
||||
"SelectTestSuites": "Выбрать наборы тестов",
|
||||
"SelectTestCases": "Выбрать тест-кейсы",
|
||||
"TestLibrary": "Библиотека тестов"
|
||||
"TestLibrary": "Библиотека тестов",
|
||||
"TestResult": "Результат",
|
||||
"TestRunResult": "Результат",
|
||||
"TestRunStatus": "Статус",
|
||||
"StatusNonTested": "Не протестировано",
|
||||
"StatusBlocked": "Заблокировано",
|
||||
"StatusPassed": "Пройдено",
|
||||
"StatusFailed": "Не пройдено",
|
||||
"SelectTestCase": "Выбрать тест-кейс",
|
||||
"Save": "Сохранить",
|
||||
"SaveAndNext": "Сохранить и перейти к следующему",
|
||||
"DonePercent": "% Выполнено",
|
||||
"TestResults": "Результаты",
|
||||
"TestRunName": "Название",
|
||||
"DueDate": "Выполнить до",
|
||||
"RunAllTestCases": "Выполнить все тест-кейсы",
|
||||
"RunSelectedTestCases": "Выполнить выбранные тест-кейсы",
|
||||
"RunFilteredTestCases": "Выполнить тест-кейсы, соответствующие фильтру",
|
||||
"TestCaseDescription": "Описание тест-кейса",
|
||||
"TestResultAttributes": "Результат"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,25 @@
|
||||
"TestRunNamePlaceholder": "測試運行名稱",
|
||||
"SelectTestSuites": "選擇測試套件",
|
||||
"SelectTestCases": "選擇測試用例",
|
||||
"TestLibrary": "測試庫"
|
||||
"TestLibrary": "測試庫",
|
||||
"TestResult": "結果",
|
||||
"TestRunResult": "結果",
|
||||
"TestRunStatus": "狀態",
|
||||
"StatusNonTested": "未測試",
|
||||
"StatusBlocked": "被阻止",
|
||||
"StatusPassed": "通過",
|
||||
"StatusFailed": "失敗",
|
||||
"SelectTestCase": "選擇測試用例",
|
||||
"Save": "保存",
|
||||
"SaveAndNext": "保存並繼續",
|
||||
"DonePercent": "%完成",
|
||||
"TestResults": "結果",
|
||||
"TestRunName": "姓名",
|
||||
"DueDate": "到期日",
|
||||
"RunAllTestCases": "運行所有測試用例",
|
||||
"RunSelectedTestCases": "運行選定的測試用例",
|
||||
"RunFilteredTestCases": "運行與篩選器匹配的測試用例",
|
||||
"TestCaseDescription": "測試用例描述",
|
||||
"TestResultAttributes": "結果"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,5 +36,12 @@ loadMetadata(testManagement.icon, {
|
||||
StatusReviewComments: `${icons}#status-review-comments`,
|
||||
StatusApproved: `${icons}#status-approved`,
|
||||
StatusRejected: `${icons}#status-canceled`,
|
||||
TestLibrary: `${icons}#test-library`
|
||||
TestLibrary: `${icons}#test-library`,
|
||||
TestResult: `${icons}#testResult`,
|
||||
StatusNonTested: `${icons}#status-draft`,
|
||||
StatusBlocked: `${icons}#status-review-comments`,
|
||||
StatusPassed: `${icons}#status-approved`,
|
||||
StatusFailed: `${icons}#status-canceled`,
|
||||
Filter: `${icons}#filter`,
|
||||
Check: `${icons}#check`
|
||||
})
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2024 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, { Employee, Person, PersonAccount } from '@hcengineering/contact'
|
||||
import { AssigneeBox, AssigneePopup, personAccountByIdStore } from '@hcengineering/contact-resources'
|
||||
import { AssigneeCategory } from '@hcengineering/contact-resources/src/assignee'
|
||||
import { Account, Doc, DocumentQuery, Ref, Space, generateId } from '@hcengineering/core'
|
||||
import { RuleApplyResult, getClient, getDocRules } from '@hcengineering/presentation'
|
||||
import { TestCase } from '@hcengineering/test-management'
|
||||
import { ButtonKind, ButtonSize, IconSize, TooltipAlignment } from '@hcengineering/ui'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
import testManagement from '../../plugin'
|
||||
import { getPreviousAssignees } from '../../utils'
|
||||
|
||||
type AssigneeObject = (Doc | any) & Pick<TestCase, 'assignee'>
|
||||
|
||||
export let object: AssigneeObject | AssigneeObject[] | undefined = undefined
|
||||
export let value: AssigneeObject | AssigneeObject[] | undefined = undefined
|
||||
export let kind: ButtonKind = 'link'
|
||||
export let size: ButtonSize = 'large'
|
||||
export let avatarSize: IconSize = 'card'
|
||||
export let tooltipAlignment: TooltipAlignment | undefined = undefined
|
||||
export let width: string = 'min-content'
|
||||
export let focusIndex: number | undefined = undefined
|
||||
export let short: boolean = false
|
||||
export let shouldShowName = true
|
||||
export let shrink: number = 0
|
||||
export let isAction: boolean = false
|
||||
export let readonly: boolean = false
|
||||
export let showStatus = true
|
||||
|
||||
$: _object =
|
||||
(typeof object !== 'string' ? object : undefined) ?? (typeof value !== 'string' ? value : undefined) ?? []
|
||||
|
||||
$: docs = Array.isArray(_object) ? _object : [_object]
|
||||
$: cdocs = docs.filter((d) => '_class' in d) as Doc[]
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
let progress = false
|
||||
|
||||
const handleAssigneeChanged = async (newAssignee: Ref<Person> | undefined | null) => {
|
||||
if (newAssignee === undefined || (!Array.isArray(_object) && _object?.assignee === newAssignee)) {
|
||||
return
|
||||
}
|
||||
progress = true
|
||||
const ops = client.apply()
|
||||
if (Array.isArray(_object)) {
|
||||
for (const p of _object) {
|
||||
if ('_class' in p) {
|
||||
// Analytics.handleEvent(TrackerEvents.IssueSetAssignee, { issue: p.identifier ?? p._id })
|
||||
await ops.update(p, { assignee: newAssignee })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ('_class' in _object) {
|
||||
// Analytics.handleEvent(TrackerEvents.IssueSetAssignee, { issue: _object.identifier ?? _object._id })
|
||||
await ops.update(_object, { assignee: newAssignee })
|
||||
}
|
||||
}
|
||||
|
||||
await ops.commit()
|
||||
|
||||
progress = false
|
||||
|
||||
dispatch('change', newAssignee)
|
||||
if (isAction) dispatch('close')
|
||||
}
|
||||
|
||||
let categories: AssigneeCategory[] = []
|
||||
|
||||
function getCategories (object: AssigneeObject | AssigneeObject[]): void {
|
||||
categories = []
|
||||
if (cdocs.length > 0) {
|
||||
categories.push({
|
||||
label: testManagement.string.PreviousAssigned,
|
||||
func: async () => {
|
||||
const r: Ref<Person>[] = []
|
||||
for (const d of cdocs) {
|
||||
r.push(...(await getPreviousAssignees(d._id)))
|
||||
}
|
||||
return r
|
||||
}
|
||||
})
|
||||
}
|
||||
categories.push({
|
||||
label: testManagement.string.Members,
|
||||
func: async () => {
|
||||
const spaces = Array.from(docs.map((it) => it.space).filter((it) => it)) as Ref<Space>[]
|
||||
if (spaces.length === 0) {
|
||||
return []
|
||||
}
|
||||
const projects = await client.findAll(testManagement.class.TestProject, {
|
||||
_id: !Array.isArray(object) ? object.space : { $in: Array.from(object.map((it) => it.space)) }
|
||||
})
|
||||
if (projects === undefined) {
|
||||
return []
|
||||
}
|
||||
const store = get(personAccountByIdStore)
|
||||
const allMembers = projects.reduce((arr, p) => arr.concat(p.members), [] as Ref<Account>[])
|
||||
const accounts = allMembers
|
||||
.map((p) => store.get(p as Ref<PersonAccount>))
|
||||
.filter((p) => p !== undefined) as PersonAccount[]
|
||||
return accounts.map((p) => p.person as Ref<Employee>)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$: getCategories(_object)
|
||||
|
||||
$: sel =
|
||||
(!Array.isArray(_object)
|
||||
? _object.assignee
|
||||
: _object.reduce((v, it) => (v != null && v === it.assignee ? it.assignee : null), _object[0]?.assignee) ??
|
||||
undefined) ?? undefined
|
||||
|
||||
let rulesQuery: RuleApplyResult<Employee> | undefined
|
||||
let query: DocumentQuery<Employee>
|
||||
$: if (cdocs.length > 0) {
|
||||
rulesQuery = getDocRules<Employee>(cdocs, 'assignee')
|
||||
if (rulesQuery !== undefined) {
|
||||
query = { ...(rulesQuery?.fieldQuery ?? {}), active: true }
|
||||
} else {
|
||||
query = { _id: 'none' as Ref<Employee>, active: true }
|
||||
rulesQuery = {
|
||||
disableEdit: true,
|
||||
disableUnset: true,
|
||||
fieldQuery: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if _object}
|
||||
{#if isAction}
|
||||
<AssigneePopup
|
||||
docQuery={query}
|
||||
{categories}
|
||||
icon={contact.icon.Person}
|
||||
selected={sel}
|
||||
allowDeselect={true}
|
||||
titleDeselect={undefined}
|
||||
loading={progress}
|
||||
on:close={(evt) => {
|
||||
const result = evt.detail
|
||||
if (result === null) {
|
||||
handleAssigneeChanged(null)
|
||||
} else if (result !== undefined && result._id !== value) {
|
||||
value = result._id
|
||||
handleAssigneeChanged(result._id)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<AssigneeBox
|
||||
docQuery={query}
|
||||
{focusIndex}
|
||||
label={testManagement.string.Assignee}
|
||||
placeholder={testManagement.string.Assignee}
|
||||
value={sel}
|
||||
{categories}
|
||||
titleDeselect={testManagement.string.Unassigned}
|
||||
{size}
|
||||
{kind}
|
||||
{avatarSize}
|
||||
{width}
|
||||
{short}
|
||||
{shrink}
|
||||
{readonly}
|
||||
{shouldShowName}
|
||||
{showStatus}
|
||||
showNavigate={false}
|
||||
justify={'left'}
|
||||
showTooltip={{
|
||||
label: testManagement.string.AssignTo,
|
||||
personLabel: testManagement.string.AssignedTo,
|
||||
placeholderLabel: testManagement.string.Unassigned,
|
||||
direction: tooltipAlignment
|
||||
}}
|
||||
on:change={({ detail }) => handleAssigneeChanged(detail)}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -24,7 +24,6 @@
|
||||
import { Button, createFocusManager, EditBox, FocusHandler, IconAttachment, getLocation } from '@hcengineering/ui'
|
||||
|
||||
import StatusEditor from './StatusEditor.svelte'
|
||||
import AssigneeEditor from './AssigneeEditor.svelte'
|
||||
import ProjectPresenter from '../project/ProjectPresenter.svelte'
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
@@ -164,12 +163,6 @@
|
||||
/>
|
||||
|
||||
<svelte:fragment slot="pool">
|
||||
<AssigneeEditor
|
||||
object={{ ...object, space }}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
on:change={({ detail }) => (object.assignee = detail)}
|
||||
/>
|
||||
<StatusEditor bind:value={object.status} {object} kind="regular" />
|
||||
</svelte:fragment>
|
||||
|
||||
|
||||
@@ -13,13 +13,16 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
|
||||
import { AttachmentStyleBoxCollabEditor } from '@hcengineering/attachment-resources'
|
||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { type Class, type Ref } from '@hcengineering/core'
|
||||
import { TestCase } from '@hcengineering/test-management'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { EditBox, Breadcrumb } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { DocAttributeBar } from '@hcengineering/view-resources'
|
||||
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let _id: Ref<TestCase>
|
||||
@@ -102,5 +105,9 @@
|
||||
boundary={content}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<DocAttributeBar {object} ignoreKeys={['name']} />
|
||||
</svelte:fragment>
|
||||
</Panel>
|
||||
{/if}
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { Doc, DocumentQuery, Ref, Space } from '@hcengineering/core'
|
||||
import { ButtonWithDropdown, IconDropdown, SelectPopupValueType } from '@hcengineering/ui'
|
||||
import { selectionStore } from '@hcengineering/view-resources'
|
||||
import type { TestProject } from '@hcengineering/test-management'
|
||||
|
||||
import testManagement from '../../plugin'
|
||||
import { showCreateTestRunPopup } from '../../utils'
|
||||
|
||||
export let query: DocumentQuery<Doc> = {}
|
||||
export let space: Ref<Space>
|
||||
|
||||
const project: Ref<TestProject> = space as any
|
||||
|
||||
const commonDropdownItems = [
|
||||
{
|
||||
id: testManagement.string.RunAllTestCases,
|
||||
label: testManagement.string.RunAllTestCases,
|
||||
icon: testManagement.icon.TestRuns
|
||||
},
|
||||
{
|
||||
id: testManagement.string.RunFilteredTestCases,
|
||||
label: testManagement.string.RunFilteredTestCases,
|
||||
icon: testManagement.icon.Filter
|
||||
}
|
||||
]
|
||||
|
||||
let dropdownItems: SelectPopupValueType[] = []
|
||||
|
||||
$: dropdownItems =
|
||||
$selectionStore?.docs?.length > 0
|
||||
? [
|
||||
...commonDropdownItems,
|
||||
{
|
||||
id: testManagement.string.RunSelectedTestCases,
|
||||
label: testManagement.string.RunSelectedTestCases,
|
||||
icon: testManagement.icon.Check
|
||||
}
|
||||
]
|
||||
: commonDropdownItems
|
||||
|
||||
async function handleDropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
|
||||
switch (res) {
|
||||
case testManagement.string.RunAllTestCases: {
|
||||
await showCreateTestRunPopup({ space: project })
|
||||
return
|
||||
}
|
||||
case testManagement.string.RunFilteredTestCases: {
|
||||
await showCreateTestRunPopup({ query, space: project })
|
||||
return
|
||||
}
|
||||
case testManagement.string.RunSelectedTestCases: {
|
||||
await showCreateTestRunPopup({ space: project, testCases: $selectionStore?.docs as any })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleRunAllTestCases = async (): Promise<void> => {
|
||||
await showCreateTestRunPopup({ space: project })
|
||||
}
|
||||
</script>
|
||||
|
||||
<ButtonWithDropdown
|
||||
icon={testManagement.icon.TestRuns}
|
||||
justify={'left'}
|
||||
kind={'primary'}
|
||||
label={testManagement.string.RunAllTestCases}
|
||||
dropdownIcon={IconDropdown}
|
||||
{dropdownItems}
|
||||
on:click={handleRunAllTestCases}
|
||||
on:dropdown-selected={(ev) => {
|
||||
void handleDropdownItemSelected(ev.detail)
|
||||
}}
|
||||
/>
|
||||
@@ -43,7 +43,13 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
function handlePopupOpen (event: MouseEvent) {
|
||||
$: itemsInfo = defaultTestCaseStatuses.map((status) => ({
|
||||
id: status,
|
||||
isSelected: value === status,
|
||||
...testCaseStatusAssets[status]
|
||||
}))
|
||||
|
||||
function handlePopupOpen (event: MouseEvent): void {
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{ value: itemsInfo, placeholder: testManagement.string.SetStatus },
|
||||
@@ -52,7 +58,7 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function changeStatus (newStatus: TestCase['status'] | null | undefined) {
|
||||
async function changeStatus (newStatus: TestCase['status'] | null | undefined): Promise<void> {
|
||||
if (disabled || newStatus == null || value === newStatus) {
|
||||
return
|
||||
}
|
||||
@@ -65,12 +71,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: itemsInfo = defaultTestCaseStatuses.map((status) => ({
|
||||
id: status,
|
||||
isSelected: value === status,
|
||||
...testCaseStatusAssets[status]
|
||||
}))
|
||||
|
||||
$: icon = value === undefined ? testManagement.icon.StatusDraft : testCaseStatusAssets[value].icon
|
||||
$: label = value === undefined ? testManagement.string.StatusDraft : testCaseStatusAssets[value].label
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { createEventDispatcher, onMount } from 'svelte'
|
||||
|
||||
import { AttachmentStyleBoxCollabEditor } from '@hcengineering/attachment-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { type Class, type Ref } from '@hcengineering/core'
|
||||
import { TestCase } from '@hcengineering/test-management'
|
||||
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let object: TestCase | undefined = undefined
|
||||
export let _id: Ref<TestCase>
|
||||
export let _class: Ref<Class<TestCase>>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: _id !== undefined &&
|
||||
_class !== undefined &&
|
||||
(object === undefined || object._id !== _id) &&
|
||||
query.query(_class, { _id }, async (result) => {
|
||||
;[object] = result
|
||||
})
|
||||
|
||||
$: descriptionKey = hierarchy.getAttribute(testManagement.class.TestCase, 'description')
|
||||
|
||||
onMount(() => dispatch('open', { ignoreKeys: [] }))
|
||||
</script>
|
||||
|
||||
{#if object}
|
||||
<div class="w-full h-full">
|
||||
<AttachmentStyleBoxCollabEditor
|
||||
focusIndex={30}
|
||||
{object}
|
||||
key={{ key: 'description', attr: descriptionKey }}
|
||||
identifier={object?._id}
|
||||
placeholder={testManagement.string.DescriptionPlaceholder}
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,47 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 type { Class, Ref } from '@hcengineering/core'
|
||||
import { DocPopup } from '@hcengineering/presentation'
|
||||
import { TestCase } from '@hcengineering/test-management'
|
||||
|
||||
import TestCasePresenter from './TestCasePresenter.svelte'
|
||||
|
||||
export let _class: Ref<Class<TestCase>>
|
||||
export let objects: TestCase[] = []
|
||||
export let allowDeselect = false
|
||||
export let closeAfterSelect: boolean = false
|
||||
export let shadows = true
|
||||
export let readonly = false
|
||||
export let width: 'medium' | 'large' | 'full' = 'medium'
|
||||
</script>
|
||||
|
||||
<DocPopup
|
||||
{_class}
|
||||
{objects}
|
||||
multiSelect={true}
|
||||
{allowDeselect}
|
||||
{closeAfterSelect}
|
||||
{shadows}
|
||||
{width}
|
||||
{readonly}
|
||||
on:update
|
||||
on:close
|
||||
groupBy={'attachedTo'}
|
||||
>
|
||||
<svelte:fragment slot="item" let:item={testCase}>
|
||||
<TestCasePresenter value={testCase} disabled={readonly} />
|
||||
</svelte:fragment>
|
||||
</DocPopup>
|
||||
@@ -26,13 +26,14 @@
|
||||
export let disabled: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let noUnderline: boolean = false
|
||||
export let onClick: ((event: MouseEvent) => void) | undefined = undefined
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
{#if inline}
|
||||
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
|
||||
{:else}
|
||||
<DocNavLink object={value} {disabled} {accent} {noUnderline}>
|
||||
<DocNavLink object={value} {disabled} {accent} {noUnderline} {onClick}>
|
||||
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
|
||||
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
|
||||
{value.name}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { IntlString } from '@hcengineering/platform'
|
||||
import { Button, ButtonKind, ButtonShape, ButtonSize, Label, TooltipAlignment, showPopup } from '@hcengineering/ui'
|
||||
import { TestCase } from '@hcengineering/test-management'
|
||||
|
||||
import TestCasePopup from './TestCasePopup.svelte'
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let objects: TestCase[]
|
||||
export let selectedObjects: TestCase[]
|
||||
export let label: IntlString = testManagement.string.TestCases
|
||||
export let focusIndex = -1
|
||||
export let focus = false
|
||||
export let labelDirection: TooltipAlignment | undefined = undefined
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
export let size: ButtonSize = 'large'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
export let shape: ButtonShape = undefined
|
||||
export let width: string | undefined = undefined
|
||||
export let readonly = false
|
||||
|
||||
const showSpacesPopup = (ev: MouseEvent): void => {
|
||||
showPopup(
|
||||
TestCasePopup,
|
||||
{
|
||||
objects,
|
||||
readonly
|
||||
},
|
||||
ev.target as HTMLElement,
|
||||
() => {}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button
|
||||
id="testcase.selector"
|
||||
{focus}
|
||||
{shape}
|
||||
{focusIndex}
|
||||
icon={testManagement.icon.TestCase}
|
||||
{size}
|
||||
{kind}
|
||||
{justify}
|
||||
{width}
|
||||
showTooltip={{ label, direction: labelDirection }}
|
||||
on:click={showSpacesPopup}
|
||||
>
|
||||
<span slot="content" class="overflow-label disabled text">
|
||||
{selectedObjects?.length ?? 0}
|
||||
<Label {label} />
|
||||
</span>
|
||||
</Button>
|
||||
@@ -0,0 +1,105 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { createEventDispatcher, onMount } from 'svelte'
|
||||
|
||||
import { AttachmentStyleBoxCollabEditor } from '@hcengineering/attachment-resources'
|
||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { type Class, type Ref, Doc, Mixin, WithLookup } from '@hcengineering/core'
|
||||
import { TestCase, TestResult } from '@hcengineering/test-management'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { Label, Scroller } from '@hcengineering/ui'
|
||||
import { DocAttributeBar, getDocMixins } from '@hcengineering/view-resources'
|
||||
|
||||
import RightHeader from './RightHeader.svelte'
|
||||
import TestCaseDetails from '../test-case/TestCaseDetails.svelte'
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let _id: Ref<TestResult>
|
||||
export let _class: Ref<Class<TestResult>>
|
||||
|
||||
let object: WithLookup<TestResult> | undefined
|
||||
|
||||
const testCase = object?.$lookup?.testCase as TestCase | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let mixins: Mixin<Doc>[] = []
|
||||
$: mixins = object ? getDocMixins(object, false) : []
|
||||
|
||||
let descriptionBox: AttachmentStyleBoxCollabEditor
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: _id !== undefined &&
|
||||
_class !== undefined &&
|
||||
query.query(
|
||||
_class,
|
||||
{ _id },
|
||||
async (result) => {
|
||||
;[object] = result
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
testCase: testManagement.class.TestCase
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let content: HTMLElement
|
||||
|
||||
$: descriptionKey = hierarchy.getAttribute(testManagement.class.TestResult, 'description')
|
||||
|
||||
onMount(() => dispatch('open', { ignoreKeys: [] }))
|
||||
</script>
|
||||
|
||||
{#if object}
|
||||
<ActionContext context={{ mode: 'editor' }} />
|
||||
<Panel
|
||||
{object}
|
||||
title={testCase?.name ?? object?.name}
|
||||
isHeader={false}
|
||||
isAside={true}
|
||||
isSub={false}
|
||||
adaptive={'default'}
|
||||
on:open
|
||||
on:close={() => dispatch('close')}
|
||||
>
|
||||
<div class="space-divider" />
|
||||
<div class="w-full mt-6">
|
||||
<AttachmentStyleBoxCollabEditor
|
||||
focusIndex={30}
|
||||
{object}
|
||||
key={{ key: 'description', attr: descriptionKey }}
|
||||
bind:this={descriptionBox}
|
||||
identifier={object?._id}
|
||||
placeholder={testManagement.string.DescriptionPlaceholder}
|
||||
boundary={content}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<DocAttributeBar {object} {mixins} ignoreKeys={['name']} />
|
||||
<RightHeader>
|
||||
<Label label={testManagement.string.TestCaseDescription} />
|
||||
</RightHeader>
|
||||
<Scroller padding={'0.5rem 2rem'}>
|
||||
<TestCaseDetails _id={object.testCase} object={testCase} _class={testManagement.class.TestCase} />
|
||||
</Scroller>
|
||||
</svelte:fragment>
|
||||
</Panel>
|
||||
{/if}
|
||||
@@ -0,0 +1,24 @@
|
||||
<!--
|
||||
// Copyright © 2024 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.
|
||||
-->
|
||||
|
||||
<div class="header flex-between min-h-8 pl-4 pr-4 font-medium text-md bottom-divider top-divider">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { Button } from '@hcengineering/ui'
|
||||
|
||||
import testManagement from '../../plugin'
|
||||
</script>
|
||||
|
||||
<div class="flex-grow flex-shrink">
|
||||
<Button
|
||||
label={testManagement.string.Save}
|
||||
kind={'primary'}
|
||||
on:click={() => {
|
||||
// TODO: Add next test result logic
|
||||
}}
|
||||
showTooltip={{ label: testManagement.string.Save }}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { TestResult } from '@hcengineering/test-management'
|
||||
import TestResultPresenter from './TestResultPresenter.svelte'
|
||||
|
||||
export let object: TestResult
|
||||
</script>
|
||||
|
||||
<div class="antiHSpacer x2" />
|
||||
<div class="fs-title flex-row-center">
|
||||
<TestResultPresenter value={object} shouldShowAvatar={false} disabled noUnderline />
|
||||
</div>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2024 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 { TestCase, TestResult } from '@hcengineering/test-management'
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
import { Icon, tooltip } from '@hcengineering/ui'
|
||||
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
|
||||
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let value: WithLookup<TestResult> | undefined
|
||||
export let inline: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let noUnderline: boolean = false
|
||||
export let shouldShowAvatar = true
|
||||
|
||||
let testCase: TestCase | undefined = undefined
|
||||
$: testCase = value?.$lookup?.testCase as TestCase | undefined
|
||||
$: title = testCase?.name ?? value?.name
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
{#if inline}
|
||||
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
|
||||
{:else}
|
||||
<DocNavLink object={value} {disabled} {accent} {noUnderline}>
|
||||
<div class="flex-presenter" use:tooltip={{ label: testManagement.string.TestResult }}>
|
||||
{#if shouldShowAvatar}
|
||||
<div class="icon">
|
||||
<Icon icon={testManagement.icon.TestResult} size="small" />
|
||||
</div>
|
||||
{/if}
|
||||
<span {title} class="overflow-label label" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
</DocNavLink>
|
||||
{/if}
|
||||
{/if}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { createEventDispatcher } from 'svelte'
|
||||
import { Data } from '@hcengineering/core'
|
||||
import { TestResult } from '@hcengineering/test-management'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
Button,
|
||||
ButtonKind,
|
||||
ButtonSize,
|
||||
Icon,
|
||||
SelectPopup,
|
||||
eventToHTMLElement,
|
||||
showPopup,
|
||||
Label
|
||||
} from '@hcengineering/ui'
|
||||
|
||||
import { defaultTestRunStatuses, testRunStatusAssets } from '../../types'
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let value: TestResult['status'] | undefined
|
||||
export let object: TestResult | Data<TestResult>
|
||||
export let kind: ButtonKind = 'link'
|
||||
export let size: ButtonSize = 'large'
|
||||
export let justify: 'left' | 'center' = 'left'
|
||||
export let width: string | undefined = undefined
|
||||
export let disabled = false
|
||||
export let shouldShowAvatar: boolean = true
|
||||
export let accent: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
$: itemsInfo = defaultTestRunStatuses.map((status) => ({
|
||||
id: status,
|
||||
isSelected: value === status,
|
||||
...testRunStatusAssets[status]
|
||||
}))
|
||||
|
||||
function handlePopupOpen (event: MouseEvent): void {
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{ value: itemsInfo, placeholder: testManagement.string.SetStatus },
|
||||
eventToHTMLElement(event),
|
||||
changeStatus
|
||||
)
|
||||
}
|
||||
|
||||
async function changeStatus (newStatus: TestResult['status'] | null | undefined): Promise<void> {
|
||||
if (disabled || newStatus == null || value === newStatus) {
|
||||
return
|
||||
}
|
||||
|
||||
value = newStatus
|
||||
dispatch('change', value)
|
||||
|
||||
if (object !== undefined && '_id' in object) {
|
||||
await client.update(object, { status: newStatus })
|
||||
}
|
||||
}
|
||||
|
||||
$: icon = value === undefined ? testManagement.icon.StatusNonTested : testRunStatusAssets[value].icon
|
||||
$: label = value === undefined ? testManagement.string.StatusNonTested : testRunStatusAssets[value].label
|
||||
</script>
|
||||
|
||||
{#if kind === 'list'}
|
||||
<button
|
||||
class="flex-no-shrink clear-mins cursor-pointer content-pointer-events-none"
|
||||
{disabled}
|
||||
on:click={handlePopupOpen}
|
||||
>
|
||||
<Icon {icon} {size} />
|
||||
</button>
|
||||
{:else if kind === 'list-header'}
|
||||
<div class="flex-row-center pl-0-5">
|
||||
{#if shouldShowAvatar}
|
||||
<Icon {icon} {size} />
|
||||
{/if}
|
||||
<span class="overflow-label" class:ml-1-5={shouldShowAvatar} class:fs-bold={accent}><Label {label} /></span>
|
||||
</div>
|
||||
{:else}
|
||||
<Button
|
||||
{label}
|
||||
{kind}
|
||||
{icon}
|
||||
{justify}
|
||||
{size}
|
||||
{width}
|
||||
{disabled}
|
||||
showTooltip={{ label: testManagement.string.SetStatus }}
|
||||
on:click={handlePopupOpen}
|
||||
/>
|
||||
{/if}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { TestResult, TestRunStatus } from '@hcengineering/test-management'
|
||||
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
|
||||
import StatusEditor from './TestResultStatusEditor.svelte'
|
||||
|
||||
export let value: TestRunStatus
|
||||
export let object: TestResult
|
||||
export let onChange: ((value: TestRunStatus) => void) | undefined = undefined
|
||||
export let kind: ButtonKind = 'link'
|
||||
export let size: ButtonSize = 'large'
|
||||
export let justify: 'left' | 'center' = 'left'
|
||||
export let width: string | undefined = '100%'
|
||||
export let shouldShowAvatar: boolean = true
|
||||
export let accent: boolean = false
|
||||
|
||||
$: disabled = onChange === undefined
|
||||
</script>
|
||||
|
||||
<StatusEditor
|
||||
{value}
|
||||
{object}
|
||||
{kind}
|
||||
{size}
|
||||
{width}
|
||||
{justify}
|
||||
{disabled}
|
||||
{accent}
|
||||
{shouldShowAvatar}
|
||||
on:change={({ detail }) => onChange?.(detail)}
|
||||
/>
|
||||
@@ -17,26 +17,49 @@
|
||||
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
||||
import { ObjectBox } from '@hcengineering/view-resources'
|
||||
import core, { Data, Ref, generateId, makeCollaborativeDoc } from '@hcengineering/core'
|
||||
import core, { Data, DocumentQuery, Ref, generateId, makeCollaborativeDoc } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { Card, SpaceSelector, getClient } from '@hcengineering/presentation'
|
||||
import { TestRun, TestProject } from '@hcengineering/test-management'
|
||||
import { EditBox } from '@hcengineering/ui'
|
||||
import { Card, SpaceSelector, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
TestCase,
|
||||
TestRun,
|
||||
TestProject,
|
||||
TestResult,
|
||||
TestRunStatus,
|
||||
TestManagementEvents
|
||||
} from '@hcengineering/test-management'
|
||||
import { DatePresenter, EditBox, Loading, navigate } from '@hcengineering/ui'
|
||||
import { EmptyMarkup } from '@hcengineering/text'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
import { getTestRunsLink } from '../../navigation'
|
||||
import testManagement from '../../plugin'
|
||||
import ProjectPresenter from '../project/ProjectSpacePresenter.svelte'
|
||||
import TestCaseSelector from '../test-case/TestCaseSelector.svelte'
|
||||
|
||||
export let space: Ref<TestProject>
|
||||
export let query: DocumentQuery<TestCase> = {}
|
||||
export let testCases: TestCase[]
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
let isLoading = testCases === undefined
|
||||
|
||||
if (testCases === undefined) {
|
||||
const client = createQuery()
|
||||
const spaceQuery = space !== undefined ? { space } : {}
|
||||
client.query(testManagement.class.TestCase, { ...spaceQuery, ...(query ?? {}) }, (result) => {
|
||||
testCases = result
|
||||
isLoading = false
|
||||
})
|
||||
}
|
||||
|
||||
const id: Ref<TestRun> = generateId()
|
||||
|
||||
const object: Data<TestRun> = {
|
||||
name: '' as IntlString,
|
||||
description: makeCollaborativeDoc(id, 'description')
|
||||
description: makeCollaborativeDoc(id, 'description'),
|
||||
dueDate: undefined
|
||||
}
|
||||
|
||||
let _space = space
|
||||
@@ -46,15 +69,52 @@
|
||||
let descriptionBox: AttachmentStyledBox
|
||||
let attachments: Map<Ref<Attachment>, Attachment> = new Map<Ref<Attachment>, Attachment>()
|
||||
|
||||
async function onSave () {
|
||||
await client.createDoc(testManagement.class.TestRun, _space, object)
|
||||
async function onSave (): Promise<void> {
|
||||
try {
|
||||
const applyOp = client.apply()
|
||||
await applyOp.createDoc(testManagement.class.TestRun, _space, object, id)
|
||||
const testCasesArray = testCases instanceof Array ? testCases : [testCases]
|
||||
const createPromises = testCasesArray.map((testCase) => {
|
||||
const testResultId: Ref<TestResult> = generateId()
|
||||
const testResultData: Data<TestResult> = {
|
||||
attachedTo: id,
|
||||
attachedToClass: testManagement.class.TestRun,
|
||||
name: testCase.name,
|
||||
testCase: testCase._id,
|
||||
testSuite: testCase.attachedTo,
|
||||
collection: 'results',
|
||||
description: makeCollaborativeDoc(testResultId, 'description'),
|
||||
status: TestRunStatus.Untested
|
||||
}
|
||||
return applyOp.addCollection(
|
||||
testManagement.class.TestResult,
|
||||
_space,
|
||||
id,
|
||||
testManagement.class.TestRun,
|
||||
'results',
|
||||
testResultData,
|
||||
testResultId
|
||||
)
|
||||
})
|
||||
await Promise.all(createPromises)
|
||||
const opResult = await applyOp.commit()
|
||||
if (!opResult.result) {
|
||||
throw new Error('Failed to create test run')
|
||||
} else {
|
||||
Analytics.handleEvent(TestManagementEvents.TestRunCreated, { id })
|
||||
navigate(getTestRunsLink(id))
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={testManagement.string.CreateTestRun}
|
||||
okAction={onSave}
|
||||
canSave={object.name !== ''}
|
||||
canSave={object.name !== '' && !isLoading}
|
||||
okLabel={testManagement.string.CreateTestRun}
|
||||
gap={'gapV-4'}
|
||||
on:close={() => dispatch('close')}
|
||||
@@ -99,37 +159,22 @@
|
||||
}}
|
||||
/>
|
||||
<svelte:fragment slot="pool">
|
||||
<ObjectBox
|
||||
_class={testManagement.class.TestSuite}
|
||||
value={null}
|
||||
docQuery={{
|
||||
space: _space
|
||||
}}
|
||||
kind={'regular'}
|
||||
size={'small'}
|
||||
label={testManagement.string.SelectTestSuites}
|
||||
icon={testManagement.icon.TestSuite}
|
||||
searchField={'title'}
|
||||
allowDeselect={true}
|
||||
showNavigate={false}
|
||||
docProps={{ disabled: true, noUnderline: true }}
|
||||
focusIndex={20000}
|
||||
/>
|
||||
<ObjectBox
|
||||
_class={testManagement.class.TestCase}
|
||||
value={null}
|
||||
docQuery={{
|
||||
space: _space
|
||||
}}
|
||||
kind={'regular'}
|
||||
size={'small'}
|
||||
label={testManagement.string.SelectTestCases}
|
||||
icon={testManagement.icon.TestCase}
|
||||
searchField={'title'}
|
||||
allowDeselect={true}
|
||||
showNavigate={false}
|
||||
docProps={{ disabled: true, noUnderline: true }}
|
||||
focusIndex={20000}
|
||||
/>
|
||||
<div id="duedate-editor">
|
||||
<DatePresenter
|
||||
focusIndex={10}
|
||||
bind:value={object.dueDate}
|
||||
labelNull={testManagement.string.DueDate}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
editable
|
||||
/>
|
||||
</div>
|
||||
<div id="test-cases-selector">
|
||||
{#if isLoading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<TestCaseSelector objects={testCases} selectedObjects={testCases} readonly={true} />
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Card>
|
||||
|
||||
@@ -13,6 +13,97 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentStyleBoxCollabEditor } from '@hcengineering/attachment-resources'
|
||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { type Class, type Ref } from '@hcengineering/core'
|
||||
import { TestRun } from '@hcengineering/test-management'
|
||||
import { Panel } from '@hcengineering/panel'
|
||||
import { EditBox } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
|
||||
import testManagement from '../../plugin'
|
||||
import TestRunAside from './TestRunAside.svelte'
|
||||
|
||||
export let _id: Ref<TestRun>
|
||||
export let _class: Ref<Class<TestRun>>
|
||||
|
||||
let object: TestRun | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let oldLabel: string | undefined = ''
|
||||
let rawLabel: string | undefined = ''
|
||||
let descriptionBox: AttachmentStyleBoxCollabEditor
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
$: _id !== undefined &&
|
||||
_class !== undefined &&
|
||||
query.query(_class, { _id }, async (result) => {
|
||||
;[object] = result
|
||||
})
|
||||
|
||||
async function change<K extends keyof TestRun> (field: K, value: TestRun[K]) {
|
||||
if (object !== undefined) {
|
||||
await client.update(object, { [field]: value })
|
||||
}
|
||||
}
|
||||
|
||||
let content: HTMLElement
|
||||
|
||||
$: if (oldLabel !== object?.name) {
|
||||
oldLabel = object?.name
|
||||
rawLabel = object?.name
|
||||
}
|
||||
|
||||
$: descriptionKey = hierarchy.getAttribute(testManagement.class.TestRun, 'description')
|
||||
|
||||
onMount(() => dispatch('open', { ignoreKeys: [] }))
|
||||
</script>
|
||||
|
||||
<div class="antiNav-subheader"></div>
|
||||
{#if object}
|
||||
<ActionContext context={{ mode: 'editor' }} />
|
||||
<Panel
|
||||
{object}
|
||||
title={object.name}
|
||||
isHeader={false}
|
||||
isAside={true}
|
||||
isSub={false}
|
||||
adaptive={'default'}
|
||||
on:open
|
||||
on:close={() => dispatch('close')}
|
||||
>
|
||||
<EditBox
|
||||
bind:value={rawLabel}
|
||||
placeholder={testManagement.string.NamePlaceholder}
|
||||
kind="large-style"
|
||||
on:blur={async () => {
|
||||
const trimmedLabel = rawLabel?.trim()
|
||||
|
||||
if (trimmedLabel?.length === 0) {
|
||||
rawLabel = oldLabel
|
||||
} else if (trimmedLabel !== object?.name) {
|
||||
await change('name', trimmedLabel ?? '')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="w-full mt-6">
|
||||
<AttachmentStyleBoxCollabEditor
|
||||
focusIndex={30}
|
||||
{object}
|
||||
key={{ key: 'description', attr: descriptionKey }}
|
||||
bind:this={descriptionBox}
|
||||
identifier={object?._id}
|
||||
placeholder={testManagement.string.DescriptionPlaceholder}
|
||||
boundary={content}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<TestRunAside {object} />
|
||||
</svelte:fragment>
|
||||
</Panel>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { ActionContext } from '@hcengineering/presentation'
|
||||
import { TestCase } from '@hcengineering/test-management'
|
||||
import { AnyComponent, AnySvelteComponent, registerFocus } from '@hcengineering/ui'
|
||||
import { ViewOptions, Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
import { List, ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let query: DocumentQuery<TestCase> | undefined = undefined
|
||||
export let viewlet: Viewlet
|
||||
export let viewOptions: ViewOptions
|
||||
export let disableHeader: boolean = false
|
||||
export let compactMode: boolean = false
|
||||
export let configurations: Record<Ref<Class<Doc>>, Viewlet['config']> = {}
|
||||
export let preference: ViewletPreference[] = []
|
||||
export let createItemDialog: AnySvelteComponent | AnyComponent | undefined = undefined
|
||||
export let createItemLabel: IntlString | undefined = undefined
|
||||
export let createItemDialogProps: Record<string, any> | undefined = undefined
|
||||
|
||||
let list: List
|
||||
|
||||
const listProvider = new ListSelectionProvider(
|
||||
(offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection, noScroll?: boolean) => {
|
||||
if (dir === 'vertical') {
|
||||
// Select next
|
||||
list?.select(offset, of, noScroll)
|
||||
}
|
||||
}
|
||||
)
|
||||
let docs: Doc[] = []
|
||||
function select () {
|
||||
listProvider.update(docs)
|
||||
listProvider.updateFocus(docs[0])
|
||||
list?.select(0, undefined)
|
||||
}
|
||||
const selection = listProvider.selection
|
||||
|
||||
// Focusable control with index
|
||||
let focused = false
|
||||
export let focusIndex = -1
|
||||
registerFocus(focusIndex, {
|
||||
focus: () => {
|
||||
;(window.document.activeElement as HTMLElement).blur()
|
||||
focused = true
|
||||
select()
|
||||
return true
|
||||
},
|
||||
isFocus: () => focused
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<ActionContext
|
||||
context={{
|
||||
mode: 'browser'
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if viewlet}
|
||||
<List
|
||||
bind:this={list}
|
||||
_class={testManagement.class.TestCase}
|
||||
{viewOptions}
|
||||
viewOptionsConfig={viewlet.viewOptions?.other}
|
||||
config={preference.find((it) => it.attachedTo === viewlet._id)?.config ?? viewlet.config}
|
||||
{configurations}
|
||||
{query}
|
||||
flatHeaders={true}
|
||||
{disableHeader}
|
||||
{createItemDialog}
|
||||
{createItemDialogProps}
|
||||
{createItemLabel}
|
||||
{listProvider}
|
||||
selectedObjectIds={$selection ?? []}
|
||||
{compactMode}
|
||||
on:row-focus={(event) => {
|
||||
listProvider.updateFocus(event.detail ?? undefined)
|
||||
}}
|
||||
on:check={(event) => {
|
||||
listProvider.updateSelection(event.detail.docs, event.detail.value)
|
||||
}}
|
||||
on:content={(evt) => {
|
||||
docs = evt.detail
|
||||
listProvider.update(evt.detail)
|
||||
dispatch('docs', docs)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
+11
-18
@@ -13,52 +13,45 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Ref } from '@hcengineering/core'
|
||||
import { Doc, DocumentQuery } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { testManagementId, TestSuite } from '@hcengineering/test-management'
|
||||
import { Button, Icon, IconAdd, Label, Loading, Scroller, showPopup, SectionEmpty } from '@hcengineering/ui'
|
||||
import { Button, Icon, IconAdd, Label, Loading, Scroller, SectionEmpty } from '@hcengineering/ui'
|
||||
import { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
import { NavLink, Table, ViewletsSettingButton } from '@hcengineering/view-resources'
|
||||
import { Table, ViewletsSettingButton } from '@hcengineering/view-resources'
|
||||
import testManagement from '../../plugin'
|
||||
import CreateTestCase from '../test-case/CreateTestCase.svelte'
|
||||
import FileDuo from '../icons/FileDuo.svelte'
|
||||
|
||||
export let objectId: Ref<TestSuite>
|
||||
export let baseQuery: DocumentQuery<Doc> = {}
|
||||
let testCases: number
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(testManagement.class.TestCase, { suite: objectId }, (res) => {
|
||||
$: query.query(testManagement.class.TestCase, baseQuery, (res) => {
|
||||
testCases = res.length
|
||||
})
|
||||
|
||||
const createTestCase = (ev: MouseEvent): void => {
|
||||
showPopup(CreateTestCase, { testSuiteId: objectId }, ev.target as HTMLElement)
|
||||
}
|
||||
|
||||
let viewlet: Viewlet | undefined
|
||||
let preference: ViewletPreference | undefined
|
||||
let loading = true
|
||||
</script>
|
||||
|
||||
<!--TODO: Finish implementation-->
|
||||
<div class="antiSection max-h-125 clear-mins">
|
||||
<div class="antiSection-header">
|
||||
<div class="antiSection-header__icon">
|
||||
<Icon icon={testManagement.icon.TestCase} size={'small'} />
|
||||
</div>
|
||||
<span class="antiSection-header__title">
|
||||
<NavLink app={testManagementId} space={objectId}>
|
||||
<Label label={testManagement.string.TestCases} />
|
||||
</NavLink>
|
||||
<Label label={testManagement.string.TestCases} />
|
||||
</span>
|
||||
<div class="flex-row-center gap-2 reverse">
|
||||
<ViewletsSettingButton
|
||||
viewletQuery={{ _id: testManagement.viewlet.SuiteTestCases }}
|
||||
viewletQuery={{ _id: testManagement.viewlet.ListTestCase }}
|
||||
kind={'tertiary'}
|
||||
bind:viewlet
|
||||
bind:preference
|
||||
bind:loading
|
||||
/>
|
||||
<Button id="appls.add" icon={IconAdd} kind={'ghost'} on:click={createTestCase} />
|
||||
<Button id="appls.add" icon={IconAdd} kind={'ghost'} on:click={() => {}} />
|
||||
</div>
|
||||
</div>
|
||||
{#if testCases > 0}
|
||||
@@ -67,7 +60,7 @@
|
||||
<Table
|
||||
_class={testManagement.class.TestCase}
|
||||
config={preference?.config ?? viewlet.config}
|
||||
query={{ suite: objectId }}
|
||||
query={baseQuery}
|
||||
loadingProps={{ length: testCases }}
|
||||
/>
|
||||
</Scroller>
|
||||
@@ -78,7 +71,7 @@
|
||||
<SectionEmpty icon={FileDuo} label={testManagement.string.NoTestCases}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span class="over-underline content-color" on:click={createTestCase}>
|
||||
<span class="over-underline content-color">
|
||||
<Label label={testManagement.string.CreateTestCase} />
|
||||
</span>
|
||||
</SectionEmpty>
|
||||
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { WithLookup } from '@hcengineering/core'
|
||||
import type { TestRun } from '@hcengineering/test-management'
|
||||
import { Scroller } from '@hcengineering/ui'
|
||||
import { DocAttributeBar } from '@hcengineering/view-resources'
|
||||
|
||||
import TestRunStats from './TestRunStats.svelte'
|
||||
|
||||
export let object: WithLookup<TestRun>
|
||||
export let readonly: boolean = false
|
||||
</script>
|
||||
|
||||
<Scroller>
|
||||
<TestRunStats _id={object._id} />
|
||||
<div class="space-divider" />
|
||||
<DocAttributeBar {object} {readonly} ignoreKeys={['name']} />
|
||||
<div class="space-divider bottom" />
|
||||
</Scroller>
|
||||
@@ -13,6 +13,28 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { TestRun } from '@hcengineering/test-management'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { tooltip } from '@hcengineering/ui'
|
||||
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
|
||||
|
||||
export let value: TestRun | undefined
|
||||
export let inline: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let noUnderline: boolean = false
|
||||
</script>
|
||||
|
||||
<div class="antiNav-subheader"></div>
|
||||
{#if value}
|
||||
{#if inline}
|
||||
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
|
||||
{:else}
|
||||
<DocNavLink object={value} {disabled} {accent} {noUnderline}>
|
||||
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
|
||||
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
|
||||
{value.name}
|
||||
</span>
|
||||
</div>
|
||||
</DocNavLink>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { BreadcrumbsElement } from '@hcengineering/presentation'
|
||||
import { ScrollerBar } from '@hcengineering/ui'
|
||||
|
||||
import { type TestRunStats } from '../../testRunUtils'
|
||||
|
||||
export let value: TestRunStats
|
||||
|
||||
let divScroll: HTMLElement
|
||||
</script>
|
||||
|
||||
<!--TODO: Refactor and get rid of harcoded values-->
|
||||
<ScrollerBar gap="none" bind:scroller={divScroll}>
|
||||
<BreadcrumbsElement
|
||||
noGap
|
||||
label={value.untested.toString()}
|
||||
position={'start'}
|
||||
color={'#4CA6EE'}
|
||||
fontColor="white"
|
||||
title="Untested"
|
||||
selected
|
||||
/>
|
||||
<BreadcrumbsElement
|
||||
label={value.blocked.toString()}
|
||||
noGap
|
||||
position={'middle'}
|
||||
color={'#D27540'}
|
||||
selected
|
||||
fontColor="white"
|
||||
title="Failed"
|
||||
/>
|
||||
<BreadcrumbsElement
|
||||
label={value.failed.toString()}
|
||||
noGap
|
||||
position={'middle'}
|
||||
color={'#D15045'}
|
||||
selected
|
||||
fontColor="white"
|
||||
title="Failed"
|
||||
/>
|
||||
<BreadcrumbsElement
|
||||
noGap
|
||||
label={value.completed.toString()}
|
||||
position={'end'}
|
||||
color={'#46A44F'}
|
||||
fontColor="white"
|
||||
title="Passed"
|
||||
selected
|
||||
/>
|
||||
</ScrollerBar>
|
||||
@@ -0,0 +1,55 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 '@hcengineering/core'
|
||||
import type { TestRun } from '@hcengineering/test-management'
|
||||
import { Label, ProgressCircle, Loading } from '@hcengineering/ui'
|
||||
|
||||
import TestRunResult from './TestRunResult.svelte'
|
||||
import { type TestRunStats, getTestRunStats } from '../../testRunUtils'
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let _id: Ref<TestRun>
|
||||
let stats: TestRunStats | undefined = undefined
|
||||
|
||||
let isLoading = true
|
||||
|
||||
getTestRunStats(_id).then((newStats) => {
|
||||
stats = newStats
|
||||
isLoading = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="popupPanel-body__aside-grid">
|
||||
<span class="labelOnPanel"><Label label={testManagement.string.TestResults} /> </span>
|
||||
{#if !isLoading && stats !== undefined}
|
||||
<TestRunResult value={stats} />
|
||||
{:else}
|
||||
<Loading />
|
||||
{/if}
|
||||
<span class="labelOnPanel">
|
||||
<Label label={testManagement.string.DonePercent} />
|
||||
</span>
|
||||
{#if !isLoading}
|
||||
<div class="flex-row-center content-color text-sm pointer-events-none">
|
||||
<div class="mr-1">
|
||||
<ProgressCircle value={stats?.done ?? 0} />
|
||||
</div>
|
||||
{stats?.done ?? 0}
|
||||
</div>
|
||||
{:else}
|
||||
<Loading />
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { Breadcrumb, Header } from '@hcengineering/ui'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
|
||||
export let header: IntlString
|
||||
</script>
|
||||
|
||||
<Header adaptive={'disabled'}>
|
||||
<Breadcrumb label={header} size={'large'} />
|
||||
</Header>
|
||||
<slot />
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Copyright © 2024 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.
|
||||
//
|
||||
|
||||
import type { TestCase, TestResult } from '@hcengineering/test-management'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { type Writable, writable } from 'svelte/store'
|
||||
|
||||
export const currentTestCase: Writable<Ref<TestCase> | undefined> = writable(undefined)
|
||||
|
||||
export const selectedTestRun: Writable<TestResult | undefined> = writable(undefined)
|
||||
@@ -21,7 +21,6 @@
|
||||
import { EditBox, Breadcrumb } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
|
||||
import TestCasesList from './TestCasesList.svelte'
|
||||
import testManagement from '../../plugin'
|
||||
|
||||
export let _id: Ref<TestSuite>
|
||||
@@ -95,9 +94,5 @@
|
||||
showButtons={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full mt-6">
|
||||
<TestCasesList objectId={object._id} />
|
||||
</div>
|
||||
</Panel>
|
||||
{/if}
|
||||
|
||||
@@ -28,9 +28,17 @@ import CreateTestRun from './components/test-run/CreateTestRun.svelte'
|
||||
import TestCaseStatusPresenter from './components/test-case/TestCaseStatusPresenter.svelte'
|
||||
import EditTestRun from './components/test-run/EditTestRun.svelte'
|
||||
import TestRunPresenter from './components/test-run/TestRunPresenter.svelte'
|
||||
import RunSelectedTestsButton from './components/test-case/RunSelectedTestsButton.svelte'
|
||||
import TestResultStatusPresenter from './components/test-result/TestResultStatusPresenter.svelte'
|
||||
import TestResultStatusEditor from './components/test-result/TestResultStatusEditor.svelte'
|
||||
import TestRunResult from './components/test-run/TestRunResult.svelte'
|
||||
import TestResultPresenter from './components/test-result/TestResultPresenter.svelte'
|
||||
import EditTestResult from './components/test-result/EditTestResult.svelte'
|
||||
import TestResultHeader from './components/test-result/TestResultHeader.svelte'
|
||||
import TestResultFooter from './components/test-result/TestResultFooter.svelte'
|
||||
|
||||
import { CreateChildTestSuiteAction, EditTestSuiteAction } from './utils'
|
||||
import { resolveLocation, getTestSuiteLink } from './navigation'
|
||||
import { CreateChildTestSuiteAction, EditTestSuiteAction, RunSelectedTestsAction } from './utils'
|
||||
import { resolveLocation, getAttachedObjectLink } from './navigation'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
@@ -47,16 +55,26 @@ export default async (): Promise<Resources> => ({
|
||||
TestCaseStatusPresenter,
|
||||
EditTestRun,
|
||||
TestRunPresenter,
|
||||
TestSuiteRefPresenter
|
||||
TestSuiteRefPresenter,
|
||||
RunSelectedTestsButton,
|
||||
TestResultStatusPresenter,
|
||||
TestResultStatusEditor,
|
||||
TestRunResult,
|
||||
TestResultPresenter,
|
||||
EditTestResult,
|
||||
TestResultHeader,
|
||||
TestResultFooter
|
||||
},
|
||||
function: {
|
||||
GetTestSuiteLink: getTestSuiteLink
|
||||
GetTestSuiteLink: getAttachedObjectLink,
|
||||
GetTestRunLink: getAttachedObjectLink
|
||||
},
|
||||
resolver: {
|
||||
Location: resolveLocation
|
||||
},
|
||||
actionImpl: {
|
||||
CreateChildTestSuite: CreateChildTestSuiteAction,
|
||||
EditTestSuite: EditTestSuiteAction
|
||||
EditTestSuite: EditTestSuiteAction,
|
||||
RunSelectedTests: RunSelectedTestsAction
|
||||
}
|
||||
})
|
||||
|
||||
@@ -61,14 +61,9 @@ async function generateProjectLocation (
|
||||
}
|
||||
}
|
||||
|
||||
export function getTestSuiteLink (testSuite: Ref<TestSuite>): Location {
|
||||
export function getAttachedObjectLink (parentDoc: Ref<Doc>): Location {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.query =
|
||||
testSuite === undefined
|
||||
? undefined
|
||||
: {
|
||||
[SUITE_KEY]: testSuite
|
||||
}
|
||||
loc.query = parentDoc === undefined ? undefined : { attachedTo: parentDoc }
|
||||
|
||||
return loc
|
||||
}
|
||||
@@ -84,6 +79,16 @@ export function getTestSuiteIdFromLocation (): Ref<TestSuite> {
|
||||
return (location?.query?.[SUITE_KEY] as Ref<TestSuite>) ?? testManagement.ids.NoParent
|
||||
}
|
||||
|
||||
export function getTestRunsLink (parentDoc: Ref<Doc>): Location {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path.length = 4
|
||||
loc.path[3] = 'testRuns'
|
||||
loc.fragment = undefined
|
||||
loc.query = parentDoc === undefined ? undefined : { attachedTo: parentDoc }
|
||||
|
||||
return loc
|
||||
}
|
||||
|
||||
export async function resolveLocation (loc: Location): Promise<ResolvedLocation | undefined> {
|
||||
if (loc.path[2] !== testManagementId) {
|
||||
return undefined
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// Copyright © 2024 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.
|
||||
//
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import testManagement, { type TestRun, type TestCase, TestRunStatus } from '@hcengineering/test-management'
|
||||
|
||||
export async function getTestCases (objectId: Ref<TestRun>): Promise<TestCase[]> {
|
||||
if (objectId === undefined) {
|
||||
return []
|
||||
}
|
||||
const client = getClient()
|
||||
const testResults = await client.findAll(testManagement.class.TestResult, { attachedTo: objectId })
|
||||
const testCaseIds = testResults.map((testResult) => testResult.testCase)
|
||||
return await client.findAll(testManagement.class.TestCase, { _id: { $in: testCaseIds } })
|
||||
}
|
||||
|
||||
export interface TestRunStats {
|
||||
readonly done: number
|
||||
readonly untested: number
|
||||
readonly blocked: number
|
||||
readonly completed: number
|
||||
readonly failed: number
|
||||
}
|
||||
|
||||
async function getTestResultCount (objectId: Ref<TestRun>, status: TestRunStatus): Promise<number> {
|
||||
const client = getClient()
|
||||
const testResults = await client.findAll(
|
||||
testManagement.class.TestResult,
|
||||
{
|
||||
attachedTo: objectId,
|
||||
status
|
||||
},
|
||||
{ limit: 0, total: true }
|
||||
)
|
||||
return testResults.total > 0 ? testResults.total : 0
|
||||
}
|
||||
|
||||
export async function getTestRunStats (objectId: Ref<TestRun>): Promise<TestRunStats> {
|
||||
const untested = await getTestResultCount(objectId, TestRunStatus.Untested)
|
||||
const blocked = await getTestResultCount(objectId, TestRunStatus.Blocked)
|
||||
const completed = await getTestResultCount(objectId, TestRunStatus.Passed)
|
||||
const failed = await getTestResultCount(objectId, TestRunStatus.Failed)
|
||||
const total = untested + blocked + completed + failed
|
||||
|
||||
const done = total > 0 ? ((total - untested) * 100) / total : 0
|
||||
return {
|
||||
done,
|
||||
untested,
|
||||
blocked,
|
||||
completed,
|
||||
failed
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
//
|
||||
import { type Asset, type IntlString } from '@hcengineering/platform'
|
||||
|
||||
import testManagement, { TestCaseStatus } from '@hcengineering/test-management'
|
||||
import testManagement, { TestCaseStatus, TestRunStatus } from '@hcengineering/test-management'
|
||||
|
||||
/** @public */
|
||||
export const defaultTestCaseStatuses = [
|
||||
@@ -39,3 +39,31 @@ export const testCaseStatusAssets: Record<TestCaseStatus, { icon: Asset, label:
|
||||
[TestCaseStatus.Approved]: { icon: testManagement.icon.StatusApproved, label: testManagement.string.StatusApproved },
|
||||
[TestCaseStatus.Rejected]: { icon: testManagement.icon.StatusRejected, label: testManagement.string.StatusRejected }
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const defaultTestRunStatuses = [
|
||||
TestRunStatus.Untested,
|
||||
TestRunStatus.Blocked,
|
||||
TestRunStatus.Passed,
|
||||
TestRunStatus.Failed
|
||||
]
|
||||
|
||||
/** @public */
|
||||
export const testRunStatusAssets: Record<TestRunStatus, { icon: Asset, label: IntlString }> = {
|
||||
[TestRunStatus.Untested]: {
|
||||
icon: testManagement.icon.StatusNonTested,
|
||||
label: testManagement.string.StatusNonTested
|
||||
},
|
||||
[TestRunStatus.Blocked]: {
|
||||
icon: testManagement.icon.StatusBlocked,
|
||||
label: testManagement.string.StatusBlocked
|
||||
},
|
||||
[TestRunStatus.Passed]: {
|
||||
icon: testManagement.icon.StatusPassed,
|
||||
label: testManagement.string.StatusPassed
|
||||
},
|
||||
[TestRunStatus.Failed]: {
|
||||
icon: testManagement.icon.StatusFailed,
|
||||
label: testManagement.string.StatusFailed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Contact } from '@hcengineering/contact'
|
||||
import core, { type Doc, type Ref, type TxCreateDoc, type TxUpdateDoc } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import type { Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import { type TestProject, type TestCase, type TestSuite } from '@hcengineering/test-management'
|
||||
|
||||
@@ -23,34 +21,7 @@ import CreateTestSuiteComponent from './components/test-suite/CreateTestSuite.sv
|
||||
import EditTestSuiteComponent from './components/test-suite/EditTestSuite.svelte'
|
||||
import CreateTestCase from './components/test-case/CreateTestCase.svelte'
|
||||
import CreateProject from './components/project/CreateProject.svelte'
|
||||
|
||||
export async function getPreviousAssignees (objectId: Ref<Doc> | undefined): Promise<Array<Ref<Contact>>> {
|
||||
if (objectId === undefined) {
|
||||
return []
|
||||
}
|
||||
const client = getClient()
|
||||
const createTx = (
|
||||
await client.findAll<TxCreateDoc<TestCase>>(core.class.TxCreateDoc, {
|
||||
objectId: objectId as Ref<TestCase>
|
||||
})
|
||||
)[0]
|
||||
const updateTxes = await client.findAll<TxUpdateDoc<TestCase>>(
|
||||
core.class.TxUpdateDoc,
|
||||
{ objectId: objectId as Ref<TestCase>, 'operations.assignee': { $exists: true } },
|
||||
{ sort: { modifiedOn: -1 } }
|
||||
)
|
||||
const set = new Set<Ref<Contact>>()
|
||||
const createAssignee = createTx?.attributes?.assignee
|
||||
for (const tx of updateTxes) {
|
||||
const assignee = tx.operations.assignee
|
||||
if (assignee == null) continue
|
||||
set.add(assignee)
|
||||
}
|
||||
if (createAssignee != null) {
|
||||
set.add(createAssignee)
|
||||
}
|
||||
return Array.from(set)
|
||||
}
|
||||
import CreateTestRun from './components/test-run/CreateTestRun.svelte'
|
||||
|
||||
export async function showCreateTestSuitePopup (
|
||||
space: Ref<TestProject> | undefined,
|
||||
@@ -71,6 +42,14 @@ export async function showCreateProjectPopup (): Promise<void> {
|
||||
showPopup(CreateProject, {}, 'top')
|
||||
}
|
||||
|
||||
export async function showCreateTestRunPopup (options: {
|
||||
testCases?: TestCase[]
|
||||
query?: DocumentQuery<Doc>
|
||||
space: Ref<TestProject>
|
||||
}): Promise<void> {
|
||||
showPopup(CreateTestRun, options, 'top')
|
||||
}
|
||||
|
||||
export async function CreateChildTestSuiteAction (doc: TestSuite): Promise<void> {
|
||||
await showCreateTestSuitePopup(doc.space, doc._id)
|
||||
}
|
||||
@@ -78,3 +57,12 @@ export async function CreateChildTestSuiteAction (doc: TestSuite): Promise<void>
|
||||
export async function EditTestSuiteAction (doc: TestSuite): Promise<void> {
|
||||
await showEditTestSuitePopup(doc._id)
|
||||
}
|
||||
|
||||
export async function RunSelectedTestsAction (testCases: TestCase[]): Promise<void> {
|
||||
if (testCases?.length > 0) {
|
||||
const space = testCases[0].space
|
||||
await showCreateTestRunPopup({ testCases, space })
|
||||
} else {
|
||||
console.error('No test cases selected')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright © 2024 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.
|
||||
//
|
||||
|
||||
export enum TestManagementEvents {
|
||||
TestCaseCreated = 'testManagement.TestCaseCreated',
|
||||
TestCaseEdited = 'testManagement.TestCaseEdited',
|
||||
TestCaseDeleted = 'testManagement.TestCaseDeleted',
|
||||
TestSuiteCreated = 'testManagement.TestSuiteCreated',
|
||||
TestSuiteEdited = 'testManagement.TestSuiteEdited',
|
||||
TestSuiteDeleted = 'testManagement.TestSuiteDeleted',
|
||||
TestRunCreated = 'testManagement.TestRunCreated',
|
||||
TestRunEdited = 'testManagement.TestRunEdited',
|
||||
TestRunDeleted = 'testManagement.TestRunDeleted'
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
import { testManagementId, testManagementPlugin } from './plugin'
|
||||
|
||||
export * from './types'
|
||||
export * from './analytics'
|
||||
export { testManagementId }
|
||||
|
||||
export default testManagementPlugin
|
||||
|
||||
@@ -28,7 +28,7 @@ import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platfor
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { type AnyComponent, type Location, type ResolvedLocation } from '@hcengineering/ui'
|
||||
|
||||
import { ActionCategory, Viewlet } from '@hcengineering/view'
|
||||
import { Action, ActionCategory, ViewAction, Viewlet } from '@hcengineering/view'
|
||||
import {
|
||||
TestSuite,
|
||||
TestCase,
|
||||
@@ -37,8 +37,8 @@ import {
|
||||
TestCasePriority,
|
||||
TestCaseStatus,
|
||||
TestRun,
|
||||
TestRunItem,
|
||||
TestRunResult
|
||||
TestRunStatus,
|
||||
TestResult
|
||||
} from './types'
|
||||
|
||||
/** @public */
|
||||
@@ -49,6 +49,17 @@ export const testManagementPlugin = plugin(testManagementId, {
|
||||
app: {
|
||||
TestManagement: '' as Ref<Doc>
|
||||
},
|
||||
action: {
|
||||
DeleteTestCase: '' as Ref<Action<Doc, any>>,
|
||||
CreateChildTestSuite: '' as Ref<Action>,
|
||||
EditTestSuite: '' as Ref<Action>,
|
||||
RunSelectedTests: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
CreateChildTestSuite: '' as ViewAction,
|
||||
EditTestSuite: '' as ViewAction,
|
||||
RunSelectedTests: '' as ViewAction
|
||||
},
|
||||
icon: {
|
||||
TestManagement: '' as Asset,
|
||||
TestManagementVersion: '' as Asset,
|
||||
@@ -68,7 +79,14 @@ export const testManagementPlugin = plugin(testManagementId, {
|
||||
StatusApproved: '' as Asset,
|
||||
StatusRejected: '' as Asset,
|
||||
Document: '' as Asset,
|
||||
TestLibrary: '' as Asset
|
||||
TestLibrary: '' as Asset,
|
||||
TestResult: '' as Asset,
|
||||
StatusNonTested: '' as Asset,
|
||||
StatusBlocked: '' as Asset,
|
||||
StatusPassed: '' as Asset,
|
||||
StatusFailed: '' as Asset,
|
||||
Filter: '' as Asset,
|
||||
Check: '' as Asset
|
||||
},
|
||||
class: {
|
||||
TestCase: '' as Ref<Class<TestCase>>,
|
||||
@@ -78,8 +96,8 @@ export const testManagementPlugin = plugin(testManagementId, {
|
||||
TypeTestCasePriority: '' as Ref<Class<Type<TestCasePriority>>>,
|
||||
TypeTestCaseStatus: '' as Ref<Class<Type<TestCaseStatus>>>,
|
||||
TestRun: '' as Ref<Class<TestRun>>,
|
||||
TestRunItem: '' as Ref<Class<TestRunItem>>,
|
||||
TypeTestRunResult: '' as Ref<Class<Type<TestRunResult>>>
|
||||
TypeTestRunStatus: '' as Ref<Class<Type<TestRunStatus>>>,
|
||||
TestResult: '' as Ref<Class<TestResult>>
|
||||
},
|
||||
descriptors: {
|
||||
ProjectType: '' as Ref<SpaceTypeDescriptor>
|
||||
@@ -150,14 +168,28 @@ export const testManagementPlugin = plugin(testManagementId, {
|
||||
TestRunName: '' as IntlString,
|
||||
NoTestCases: '' as IntlString,
|
||||
DueDate: '' as IntlString,
|
||||
TestRunItems: '' as IntlString,
|
||||
TestRunResult: '' as IntlString,
|
||||
TestRunItem: '' as IntlString,
|
||||
TestRunStatus: '' as IntlString,
|
||||
TestRunNamePlaceholder: '' as IntlString,
|
||||
TestRunResult: '' as IntlString,
|
||||
SelectTestSuites: '' as IntlString,
|
||||
SelectTestCases: '' as IntlString,
|
||||
CreateTestRun: '' as IntlString,
|
||||
TestLibrary: '' as IntlString
|
||||
TestLibrary: '' as IntlString,
|
||||
TestResult: '' as IntlString,
|
||||
StatusNonTested: '' as IntlString,
|
||||
StatusBlocked: '' as IntlString,
|
||||
StatusPassed: '' as IntlString,
|
||||
StatusFailed: '' as IntlString,
|
||||
SelectTestCase: '' as IntlString,
|
||||
Save: '' as IntlString,
|
||||
SaveAndNext: '' as IntlString,
|
||||
DonePercent: '' as IntlString,
|
||||
TestResults: '' as IntlString,
|
||||
RunAllTestCases: '' as IntlString,
|
||||
RunSelectedTestCases: '' as IntlString,
|
||||
RunFilteredTestCases: '' as IntlString,
|
||||
TestCaseDescription: '' as IntlString,
|
||||
TestResultAttributes: '' as IntlString
|
||||
},
|
||||
category: {
|
||||
TestManagement: '' as Ref<ActionCategory>
|
||||
@@ -171,7 +203,12 @@ export const testManagementPlugin = plugin(testManagementId, {
|
||||
PriorityIconPresenter: '' as AnyComponent,
|
||||
TestCaseStatusPresenter: '' as AnyComponent,
|
||||
TestSuites: '' as AnyComponent,
|
||||
CreateTestSuite: '' as AnyComponent
|
||||
CreateTestSuite: '' as AnyComponent,
|
||||
TestRunFromSelection: '' as AnyComponent,
|
||||
TestResultStatusPresenter: '' as AnyComponent,
|
||||
TestResultStatusEditor: '' as AnyComponent,
|
||||
TestRunResult: '' as AnyComponent,
|
||||
TestResultHeader: '' as AnyComponent
|
||||
},
|
||||
ids: {
|
||||
NoParent: '' as Ref<TestSuite>,
|
||||
@@ -193,9 +230,9 @@ export const testManagementPlugin = plugin(testManagementId, {
|
||||
viewlet: {
|
||||
TableTestCase: '' as Ref<Viewlet>,
|
||||
TableTestSuites: '' as Ref<Viewlet>,
|
||||
TableTestRun: '' as Ref<Viewlet>,
|
||||
SuiteTestCases: '' as Ref<Viewlet>,
|
||||
ListTestCase: '' as Ref<Viewlet>
|
||||
ListTestCase: '' as Ref<Viewlet>,
|
||||
TestResultList: '' as Ref<Viewlet>,
|
||||
TableTestResult: '' as Ref<Viewlet>
|
||||
},
|
||||
testCaseTypeStatus: {
|
||||
Draft: '' as Ref<Status>,
|
||||
@@ -207,7 +244,8 @@ export const testManagementPlugin = plugin(testManagementId, {
|
||||
TestCase: '' as Ref<TestCase>
|
||||
},
|
||||
function: {
|
||||
GetTestSuiteLink: '' as Resource<(doc: Ref<Doc>) => Location>
|
||||
GetTestSuiteLink: '' as Resource<(doc: Ref<Doc>) => Location>,
|
||||
GetTestRunLink: '' as Resource<(doc: Ref<Doc>) => Location>
|
||||
},
|
||||
resolver: {
|
||||
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
|
||||
|
||||
@@ -103,20 +103,26 @@ export interface TestRun extends Doc {
|
||||
name: string
|
||||
description: CollaborativeDoc
|
||||
dueDate?: Timestamp
|
||||
items?: CollectionSize<TestRunItem>
|
||||
results?: CollectionSize<TestResult>
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export enum TestRunResult {
|
||||
Passed,
|
||||
export enum TestRunStatus {
|
||||
Untested,
|
||||
Blocked,
|
||||
Passed,
|
||||
Failed
|
||||
}
|
||||
|
||||
// TODO: Refactor to associations
|
||||
/** @public */
|
||||
export interface TestRunItem extends AttachedDoc {
|
||||
testRun: Ref<TestRun>
|
||||
export interface TestResult extends AttachedDoc<TestRun, 'results', TestProject> {
|
||||
name: string
|
||||
testCase: Ref<TestCase>
|
||||
result?: TestRunResult
|
||||
testSuite?: Ref<TestSuite>
|
||||
status?: TestRunStatus
|
||||
description: CollaborativeDoc
|
||||
assignee?: Ref<Employee>
|
||||
attachments?: CollectionSize<Attachment>
|
||||
comments?: number
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright © 2024 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.
|
||||
import { type Doc } from '@hcengineering/core'
|
||||
|
||||
import { FoldersManager, emptyFoldersState, type FoldersState } from '../components/folders/store/folderUtils'
|
||||
|
||||
describe('foldersManager', () => {
|
||||
test('folders tree', () => {
|
||||
const docs: Doc[] = [
|
||||
{
|
||||
name: 'Child 1',
|
||||
description: '',
|
||||
parent: '673ef344595d5369f68ced98',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732178763949,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732178763949,
|
||||
docUpdateMessages: 1,
|
||||
_id: '673ef34b595d5369f68ceda2',
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
name: 'Child 2',
|
||||
description: '',
|
||||
parent: '673ef344595d5369f68ced98',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732178770252,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732178770252,
|
||||
docUpdateMessages: 1,
|
||||
_id: '673ef352595d5369f68cedac',
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
_id: '673434f674993781b4195e1b',
|
||||
name: 'New suite',
|
||||
description: '',
|
||||
parent: 'testManagement:ids:NoParent',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1731495620083,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1731474678014,
|
||||
docUpdateMessages: 2,
|
||||
testCases: 1,
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
name: 'Parent',
|
||||
description: '',
|
||||
parent: 'testManagement:ids:NoParent',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732178756247,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732178756247,
|
||||
docUpdateMessages: 1,
|
||||
_id: '673ef344595d5369f68ced98',
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
_id: '6734863403ae9eca5b01e4fd',
|
||||
name: 'test',
|
||||
description: '',
|
||||
parent: 'testManagement:ids:NoParent',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1731495529218,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1731495476678,
|
||||
docUpdateMessages: 4,
|
||||
testCases: 3,
|
||||
space: '673434ed74993781b4195e02'
|
||||
}
|
||||
] as any
|
||||
const foldersManager = new FoldersManager('name', 'parent', 'testManagement:ids:NoParent' as any, false, () => {})
|
||||
const foldersState = foldersManager.getNestedStructure(docs)
|
||||
|
||||
expect(foldersState.folders.length).toBe(3)
|
||||
expect(foldersState.folderById.size).toBe(5)
|
||||
expect(foldersState.descendants.get('673ef344595d5369f68ced98' as any)?.length).toBe(2)
|
||||
})
|
||||
|
||||
test('plain list', () => {
|
||||
const docs: Doc[] = [
|
||||
{
|
||||
_id: '6739541d04aed0096599e03a',
|
||||
name: '3 items',
|
||||
description: '6739541704aed0096599e032%description:HEAD:0',
|
||||
_class: 'testManagement:class:TestRun',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1731810333365,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1731810333307,
|
||||
docUpdateMessages: 4,
|
||||
testCases: 3,
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
name: 'Test run 1',
|
||||
description: '673ef510595d5369f68cee0a%description:HEAD:0',
|
||||
_class: 'testManagement:class:TestRun',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732179220778,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732179220778,
|
||||
testCases: 2,
|
||||
docUpdateMessages: 3,
|
||||
_id: '673ef514595d5369f68cee12',
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
name: 'Test run 2',
|
||||
description: '673ef516595d5369f68cee18%description:HEAD:0',
|
||||
_class: 'testManagement:class:TestRun',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732179227645,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732179227611,
|
||||
testCases: 2,
|
||||
docUpdateMessages: 3,
|
||||
_id: '673ef51b595d5369f68cee20',
|
||||
space: '673434ed74993781b4195e02'
|
||||
}
|
||||
] as any
|
||||
const foldersManager = new FoldersManager('name', 'parent', 'noParent' as any, true, () => {})
|
||||
const foldersState = foldersManager.getPlainList(docs)
|
||||
|
||||
expect(foldersState.folders.length).toBe(3)
|
||||
expect(foldersState.folderById.size).toBe(3)
|
||||
expect(foldersState.descendants.size).toBe(0)
|
||||
})
|
||||
|
||||
test('test state update', () => {
|
||||
const docs: Doc[] = [
|
||||
{
|
||||
_id: '673434f674993781b4195e1b',
|
||||
name: 'New suite',
|
||||
description: '',
|
||||
parent: 'testManagement:ids:NoParent',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1731495620083,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1731474678014,
|
||||
docUpdateMessages: 2,
|
||||
testCases: 1,
|
||||
space: '673434ed74993781b4195e02'
|
||||
}
|
||||
] as any
|
||||
let folderState = emptyFoldersState()
|
||||
|
||||
expect(folderState.folders.length).toBe(0)
|
||||
expect(folderState.descendants.size).toBe(0)
|
||||
expect(folderState.folderById.size).toBe(0)
|
||||
|
||||
const foldersManager = new FoldersManager(
|
||||
'name',
|
||||
'parent',
|
||||
'testManagement:ids:NoParent' as any,
|
||||
false,
|
||||
(state: FoldersState) => {
|
||||
folderState = state
|
||||
}
|
||||
)
|
||||
foldersManager.setFolders(docs)
|
||||
|
||||
expect(folderState.folders.length).toBe(1)
|
||||
expect(folderState.folderById.size).toBe(1)
|
||||
})
|
||||
})
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
import { Action, IconEdit } from '@hcengineering/ui'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
import { TreeItem, getActions as getContributedActions } from '../index'
|
||||
import { TreeItem, getActions as getContributedActions } from '../../index'
|
||||
|
||||
export let folders: Ref<Doc>[]
|
||||
export let folderById: Map<Ref<Doc>, Doc>
|
||||
+51
-37
@@ -20,9 +20,9 @@
|
||||
import { getResource, type Resource } from '@hcengineering/platform'
|
||||
import { IntlString, Asset } from '@hcengineering/platform'
|
||||
|
||||
import { FoldersManager, FoldersStore, FoldersState } from '../stores/folderStore'
|
||||
import { FoldersStore, FoldersState, emptyFoldersState, getFoldersManager } from './store/folderStore'
|
||||
import FolderTreeLevel from './FolderTreeLevel.svelte'
|
||||
import { TreeNode, TreeItem, getActions as getContributedActions } from '../index'
|
||||
import { TreeNode, TreeItem, getActions as getContributedActions } from '../../index'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let query: DocumentQuery<Doc>
|
||||
@@ -32,14 +32,15 @@
|
||||
export let getFolderLink: Resource<(doc: Ref<Doc> | undefined) => Location>
|
||||
export let allObjectsIcon: Asset
|
||||
export let allObjectsLabel: IntlString
|
||||
export let plainList: boolean = false
|
||||
|
||||
export let forciblyСollapsed: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let foldersState: FoldersState = FoldersState.empty()
|
||||
let foldersState: FoldersState = emptyFoldersState()
|
||||
|
||||
const foldersManager: FoldersManager = new FoldersManager(titleKey, parentKey, noParentId)
|
||||
const foldersManager = getFoldersManager(titleKey, parentKey, noParentId, plainList)
|
||||
|
||||
FoldersStore.subscribe((newState) => {
|
||||
foldersState = newState
|
||||
@@ -54,6 +55,9 @@
|
||||
query ?? {},
|
||||
(result) => {
|
||||
foldersManager.setFolders(result)
|
||||
if (plainList && foldersState.folders?.length > 0) {
|
||||
handleFolderSelected(foldersState.folders[0])
|
||||
}
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
@@ -65,10 +69,8 @@
|
||||
async function handleFolderSelected (_id: Ref<Doc>): Promise<void> {
|
||||
selected = _id
|
||||
visibleItem = selected !== undefined ? foldersState.folderById.get(selected) : undefined
|
||||
if (getFolderLink) {
|
||||
const getFolderLinkFunction = await getResource(getFolderLink)
|
||||
navigate(getFolderLinkFunction(_id))
|
||||
}
|
||||
const getFolderLinkFunction = await getResource(getFolderLink)
|
||||
navigate(getFolderLinkFunction(_id))
|
||||
}
|
||||
|
||||
async function handleAllItemsSelected (): Promise<void> {
|
||||
@@ -100,17 +102,46 @@
|
||||
</script>
|
||||
|
||||
<Scroller padding={'1rem 0'}>
|
||||
<TreeNode
|
||||
_id={noParentId}
|
||||
icon={allObjectsIcon}
|
||||
label={allObjectsLabel}
|
||||
selected={selected === noParentId}
|
||||
type={'nested-selectable'}
|
||||
empty={foldersState?.folders?.length === 0}
|
||||
actions={() => getRootActions()}
|
||||
{forciblyСollapsed}
|
||||
on:click={handleAllItemsSelected}
|
||||
>
|
||||
{#if noParentId !== undefined}
|
||||
<TreeNode
|
||||
_id={noParentId}
|
||||
icon={allObjectsIcon}
|
||||
label={allObjectsLabel}
|
||||
selected={selected === noParentId}
|
||||
type={'nested-selectable'}
|
||||
empty={foldersState?.folders?.length === 0}
|
||||
actions={() => getRootActions()}
|
||||
{forciblyСollapsed}
|
||||
on:click={handleAllItemsSelected}
|
||||
>
|
||||
<FolderTreeLevel
|
||||
folders={foldersState.folders}
|
||||
descendants={foldersState.descendants}
|
||||
folderById={foldersState.folderById}
|
||||
{selected}
|
||||
on:selected={(ev) => {
|
||||
handleFolderSelected(ev.detail)
|
||||
}}
|
||||
/>
|
||||
<svelte:fragment slot="visible">
|
||||
{#if (selected || forciblyСollapsed) && visibleItem !== undefined}
|
||||
{@const folder = visibleItem}
|
||||
<TreeItem
|
||||
_id={folder._id}
|
||||
folderIcon
|
||||
iconProps={{ fill: 'var(--global-accent-IconColor)' }}
|
||||
title={foldersManager.getTitle(folder)}
|
||||
selected
|
||||
isFold
|
||||
empty
|
||||
actions={async () => await getFolderActions(folder)}
|
||||
shouldTooltip
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
{:else}
|
||||
<FolderTreeLevel
|
||||
folders={foldersState.folders}
|
||||
descendants={foldersState.descendants}
|
||||
@@ -120,22 +151,5 @@
|
||||
handleFolderSelected(ev.detail)
|
||||
}}
|
||||
/>
|
||||
<svelte:fragment slot="visible">
|
||||
{#if (selected || forciblyСollapsed) && visibleItem}
|
||||
{@const folder = visibleItem}
|
||||
<TreeItem
|
||||
_id={folder._id}
|
||||
folderIcon
|
||||
iconProps={{ fill: 'var(--global-accent-IconColor)' }}
|
||||
title={foldersManager.getTitle(folder)}
|
||||
selected
|
||||
isFold
|
||||
empty
|
||||
actions={async () => await getFolderActions(folder)}
|
||||
shouldTooltip
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
{/if}
|
||||
</Scroller>
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright © 2024 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.
|
||||
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
import { type FoldersState, FoldersManager, emptyFoldersState } from './folderUtils'
|
||||
|
||||
export { type FoldersState, emptyFoldersState }
|
||||
|
||||
export const FoldersStore: Writable<FoldersState> = writable(emptyFoldersState())
|
||||
|
||||
export const SelectedFolderStore: Writable<Ref<Doc> | undefined> = writable(undefined)
|
||||
|
||||
export function setSelectedFolder (_id: Ref<Doc> | undefined): void {
|
||||
SelectedFolderStore.set(_id)
|
||||
}
|
||||
|
||||
export function getFoldersManager (
|
||||
titleKey: string,
|
||||
parentKey: string,
|
||||
noParentId: Ref<Doc>,
|
||||
plainList: boolean
|
||||
): FoldersManager {
|
||||
return new FoldersManager(titleKey, parentKey, noParentId, plainList, (state: FoldersState) => {
|
||||
FoldersStore.set(state)
|
||||
})
|
||||
}
|
||||
+53
-28
@@ -12,41 +12,40 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export class FoldersState {
|
||||
folders: Array<Ref<Doc>>
|
||||
folderById: Map<Ref<Doc>, Doc>
|
||||
descendants: Map<Ref<Doc>, Doc[]>
|
||||
|
||||
constructor (folders: Array<Ref<Doc>>, folderById: Map<Ref<Doc>, Doc>, descendants: Map<Ref<Doc>, Doc[]>) {
|
||||
this.folders = folders
|
||||
this.folderById = folderById
|
||||
this.descendants = descendants
|
||||
}
|
||||
|
||||
static empty (): FoldersState {
|
||||
return new FoldersState([], new Map<Ref<Doc>, Doc>(), new Map<Ref<Doc>, Doc[]>())
|
||||
}
|
||||
export interface FoldersState {
|
||||
readonly folders: Array<Ref<Doc>>
|
||||
readonly folderById: Map<Ref<Doc>, Doc>
|
||||
readonly descendants: Map<Ref<Doc>, Doc[]>
|
||||
}
|
||||
|
||||
export const FoldersStore: Writable<FoldersState> = writable(FoldersState.empty())
|
||||
|
||||
export const SelectedFolderStore: Writable<Ref<Doc> | undefined> = writable(undefined)
|
||||
|
||||
export function setSelectedFolder (_id: Ref<Doc> | undefined): void {
|
||||
SelectedFolderStore.set(_id)
|
||||
export function emptyFoldersState (): FoldersState {
|
||||
return {
|
||||
folders: [],
|
||||
folderById: new Map<Ref<Doc>, Doc>(),
|
||||
descendants: new Map<Ref<Doc>, Doc[]>()
|
||||
}
|
||||
}
|
||||
|
||||
export class FoldersManager {
|
||||
titleKey: string
|
||||
parentKey: string
|
||||
noParentId: Ref<Doc>
|
||||
plainList: boolean
|
||||
updateStore: (state: FoldersState) => void
|
||||
|
||||
constructor (titleKey: string, parentKey: string, noParentId: Ref<Doc>) {
|
||||
constructor (
|
||||
titleKey: string,
|
||||
parentKey: string,
|
||||
noParentId: Ref<Doc>,
|
||||
plainList: boolean,
|
||||
updateStore: (state: FoldersState) => void
|
||||
) {
|
||||
this.titleKey = titleKey
|
||||
this.parentKey = parentKey
|
||||
this.noParentId = noParentId
|
||||
this.plainList = plainList
|
||||
this.updateStore = updateStore
|
||||
}
|
||||
|
||||
public getTitle (doc: Doc): string {
|
||||
@@ -64,16 +63,17 @@ export class FoldersManager {
|
||||
}
|
||||
|
||||
public setFolders (result: Doc[]): void {
|
||||
const newState: FoldersState = this.plainList ? this.getPlainList(result) : this.getNestedStructure(result)
|
||||
this.updateStore(newState)
|
||||
}
|
||||
|
||||
public getNestedStructure (result: Doc[]): FoldersState {
|
||||
let folders: Array<Ref<Doc>> = []
|
||||
const folderById: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||
const descendants: Map<Ref<Doc>, Doc[]> = new Map<Ref<Doc>, Doc[]>()
|
||||
|
||||
for (const doc of result) {
|
||||
const mappedDoc = {
|
||||
title: this.getTitle(doc),
|
||||
parent: this.getParent(doc),
|
||||
...doc
|
||||
}
|
||||
const mappedDoc = this.mapDoc(doc)
|
||||
const current = descendants.get(this.getParent(mappedDoc)) ?? []
|
||||
current.push(mappedDoc)
|
||||
descendants.set(this.getParent(mappedDoc), current)
|
||||
@@ -81,6 +81,31 @@ export class FoldersManager {
|
||||
}
|
||||
|
||||
folders = this.getDescendants(descendants, this.noParentId)
|
||||
FoldersStore.set(new FoldersState(folders, folderById, descendants))
|
||||
return { folders, folderById, descendants }
|
||||
}
|
||||
|
||||
public getPlainList (result: Doc[]): FoldersState {
|
||||
const folderById: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||
|
||||
for (const doc of result) {
|
||||
const mappedDoc = this.mapDoc(doc)
|
||||
folderById.set(mappedDoc._id, mappedDoc)
|
||||
}
|
||||
const folders: Array<Ref<Doc>> = result.map((doc) => doc._id)
|
||||
|
||||
return {
|
||||
folders,
|
||||
folderById,
|
||||
descendants: new Map<Ref<Doc>, Doc[]>()
|
||||
}
|
||||
}
|
||||
|
||||
private mapDoc (doc: Doc): Doc {
|
||||
const mappedDoc = {
|
||||
title: this.getTitle(doc),
|
||||
parent: this.getParent(doc),
|
||||
...doc
|
||||
}
|
||||
return mappedDoc
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ import ImageViewer from './components/viewer/ImageViewer.svelte'
|
||||
import VideoViewer from './components/viewer/VideoViewer.svelte'
|
||||
import PDFViewer from './components/viewer/PDFViewer.svelte'
|
||||
import TextViewer from './components/viewer/TextViewer.svelte'
|
||||
import FoldersBrowser from './components/FoldersBrowser.svelte'
|
||||
import FoldersBrowser from './components/folders/FoldersBrowser.svelte'
|
||||
|
||||
import { blobImageMetadata, blobVideoMetadata } from './blob'
|
||||
|
||||
@@ -167,6 +167,8 @@ export { default as List } from './components/list/List.svelte'
|
||||
export { default as NavLink } from './components/navigator/NavLink.svelte'
|
||||
export { default as StatusPresenter } from './components/status/StatusPresenter.svelte'
|
||||
export { default as StatusRefPresenter } from './components/status/StatusRefPresenter.svelte'
|
||||
export { default as FoldersBrowser } from './components/folders/FoldersBrowser.svelte'
|
||||
export { default as ListView } from './components/list/ListView.svelte'
|
||||
|
||||
export * from './filter'
|
||||
export * from './middleware'
|
||||
@@ -189,6 +191,7 @@ export {
|
||||
} from './utils'
|
||||
export * from './viewOptions'
|
||||
export {
|
||||
ArrayEditor,
|
||||
BooleanEditor,
|
||||
BooleanPresenter,
|
||||
ClassAttributeBar,
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
export let createComponentProps: Record<string, any> = {}
|
||||
export let mainComponentLabel: IntlString
|
||||
export let mainComponentIcon: Asset | undefined = undefined
|
||||
export let mainHeaderComponent: AnyComponent | undefined = undefined
|
||||
export let query: DocumentQuery<Doc> = {}
|
||||
export let syncWithLocationQuery: boolean = true
|
||||
export let mainComponent: AnyComponent | AnySvelteComponent
|
||||
@@ -165,6 +166,17 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<Breadcrumb icon={mainComponentIcon} label={mainComponentLabel} size={'large'} isCurrent />
|
||||
<svelte:fragment slot="actions">
|
||||
{#if mainHeaderComponent}
|
||||
<Component
|
||||
is={mainHeaderComponent}
|
||||
props={{
|
||||
query: resultQuery,
|
||||
space
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Header>
|
||||
<Component
|
||||
is={mainComponent}
|
||||
|
||||
Reference in New Issue
Block a user