mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-13 13:17:45 +02:00
UBERF-8584: Add test runs (#7235)
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user