UBERF-8584: Add test runs (#7235)

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artyom Savchenko
2024-11-29 01:36:41 +07:00
committed by GitHub
parent 61f7770a02
commit da85d6dd5f
60 changed files with 2379 additions and 554 deletions
@@ -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)
})
})
@@ -19,7 +19,7 @@
import { Action, IconEdit } from '@hcengineering/ui'
import { getResource } from '@hcengineering/platform'
import { TreeItem, getActions as getContributedActions } from '../index'
import { TreeItem, getActions as getContributedActions } from '../../index'
export let folders: Ref<Doc>[]
export let folderById: Map<Ref<Doc>, Doc>
@@ -20,9 +20,9 @@
import { getResource, type Resource } from '@hcengineering/platform'
import { IntlString, Asset } from '@hcengineering/platform'
import { FoldersManager, FoldersStore, FoldersState } from '../stores/folderStore'
import { FoldersStore, FoldersState, emptyFoldersState, getFoldersManager } from './store/folderStore'
import FolderTreeLevel from './FolderTreeLevel.svelte'
import { TreeNode, TreeItem, getActions as getContributedActions } from '../index'
import { TreeNode, TreeItem, getActions as getContributedActions } from '../../index'
export let _class: Ref<Class<Doc>>
export let query: DocumentQuery<Doc>
@@ -32,14 +32,15 @@
export let getFolderLink: Resource<(doc: Ref<Doc> | undefined) => Location>
export let allObjectsIcon: Asset
export let allObjectsLabel: IntlString
export let plainList: boolean = false
export let forciblyСollapsed: boolean = false
const client = getClient()
let foldersState: FoldersState = FoldersState.empty()
let foldersState: FoldersState = emptyFoldersState()
const foldersManager: FoldersManager = new FoldersManager(titleKey, parentKey, noParentId)
const foldersManager = getFoldersManager(titleKey, parentKey, noParentId, plainList)
FoldersStore.subscribe((newState) => {
foldersState = newState
@@ -54,6 +55,9 @@
query ?? {},
(result) => {
foldersManager.setFolders(result)
if (plainList && foldersState.folders?.length > 0) {
handleFolderSelected(foldersState.folders[0])
}
},
{
sort: {
@@ -65,10 +69,8 @@
async function handleFolderSelected (_id: Ref<Doc>): Promise<void> {
selected = _id
visibleItem = selected !== undefined ? foldersState.folderById.get(selected) : undefined
if (getFolderLink) {
const getFolderLinkFunction = await getResource(getFolderLink)
navigate(getFolderLinkFunction(_id))
}
const getFolderLinkFunction = await getResource(getFolderLink)
navigate(getFolderLinkFunction(_id))
}
async function handleAllItemsSelected (): Promise<void> {
@@ -100,17 +102,46 @@
</script>
<Scroller padding={'1rem 0'}>
<TreeNode
_id={noParentId}
icon={allObjectsIcon}
label={allObjectsLabel}
selected={selected === noParentId}
type={'nested-selectable'}
empty={foldersState?.folders?.length === 0}
actions={() => getRootActions()}
{forciblyСollapsed}
on:click={handleAllItemsSelected}
>
{#if noParentId !== undefined}
<TreeNode
_id={noParentId}
icon={allObjectsIcon}
label={allObjectsLabel}
selected={selected === noParentId}
type={'nested-selectable'}
empty={foldersState?.folders?.length === 0}
actions={() => getRootActions()}
{forciblyСollapsed}
on:click={handleAllItemsSelected}
>
<FolderTreeLevel
folders={foldersState.folders}
descendants={foldersState.descendants}
folderById={foldersState.folderById}
{selected}
on:selected={(ev) => {
handleFolderSelected(ev.detail)
}}
/>
<svelte:fragment slot="visible">
{#if (selected || forciblyСollapsed) && visibleItem !== undefined}
{@const folder = visibleItem}
<TreeItem
_id={folder._id}
folderIcon
iconProps={{ fill: 'var(--global-accent-IconColor)' }}
title={foldersManager.getTitle(folder)}
selected
isFold
empty
actions={async () => await getFolderActions(folder)}
shouldTooltip
forciblyСollapsed
/>
{/if}
</svelte:fragment>
</TreeNode>
{:else}
<FolderTreeLevel
folders={foldersState.folders}
descendants={foldersState.descendants}
@@ -120,22 +151,5 @@
handleFolderSelected(ev.detail)
}}
/>
<svelte:fragment slot="visible">
{#if (selected || forciblyСollapsed) && visibleItem}
{@const folder = visibleItem}
<TreeItem
_id={folder._id}
folderIcon
iconProps={{ fill: 'var(--global-accent-IconColor)' }}
title={foldersManager.getTitle(folder)}
selected
isFold
empty
actions={async () => await getFolderActions(folder)}
shouldTooltip
forciblyСollapsed
/>
{/if}
</svelte:fragment>
</TreeNode>
{/if}
</Scroller>
@@ -0,0 +1,38 @@
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
import { type Doc, type Ref } from '@hcengineering/core'
import { writable, type Writable } from 'svelte/store'
import { type FoldersState, FoldersManager, emptyFoldersState } from './folderUtils'
export { type FoldersState, emptyFoldersState }
export const FoldersStore: Writable<FoldersState> = writable(emptyFoldersState())
export const SelectedFolderStore: Writable<Ref<Doc> | undefined> = writable(undefined)
export function setSelectedFolder (_id: Ref<Doc> | undefined): void {
SelectedFolderStore.set(_id)
}
export function getFoldersManager (
titleKey: string,
parentKey: string,
noParentId: Ref<Doc>,
plainList: boolean
): FoldersManager {
return new FoldersManager(titleKey, parentKey, noParentId, plainList, (state: FoldersState) => {
FoldersStore.set(state)
})
}
@@ -12,41 +12,40 @@
// limitations under the License.
import { type Doc, type Ref } from '@hcengineering/core'
import { writable, type Writable } from 'svelte/store'
export class FoldersState {
folders: Array<Ref<Doc>>
folderById: Map<Ref<Doc>, Doc>
descendants: Map<Ref<Doc>, Doc[]>
constructor (folders: Array<Ref<Doc>>, folderById: Map<Ref<Doc>, Doc>, descendants: Map<Ref<Doc>, Doc[]>) {
this.folders = folders
this.folderById = folderById
this.descendants = descendants
}
static empty (): FoldersState {
return new FoldersState([], new Map<Ref<Doc>, Doc>(), new Map<Ref<Doc>, Doc[]>())
}
export interface FoldersState {
readonly folders: Array<Ref<Doc>>
readonly folderById: Map<Ref<Doc>, Doc>
readonly descendants: Map<Ref<Doc>, Doc[]>
}
export const FoldersStore: Writable<FoldersState> = writable(FoldersState.empty())
export const SelectedFolderStore: Writable<Ref<Doc> | undefined> = writable(undefined)
export function setSelectedFolder (_id: Ref<Doc> | undefined): void {
SelectedFolderStore.set(_id)
export function emptyFoldersState (): FoldersState {
return {
folders: [],
folderById: new Map<Ref<Doc>, Doc>(),
descendants: new Map<Ref<Doc>, Doc[]>()
}
}
export class FoldersManager {
titleKey: string
parentKey: string
noParentId: Ref<Doc>
plainList: boolean
updateStore: (state: FoldersState) => void
constructor (titleKey: string, parentKey: string, noParentId: Ref<Doc>) {
constructor (
titleKey: string,
parentKey: string,
noParentId: Ref<Doc>,
plainList: boolean,
updateStore: (state: FoldersState) => void
) {
this.titleKey = titleKey
this.parentKey = parentKey
this.noParentId = noParentId
this.plainList = plainList
this.updateStore = updateStore
}
public getTitle (doc: Doc): string {
@@ -64,16 +63,17 @@ export class FoldersManager {
}
public setFolders (result: Doc[]): void {
const newState: FoldersState = this.plainList ? this.getPlainList(result) : this.getNestedStructure(result)
this.updateStore(newState)
}
public getNestedStructure (result: Doc[]): FoldersState {
let folders: Array<Ref<Doc>> = []
const folderById: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
const descendants: Map<Ref<Doc>, Doc[]> = new Map<Ref<Doc>, Doc[]>()
for (const doc of result) {
const mappedDoc = {
title: this.getTitle(doc),
parent: this.getParent(doc),
...doc
}
const mappedDoc = this.mapDoc(doc)
const current = descendants.get(this.getParent(mappedDoc)) ?? []
current.push(mappedDoc)
descendants.set(this.getParent(mappedDoc), current)
@@ -81,6 +81,31 @@ export class FoldersManager {
}
folders = this.getDescendants(descendants, this.noParentId)
FoldersStore.set(new FoldersState(folders, folderById, descendants))
return { folders, folderById, descendants }
}
public getPlainList (result: Doc[]): FoldersState {
const folderById: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
for (const doc of result) {
const mappedDoc = this.mapDoc(doc)
folderById.set(mappedDoc._id, mappedDoc)
}
const folders: Array<Ref<Doc>> = result.map((doc) => doc._id)
return {
folders,
folderById,
descendants: new Map<Ref<Doc>, Doc[]>()
}
}
private mapDoc (doc: Doc): Doc {
const mappedDoc = {
title: this.getTitle(doc),
parent: this.getParent(doc),
...doc
}
return mappedDoc
}
}
+4 -1
View File
@@ -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,