mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
UBERF-5825: Fix github issues (#4924)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
fb6410fc74
commit
04d35dcead
@ -19,7 +19,7 @@
|
||||
"name": "_phase:validate",
|
||||
"dependencies": {
|
||||
"self": ["_phase:build"],
|
||||
"upstream": ["_phase:validate"]
|
||||
"upstream": ["_phase:validate", "_phase:build"]
|
||||
},
|
||||
"ignoreMissingScript": true,
|
||||
"allowWarningsOnSuccess": false
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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, {})
|
||||
|
@ -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>
|
||||
|
@ -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 }[]
|
||||
}
|
||||
|
@ -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
|
||||
? [
|
||||
{
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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" />
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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)
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
let extensions: ComponentPointExtension[] = []
|
||||
|
||||
getClient()
|
||||
void getClient()
|
||||
.findAll<ComponentPointExtension>(plugin.class.ComponentPointExtension, {
|
||||
extension
|
||||
})
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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" />
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,7 @@
|
||||
</script>
|
||||
|
||||
<ButtonBase
|
||||
id="priorityButton"
|
||||
type={'type-button-icon'}
|
||||
size={'small'}
|
||||
{kind}
|
||||
|
@ -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": {}
|
||||
}
|
||||
|
@ -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": {}
|
||||
}
|
@ -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": {}
|
||||
}
|
@ -285,7 +285,8 @@
|
||||
"RelatedIssueTargetDescription": "Настройка проекта по умолчанию для Класса или пространства",
|
||||
"MapRelatedIssues": "Настроить проекты по умолчанию для связанных задач",
|
||||
"DefaultIssueStatus": "Статус по умолчанию",
|
||||
"IssueStatus": "Cтатус"
|
||||
"IssueStatus": "Cтатус",
|
||||
"Extensions": "Дополнительно"
|
||||
},
|
||||
"status": {}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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}
|
||||
|
||||
|
@ -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} />
|
||||
|
@ -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}
|
||||
|
@ -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[] }) =>
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
@ -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[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user