TSK-955: Fix status display (#2840)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-03-27 22:33:23 +07:00 committed by GitHub
parent 948f39d669
commit 57a925b038
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 145 additions and 61 deletions

View File

@ -40,5 +40,8 @@ export const calcRank = (prev?: { rank: string }, next?: { rank: string }): stri
const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min() const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min()
const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max() const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max()
if (a.equals(b)) {
return a.genNext().toString()
}
return a.between(b).toString() return a.between(b).toString()
} }

View File

@ -104,6 +104,10 @@ export function getClient (): TxOperations {
* @public * @public
*/ */
export function setClient (_client: Client): void { export function setClient (_client: Client): void {
if (liveQuery !== undefined) {
void liveQuery.close()
}
const needRefresh = liveQuery !== undefined
liveQuery = new LQ(_client) liveQuery = new LQ(_client)
client = new UIClient(_client, liveQuery) client = new UIClient(_client, liveQuery)
_client.notify = (tx: Tx) => { _client.notify = (tx: Tx) => {
@ -111,17 +115,23 @@ export function setClient (_client: Client): void {
txListeners.forEach((it) => it(tx)) txListeners.forEach((it) => it(tx))
} }
if (needRefresh) {
refreshClient()
}
} }
/** /**
* @public * @public
*/ */
export function refreshClient (): void { export function refreshClient (): void {
if (liveQuery !== undefined) { void liveQuery?.refreshConnect()
void liveQuery.refreshConnect() for (const q of globalQueries) {
q.refreshClient()
} }
} }
const globalQueries: LiveQuery[] = []
/** /**
* @public * @public
*/ */
@ -137,6 +147,8 @@ export class LiveQuery {
onDestroy(() => { onDestroy(() => {
this.unsubscribe() this.unsubscribe()
}) })
} else {
globalQueries.push(this)
} }
} }
@ -149,11 +161,21 @@ export class LiveQuery {
if (!this.needUpdate(_class, query, callback, options)) { if (!this.needUpdate(_class, query, callback, options)) {
return false return false
} }
return this.doQuery<T>(_class, query, callback, options)
}
private doQuery<T extends Doc>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
callback: (result: FindResult<T>) => void,
options: FindOptions<T> | undefined
): boolean {
this.unsubscribe() this.unsubscribe()
this.oldCallback = callback this.oldCallback = callback
this.oldClass = _class this.oldClass = _class
this.oldOptions = options this.oldOptions = options
this.oldQuery = query this.oldQuery = query
const unsub = liveQuery.query(_class, query, callback, options) const unsub = liveQuery.query(_class, query, callback, options)
this.unsubscribe = () => { this.unsubscribe = () => {
unsub() unsub()
@ -166,6 +188,16 @@ export class LiveQuery {
return true return true
} }
refreshClient (): void {
if (this.oldClass !== undefined && this.oldQuery !== undefined && this.oldCallback !== undefined) {
const _class = this.oldClass
const query = this.oldQuery
const callback = this.oldCallback
const options = this.oldOptions
this.doQuery(_class, query, callback, options)
}
}
private needUpdate<T extends Doc>( private needUpdate<T extends Doc>(
_class: Ref<Class<T>>, _class: Ref<Class<T>>,
query: DocumentQuery<T>, query: DocumentQuery<T>,

View File

@ -64,7 +64,7 @@ interface Query {
* @public * @public
*/ */
export class LiveQuery extends TxProcessor implements Client { export class LiveQuery extends TxProcessor implements Client {
private readonly client: Client private client: Client
private readonly queries: Map<Ref<Class<Doc>>, Query[]> = new Map<Ref<Class<Doc>>, Query[]>() private readonly queries: Map<Ref<Class<Doc>>, Query[]> = new Map<Ref<Class<Doc>>, Query[]>()
private readonly queue: Query[] = [] private readonly queue: Query[] = []
@ -73,6 +73,11 @@ export class LiveQuery extends TxProcessor implements Client {
this.client = client this.client = client
} }
async updateClient (client: Client): Promise<void> {
this.client = client
await this.refreshConnect()
}
async close (): Promise<void> { async close (): Promise<void> {
return await this.client.close() return await this.client.close()
} }
@ -89,7 +94,20 @@ export class LiveQuery extends TxProcessor implements Client {
async refreshConnect (): Promise<void> { async refreshConnect (): Promise<void> {
for (const q of [...this.queue]) { for (const q of [...this.queue]) {
if (!(await this.removeFromQueue(q))) { if (!(await this.removeFromQueue(q))) {
try {
await this.refresh(q) await this.refresh(q)
} catch (err) {
console.error(err)
}
}
}
for (const v of this.queries.values()) {
for (const q of v) {
try {
await this.refresh(q)
} catch (err) {
console.error(err)
}
} }
} }
} }

View File

@ -50,7 +50,7 @@ class RequestPromise {
resolve!: (value?: any) => void resolve!: (value?: any) => void
reject!: (reason?: any) => void reject!: (reason?: any) => void
reconnect?: () => void reconnect?: () => void
constructor () { constructor (readonly method: string, readonly params: any[]) {
this.promise = new Promise((resolve, reject) => { this.promise = new Promise((resolve, reject) => {
this.resolve = resolve this.resolve = resolve
this.reject = reject this.reject = reject
@ -64,6 +64,7 @@ class Connection implements ClientConnection {
private lastId = 0 private lastId = 0
private readonly interval: number private readonly interval: number
private readonly sessionId = generateId() as string private readonly sessionId = generateId() as string
private closed = false
constructor ( constructor (
private readonly url: string, private readonly url: string,
@ -80,6 +81,7 @@ class Connection implements ClientConnection {
} }
async close (): Promise<void> { async close (): Promise<void> {
this.closed = true
clearInterval(this.interval) clearInterval(this.interval)
if (this.websocket !== null) { if (this.websocket !== null) {
if (this.websocket instanceof Promise) { if (this.websocket instanceof Promise) {
@ -158,7 +160,7 @@ class Connection implements ClientConnection {
} }
this.requests.delete(resp.id) this.requests.delete(resp.id)
if (resp.error !== undefined) { if (resp.error !== undefined) {
console.log('ERROR', resp.id) console.log('ERROR', promise, resp.id)
promise.reject(new PlatformError(resp.error)) promise.reject(new PlatformError(resp.error))
} else { } else {
promise.resolve(resp.result) promise.resolve(resp.result)
@ -212,8 +214,11 @@ class Connection implements ClientConnection {
// If not defined, on reconnect with timeout, will retry automatically. // If not defined, on reconnect with timeout, will retry automatically.
retry?: () => Promise<boolean> retry?: () => Promise<boolean>
}): Promise<any> { }): Promise<any> {
if (this.closed) {
throw new PlatformError(unknownError('connection closed'))
}
const id = this.lastId++ const id = this.lastId++
const promise = new RequestPromise() const promise = new RequestPromise(data.method, data.params)
const sendData = async (): Promise<void> => { const sendData = async (): Promise<void> => {
if (this.websocket instanceof Promise) { if (this.websocket instanceof Promise) {

View File

@ -34,10 +34,8 @@ import { FilterQuery } from '@hcengineering/view-resources'
import { get, writable } from 'svelte/store' import { get, writable } from 'svelte/store'
import contact from './plugin' import contact from './plugin'
const client = getClient()
export async function getChannelProviders (): Promise<Map<Ref<ChannelProvider>, ChannelProvider>> { export async function getChannelProviders (): Promise<Map<Ref<ChannelProvider>, ChannelProvider>> {
const cp = await client.findAll(contact.class.ChannelProvider, {}) const cp = await getClient().findAll(contact.class.ChannelProvider, {})
const map = new Map<Ref<ChannelProvider>, ChannelProvider>() const map = new Map<Ref<ChannelProvider>, ChannelProvider>()
for (const provider of cp) { for (const provider of cp) {
map.set(provider._id, provider) map.set(provider._id, provider)

View File

@ -39,6 +39,8 @@ export const genRanks = (count: number): Generator<string, void, unknown> =>
export const calcRank = (prev?: { rank: string }, next?: { rank: string }): string => { export const calcRank = (prev?: { rank: string }, next?: { rank: string }): string => {
const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min() const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min()
const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max() const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max()
if (a.equals(b)) {
return a.genNext().toString()
}
return a.between(b).toString() return a.between(b).toString()
} }

View File

@ -15,6 +15,7 @@
<svg <svg
class="svg-{size}" class="svg-{size}"
{fill} {fill}
id={category._id}
style:transform={category._id === tracker.issueStatusCategory.Started ? 'rotate(-90deg)' : ''} style:transform={category._id === tracker.issueStatusCategory.Started ? 'rotate(-90deg)' : ''}
viewBox="0 0 14 14" viewBox="0 0 14 14"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@ -4,7 +4,7 @@
import { Issue, IssueStatus } from '@hcengineering/tracker' import { Issue, IssueStatus } from '@hcengineering/tracker'
import { Label, ticker } from '@hcengineering/ui' import { Label, ticker } from '@hcengineering/ui'
import tracker from '../../plugin' import tracker from '../../plugin'
import { statusByIdStore } from '../../utils' import { statusStore } from '../../utils'
import Duration from './Duration.svelte' import Duration from './Duration.svelte'
import StatusPresenter from './StatusPresenter.svelte' import StatusPresenter from './StatusPresenter.svelte'
@ -83,7 +83,7 @@
displaySt = result displaySt = result
} }
$: updateStatus(txes, $statusByIdStore, $ticker) $: updateStatus(txes, $statusStore.byId, $ticker)
</script> </script>
<div class="flex-row mt-4 mb-4"> <div class="flex-row mt-4 mb-4">

View File

@ -40,7 +40,7 @@
$: if (value.category === tracker.issueStatusCategory.Started) { $: if (value.category === tracker.issueStatusCategory.Started) {
const _s = [ const _s = [
...$statusStore.filter( ...$statusStore.statuses.filter(
(it) => it.attachedTo === value.attachedTo && it.category === tracker.issueStatusCategory.Started (it) => it.attachedTo === value.attachedTo && it.category === tracker.issueStatusCategory.Started
) )
] ]

View File

@ -65,7 +65,7 @@
) )
} }
$: statuses = $statusStore.filter((it) => it.attachedTo === value?.space) $: statuses = $statusStore.statuses.filter((it) => it.attachedTo === value?.space)
$: selectedStatus = statuses?.find((status) => status._id === value.status) ?? statuses?.[0] $: selectedStatus = statuses?.find((status) => status._id === value.status) ?? statuses?.[0]
$: selectedStatusLabel = shouldShowLabel ? selectedStatus?.name : undefined $: selectedStatusLabel = shouldShowLabel ? selectedStatus?.name : undefined

View File

@ -14,7 +14,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { IssueStatus } from '@hcengineering/tracker' import { IssueStatus } from '@hcengineering/tracker'
import { statusByIdStore } from '../../utils' import { statusStore } from '../../utils'
import IssueStatusIcon from './IssueStatusIcon.svelte' import IssueStatusIcon from './IssueStatusIcon.svelte'
export let value: IssueStatus | undefined export let value: IssueStatus | undefined
@ -22,7 +22,7 @@
</script> </script>
{#if value} {#if value}
{@const icon = $statusByIdStore.get(value._id)?.$lookup?.category?.icon} {@const icon = $statusStore.byId.get(value._id)?.$lookup?.category?.icon}
<div class="flex-presenter"> <div class="flex-presenter">
{#if icon} {#if icon}
<IssueStatusIcon {value} {size} /> <IssueStatusIcon {value} {size} />

View File

@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import { Ref } from '@hcengineering/core' import { Ref } from '@hcengineering/core'
import { IssueStatus } from '@hcengineering/tracker' import { IssueStatus } from '@hcengineering/tracker'
import { statusByIdStore } from '../../utils' import { statusStore } from '../../utils'
import StatusPresenter from './StatusPresenter.svelte' import StatusPresenter from './StatusPresenter.svelte'
export let value: Ref<IssueStatus> | undefined export let value: Ref<IssueStatus> | undefined
@ -23,5 +23,5 @@
</script> </script>
{#if value} {#if value}
<StatusPresenter value={$statusByIdStore.get(value)} {size} /> <StatusPresenter value={$statusStore.byId.get(value)} {size} />
{/if} {/if}

View File

@ -26,16 +26,16 @@
</script> </script>
{#if value} {#if value}
<DocNavLink object={value} {onClick} component={tracker.component.EditIssue} inline shrink={1}>
<span <span
class="name overflow-label select-text" class="name overflow-label select-text"
class:with-margin={shouldUseMargin} class:with-margin={shouldUseMargin}
style:max-width={showParent ? `${value.parents.length !== 0 ? 95 : 100}%` : '100%'} style:max-width={showParent ? `${value.parents.length !== 0 ? 95 : 100}%` : '100%'}
title={value.title} title={value.title}
> >
<DocNavLink object={value} {onClick} component={tracker.component.EditIssue} inline shrink={1}>
{value.title} {value.title}
</span>
</DocNavLink> </DocNavLink>
</span>
{#if showParent} {#if showParent}
<ParentNamesPresenter {value} /> <ParentNamesPresenter {value} />
{/if} {/if}

View File

@ -29,7 +29,7 @@
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { getIssueId } from '../../../issues' import { getIssueId } from '../../../issues'
import tracker from '../../../plugin' import tracker from '../../../plugin'
import { statusByIdStore, statusStore, subIssueListProvider } from '../../../utils' import { statusStore, subIssueListProvider } from '../../../utils'
export let value: WithLookup<Issue> export let value: WithLookup<Issue>
export let currentProject: Project | undefined = undefined export let currentProject: Project | undefined = undefined
@ -77,7 +77,7 @@
} }
$: if (subIssues) { $: if (subIssues) {
const doneStatuses = $statusStore const doneStatuses = $statusStore.statuses
.filter((s) => s.category === tracker.issueStatusCategory.Completed) .filter((s) => s.category === tracker.issueStatusCategory.Completed)
.map((p) => p._id) .map((p) => p._id)
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
@ -115,7 +115,7 @@
id: iss._id, id: iss._id,
text, text,
isSelected: iss._id === value._id, isSelected: iss._id === value._id,
...getIssueStatusIcon(iss, $statusByIdStore) ...getIssueStatusIcon(iss, $statusStore.byId)
} }
}), }),
width: 'large' width: 'large'

View File

@ -29,7 +29,7 @@
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { getIssueId } from '../../../issues' import { getIssueId } from '../../../issues'
import tracker from '../../../plugin' import tracker from '../../../plugin'
import { statusByIdStore, statusStore, subIssueListProvider } from '../../../utils' import { statusStore, subIssueListProvider } from '../../../utils'
export let object: WithLookup<Doc & { related: number }> | undefined export let object: WithLookup<Doc & { related: number }> | undefined
export let value: WithLookup<Doc & { related: number }> | undefined export let value: WithLookup<Doc & { related: number }> | undefined
@ -69,7 +69,7 @@
} }
$: if (subIssues) { $: if (subIssues) {
const doneStatuses = $statusStore const doneStatuses = $statusStore.statuses
.filter((s) => s.category === tracker.issueStatusCategory.Completed) .filter((s) => s.category === tracker.issueStatusCategory.Completed)
.map((p) => p._id) .map((p) => p._id)
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
@ -101,7 +101,7 @@
value: subIssues.map((iss) => { value: subIssues.map((iss) => {
const text = currentProject ? `${getIssueId(currentProject, iss)} ${iss.title}` : iss.title const text = currentProject ? `${getIssueId(currentProject, iss)} ${iss.title}` : iss.title
return { id: iss._id, text, isSelected: false, ...getIssueStatusIcon(iss, $statusByIdStore) } return { id: iss._id, text, isSelected: false, ...getIssueStatusIcon(iss, $statusStore.byId) }
}), }),
width: 'large' width: 'large'
}, },

View File

@ -17,7 +17,7 @@
import { Issue } from '@hcengineering/tracker' import { Issue } from '@hcengineering/tracker'
import { floorFractionDigits, Label } from '@hcengineering/ui' import { floorFractionDigits, Label } from '@hcengineering/ui'
import tracker from '../../plugin' import tracker from '../../plugin'
import { statusByIdStore } from '../../utils' import { statusStore } from '../../utils'
import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte' import EstimationProgressCircle from '../issues/timereport/EstimationProgressCircle.svelte'
import TimePresenter from '../issues/timereport/TimePresenter.svelte' import TimePresenter from '../issues/timereport/TimePresenter.svelte'
export let docs: Issue[] | undefined = undefined export let docs: Issue[] | undefined = undefined
@ -28,13 +28,13 @@
$: noParents = docs?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>)) $: noParents = docs?.filter((it) => !ids.has(it.attachedTo as Ref<Issue>))
$: rootNoBacklogIssues = noParents?.filter( $: rootNoBacklogIssues = noParents?.filter(
(it) => $statusByIdStore.get(it.status)?.category !== tracker.issueStatusCategory.Backlog (it) => $statusStore.byId.get(it.status)?.category !== tracker.issueStatusCategory.Backlog
) )
$: totalEstimation = floorFractionDigits( $: totalEstimation = floorFractionDigits(
(rootNoBacklogIssues ?? [{ estimation: 0, childInfo: [] } as unknown as Issue]) (rootNoBacklogIssues ?? [{ estimation: 0, childInfo: [] } as unknown as Issue])
.map((it) => { .map((it) => {
const cat = $statusByIdStore.get(it.status)?.category const cat = $statusStore.byId.get(it.status)?.category
let retEst = it.estimation let retEst = it.estimation
if (it.childInfo?.length > 0) { if (it.childInfo?.length > 0) {

View File

@ -66,10 +66,10 @@
} }
async function addStatus () { async function addStatus () {
if (editingStatus?.name && editingStatus?.category && $statusStore) { if (editingStatus?.name && editingStatus?.category) {
const categoryStatuses = $statusStore.filter((s) => s.category === editingStatus!.category) const categoryStatuses = $statusStore.statuses.filter((s) => s.category === editingStatus!.category)
const prevStatus = categoryStatuses[categoryStatuses.length - 1] const prevStatus = categoryStatuses[categoryStatuses.length - 1]
const nextStatus = $statusStore[$statusStore.findIndex(({ _id }) => _id === prevStatus._id) + 1] const nextStatus = $statusStore.statuses[$statusStore.statuses.findIndex(({ _id }) => _id === prevStatus._id) + 1]
isSaving = true isSaving = true
await client.addCollection( await client.addCollection(
@ -93,9 +93,9 @@
} }
async function editStatus () { async function editStatus () {
if ($statusStore && statusCategories && editingStatus?.name && editingStatus?.category && '_id' in editingStatus) { if (statusCategories && editingStatus?.name && editingStatus?.category && '_id' in editingStatus) {
const statusId = '_id' in editingStatus ? editingStatus._id : undefined const statusId = '_id' in editingStatus ? editingStatus._id : undefined
const status = statusId && $statusStore.find(({ _id }) => _id === statusId) const status = statusId && $statusStore.byId.get(statusId)
if (!status) { if (!status) {
return return
@ -157,12 +157,14 @@
}, },
undefined, undefined,
async (result) => { async (result) => {
if (result && project && $statusStore) { if (result && project) {
isSaving = true isSaving = true
await client.removeDoc(status._class, status.space, status._id) await client.removeDoc(status._class, status.space, status._id)
if (project.defaultIssueStatus === status._id) { if (project.defaultIssueStatus === status._id) {
const newDefaultStatus = $statusStore.find((s) => s._id !== status._id && s.category === status.category) const newDefaultStatus = $statusStore.statuses.find(
(s) => s._id !== status._id && s.category === status.category && s.space === status.space
)
if (newDefaultStatus?._id) { if (newDefaultStatus?._id) {
await updateProjectDefaultStatus(newDefaultStatus._id) await updateProjectDefaultStatus(newDefaultStatus._id)
} }
@ -196,12 +198,12 @@
} }
async function handleDrop (toItem: IssueStatus) { async function handleDrop (toItem: IssueStatus) {
if ($statusStore && draggingStatus?._id !== toItem._id && draggingStatus?.category === toItem.category) { if (draggingStatus?._id !== toItem._id && draggingStatus?.category === toItem.category) {
const fromIndex = getStatusIndex(draggingStatus) const fromIndex = getStatusIndex(draggingStatus)
const toIndex = getStatusIndex(toItem) const toIndex = getStatusIndex(toItem)
const [prev, next] = [ const [prev, next] = [
$statusStore[fromIndex < toIndex ? toIndex : toIndex - 1], $statusStore.statuses[fromIndex < toIndex ? toIndex : toIndex - 1],
$statusStore[fromIndex < toIndex ? toIndex + 1 : toIndex] $statusStore.statuses[fromIndex < toIndex ? toIndex + 1 : toIndex]
] ]
isSaving = true isSaving = true
@ -213,7 +215,7 @@
} }
function getStatusIndex (status: IssueStatus) { function getStatusIndex (status: IssueStatus) {
return $statusStore?.findIndex(({ _id }) => _id === status._id) ?? -1 return $statusStore.statuses.findIndex(({ _id }) => _id === status._id) ?? -1
} }
function resetDrag () { function resetDrag () {
@ -242,14 +244,14 @@
</div> </div>
</svelte:fragment> </svelte:fragment>
{#if project === undefined || statusCategories === undefined || $statusStore === undefined} {#if project === undefined || statusCategories === undefined || $statusStore.statuses.length === 0}
<Loading /> <Loading />
{:else} {:else}
<Scroller> <Scroller>
<div class="popupPanel-body__main-content py-10 clear-mins"> <div class="popupPanel-body__main-content py-10 clear-mins">
{#each statusCategories as category} {#each statusCategories as category}
{@const statuses = {@const statuses =
$statusStore?.filter((s) => s.attachedTo === projectId && s.category === category._id) ?? []} $statusStore.statuses.filter((s) => s.attachedTo === projectId && s.category === category._id) ?? []}
{@const isSingle = statuses.length === 1} {@const isSingle = statuses.length === 1}
<div class="flex-between category-name"> <div class="flex-between category-name">
<Label label={category.label} /> <Label label={category.label} />

View File

@ -677,16 +677,27 @@ export async function removeProject (project: Project): Promise<void> {
await client.removeDoc(tracker.class.Project, core.space.Space, project._id) await client.removeDoc(tracker.class.Project, core.space.Space, project._id)
} }
/**
* @public
*/
export interface StatusStore {
statuses: Array<WithLookup<IssueStatus>>
byId: IdMap<WithLookup<IssueStatus>>
version: number
}
// Issue status live query // Issue status live query
export const statusByIdStore = writable<IdMap<WithLookup<IssueStatus>>>(new Map()) export const statusStore = writable<StatusStore>({ statuses: [], byId: new Map(), version: 0 })
export const statusStore = writable<Array<WithLookup<IssueStatus>>>([])
const query = createQuery(true) const query = createQuery(true)
query.query( query.query(
tracker.class.IssueStatus, tracker.class.IssueStatus,
{}, {},
(res) => { (res) => {
statusStore.set(res) statusStore.update((old) => ({
statusByIdStore.set(toIdMap(res)) version: old.version + 1,
statuses: res,
byId: toIdMap(res)
}))
}, },
{ {
lookup: { lookup: {

View File

@ -39,6 +39,8 @@ export const genRanks = (count: number): Generator<string, void, unknown> =>
export const calcRank = (prev?: { rank: string }, next?: { rank: string }): string => { export const calcRank = (prev?: { rank: string }, next?: { rank: string }): string => {
const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min() const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min()
const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max() const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max()
if (a.equals(b)) {
return a.genNext().toString()
}
return a.between(b).toString() return a.between(b).toString()
} }

View File

@ -39,6 +39,8 @@ export async function connect (title: string): Promise<Client | undefined> {
} }
_token = token _token = token
let clientSet = false
const clientFactory = await getResource(client.function.GetClient) const clientFactory = await getResource(client.function.GetClient)
_client = await clientFactory( _client = await clientFactory(
token, token,
@ -54,7 +56,15 @@ export async function connect (title: string): Promise<Client | undefined> {
}) })
}, },
// We need to refresh all active live queries and clear old queries. // We need to refresh all active live queries and clear old queries.
refreshClient () => {
try {
if (clientSet) {
refreshClient()
}
} catch (err) {
console.error(err)
}
}
) )
console.log('logging in as', email) console.log('logging in as', email)
@ -72,6 +82,7 @@ export async function connect (title: string): Promise<Client | undefined> {
// Update on connect, so it will be triggered // Update on connect, so it will be triggered
setClient(_client) setClient(_client)
clientSet = true
return return
} }
@ -105,6 +116,7 @@ export async function connect (title: string): Promise<Client | undefined> {
return _client return _client
} }
function clearMetadata (ws: string): void { function clearMetadata (ws: string): void {
const tokens = fetchMetadataLocalStorage(login.metadata.LoginTokens) const tokens = fetchMetadataLocalStorage(login.metadata.LoginTokens)
if (tokens !== null) { if (tokens !== null) {

View File

@ -16,8 +16,8 @@
"dev-uitest": "cross-env PLATFORM_URI=http://localhost:8080 PLATFORM_TRANSACTOR=ws://localhost:3333 SETTING=storage-dev.json playwright test --browser chromium --reporter list,html -c ./tests/playwright.config.ts", "dev-uitest": "cross-env PLATFORM_URI=http://localhost:8080 PLATFORM_TRANSACTOR=ws://localhost:3333 SETTING=storage-dev.json playwright test --browser chromium --reporter list,html -c ./tests/playwright.config.ts",
"debug": "playwright test --browser chromium -c ./tests/playwright.config.ts --debug --headed", "debug": "playwright test --browser chromium -c ./tests/playwright.config.ts --debug --headed",
"dev-debug": "cross-env PLATFORM_URI=http://localhost:8080 PLATFORM_TRANSACTOR=ws://localhost:3333 SETTING=storage-dev.json playwright test --browser chromium -c ./tests/playwright.config.ts --debug --headed", "dev-debug": "cross-env PLATFORM_URI=http://localhost:8080 PLATFORM_TRANSACTOR=ws://localhost:3333 SETTING=storage-dev.json playwright test --browser chromium -c ./tests/playwright.config.ts --debug --headed",
"codegen": "playwright codegen --load-storage storage.json http://localhost:8083/workbench", "codegen": "playwright codegen --load-storage storage.json http://localhost:8083/workbench/sanity-ws/",
"dev-codegen": "cross-env playwright codegen --load-storage storage-dev.json http://localhost:8080/workbench" "dev-codegen": "cross-env playwright codegen --load-storage storage-dev.json http://localhost:8080/workbench/sanity-ws/"
}, },
"devDependencies": { "devDependencies": {
"@hcengineering/platform-rig": "^0.6.0", "@hcengineering/platform-rig": "^0.6.0",

View File

@ -205,9 +205,7 @@ test('create-issue-draft', async ({ page }) => {
// Click text=Issues >> nth=1 // Click text=Issues >> nth=1
await page.locator('text=Issues').nth(1).click() await page.locator('text=Issues').nth(1).click()
await expect(page).toHaveURL( await expect(page).toHaveURL(/.*\/workbench\/sanity-ws\/tracker\/tracker%3Aproject%3ADefaultProject\/issues/)
'http://localhost:8083/workbench/sanity-ws/tracker/tracker%3Aproject%3ADefaultProject/issues'
)
await expect(page.locator('#new-issue')).toHaveText('New issue') await expect(page.locator('#new-issue')).toHaveText('New issue')
// Click button:has-text("New issue") // Click button:has-text("New issue")
await page.locator('#new-issue').click() await page.locator('#new-issue').click()