mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-10 03:37:43 +02:00
UBERF-8584: Add test runs (#7235)
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { type Doc } from '@hcengineering/core'
|
||||
|
||||
import { FoldersManager, emptyFoldersState, type FoldersState } from '../components/folders/store/folderUtils'
|
||||
|
||||
describe('foldersManager', () => {
|
||||
test('folders tree', () => {
|
||||
const docs: Doc[] = [
|
||||
{
|
||||
name: 'Child 1',
|
||||
description: '',
|
||||
parent: '673ef344595d5369f68ced98',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732178763949,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732178763949,
|
||||
docUpdateMessages: 1,
|
||||
_id: '673ef34b595d5369f68ceda2',
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
name: 'Child 2',
|
||||
description: '',
|
||||
parent: '673ef344595d5369f68ced98',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732178770252,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732178770252,
|
||||
docUpdateMessages: 1,
|
||||
_id: '673ef352595d5369f68cedac',
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
_id: '673434f674993781b4195e1b',
|
||||
name: 'New suite',
|
||||
description: '',
|
||||
parent: 'testManagement:ids:NoParent',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1731495620083,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1731474678014,
|
||||
docUpdateMessages: 2,
|
||||
testCases: 1,
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
name: 'Parent',
|
||||
description: '',
|
||||
parent: 'testManagement:ids:NoParent',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732178756247,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732178756247,
|
||||
docUpdateMessages: 1,
|
||||
_id: '673ef344595d5369f68ced98',
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
_id: '6734863403ae9eca5b01e4fd',
|
||||
name: 'test',
|
||||
description: '',
|
||||
parent: 'testManagement:ids:NoParent',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1731495529218,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1731495476678,
|
||||
docUpdateMessages: 4,
|
||||
testCases: 3,
|
||||
space: '673434ed74993781b4195e02'
|
||||
}
|
||||
] as any
|
||||
const foldersManager = new FoldersManager('name', 'parent', 'testManagement:ids:NoParent' as any, false, () => {})
|
||||
const foldersState = foldersManager.getNestedStructure(docs)
|
||||
|
||||
expect(foldersState.folders.length).toBe(3)
|
||||
expect(foldersState.folderById.size).toBe(5)
|
||||
expect(foldersState.descendants.get('673ef344595d5369f68ced98' as any)?.length).toBe(2)
|
||||
})
|
||||
|
||||
test('plain list', () => {
|
||||
const docs: Doc[] = [
|
||||
{
|
||||
_id: '6739541d04aed0096599e03a',
|
||||
name: '3 items',
|
||||
description: '6739541704aed0096599e032%description:HEAD:0',
|
||||
_class: 'testManagement:class:TestRun',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1731810333365,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1731810333307,
|
||||
docUpdateMessages: 4,
|
||||
testCases: 3,
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
name: 'Test run 1',
|
||||
description: '673ef510595d5369f68cee0a%description:HEAD:0',
|
||||
_class: 'testManagement:class:TestRun',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732179220778,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732179220778,
|
||||
testCases: 2,
|
||||
docUpdateMessages: 3,
|
||||
_id: '673ef514595d5369f68cee12',
|
||||
space: '673434ed74993781b4195e02'
|
||||
},
|
||||
{
|
||||
name: 'Test run 2',
|
||||
description: '673ef516595d5369f68cee18%description:HEAD:0',
|
||||
_class: 'testManagement:class:TestRun',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1732179227645,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1732179227611,
|
||||
testCases: 2,
|
||||
docUpdateMessages: 3,
|
||||
_id: '673ef51b595d5369f68cee20',
|
||||
space: '673434ed74993781b4195e02'
|
||||
}
|
||||
] as any
|
||||
const foldersManager = new FoldersManager('name', 'parent', 'noParent' as any, true, () => {})
|
||||
const foldersState = foldersManager.getPlainList(docs)
|
||||
|
||||
expect(foldersState.folders.length).toBe(3)
|
||||
expect(foldersState.folderById.size).toBe(3)
|
||||
expect(foldersState.descendants.size).toBe(0)
|
||||
})
|
||||
|
||||
test('test state update', () => {
|
||||
const docs: Doc[] = [
|
||||
{
|
||||
_id: '673434f674993781b4195e1b',
|
||||
name: 'New suite',
|
||||
description: '',
|
||||
parent: 'testManagement:ids:NoParent',
|
||||
_class: 'testManagement:class:TestSuite',
|
||||
modifiedBy: '67286f072788ebca822ea1b2',
|
||||
modifiedOn: 1731495620083,
|
||||
createdBy: '67286f072788ebca822ea1b2',
|
||||
createdOn: 1731474678014,
|
||||
docUpdateMessages: 2,
|
||||
testCases: 1,
|
||||
space: '673434ed74993781b4195e02'
|
||||
}
|
||||
] as any
|
||||
let folderState = emptyFoldersState()
|
||||
|
||||
expect(folderState.folders.length).toBe(0)
|
||||
expect(folderState.descendants.size).toBe(0)
|
||||
expect(folderState.folderById.size).toBe(0)
|
||||
|
||||
const foldersManager = new FoldersManager(
|
||||
'name',
|
||||
'parent',
|
||||
'testManagement:ids:NoParent' as any,
|
||||
false,
|
||||
(state: FoldersState) => {
|
||||
folderState = state
|
||||
}
|
||||
)
|
||||
foldersManager.setFolders(docs)
|
||||
|
||||
expect(folderState.folders.length).toBe(1)
|
||||
expect(folderState.folderById.size).toBe(1)
|
||||
})
|
||||
})
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
import { Action, IconEdit } from '@hcengineering/ui'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
import { TreeItem, getActions as getContributedActions } from '../index'
|
||||
import { TreeItem, getActions as getContributedActions } from '../../index'
|
||||
|
||||
export let folders: Ref<Doc>[]
|
||||
export let folderById: Map<Ref<Doc>, Doc>
|
||||
+51
-37
@@ -20,9 +20,9 @@
|
||||
import { getResource, type Resource } from '@hcengineering/platform'
|
||||
import { IntlString, Asset } from '@hcengineering/platform'
|
||||
|
||||
import { FoldersManager, FoldersStore, FoldersState } from '../stores/folderStore'
|
||||
import { FoldersStore, FoldersState, emptyFoldersState, getFoldersManager } from './store/folderStore'
|
||||
import FolderTreeLevel from './FolderTreeLevel.svelte'
|
||||
import { TreeNode, TreeItem, getActions as getContributedActions } from '../index'
|
||||
import { TreeNode, TreeItem, getActions as getContributedActions } from '../../index'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let query: DocumentQuery<Doc>
|
||||
@@ -32,14 +32,15 @@
|
||||
export let getFolderLink: Resource<(doc: Ref<Doc> | undefined) => Location>
|
||||
export let allObjectsIcon: Asset
|
||||
export let allObjectsLabel: IntlString
|
||||
export let plainList: boolean = false
|
||||
|
||||
export let forciblyСollapsed: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let foldersState: FoldersState = FoldersState.empty()
|
||||
let foldersState: FoldersState = emptyFoldersState()
|
||||
|
||||
const foldersManager: FoldersManager = new FoldersManager(titleKey, parentKey, noParentId)
|
||||
const foldersManager = getFoldersManager(titleKey, parentKey, noParentId, plainList)
|
||||
|
||||
FoldersStore.subscribe((newState) => {
|
||||
foldersState = newState
|
||||
@@ -54,6 +55,9 @@
|
||||
query ?? {},
|
||||
(result) => {
|
||||
foldersManager.setFolders(result)
|
||||
if (plainList && foldersState.folders?.length > 0) {
|
||||
handleFolderSelected(foldersState.folders[0])
|
||||
}
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
@@ -65,10 +69,8 @@
|
||||
async function handleFolderSelected (_id: Ref<Doc>): Promise<void> {
|
||||
selected = _id
|
||||
visibleItem = selected !== undefined ? foldersState.folderById.get(selected) : undefined
|
||||
if (getFolderLink) {
|
||||
const getFolderLinkFunction = await getResource(getFolderLink)
|
||||
navigate(getFolderLinkFunction(_id))
|
||||
}
|
||||
const getFolderLinkFunction = await getResource(getFolderLink)
|
||||
navigate(getFolderLinkFunction(_id))
|
||||
}
|
||||
|
||||
async function handleAllItemsSelected (): Promise<void> {
|
||||
@@ -100,17 +102,46 @@
|
||||
</script>
|
||||
|
||||
<Scroller padding={'1rem 0'}>
|
||||
<TreeNode
|
||||
_id={noParentId}
|
||||
icon={allObjectsIcon}
|
||||
label={allObjectsLabel}
|
||||
selected={selected === noParentId}
|
||||
type={'nested-selectable'}
|
||||
empty={foldersState?.folders?.length === 0}
|
||||
actions={() => getRootActions()}
|
||||
{forciblyСollapsed}
|
||||
on:click={handleAllItemsSelected}
|
||||
>
|
||||
{#if noParentId !== undefined}
|
||||
<TreeNode
|
||||
_id={noParentId}
|
||||
icon={allObjectsIcon}
|
||||
label={allObjectsLabel}
|
||||
selected={selected === noParentId}
|
||||
type={'nested-selectable'}
|
||||
empty={foldersState?.folders?.length === 0}
|
||||
actions={() => getRootActions()}
|
||||
{forciblyСollapsed}
|
||||
on:click={handleAllItemsSelected}
|
||||
>
|
||||
<FolderTreeLevel
|
||||
folders={foldersState.folders}
|
||||
descendants={foldersState.descendants}
|
||||
folderById={foldersState.folderById}
|
||||
{selected}
|
||||
on:selected={(ev) => {
|
||||
handleFolderSelected(ev.detail)
|
||||
}}
|
||||
/>
|
||||
<svelte:fragment slot="visible">
|
||||
{#if (selected || forciblyСollapsed) && visibleItem !== undefined}
|
||||
{@const folder = visibleItem}
|
||||
<TreeItem
|
||||
_id={folder._id}
|
||||
folderIcon
|
||||
iconProps={{ fill: 'var(--global-accent-IconColor)' }}
|
||||
title={foldersManager.getTitle(folder)}
|
||||
selected
|
||||
isFold
|
||||
empty
|
||||
actions={async () => await getFolderActions(folder)}
|
||||
shouldTooltip
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
{:else}
|
||||
<FolderTreeLevel
|
||||
folders={foldersState.folders}
|
||||
descendants={foldersState.descendants}
|
||||
@@ -120,22 +151,5 @@
|
||||
handleFolderSelected(ev.detail)
|
||||
}}
|
||||
/>
|
||||
<svelte:fragment slot="visible">
|
||||
{#if (selected || forciblyСollapsed) && visibleItem}
|
||||
{@const folder = visibleItem}
|
||||
<TreeItem
|
||||
_id={folder._id}
|
||||
folderIcon
|
||||
iconProps={{ fill: 'var(--global-accent-IconColor)' }}
|
||||
title={foldersManager.getTitle(folder)}
|
||||
selected
|
||||
isFold
|
||||
empty
|
||||
actions={async () => await getFolderActions(folder)}
|
||||
shouldTooltip
|
||||
forciblyСollapsed
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
{/if}
|
||||
</Scroller>
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
import { type FoldersState, FoldersManager, emptyFoldersState } from './folderUtils'
|
||||
|
||||
export { type FoldersState, emptyFoldersState }
|
||||
|
||||
export const FoldersStore: Writable<FoldersState> = writable(emptyFoldersState())
|
||||
|
||||
export const SelectedFolderStore: Writable<Ref<Doc> | undefined> = writable(undefined)
|
||||
|
||||
export function setSelectedFolder (_id: Ref<Doc> | undefined): void {
|
||||
SelectedFolderStore.set(_id)
|
||||
}
|
||||
|
||||
export function getFoldersManager (
|
||||
titleKey: string,
|
||||
parentKey: string,
|
||||
noParentId: Ref<Doc>,
|
||||
plainList: boolean
|
||||
): FoldersManager {
|
||||
return new FoldersManager(titleKey, parentKey, noParentId, plainList, (state: FoldersState) => {
|
||||
FoldersStore.set(state)
|
||||
})
|
||||
}
|
||||
+53
-28
@@ -12,41 +12,40 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export class FoldersState {
|
||||
folders: Array<Ref<Doc>>
|
||||
folderById: Map<Ref<Doc>, Doc>
|
||||
descendants: Map<Ref<Doc>, Doc[]>
|
||||
|
||||
constructor (folders: Array<Ref<Doc>>, folderById: Map<Ref<Doc>, Doc>, descendants: Map<Ref<Doc>, Doc[]>) {
|
||||
this.folders = folders
|
||||
this.folderById = folderById
|
||||
this.descendants = descendants
|
||||
}
|
||||
|
||||
static empty (): FoldersState {
|
||||
return new FoldersState([], new Map<Ref<Doc>, Doc>(), new Map<Ref<Doc>, Doc[]>())
|
||||
}
|
||||
export interface FoldersState {
|
||||
readonly folders: Array<Ref<Doc>>
|
||||
readonly folderById: Map<Ref<Doc>, Doc>
|
||||
readonly descendants: Map<Ref<Doc>, Doc[]>
|
||||
}
|
||||
|
||||
export const FoldersStore: Writable<FoldersState> = writable(FoldersState.empty())
|
||||
|
||||
export const SelectedFolderStore: Writable<Ref<Doc> | undefined> = writable(undefined)
|
||||
|
||||
export function setSelectedFolder (_id: Ref<Doc> | undefined): void {
|
||||
SelectedFolderStore.set(_id)
|
||||
export function emptyFoldersState (): FoldersState {
|
||||
return {
|
||||
folders: [],
|
||||
folderById: new Map<Ref<Doc>, Doc>(),
|
||||
descendants: new Map<Ref<Doc>, Doc[]>()
|
||||
}
|
||||
}
|
||||
|
||||
export class FoldersManager {
|
||||
titleKey: string
|
||||
parentKey: string
|
||||
noParentId: Ref<Doc>
|
||||
plainList: boolean
|
||||
updateStore: (state: FoldersState) => void
|
||||
|
||||
constructor (titleKey: string, parentKey: string, noParentId: Ref<Doc>) {
|
||||
constructor (
|
||||
titleKey: string,
|
||||
parentKey: string,
|
||||
noParentId: Ref<Doc>,
|
||||
plainList: boolean,
|
||||
updateStore: (state: FoldersState) => void
|
||||
) {
|
||||
this.titleKey = titleKey
|
||||
this.parentKey = parentKey
|
||||
this.noParentId = noParentId
|
||||
this.plainList = plainList
|
||||
this.updateStore = updateStore
|
||||
}
|
||||
|
||||
public getTitle (doc: Doc): string {
|
||||
@@ -64,16 +63,17 @@ export class FoldersManager {
|
||||
}
|
||||
|
||||
public setFolders (result: Doc[]): void {
|
||||
const newState: FoldersState = this.plainList ? this.getPlainList(result) : this.getNestedStructure(result)
|
||||
this.updateStore(newState)
|
||||
}
|
||||
|
||||
public getNestedStructure (result: Doc[]): FoldersState {
|
||||
let folders: Array<Ref<Doc>> = []
|
||||
const folderById: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||
const descendants: Map<Ref<Doc>, Doc[]> = new Map<Ref<Doc>, Doc[]>()
|
||||
|
||||
for (const doc of result) {
|
||||
const mappedDoc = {
|
||||
title: this.getTitle(doc),
|
||||
parent: this.getParent(doc),
|
||||
...doc
|
||||
}
|
||||
const mappedDoc = this.mapDoc(doc)
|
||||
const current = descendants.get(this.getParent(mappedDoc)) ?? []
|
||||
current.push(mappedDoc)
|
||||
descendants.set(this.getParent(mappedDoc), current)
|
||||
@@ -81,6 +81,31 @@ export class FoldersManager {
|
||||
}
|
||||
|
||||
folders = this.getDescendants(descendants, this.noParentId)
|
||||
FoldersStore.set(new FoldersState(folders, folderById, descendants))
|
||||
return { folders, folderById, descendants }
|
||||
}
|
||||
|
||||
public getPlainList (result: Doc[]): FoldersState {
|
||||
const folderById: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||
|
||||
for (const doc of result) {
|
||||
const mappedDoc = this.mapDoc(doc)
|
||||
folderById.set(mappedDoc._id, mappedDoc)
|
||||
}
|
||||
const folders: Array<Ref<Doc>> = result.map((doc) => doc._id)
|
||||
|
||||
return {
|
||||
folders,
|
||||
folderById,
|
||||
descendants: new Map<Ref<Doc>, Doc[]>()
|
||||
}
|
||||
}
|
||||
|
||||
private mapDoc (doc: Doc): Doc {
|
||||
const mappedDoc = {
|
||||
title: this.getTitle(doc),
|
||||
parent: this.getParent(doc),
|
||||
...doc
|
||||
}
|
||||
return mappedDoc
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ import ImageViewer from './components/viewer/ImageViewer.svelte'
|
||||
import VideoViewer from './components/viewer/VideoViewer.svelte'
|
||||
import PDFViewer from './components/viewer/PDFViewer.svelte'
|
||||
import TextViewer from './components/viewer/TextViewer.svelte'
|
||||
import FoldersBrowser from './components/FoldersBrowser.svelte'
|
||||
import FoldersBrowser from './components/folders/FoldersBrowser.svelte'
|
||||
|
||||
import { blobImageMetadata, blobVideoMetadata } from './blob'
|
||||
|
||||
@@ -167,6 +167,8 @@ export { default as List } from './components/list/List.svelte'
|
||||
export { default as NavLink } from './components/navigator/NavLink.svelte'
|
||||
export { default as StatusPresenter } from './components/status/StatusPresenter.svelte'
|
||||
export { default as StatusRefPresenter } from './components/status/StatusRefPresenter.svelte'
|
||||
export { default as FoldersBrowser } from './components/folders/FoldersBrowser.svelte'
|
||||
export { default as ListView } from './components/list/ListView.svelte'
|
||||
|
||||
export * from './filter'
|
||||
export * from './middleware'
|
||||
@@ -189,6 +191,7 @@ export {
|
||||
} from './utils'
|
||||
export * from './viewOptions'
|
||||
export {
|
||||
ArrayEditor,
|
||||
BooleanEditor,
|
||||
BooleanPresenter,
|
||||
ClassAttributeBar,
|
||||
|
||||
Reference in New Issue
Block a user