UBERF-5825: Fix github issues (#4924)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-03-13 12:43:12 +07:00 committed by GitHub
parent fb6410fc74
commit 04d35dcead
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 342 additions and 126 deletions

View File

@ -19,7 +19,7 @@
"name": "_phase:validate",
"dependencies": {
"self": ["_phase:build"],
"upstream": ["_phase:validate"]
"upstream": ["_phase:validate", "_phase:build"]
},
"ignoreMissingScript": true,
"allowWarningsOnSuccess": false

View File

@ -51,6 +51,7 @@
"@hcengineering/tracker-resources": "^0.6.0",
"@hcengineering/ui": "^0.6.11",
"@hcengineering/view": "^0.6.9",
"@hcengineering/workbench": "^0.6.9"
"@hcengineering/workbench": "^0.6.9",
"@hcengineering/model-preference": "^0.6.0"
}
}

View File

@ -36,6 +36,7 @@ import {
TIssueTemplate,
TMilestone,
TProject,
TProjectTargetPreference,
TRelatedIssueTarget,
TTimeSpendReport,
TTypeEstimation,
@ -401,7 +402,8 @@ export function createModel (builder: Builder): void {
TTypeReportedTime,
TRelatedIssueTarget,
TTypeEstimation,
TTypeRemainingTime
TTypeRemainingTime,
TProjectTargetPreference
)
builder.mixin(tracker.class.Project, core.class.Class, activity.mixin.ActivityDoc, {})

View File

@ -44,7 +44,8 @@ export default mergeIds(trackerId, tracker, {
Unarchive: '' as IntlString,
UnarchiveConfirm: '' as IntlString,
AllProjects: '' as IntlString,
MapRelatedIssues: '' as IntlString
MapRelatedIssues: '' as IntlString,
Extensions: '' as IntlString
},
activity: {
TxIssueCreated: '' as AnyComponent,
@ -61,7 +62,8 @@ export default mergeIds(trackerId, tracker, {
EditRelatedTargetsPopup: '' as AnyComponent,
SettingsRelatedTargets: '' as AnyComponent,
IssueSearchIcon: '' as AnyComponent,
MembersArrayEditor: '' as AnyComponent
MembersArrayEditor: '' as AnyComponent,
IssueExtra: '' as AnyComponent
},
app: {
Tracker: '' as Ref<Application>

View File

@ -38,6 +38,7 @@ import {
TypeDate,
TypeMarkup,
TypeNumber,
TypeRecord,
TypeRef,
TypeString,
UX
@ -45,9 +46,10 @@ import {
import attachment from '@hcengineering/model-attachment'
import core, { TAttachedDoc, TDoc, TStatus, TType } from '@hcengineering/model-core'
import task, { TTask, TProject as TTaskProject } from '@hcengineering/model-task'
import { type IntlString } from '@hcengineering/platform'
import { getEmbeddedLabel, type IntlString } from '@hcengineering/platform'
import tags, { type TagElement } from '@hcengineering/tags'
import {
type ProjectTargetPreference,
type Component,
type Issue,
type IssueChildInfo,
@ -68,6 +70,8 @@ import {
import tracker from './plugin'
import { type TaskType } from '@hcengineering/task'
import preference, { TPreference } from '@hcengineering/model-preference'
export const DOMAIN_TRACKER = 'tracker' as Domain
@Model(tracker.class.IssueStatus, core.class.Status)
@ -392,3 +396,15 @@ export class TTypeEstimation extends TType {}
@UX(core.string.Number)
@Model(tracker.class.TypeRemainingTime, core.class.Type)
export class TTypeRemainingTime extends TType {}
@Model(tracker.class.ProjectTargetPreference, preference.class.Preference)
export class TProjectTargetPreference extends TPreference implements ProjectTargetPreference {
@Prop(TypeRef(core.class.Space), core.string.Space)
declare attachedTo: Ref<Project>
@Prop(TypeDate(), tracker.string.LastUpdated)
usedOn!: Timestamp
@Prop(TypeRecord(), getEmbeddedLabel('Properties'))
props?: { key: string, value: any }[]
}

View File

@ -19,9 +19,9 @@ import { type Builder } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import task from '@hcengineering/model-task'
import view, { showColorsViewOption } from '@hcengineering/model-view'
import tags from '@hcengineering/tags'
import { type BuildModelKey, type ViewOptionsModel } from '@hcengineering/view'
import tracker from './plugin'
import tags from '@hcengineering/tags'
export const issuesOptions = (kanban: boolean): ViewOptionsModel => ({
groupBy: [
@ -126,6 +126,13 @@ export function issueConfig (
displayProps: { compression: true },
props: { kind: 'list', full: false }
},
{
key: '',
label: tracker.string.Extensions,
presenter: tracker.component.IssueExtra,
displayProps: { compression: true },
props: { kind: 'list', full: false }
},
...(milestone
? [
{

View File

@ -56,6 +56,17 @@ export class Hierarchy {
return new Proxy(doc, this.getMixinProxyHandler(mixin)) as M
}
asIf<D extends Doc, M extends D>(doc: D | undefined, mixin: Ref<Mixin<M>>): M | undefined {
if (doc === undefined) {
return undefined
}
return this.hasMixin(doc, mixin) ? this.as(doc, mixin) : undefined
}
asIfArray<D extends Doc, M extends D>(docs: D[], mixin: Ref<Mixin<M>>): M[] {
return docs.map((it) => this.asIf(it, mixin)).filter((it) => it !== undefined) as M[]
}
static toDoc<D extends Doc>(doc: D): D {
return _toDoc(doc)
}

View File

@ -73,7 +73,7 @@
}
}
function handleOkClick () {
function handleOkClick (): void {
if (canSave) {
if (okProcessing) {
return
@ -191,15 +191,19 @@
{#if $$slots.buttons}
<slot name="buttons" />
{/if}
<Button
loading={okProcessing}
focusIndex={10001}
disabled={!canSave}
label={okLabel}
kind={'primary'}
size={'large'}
on:click={handleOkClick}
/>
{#if $$slots['after-buttons']}
<slot name="after-buttons" {handleOkClick} {okProcessing} focusIndex={10001} {canSave} {okLabel} />
{:else}
<Button
loading={okProcessing}
focusIndex={10001}
disabled={!canSave}
label={okLabel}
kind={'primary'}
size={'large'}
on:click={handleOkClick}
/>
{/if}
</div>
<div class="buttons-group small-gap text-sm">
<slot name="footer" />

View File

@ -13,14 +13,16 @@
// limitations under the License.
-->
<script lang="ts">
import type { IntlString } from '@hcengineering/platform'
import { translate, type IntlString } from '@hcengineering/platform'
import { Button, FocusHandler, Label, createFocusManager } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
import MessageViewer from './MessageViewer.svelte'
export let label: IntlString
export let labelProps: IntlString
export let message: IntlString
export let richMessage: boolean = false
export let params: Record<string, any> = {}
export let okLabel: IntlString | undefined = undefined
export let canSubmit = true
@ -36,7 +38,15 @@
<div class="msgbox-container">
<div class="overflow-label fs-title mb-4"><Label {label} params={labelProps ?? {}} /></div>
<div class="message"><Label label={message} {params} /></div>
<div class="message">
{#if richMessage}
{#await translate(message, params) then msg}
<MessageViewer message={msg} />
{/await}
{:else}
<Label label={message} {params} />
{/if}
</div>
<div class="footer">
<Button
focus
@ -48,7 +58,7 @@
on:click={() => {
processing = true
if (action !== undefined) {
action().then(() => {
void action().then(() => {
processing = false
dispatch('close', true)
})

View File

@ -25,10 +25,10 @@
export let accent: boolean = false
export let noOverflow: boolean = false
function clickHandler (e: MouseEvent) {
function clickHandler (e: MouseEvent): void {
if (disabled) return
if (onClick) {
if (onClick !== undefined) {
e.preventDefault()
e.stopPropagation()
onClick(e)

View File

@ -9,7 +9,7 @@
let extensions: ComponentPointExtension[] = []
getClient()
void getClient()
.findAll<ComponentPointExtension>(plugin.class.ComponentPointExtension, {
extension
})

View File

@ -14,10 +14,14 @@
$: filteredExtensions = $extensions.filter((it) => it.components[kind] !== undefined)
</script>
{#each filteredExtensions as extension}
{@const state = manager.getState(extension._id)}
{@const component = extension.components[kind]}
{#if component}
<Component is={component} props={{ kind, state, space, ...props }} />
{/if}
{/each}
{#if filteredExtensions.length > 0}
{#each filteredExtensions as extension}
{@const state = manager.getState(extension._id)}
{@const component = extension.components[kind]}
{#if component}
<Component is={component} props={{ kind, state, space, ...props }} />
{/if}
{/each}
{:else}
<slot />
{/if}

View File

@ -1,18 +1,18 @@
import {
SortingOrder,
type Class,
type Doc,
type DocData,
type Ref,
SortingOrder,
type Space,
type TxOperations
} from '@hcengineering/core'
import { getResource } from '@hcengineering/platform'
import { onDestroy } from 'svelte'
import { type Writable, writable } from 'svelte/store'
import { writable, type Writable } from 'svelte/store'
import { type LiveQuery } from '../..'
import presentation from '../../plugin'
import { type DocCreateExtension } from '../../types'
import { type DocCreatePhase, type DocCreateExtension } from '../../types'
import { createQuery } from '../../utils'
export class DocCreateExtensionManager {
@ -51,10 +51,16 @@ export class DocCreateExtensionManager {
)
}
async commit (ops: TxOperations, docId: Ref<Doc>, space: Ref<Space>, data: DocData<Doc>): Promise<void> {
async commit (
ops: TxOperations,
docId: Ref<Doc>,
space: Space,
data: DocData<Doc>,
phase: DocCreatePhase
): Promise<void> {
for (const e of this._extensions) {
const applyOp = await getResource(e.apply)
await applyOp?.(ops, docId, space, data, this.getState(e._id))
await applyOp?.(ops, docId, space, data, this.getState(e._id), phase)
}
}

View File

@ -88,22 +88,26 @@ export interface ComponentPointExtension extends Doc, ComponentExt {
extension: ComponentExtensionId
}
export type DocCreatePhase = 'pre' | 'post'
/**
* @public
*/
export type DocCreateFunction = (
client: TxOperations,
id: Ref<Doc>,
space: Ref<Space>,
space: Space,
document: DocData<Doc>,
extraData: Record<string, any>
extraData: Record<string, any>,
phase: DocCreatePhase
) => Promise<void>
/**
* @public
*/
export type CreateExtensionKind = 'header' | 'title' | 'body' | 'footer' | 'pool' | 'buttons'
export type CreateExtensionKind = 'header' | 'title' | 'body' | 'footer' | 'pool' | 'buttons' | 'createButton'
/**
* @public

View File

@ -206,7 +206,7 @@ export class LiveQuery implements WithTx, Client {
}
}
}
if (options?.limit === 1) {
if (options?.limit === 1 && options.total !== true) {
const docs = this.documentRefs.get(classKey)
if (docs !== undefined) {
const _docs = Array.from(docs.values()).map((it) => it.doc)

View File

@ -39,6 +39,7 @@
export let inheritFont: boolean = false
export let tooltip: LabelAndProps | undefined = undefined
export let element: HTMLButtonElement | undefined = undefined
export let id: string | undefined = undefined
let actualIconSize: IconSize = 'small'
@ -77,6 +78,7 @@
</script>
<button
{id}
bind:this={element}
class="font-medium-14 {kind} {size} {type}"
class:loading

View File

@ -24,18 +24,24 @@
SelectPopupValueType,
eventToHTMLElement,
showPopup,
LabelAndProps
LabelAndProps,
ButtonSize
} from '../index'
import { createEventDispatcher } from 'svelte'
export let dropdownItems: SelectPopupValueType[]
export let label: IntlString | undefined = undefined
export let labelParams: Record<string, any> = {}
export let kind: ButtonKind = 'primary'
export let size: ButtonSize = 'medium'
export let justify: 'left' | 'center' = 'center'
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let dropdownIcon: Asset | AnySvelteComponent | undefined = undefined
export let showTooltipMain: LabelAndProps | undefined = undefined
export let mainButtonId: string | undefined = undefined
export let disabled: boolean = false
export let loading: boolean = false
export let focusIndex: number | undefined = undefined
const dispatch = createEventDispatcher()
@ -49,9 +55,12 @@
<div class="w-full flex-row-center">
<div class="flex-grow">
<Button
{focusIndex}
width="100%"
{icon}
{size}
{kind}
disabled={disabled || loading}
shape="rectangle-right"
{justify}
borderStyle="none"
@ -62,7 +71,7 @@
<div class="flex w-full" slot="content">
<div class="flex-row-center w-full flex-between relative">
{#if label}
<Label {label} />
<Label {label} params={labelParams} />
<slot name="content" />
<div class="{kind} vertical-divider max-h-5 h-5" />
{/if}
@ -70,7 +79,17 @@
</div>
</Button>
</div>
<Button width="1.75rem" {kind} shape="rectangle-left" justify="center" borderStyle="none" on:click={openDropdown}>
<Button
width="1.75rem"
{kind}
shape="rectangle-left"
justify="center"
borderStyle="none"
on:click={openDropdown}
{size}
{disabled}
{loading}
>
<div slot="icon">
{#if dropdownIcon}
<Icon icon={dropdownIcon} size="small" />

View File

@ -67,8 +67,7 @@ class ModelClient implements AccountClient {
if (this.notifyEnabled) {
console.debug(
'devmodel# notify=>',
testing ? JSON.stringify(cutObjectArray(tx)).slice(0, 160) : tx,
getMetadata(devmodel.metadata.DevModel)
testing ? JSON.stringify(cutObjectArray(tx)).slice(0, 160) : tx.length === 1 ? tx[0] : tx
)
}
}

View File

@ -78,6 +78,7 @@
</script>
<ButtonBase
id="priorityButton"
type={'type-button-icon'}
size={'small'}
{kind}

View File

@ -285,7 +285,8 @@
"RelatedIssueTargetDescription": "Related issue project target for Class or Space",
"MapRelatedIssues": "Configure Related issue default projects",
"DefaultIssueStatus": "Default issue status",
"IssueStatus": "Status"
"IssueStatus": "Status",
"Extensions": "Extensions"
},
"status": {}
}

View File

@ -268,7 +268,8 @@
"RelatedIssueTargetDescription": "Proyecto de destino de problema relacionado para Clase o Espacio",
"MapRelatedIssues": "Configurar proyectos predeterminados de problemas relacionados",
"DefaultIssueStatus": "Estado de problema predeterminado",
"IssueStatus": "Estado"
"IssueStatus": "Estado",
"Extensions": "Extensions"
},
"status": {}
}

View File

@ -268,7 +268,8 @@
"RelatedIssueTargetDescription": "Projeto alvo de problemas relacionados para Classe ou Espaço",
"MapRelatedIssues": "Configurar projetos padrão de problemas relacionados",
"DefaultIssueStatus": "Estado padrão do problema",
"IssueStatus": "Estado"
"IssueStatus": "Estado",
"Extensions": "Extensions"
},
"status": {}
}

View File

@ -285,7 +285,8 @@
"RelatedIssueTargetDescription": "Настройка проекта по умолчанию для Класса или пространства",
"MapRelatedIssues": "Настроить проекты по умолчанию для связанных задач",
"DefaultIssueStatus": "Статус по умолчанию",
"IssueStatus": "Cтатус"
"IssueStatus": "Cтатус",
"Extensions": "Дополнительно"
},
"status": {}
}

View File

@ -55,7 +55,8 @@
IssueStatus,
IssueTemplate,
Milestone,
Project
Project,
ProjectTargetPreference
} from '@hcengineering/tracker'
import {
Button,
@ -119,7 +120,7 @@
const parentQuery = createQuery()
let _space = draft?.space ?? space
let project: Project | undefined
// let project: Project | undefined
let object = getDefaultObjectFromDraft() ?? getDefaultObject(id)
let isAssigneeTouched = false
let kind: Ref<TaskType> | undefined = undefined
@ -217,7 +218,11 @@
$: updateIssueStatusId(object, currentProject)
$: updateAssigneeId(object, currentProject)
$: canSave = getTitle(object.title ?? '').length > 0 && object.status !== undefined && kind !== undefined
$: canSave =
getTitle(object.title ?? '').length > 0 &&
object.status !== undefined &&
kind !== undefined &&
currentProject !== undefined
$: empty = {
assignee: assignee ?? currentProject?.defaultAssignee,
@ -395,9 +400,22 @@
}
}
const projectPreferences = createQuery()
let preferences: ProjectTargetPreference[] = []
$: projectPreferences.query(tracker.class.ProjectTargetPreference, {}, (res) => {
preferences = res
})
$: spacePreferences = preferences.find((it) => it.attachedTo === _space)
async function createIssue (): Promise<void> {
const _id: Ref<Issue> = generateId()
if (!canSave || object.status === undefined || _space === undefined || kind === undefined) {
if (
!canSave ||
object.status === undefined ||
_space === undefined ||
kind === undefined ||
currentProject === undefined
) {
return
}
@ -462,7 +480,7 @@
identifier
}
await docCreateManager.commit(operations, _id, _space, value)
await docCreateManager.commit(operations, _id, currentProject, value, 'pre')
await operations.addCollection(
tracker.class.Issue,
@ -473,6 +491,7 @@
value,
_id
)
await docCreateManager.commit(operations, _id, currentProject, value, 'post')
for (const label of object.labels) {
await operations.addCollection(label._class, label.space, _id, tracker.class.Issue, 'labels', {
title: label.title,
@ -496,18 +515,19 @@
await operations.commit()
await descriptionBox.createAttachments(_id)
const parents: IssueParentInfo[] = parentIssue
? [
{ parentId: _id, parentTitle: value.title, space: parentIssue.space, identifier },
{
parentId: parentIssue._id,
parentTitle: parentIssue.title,
space: parentIssue.space,
identifier: parentIssue.identifier
},
...parentIssue.parents
]
: [{ parentId: _id, parentTitle: value.title, space: _space, identifier }]
const parents: IssueParentInfo[] =
parentIssue != null
? [
{ parentId: _id, parentTitle: value.title, space: parentIssue.space, identifier },
{
parentId: parentIssue._id,
parentTitle: parentIssue.title,
space: parentIssue.space,
identifier: parentIssue.identifier
},
...parentIssue.parents
]
: [{ parentId: _id, parentTitle: value.title, space: _space, identifier }]
await subIssuesComponent.save(parents, _id)
addNotification(
await translate(tracker.string.IssueCreated, {}, $themeStore.language),
@ -528,6 +548,9 @@
console.log('createIssue measure', res, Date.now() - d1)
})
} catch (err: any) {
resetObject()
draftController.remove()
descriptionBox?.removeDraft(false)
console.error(err)
await doneOp() // Complete in case of error
Analytics.handleError(err)
@ -644,6 +667,18 @@
undefined
}
if (targetRef === undefined) {
// Use last created issue in first.
const projects = await client.findAll(
tracker.class.ProjectTargetPreference,
{},
{ sort: { usedOn: SortingOrder.Descending } }
)
if (projects.length > 0) {
targetRef = projects[0]?.attachedTo
}
}
// Find first starred project
if (targetRef === undefined) {
const prefs = await client.findAll<SpacePreference>(
@ -674,7 +709,26 @@
milestone: object.milestone,
relatedTo,
parentIssue,
originalIssue
originalIssue,
preferences
}
function updateCurrentProjectPref (currentProject: Ref<Project>): void {
if (spacePreferences === undefined) {
void client.createDoc(tracker.class.ProjectTargetPreference, currentProject, {
attachedTo: currentProject,
props: [],
usedOn: Date.now()
})
} else {
void client.update(spacePreferences, {
usedOn: Date.now()
})
}
}
$: if (_space !== undefined) {
updateCurrentProjectPref(_space)
}
</script>
@ -698,7 +752,7 @@
label={tracker.string.Project}
bind:space={_space}
on:object={(evt) => {
project = evt.detail
currentProject = evt.detail
}}
kind={'regular'}
size={'small'}
@ -727,10 +781,10 @@
</svelte:fragment>
<svelte:fragment slot="title" let:label>
<div class="flex-row-center gap-2">
<div class="mr-2">
<div>
<Label {label} />
</div>
<TaskKindSelector projectType={project?.type} bind:value={kind} baseClass={tracker.class.Issue} />
<TaskKindSelector projectType={currentProject?.type} bind:value={kind} baseClass={tracker.class.Issue} />
{#if relatedTo}
<div class="lower mr-2">
<Label label={tracker.string.RelatedTo} />
@ -952,4 +1006,29 @@
<svelte:fragment slot="buttons">
<DocCreateExtComponent manager={docCreateManager} kind={'buttons'} space={currentProject} props={extraProps} />
</svelte:fragment>
<svelte:fragment slot="after-buttons" let:handleOkClick let:okProcessing let:focusIndex let:canSave let:okLabel>
<DocCreateExtComponent
manager={docCreateManager}
kind={'createButton'}
space={currentProject}
props={{
...extraProps,
handleOkClick,
okProcessing,
focusIndex,
canSave,
okLabel
}}
>
<Button
loading={okProcessing}
focusIndex={10001}
disabled={!canSave}
label={okLabel}
kind={'primary'}
size={'large'}
on:click={handleOkClick}
/>
</DocCreateExtComponent>
</svelte:fragment>
</Card>

View File

@ -15,9 +15,8 @@
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { translate } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { Component } from '@hcengineering/tracker'
import { Icon, Component as UIComponent, themeStore } from '@hcengineering/ui'
import { Icon, themeStore } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import tracker from '../../plugin'
@ -45,11 +44,6 @@
})
}
$: disabled = disabled || value === undefined
$: presenters =
value !== undefined ? getClient().getHierarchy().findMixinMixins(value, view.mixin.ObjectPresenter) : []
$: icon = tracker.icon.Component
</script>
<div class="flex-row-center">
@ -61,7 +55,7 @@
<div class="flex-row-center">
{#if shouldShowAvatar}
<div class="icon">
<Icon icon={presenters.length === 0 ? tracker.icon.Component : icon} size={'small'} />
<Icon icon={tracker.icon.Component} size={'small'} />
</div>
{/if}
<span title={label} class="label nowrap" class:no-underline={disabled || noUnderline} class:fs-bold={accent}>
@ -71,20 +65,4 @@
</span>
</DocNavLink>
{/if}
{#if presenters.length > 0}
<div class="flex-row-center">
{#each presenters as mixinPresenter}
<UIComponent
is={mixinPresenter.presenter}
props={{ value }}
on:open={(evt) => {
if (evt.detail.icon !== undefined) {
icon = evt.detail.icon
}
}}
/>
{/each}
</div>
{/if}
</div>

View File

@ -0,0 +1,63 @@
<!--
// Copyright © 2022 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 { getClient } from '@hcengineering/presentation'
import type { Issue } from '@hcengineering/tracker'
import { Component } from '@hcengineering/ui'
import view from '@hcengineering/view'
export let value: WithLookup<Issue>
export let shouldUseMargin: boolean = false
export let showParent: boolean = true
export let kind: 'list' | undefined = undefined
export let disabled: boolean = false
export let maxWidth: string | undefined = undefined
$: presenters =
value !== undefined ? getClient().getHierarchy().findMixinMixins(value, view.mixin.ObjectPresenter) : []
</script>
{#if value}
<span
class="presenter-label select-text p-1"
class:with-margin={shouldUseMargin}
class:list={kind === 'list'}
style:max-width={maxWidth}
title={value.title}
>
{#if presenters.length > 0}
<div class="flex-row-center">
{#each presenters as mixinPresenter}
<Component is={mixinPresenter.presenter} props={{ value }} />
{/each}
</div>
{/if}
</span>
{/if}
<style lang="scss">
.presenter-label {
overflow: hidden;
display: inline-flex;
align-items: center;
flex-shrink: 1;
min-width: 1rem;
}
.with-margin {
margin-left: 0.5rem;
}
</style>

View File

@ -15,12 +15,10 @@
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { Asset } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { taskTypeStore } from '@hcengineering/task-resources'
import TaskTypeIcon from '@hcengineering/task-resources/src/components/taskTypes/TaskTypeIcon.svelte'
import type { Issue } from '@hcengineering/tracker'
import { AnySvelteComponent, Component, Icon, tooltip } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { AnySvelteComponent, Icon, tooltip } from '@hcengineering/ui'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import tracker from '../../plugin'
@ -35,21 +33,11 @@
export let kind: 'list' | undefined = undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
$: presenters =
value !== undefined ? getClient().getHierarchy().findMixinMixins(value, view.mixin.ObjectPresenter) : []
$: taskType = value !== undefined ? $taskTypeStore.get(value.kind) : undefined
</script>
{#if inline && value}
<ObjectMention object={value} {disabled} {noUnderline} {onClick} component={tracker.component.EditIssue} />
{#if presenters.length > 0}
<div class="flex-row-center">
{#each presenters as mixinPresenter}
<Component is={mixinPresenter.presenter} props={{ value }} />
{/each}
</div>
{/if}
{:else if value}
<div class="flex-row-center">
<DocNavLink
@ -77,13 +65,6 @@
</span>
</span>
</DocNavLink>
{#if presenters.length > 0}
<div class="flex-row-center">
{#each presenters as mixinPresenter}
<Component is={mixinPresenter.presenter} props={{ value }} />
{/each}
</div>
{/if}
</div>
{/if}

View File

@ -206,13 +206,13 @@
{/if}
<ComponentExtensions
extension={tracker.extensions.EditIssueTitle}
props={{ size: 'medium', value: issue, readonly }}
props={{ size: 'medium', kind: 'ghost', space: issue.space, issue, readonly }}
/>
</svelte:fragment>
<svelte:fragment slot="pre-utils">
<ComponentExtensions
extension={tracker.extensions.EditIssueHeader}
props={{ size: 'medium', kind: 'ghost', space: issue.space, readonly }}
props={{ size: 'medium', kind: 'ghost', space: issue.space, readonly, issue }}
/>
{#if saved}
<Label label={presentation.string.Saved} />

View File

@ -122,6 +122,7 @@
/>
{:else if onlyIcon || milestoneText === undefined}
<Button
id="milestone"
{focusIndex}
{kind}
{size}
@ -136,6 +137,7 @@
/>
{:else}
<Button
id="milestone"
{focusIndex}
{kind}
{size}

View File

@ -84,6 +84,7 @@ import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
import SettingsRelatedTargets from './components/SettingsRelatedTargets.svelte'
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
import IssueExtra from './components/issues/IssueExtra.svelte'
import {
getIssueTitle,
getTitle,
@ -508,7 +509,8 @@ export default async (): Promise<Resources> => ({
MilestoneStatusIcon,
PriorityIconPresenter,
IssueSearchIcon,
MembersArrayEditor
MembersArrayEditor,
IssueExtra
},
completion: {
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>

View File

@ -76,7 +76,6 @@ export default mergeIds(trackerId, tracker, {
Completed: '' as IntlString,
Canceled: '' as IntlString,
CreateProject: '' as IntlString,
NewProject: '' as IntlString,
ProjectTitle: '' as IntlString,
ProjectTitlePlaceholder: '' as IntlString,
ProjectIdentifierPlaceholder: '' as IntlString,
@ -151,7 +150,6 @@ export default mergeIds(trackerId, tracker, {
SetDueDate: '' as IntlString,
ChangeDueDate: '' as IntlString,
ModificationDate: '' as IntlString,
Issue: '' as IntlString,
SubIssue: '' as IntlString,
IssueTemplate: '' as IntlString,
Document: '' as IntlString,
@ -333,7 +331,6 @@ export default mergeIds(trackerId, tracker, {
AssigneeEditor: '' as AnyComponent,
DueDatePresenter: '' as AnyComponent,
EditIssueTemplate: '' as AnyComponent,
CreateProject: '' as AnyComponent,
ProjectSpacePresenter: '' as AnyComponent,
NewIssueHeader: '' as AnyComponent,
IconPresenter: '' as AnyComponent,

View File

@ -42,6 +42,7 @@
"@hcengineering/attachment": "^0.6.9",
"@hcengineering/task": "^0.6.13",
"@hcengineering/tags": "^0.6.12",
"@hcengineering/preference": "^0.6.9",
"lexorank": "~1.0.4"
},
"repository": "https://github.com/hcengineering/anticrm",

View File

@ -33,6 +33,7 @@ import {
WithLookup
} from '@hcengineering/core'
import { Asset, IntlString, Plugin, Resource, plugin } from '@hcengineering/platform'
import { Preference } from '@hcengineering/preference'
import { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
import task, {
ProjectTypeDescriptor,
@ -62,6 +63,17 @@ export interface Project extends TaskProject, IconProps {
defaultTimeReportDay: TimeReportDayType
}
/**
* @public
*/
export interface ProjectTargetPreference extends Preference {
attachedTo: Ref<Project> // tracker.ids.ProjectPreferences
usedOn: Timestamp
props?: { key: string, value: any }[]
}
export type RelatedIssueKind = 'classRule' | 'spaceRule'
export interface RelatedClassRule {
@ -411,7 +423,8 @@ const pluginState = plugin(trackerId, {
TypeReportedTime: '' as Ref<Class<Type<number>>>,
TypeEstimation: '' as Ref<Class<Type<number>>>,
TypeRemainingTime: '' as Ref<Class<Type<number>>>,
RelatedIssueTarget: '' as Ref<Class<RelatedIssueTarget>>
RelatedIssueTarget: '' as Ref<Class<RelatedIssueTarget>>,
ProjectTargetPreference: '' as Ref<Class<ProjectTargetPreference>>
},
ids: {
NoParent: '' as Ref<Issue>,
@ -428,7 +441,8 @@ const pluginState = plugin(trackerId, {
EditIssue: '' as AnyComponent,
CreateIssue: '' as AnyComponent,
ProjectPresenter: '' as AnyComponent,
CreateIssueTemplate: '' as AnyComponent
CreateIssueTemplate: '' as AnyComponent,
CreateProject: '' as AnyComponent
},
attribute: {
IssueStatus: '' as Ref<Attribute<Status>>
@ -531,7 +545,9 @@ const pluginState = plugin(trackerId, {
IssueNotificationMessage: '' as IntlString,
IssueAssigneedToYou: '' as IntlString,
Project: '' as IntlString,
RelatedIssues: '' as IntlString
RelatedIssues: '' as IntlString,
Issue: '' as IntlString,
NewProject: '' as IntlString
},
extensions: {
IssueListHeader: '' as ComponentExtensionId,

View File

@ -615,7 +615,7 @@ class TServerStorage implements ServerStorage {
)
const moves = await ctx.with('process-move', {}, (ctx) => this.processMove(ctx, txes, findAll))
const triggerControl: Omit<TriggerControl, 'txFactory' | 'ctx'> = {
const triggerControl: Omit<TriggerControl, 'txFactory' | 'ctx' | 'result'> = {
removedMap,
workspace: this.workspace,
fx: triggerFx.fx,
@ -651,7 +651,8 @@ class TServerStorage implements ServerStorage {
...(await this.triggers.apply(ctx, txes, {
...triggerControl,
ctx,
findAll: fAll(ctx)
findAll: fAll(ctx),
result
}))
)
return result

View File

@ -78,7 +78,8 @@ export class Triggers {
findAll: async (clazz, query, options) => await ctrl.findAllCtx(ctx, clazz, query, options),
apply: async (tx, broadcast, target) => {
return await ctrl.applyCtx(ctx, tx, broadcast, target)
}
},
result
}))
)
}

View File

@ -144,6 +144,9 @@ export interface TriggerControl {
query: DocumentQuery<T>,
options?: FindOptions<T>
) => Promise<FindResult<T>>
// Current set of transactions to being processed for apply/bulks
result: Tx[]
}
/**

View File

@ -381,7 +381,7 @@ test.describe('Tracker filters tests', () => {
await issuesPage.checkFilter('Milestone', 'is', '1 state')
for await (const issue of iterateLocator(issuesPage.issuesList)) {
await expect(issue.locator('div.compression-bar button span.label')).toContainText(filterMilestoneName)
await expect(issue.locator('div.compression-bar #milestone span.label')).toContainText(filterMilestoneName)
}
})