diff --git a/models/test-management/src/defaultTypes.ts b/models/test-management/src/defaultTypes.ts deleted file mode 100644 index 3495aff78a..0000000000 --- a/models/test-management/src/defaultTypes.ts +++ /dev/null @@ -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 - }) -} diff --git a/models/test-management/src/index.ts b/models/test-management/src/index.ts index 45e1a86ed1..c75cb12f7d 100644 --- a/models/test-management/src/index.ts +++ b/models/test-management/src/index.ts @@ -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 + }, + 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 }, - testManagement.viewlet.TableTestRun + testManagement.viewlet.TableTestResult ) } diff --git a/models/test-management/src/plugin.ts b/models/test-management/src/plugin.ts index abf37a268f..79e2e7c588 100644 --- a/models/test-management/src/plugin.ts +++ b/models/test-management/src/plugin.ts @@ -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>, - CreateChildTestSuite: '' as Ref, - EditTestSuite: '' as Ref - }, - actionImpl: { - CreateChildTestSuite: '' as ViewAction, - EditTestSuite: '' as ViewAction - }, category: { - TestSuite: '' as Ref + TestSuite: '' as Ref, + TestCase: '' as Ref }, 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 } }) diff --git a/models/test-management/src/presenters.ts b/models/test-management/src/presenters.ts index 0b45b6df50..8d68c5f7c9 100644 --- a/models/test-management/src/presenters.ts +++ b/models/test-management/src/presenters.ts @@ -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 + }) } diff --git a/models/test-management/src/types.ts b/models/test-management/src/types.ts index 1fca881944..c0b98b211a 100644 --- a/models/test-management/src/types.ts +++ b/models/test-management/src/types.ts @@ -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 + results?: CollectionSize } /** @public */ -export function TypeTestRunResult (): Type { - return { _class: testManagement.class.TypeTestRunResult, label: testManagement.string.TestRunResult } +export function TypeTestRunStatus (): Type { + 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 +// 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 + + @Prop(TypeRef(testManagement.class.TestRun), core.string.AttachedToClass) + @Index(IndexKind.Indexed) + @Hidden() + declare attachedToClass: Ref> + + @Prop(TypeRef(testManagement.class.TestProject), core.string.Space) + @Index(IndexKind.Indexed) + @Hidden() + declare space: Ref + + @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 - @Prop(TypeTestRunResult(), testManagement.string.TestRunResult) - result?: TestRunResult + @Prop(TypeRef(testManagement.class.TestSuite), testManagement.string.TestSuite) + testSuite?: Ref + + @Prop(TypeTestRunStatus(), testManagement.string.TestRunStatus) + status?: TestRunStatus + + @Prop(TypeRef(contact.mixin.Employee), testManagement.string.TestAssignee) + assignee?: Ref + + @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) + attachments?: CollectionSize @Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments) comments?: number diff --git a/packages/presentation/src/components/breadcrumbs/BreadcrumbsElement.svelte b/packages/presentation/src/components/breadcrumbs/BreadcrumbsElement.svelte index 9bc6d3030a..94f69cf15c 100644 --- a/packages/presentation/src/components/breadcrumbs/BreadcrumbsElement.svelte +++ b/packages/presentation/src/components/breadcrumbs/BreadcrumbsElement.svelte @@ -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 @@
-
+
{#if $$slots.default} {:else} diff --git a/packages/theme/styles/components.scss b/packages/theme/styles/components.scss index 4ea43b9f7f..924821fcda 100644 --- a/packages/theme/styles/components.scss +++ b/packages/theme/styles/components.scss @@ -1493,6 +1493,10 @@ min-width: 0; width: auto; + &.noGap { + flex: none; + } + &__back { padding: 1px 0.5px; height: calc(1.5rem + 2px); diff --git a/plugins/test-management-assets/assets/icons.svg b/plugins/test-management-assets/assets/icons.svg index 6fb417124a..310dd6a509 100644 --- a/plugins/test-management-assets/assets/icons.svg +++ b/plugins/test-management-assets/assets/icons.svg @@ -3,12 +3,11 @@ - - + + - - - + + @@ -52,4 +51,21 @@ + + + + + + + + + + + + + + + + + diff --git a/plugins/test-management-assets/lang/cs.json b/plugins/test-management-assets/lang/cs.json index 78ab73bfd9..c3fcb8950a 100644 --- a/plugins/test-management-assets/lang/cs.json +++ b/plugins/test-management-assets/lang/cs.json @@ -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" } -} \ No newline at end of file +} diff --git a/plugins/test-management-assets/lang/en.json b/plugins/test-management-assets/lang/en.json index 135650b31c..8db61b86e5 100644 --- a/plugins/test-management-assets/lang/en.json +++ b/plugins/test-management-assets/lang/en.json @@ -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" } } diff --git a/plugins/test-management-assets/lang/es.json b/plugins/test-management-assets/lang/es.json new file mode 100644 index 0000000000..2017de36c7 --- /dev/null +++ b/plugins/test-management-assets/lang/es.json @@ -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" + } +} diff --git a/plugins/test-management-assets/lang/fr.json b/plugins/test-management-assets/lang/fr.json index c2f3353047..f9b85b94f8 100644 --- a/plugins/test-management-assets/lang/fr.json +++ b/plugins/test-management-assets/lang/fr.json @@ -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" } } diff --git a/plugins/test-management-assets/lang/it.json b/plugins/test-management-assets/lang/it.json new file mode 100644 index 0000000000..88acb35da4 --- /dev/null +++ b/plugins/test-management-assets/lang/it.json @@ -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" + } +} diff --git a/plugins/test-management-assets/lang/pt.json b/plugins/test-management-assets/lang/pt.json new file mode 100644 index 0000000000..2299b3cb58 --- /dev/null +++ b/plugins/test-management-assets/lang/pt.json @@ -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" + } +} diff --git a/plugins/test-management-assets/lang/ru.json b/plugins/test-management-assets/lang/ru.json index 19a45e0d6c..b94ff588d2 100644 --- a/plugins/test-management-assets/lang/ru.json +++ b/plugins/test-management-assets/lang/ru.json @@ -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": "Результат" } } diff --git a/plugins/test-management-assets/lang/zh.json b/plugins/test-management-assets/lang/zh.json index c7da64156d..784fe300ea 100644 --- a/plugins/test-management-assets/lang/zh.json +++ b/plugins/test-management-assets/lang/zh.json @@ -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": "結果" } } diff --git a/plugins/test-management-assets/src/index.ts b/plugins/test-management-assets/src/index.ts index 8a50befa21..4192353617 100644 --- a/plugins/test-management-assets/src/index.ts +++ b/plugins/test-management-assets/src/index.ts @@ -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` }) diff --git a/plugins/test-management-resources/src/components/test-case/AssigneeEditor.svelte b/plugins/test-management-resources/src/components/test-case/AssigneeEditor.svelte deleted file mode 100644 index ea405cd046..0000000000 --- a/plugins/test-management-resources/src/components/test-case/AssigneeEditor.svelte +++ /dev/null @@ -1,198 +0,0 @@ - - - -{#if _object} - {#if isAction} - { - const result = evt.detail - if (result === null) { - handleAssigneeChanged(null) - } else if (result !== undefined && result._id !== value) { - value = result._id - handleAssigneeChanged(result._id) - } - }} - /> - {:else} - handleAssigneeChanged(detail)} - /> - {/if} -{/if} diff --git a/plugins/test-management-resources/src/components/test-case/CreateTestCase.svelte b/plugins/test-management-resources/src/components/test-case/CreateTestCase.svelte index aee61920a7..78b0437035 100644 --- a/plugins/test-management-resources/src/components/test-case/CreateTestCase.svelte +++ b/plugins/test-management-resources/src/components/test-case/CreateTestCase.svelte @@ -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 @@ /> - (object.assignee = detail)} - /> diff --git a/plugins/test-management-resources/src/components/test-case/EditTestCase.svelte b/plugins/test-management-resources/src/components/test-case/EditTestCase.svelte index d4cc7ed342..dec99743de 100644 --- a/plugins/test-management-resources/src/components/test-case/EditTestCase.svelte +++ b/plugins/test-management-resources/src/components/test-case/EditTestCase.svelte @@ -13,13 +13,16 @@ // limitations under the License. --> + + { + void handleDropdownItemSelected(ev.detail) + }} +/> diff --git a/plugins/test-management-resources/src/components/test-case/StatusEditor.svelte b/plugins/test-management-resources/src/components/test-case/StatusEditor.svelte index 76560aa9bf..959a9c8ab4 100644 --- a/plugins/test-management-resources/src/components/test-case/StatusEditor.svelte +++ b/plugins/test-management-resources/src/components/test-case/StatusEditor.svelte @@ -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 { 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 diff --git a/plugins/test-management-resources/src/components/test-case/TestCaseDetails.svelte b/plugins/test-management-resources/src/components/test-case/TestCaseDetails.svelte new file mode 100644 index 0000000000..669d741fa2 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-case/TestCaseDetails.svelte @@ -0,0 +1,58 @@ + + + +{#if object} +
+ +
+{/if} diff --git a/plugins/test-management-resources/src/components/test-case/TestCasePopup.svelte b/plugins/test-management-resources/src/components/test-case/TestCasePopup.svelte new file mode 100644 index 0000000000..d7d6626541 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-case/TestCasePopup.svelte @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/plugins/test-management-resources/src/components/test-case/TestCasePresenter.svelte b/plugins/test-management-resources/src/components/test-case/TestCasePresenter.svelte index 7968cb324b..e79ec689d0 100644 --- a/plugins/test-management-resources/src/components/test-case/TestCasePresenter.svelte +++ b/plugins/test-management-resources/src/components/test-case/TestCasePresenter.svelte @@ -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 {#if value} {#if inline} {:else} - +
{value.name} diff --git a/plugins/test-management-resources/src/components/test-case/TestCaseSelector.svelte b/plugins/test-management-resources/src/components/test-case/TestCaseSelector.svelte new file mode 100644 index 0000000000..bec36ccf89 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-case/TestCaseSelector.svelte @@ -0,0 +1,67 @@ + + + + + diff --git a/plugins/test-management-resources/src/components/test-result/EditTestResult.svelte b/plugins/test-management-resources/src/components/test-result/EditTestResult.svelte new file mode 100644 index 0000000000..41020b475f --- /dev/null +++ b/plugins/test-management-resources/src/components/test-result/EditTestResult.svelte @@ -0,0 +1,105 @@ + + + +{#if object} + + dispatch('close')} + > +
+
+ +
+ + + + + + + + + + +{/if} diff --git a/plugins/test-management-resources/src/components/test-result/RightHeader.svelte b/plugins/test-management-resources/src/components/test-result/RightHeader.svelte new file mode 100644 index 0000000000..0429820bd8 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-result/RightHeader.svelte @@ -0,0 +1,24 @@ + + +
+ +
+ + diff --git a/plugins/test-management-resources/src/components/test-result/TestResultFooter.svelte b/plugins/test-management-resources/src/components/test-result/TestResultFooter.svelte new file mode 100644 index 0000000000..2554f5ed7d --- /dev/null +++ b/plugins/test-management-resources/src/components/test-result/TestResultFooter.svelte @@ -0,0 +1,31 @@ + + + + +
+
diff --git a/plugins/test-management-resources/src/components/test-result/TestResultHeader.svelte b/plugins/test-management-resources/src/components/test-result/TestResultHeader.svelte new file mode 100644 index 0000000000..668c1dde60 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-result/TestResultHeader.svelte @@ -0,0 +1,25 @@ + + + +
+
+ +
diff --git a/plugins/test-management-resources/src/components/test-result/TestResultPresenter.svelte b/plugins/test-management-resources/src/components/test-result/TestResultPresenter.svelte new file mode 100644 index 0000000000..f2f5005c15 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-result/TestResultPresenter.svelte @@ -0,0 +1,55 @@ + + + + +{#if value} + {#if inline} + + {:else} + +
+ {#if shouldShowAvatar} +
+ +
+ {/if} + + {title} + +
+
+ {/if} +{/if} diff --git a/plugins/test-management-resources/src/components/test-result/TestResultStatusEditor.svelte b/plugins/test-management-resources/src/components/test-result/TestResultStatusEditor.svelte new file mode 100644 index 0000000000..dea4e87317 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-result/TestResultStatusEditor.svelte @@ -0,0 +1,106 @@ + + + +{#if kind === 'list'} + +{:else if kind === 'list-header'} +
+ {#if shouldShowAvatar} + + {/if} + +
+{:else} +
{#if testCases > 0} @@ -67,7 +60,7 @@ @@ -78,7 +71,7 @@ - + diff --git a/plugins/test-management-resources/src/components/test-run/TestRunAside.svelte b/plugins/test-management-resources/src/components/test-run/TestRunAside.svelte new file mode 100644 index 0000000000..823f876197 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-run/TestRunAside.svelte @@ -0,0 +1,32 @@ + + + + + +
+ +
+ diff --git a/plugins/test-management-resources/src/components/test-run/TestRunPresenter.svelte b/plugins/test-management-resources/src/components/test-run/TestRunPresenter.svelte index 5157718452..9b38f419d5 100644 --- a/plugins/test-management-resources/src/components/test-run/TestRunPresenter.svelte +++ b/plugins/test-management-resources/src/components/test-run/TestRunPresenter.svelte @@ -13,6 +13,28 @@ // limitations under the License. --> -
+{#if value} + {#if inline} + + {:else} + +
+ + {value.name} + +
+
+ {/if} +{/if} diff --git a/plugins/test-management-resources/src/components/test-run/TestRunResult.svelte b/plugins/test-management-resources/src/components/test-run/TestRunResult.svelte new file mode 100644 index 0000000000..c43e70d539 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-run/TestRunResult.svelte @@ -0,0 +1,64 @@ + + + + + + + + + + diff --git a/plugins/test-management-resources/src/components/test-run/TestRunStats.svelte b/plugins/test-management-resources/src/components/test-run/TestRunStats.svelte new file mode 100644 index 0000000000..ff10978881 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-run/TestRunStats.svelte @@ -0,0 +1,55 @@ + + + +
+ + {#if !isLoading && stats !== undefined} + + {:else} + + {/if} + + + {#if !isLoading} +
+
+ +
+ {stats?.done ?? 0} +
+ {:else} + + {/if} +
diff --git a/plugins/test-management-resources/src/components/test-run/ViewContainer.svelte b/plugins/test-management-resources/src/components/test-run/ViewContainer.svelte new file mode 100644 index 0000000000..2dbeed8373 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-run/ViewContainer.svelte @@ -0,0 +1,25 @@ + + + +
+ +
+ diff --git a/plugins/test-management-resources/src/components/test-run/store/testRunStore.ts b/plugins/test-management-resources/src/components/test-run/store/testRunStore.ts new file mode 100644 index 0000000000..9f04f26ed4 --- /dev/null +++ b/plugins/test-management-resources/src/components/test-run/store/testRunStore.ts @@ -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 | undefined> = writable(undefined) + +export const selectedTestRun: Writable = writable(undefined) diff --git a/plugins/test-management-resources/src/components/test-suite/EditTestSuite.svelte b/plugins/test-management-resources/src/components/test-suite/EditTestSuite.svelte index e408edbfd2..13241c1b91 100644 --- a/plugins/test-management-resources/src/components/test-suite/EditTestSuite.svelte +++ b/plugins/test-management-resources/src/components/test-suite/EditTestSuite.svelte @@ -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 @@ -95,9 +94,5 @@ showButtons={false} />
- -
- -
{/if} diff --git a/plugins/test-management-resources/src/index.ts b/plugins/test-management-resources/src/index.ts index 3776daf5b4..f7f88cf904 100644 --- a/plugins/test-management-resources/src/index.ts +++ b/plugins/test-management-resources/src/index.ts @@ -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 => ({ component: { @@ -47,16 +55,26 @@ export default async (): Promise => ({ 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 } }) diff --git a/plugins/test-management-resources/src/navigation.ts b/plugins/test-management-resources/src/navigation.ts index 775011ce07..7d0ab508eb 100644 --- a/plugins/test-management-resources/src/navigation.ts +++ b/plugins/test-management-resources/src/navigation.ts @@ -61,14 +61,9 @@ async function generateProjectLocation ( } } -export function getTestSuiteLink (testSuite: Ref): Location { +export function getAttachedObjectLink (parentDoc: Ref): 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 { return (location?.query?.[SUITE_KEY] as Ref) ?? testManagement.ids.NoParent } +export function getTestRunsLink (parentDoc: Ref): 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 { if (loc.path[2] !== testManagementId) { return undefined diff --git a/plugins/test-management-resources/src/testRunUtils.ts b/plugins/test-management-resources/src/testRunUtils.ts new file mode 100644 index 0000000000..4552190e95 --- /dev/null +++ b/plugins/test-management-resources/src/testRunUtils.ts @@ -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): Promise { + 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, status: TestRunStatus): Promise { + 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): Promise { + 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 + } +} diff --git a/plugins/test-management-resources/src/types.ts b/plugins/test-management-resources/src/types.ts index 2b2ffc954d..40f555855a 100644 --- a/plugins/test-management-resources/src/types.ts +++ b/plugins/test-management-resources/src/types.ts @@ -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 = { + [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 + } +} diff --git a/plugins/test-management-resources/src/utils.ts b/plugins/test-management-resources/src/utils.ts index 823692ebf8..b0472678dc 100644 --- a/plugins/test-management-resources/src/utils.ts +++ b/plugins/test-management-resources/src/utils.ts @@ -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 | undefined): Promise>> { - if (objectId === undefined) { - return [] - } - const client = getClient() - const createTx = ( - await client.findAll>(core.class.TxCreateDoc, { - objectId: objectId as Ref - }) - )[0] - const updateTxes = await client.findAll>( - core.class.TxUpdateDoc, - { objectId: objectId as Ref, 'operations.assignee': { $exists: true } }, - { sort: { modifiedOn: -1 } } - ) - const set = new Set>() - 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 | undefined, @@ -71,6 +42,14 @@ export async function showCreateProjectPopup (): Promise { showPopup(CreateProject, {}, 'top') } +export async function showCreateTestRunPopup (options: { + testCases?: TestCase[] + query?: DocumentQuery + space: Ref +}): Promise { + showPopup(CreateTestRun, options, 'top') +} + export async function CreateChildTestSuiteAction (doc: TestSuite): Promise { await showCreateTestSuitePopup(doc.space, doc._id) } @@ -78,3 +57,12 @@ export async function CreateChildTestSuiteAction (doc: TestSuite): Promise export async function EditTestSuiteAction (doc: TestSuite): Promise { await showEditTestSuitePopup(doc._id) } + +export async function RunSelectedTestsAction (testCases: TestCase[]): Promise { + if (testCases?.length > 0) { + const space = testCases[0].space + await showCreateTestRunPopup({ testCases, space }) + } else { + console.error('No test cases selected') + } +} diff --git a/plugins/test-management/src/analytics.ts b/plugins/test-management/src/analytics.ts new file mode 100644 index 0000000000..b92682a729 --- /dev/null +++ b/plugins/test-management/src/analytics.ts @@ -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' +} diff --git a/plugins/test-management/src/index.ts b/plugins/test-management/src/index.ts index 90cd70a0a9..e19ee4f581 100644 --- a/plugins/test-management/src/index.ts +++ b/plugins/test-management/src/index.ts @@ -16,6 +16,7 @@ import { testManagementId, testManagementPlugin } from './plugin' export * from './types' +export * from './analytics' export { testManagementId } export default testManagementPlugin diff --git a/plugins/test-management/src/plugin.ts b/plugins/test-management/src/plugin.ts index e5104633ed..f5b6a0363e 100644 --- a/plugins/test-management/src/plugin.ts +++ b/plugins/test-management/src/plugin.ts @@ -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 }, + action: { + DeleteTestCase: '' as Ref>, + CreateChildTestSuite: '' as Ref, + EditTestSuite: '' as Ref, + RunSelectedTests: '' as Ref + }, + 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>, @@ -78,8 +96,8 @@ export const testManagementPlugin = plugin(testManagementId, { TypeTestCasePriority: '' as Ref>>, TypeTestCaseStatus: '' as Ref>>, TestRun: '' as Ref>, - TestRunItem: '' as Ref>, - TypeTestRunResult: '' as Ref>> + TypeTestRunStatus: '' as Ref>>, + TestResult: '' as Ref> }, descriptors: { ProjectType: '' as Ref @@ -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 @@ -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, @@ -193,9 +230,9 @@ export const testManagementPlugin = plugin(testManagementId, { viewlet: { TableTestCase: '' as Ref, TableTestSuites: '' as Ref, - TableTestRun: '' as Ref, - SuiteTestCases: '' as Ref, - ListTestCase: '' as Ref + ListTestCase: '' as Ref, + TestResultList: '' as Ref, + TableTestResult: '' as Ref }, testCaseTypeStatus: { Draft: '' as Ref, @@ -207,7 +244,8 @@ export const testManagementPlugin = plugin(testManagementId, { TestCase: '' as Ref }, function: { - GetTestSuiteLink: '' as Resource<(doc: Ref) => Location> + GetTestSuiteLink: '' as Resource<(doc: Ref) => Location>, + GetTestRunLink: '' as Resource<(doc: Ref) => Location> }, resolver: { Location: '' as Resource<(loc: Location) => Promise> diff --git a/plugins/test-management/src/types.ts b/plugins/test-management/src/types.ts index 1e07df32cb..7a10dab70f 100644 --- a/plugins/test-management/src/types.ts +++ b/plugins/test-management/src/types.ts @@ -103,20 +103,26 @@ export interface TestRun extends Doc { name: string description: CollaborativeDoc dueDate?: Timestamp - items?: CollectionSize + results?: CollectionSize } /** @public */ -export enum TestRunResult { - Passed, +export enum TestRunStatus { + Untested, Blocked, + Passed, Failed } +// TODO: Refactor to associations /** @public */ -export interface TestRunItem extends AttachedDoc { - testRun: Ref +export interface TestResult extends AttachedDoc { + name: string testCase: Ref - result?: TestRunResult + testSuite?: Ref + status?: TestRunStatus + description: CollaborativeDoc + assignee?: Ref + attachments?: CollectionSize comments?: number } diff --git a/plugins/view-resources/src/__tests__/folderUtils.test.ts b/plugins/view-resources/src/__tests__/folderUtils.test.ts new file mode 100644 index 0000000000..1ce0c18082 --- /dev/null +++ b/plugins/view-resources/src/__tests__/folderUtils.test.ts @@ -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) + }) +}) diff --git a/plugins/view-resources/src/components/FolderTreeLevel.svelte b/plugins/view-resources/src/components/folders/FolderTreeLevel.svelte similarity index 99% rename from plugins/view-resources/src/components/FolderTreeLevel.svelte rename to plugins/view-resources/src/components/folders/FolderTreeLevel.svelte index d8eb2bff9e..96f0ada0e7 100644 --- a/plugins/view-resources/src/components/FolderTreeLevel.svelte +++ b/plugins/view-resources/src/components/folders/FolderTreeLevel.svelte @@ -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[] export let folderById: Map, Doc> diff --git a/plugins/view-resources/src/components/FoldersBrowser.svelte b/plugins/view-resources/src/components/folders/FoldersBrowser.svelte similarity index 64% rename from plugins/view-resources/src/components/FoldersBrowser.svelte rename to plugins/view-resources/src/components/folders/FoldersBrowser.svelte index 182a8cbc0b..9fd06fbd3e 100644 --- a/plugins/view-resources/src/components/FoldersBrowser.svelte +++ b/plugins/view-resources/src/components/folders/FoldersBrowser.svelte @@ -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> export let query: DocumentQuery @@ -32,14 +32,15 @@ export let getFolderLink: Resource<(doc: Ref | 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): Promise { 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 { @@ -100,17 +102,46 @@ - getRootActions()} - {forciblyСollapsed} - on:click={handleAllItemsSelected} - > + {#if noParentId !== undefined} + getRootActions()} + {forciblyСollapsed} + on:click={handleAllItemsSelected} + > + { + handleFolderSelected(ev.detail) + }} + /> + + {#if (selected || forciblyСollapsed) && visibleItem !== undefined} + {@const folder = visibleItem} + await getFolderActions(folder)} + shouldTooltip + forciblyСollapsed + /> + {/if} + + + {:else} - - {#if (selected || forciblyСollapsed) && visibleItem} - {@const folder = visibleItem} - await getFolderActions(folder)} - shouldTooltip - forciblyСollapsed - /> - {/if} - - + {/if} diff --git a/plugins/view-resources/src/components/folders/store/folderStore.ts b/plugins/view-resources/src/components/folders/store/folderStore.ts new file mode 100644 index 0000000000..511be91c6d --- /dev/null +++ b/plugins/view-resources/src/components/folders/store/folderStore.ts @@ -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 = writable(emptyFoldersState()) + +export const SelectedFolderStore: Writable | undefined> = writable(undefined) + +export function setSelectedFolder (_id: Ref | undefined): void { + SelectedFolderStore.set(_id) +} + +export function getFoldersManager ( + titleKey: string, + parentKey: string, + noParentId: Ref, + plainList: boolean +): FoldersManager { + return new FoldersManager(titleKey, parentKey, noParentId, plainList, (state: FoldersState) => { + FoldersStore.set(state) + }) +} diff --git a/plugins/view-resources/src/stores/folderStore.ts b/plugins/view-resources/src/components/folders/store/folderUtils.ts similarity index 55% rename from plugins/view-resources/src/stores/folderStore.ts rename to plugins/view-resources/src/components/folders/store/folderUtils.ts index 5de7347c60..fea2297e23 100644 --- a/plugins/view-resources/src/stores/folderStore.ts +++ b/plugins/view-resources/src/components/folders/store/folderUtils.ts @@ -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> - folderById: Map, Doc> - descendants: Map, Doc[]> - - constructor (folders: Array>, folderById: Map, Doc>, descendants: Map, Doc[]>) { - this.folders = folders - this.folderById = folderById - this.descendants = descendants - } - - static empty (): FoldersState { - return new FoldersState([], new Map, Doc>(), new Map, Doc[]>()) - } +export interface FoldersState { + readonly folders: Array> + readonly folderById: Map, Doc> + readonly descendants: Map, Doc[]> } -export const FoldersStore: Writable = writable(FoldersState.empty()) - -export const SelectedFolderStore: Writable | undefined> = writable(undefined) - -export function setSelectedFolder (_id: Ref | undefined): void { - SelectedFolderStore.set(_id) +export function emptyFoldersState (): FoldersState { + return { + folders: [], + folderById: new Map, Doc>(), + descendants: new Map, Doc[]>() + } } export class FoldersManager { titleKey: string parentKey: string noParentId: Ref + plainList: boolean + updateStore: (state: FoldersState) => void - constructor (titleKey: string, parentKey: string, noParentId: Ref) { + constructor ( + titleKey: string, + parentKey: string, + noParentId: Ref, + 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> = [] const folderById: Map, Doc> = new Map, Doc>() const descendants: Map, Doc[]> = new Map, 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, Doc> = new Map, Doc>() + + for (const doc of result) { + const mappedDoc = this.mapDoc(doc) + folderById.set(mappedDoc._id, mappedDoc) + } + const folders: Array> = result.map((doc) => doc._id) + + return { + folders, + folderById, + descendants: new Map, Doc[]>() + } + } + + private mapDoc (doc: Doc): Doc { + const mappedDoc = { + title: this.getTitle(doc), + parent: this.getParent(doc), + ...doc + } + return mappedDoc } } diff --git a/plugins/view-resources/src/index.ts b/plugins/view-resources/src/index.ts index bb099c3cc9..ddf8b5ce41 100644 --- a/plugins/view-resources/src/index.ts +++ b/plugins/view-resources/src/index.ts @@ -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, diff --git a/plugins/workbench-resources/src/components/ComponentNavigator.svelte b/plugins/workbench-resources/src/components/ComponentNavigator.svelte index 930d78c807..84d5d3c7b8 100644 --- a/plugins/workbench-resources/src/components/ComponentNavigator.svelte +++ b/plugins/workbench-resources/src/components/ComponentNavigator.svelte @@ -47,6 +47,7 @@ export let createComponentProps: Record = {} export let mainComponentLabel: IntlString export let mainComponentIcon: Asset | undefined = undefined + export let mainHeaderComponent: AnyComponent | undefined = undefined export let query: DocumentQuery = {} export let syncWithLocationQuery: boolean = true export let mainComponent: AnyComponent | AnySvelteComponent @@ -165,6 +166,17 @@ + + {#if mainHeaderComponent} + + {/if} +