mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
UBER-953: Fix related issues (#3821)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
8b9c90b388
commit
bc3598ce15
@ -189,6 +189,9 @@ export function createModel (builder: Builder): void {
|
||||
createAction(builder, {
|
||||
...viewTemplates.open,
|
||||
target: lead.class.Funnel,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
context: {
|
||||
mode: ['browser', 'context'],
|
||||
group: 'create'
|
||||
@ -645,4 +648,26 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
lead.action.CreateGlobalLead
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: lead.component.CreateFunnel,
|
||||
_id: 'customer',
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_object: 'funnel'
|
||||
}
|
||||
},
|
||||
label: lead.string.EditFunnel,
|
||||
icon: lead.icon.Funnel,
|
||||
input: 'focus',
|
||||
category: lead.category.Lead,
|
||||
target: lead.class.Funnel,
|
||||
override: [view.action.Open],
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'associate'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -14,9 +14,11 @@
|
||||
//
|
||||
|
||||
import { Ref, TxOperations } from '@hcengineering/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
|
||||
import { leadId } from '@hcengineering/lead'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient, tryUpgrade } from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import { createKanbanTemplate, createSequence } from '@hcengineering/model-task'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
import task, { KanbanTemplate, createStates } from '@hcengineering/task'
|
||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||
import lead from './plugin'
|
||||
@ -120,5 +122,20 @@ export const leadOperation: MigrateOperation = {
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const ops = new TxOperations(client, core.account.System)
|
||||
await createDefaults(ops)
|
||||
|
||||
await tryUpgrade(client, leadId, [
|
||||
{
|
||||
state: 'related-targets',
|
||||
func: async (client): Promise<void> => {
|
||||
const ops = new TxOperations(client, core.account.ConfigUser)
|
||||
await ops.createDoc(tracker.class.RelatedIssueTarget, core.space.Configuration, {
|
||||
rule: {
|
||||
kind: 'classRule',
|
||||
ofClass: lead.class.Lead
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ export default mergeIds(leadId, lead, {
|
||||
Title: '' as IntlString,
|
||||
ManageFunnelStatuses: '' as IntlString,
|
||||
GotoLeadApplication: '' as IntlString,
|
||||
ConfigDescription: '' as IntlString
|
||||
ConfigDescription: '' as IntlString,
|
||||
EditFunnel: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
CreateLead: '' as AnyComponent,
|
||||
|
@ -88,8 +88,6 @@ export class TVacancy extends TSpaceWithStates implements Vacancy {
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
comments?: number
|
||||
|
||||
relations!: number
|
||||
|
||||
@Prop(TypeString(), recruit.string.Vacancy)
|
||||
@Index(IndexKind.FullText)
|
||||
@Hidden()
|
||||
|
@ -15,12 +15,20 @@
|
||||
|
||||
import { getCategories } from '@anticrm/skillset'
|
||||
import core, { Doc, Ref, Space, TxOperations } from '@hcengineering/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model'
|
||||
import {
|
||||
MigrateOperation,
|
||||
MigrationClient,
|
||||
MigrationUpgradeClient,
|
||||
createOrUpdate,
|
||||
tryUpgrade
|
||||
} from '@hcengineering/model'
|
||||
import tags, { TagCategory } from '@hcengineering/model-tags'
|
||||
import { createKanbanTemplate, createSequence } from '@hcengineering/model-task'
|
||||
import task, { KanbanTemplate } from '@hcengineering/task'
|
||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||
import recruit from './plugin'
|
||||
import { recruitId } from '@hcengineering/recruit'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
|
||||
export const recruitOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
@ -28,6 +36,28 @@ export const recruitOperation: MigrateOperation = {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
await createDefaults(tx)
|
||||
await fixTemplateSpace(tx)
|
||||
|
||||
await tryUpgrade(client, recruitId, [
|
||||
{
|
||||
state: 'related-targets',
|
||||
func: async (client): Promise<void> => {
|
||||
const ops = new TxOperations(client, core.account.ConfigUser)
|
||||
await ops.createDoc(tracker.class.RelatedIssueTarget, core.space.Configuration, {
|
||||
rule: {
|
||||
kind: 'classRule',
|
||||
ofClass: recruit.class.Vacancy
|
||||
}
|
||||
})
|
||||
|
||||
await ops.createDoc(tracker.class.RelatedIssueTarget, core.space.Configuration, {
|
||||
rule: {
|
||||
kind: 'classRule',
|
||||
ofClass: recruit.class.Applicant
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
756
models/tracker/src/actions.ts
Normal file
756
models/tracker/src/actions.ts
Normal file
@ -0,0 +1,756 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Builder } from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import task from '@hcengineering/model-task'
|
||||
import view, { actionTemplates, createAction } from '@hcengineering/model-view'
|
||||
import workbench, { createNavigateAction } from '@hcengineering/model-workbench'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { trackerId } from '@hcengineering/tracker'
|
||||
import { KeyBinding, ViewAction } from '@hcengineering/view'
|
||||
import tracker from './plugin'
|
||||
|
||||
import tags from '@hcengineering/tags'
|
||||
import { defaultPriorities, issuePriorities } from '@hcengineering/tracker-resources/src/types'
|
||||
|
||||
function createGotoSpecialAction (
|
||||
builder: Builder,
|
||||
id: string,
|
||||
key: KeyBinding,
|
||||
label: IntlString,
|
||||
query?: Record<string, string | null>
|
||||
): void {
|
||||
createNavigateAction(builder, key, label, tracker.app.Tracker, {
|
||||
application: trackerId,
|
||||
mode: 'space',
|
||||
spaceSpecial: id,
|
||||
spaceClass: tracker.class.Project,
|
||||
query
|
||||
})
|
||||
}
|
||||
export function createActions (builder: Builder, issuesId: string, componentsId: string, myIssuesId: string): void {
|
||||
createGotoSpecialAction(builder, issuesId, 'keyG->keyE', tracker.string.GotoIssues)
|
||||
createGotoSpecialAction(builder, issuesId, 'keyG->keyA', tracker.string.GotoActive, { mode: 'active' })
|
||||
createGotoSpecialAction(builder, issuesId, 'keyG->keyB', tracker.string.GotoBacklog, { mode: 'backlog' })
|
||||
createGotoSpecialAction(builder, componentsId, 'keyG->keyC', tracker.string.GotoComponents)
|
||||
createNavigateAction(builder, 'keyG->keyM', tracker.string.GotoMyIssues, tracker.app.Tracker, {
|
||||
application: trackerId,
|
||||
mode: 'special',
|
||||
special: myIssuesId
|
||||
})
|
||||
|
||||
createAction(builder, {
|
||||
action: workbench.actionImpl.Navigate,
|
||||
actionProps: {
|
||||
mode: 'app',
|
||||
application: trackerId
|
||||
},
|
||||
label: tracker.string.GotoTrackerApplication,
|
||||
icon: view.icon.ArrowRight,
|
||||
input: 'none',
|
||||
category: view.category.Navigation,
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['workbench', 'browser', 'editor', 'panel', 'popup']
|
||||
}
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: tracker.actionImpl.EditWorkflowStatuses,
|
||||
label: tracker.string.EditWorkflowStatuses,
|
||||
icon: view.icon.Statuses,
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Project,
|
||||
override: [task.action.EditStatuses],
|
||||
query: {},
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.EditWorkflowStatuses
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: tracker.actionImpl.EditProject,
|
||||
label: tracker.string.EditProject,
|
||||
icon: contact.icon.Edit,
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Project,
|
||||
query: {},
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.EditProject
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: tracker.actionImpl.DeleteProject,
|
||||
label: workbench.string.Archive,
|
||||
icon: view.icon.Archive,
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Project,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'edit'
|
||||
},
|
||||
override: [view.action.Archive, view.action.Delete]
|
||||
},
|
||||
tracker.action.DeleteProject
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: tracker.actionImpl.DeleteProject,
|
||||
label: workbench.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Project,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'edit'
|
||||
},
|
||||
override: [view.action.Archive, view.action.Delete]
|
||||
},
|
||||
tracker.action.DeleteProjectClean
|
||||
)
|
||||
createAction(builder, {
|
||||
label: tracker.string.Unarchive,
|
||||
icon: view.icon.Archive,
|
||||
action: view.actionImpl.UpdateDocument as ViewAction,
|
||||
actionProps: {
|
||||
key: 'archived',
|
||||
ask: true,
|
||||
value: false,
|
||||
label: tracker.string.Unarchive,
|
||||
message: tracker.string.UnarchiveConfirm
|
||||
},
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'tools'
|
||||
},
|
||||
target: tracker.class.Project
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: tracker.actionImpl.DeleteIssue,
|
||||
label: workbench.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'remove'
|
||||
},
|
||||
override: [view.action.Delete]
|
||||
},
|
||||
tracker.action.DeleteIssue
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: tracker.string.TrackerApplication, visible: true },
|
||||
tracker.category.Tracker
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.CreateIssue,
|
||||
element: 'top'
|
||||
},
|
||||
label: tracker.string.NewIssue,
|
||||
icon: tracker.icon.NewIssue,
|
||||
keyBinding: ['keyC'],
|
||||
input: 'none',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'create'
|
||||
}
|
||||
},
|
||||
tracker.action.NewIssue
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.CreateIssue,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_object: 'parentIssue',
|
||||
space: 'space'
|
||||
}
|
||||
},
|
||||
label: tracker.string.NewSubIssue,
|
||||
icon: tracker.icon.Subissue,
|
||||
keyBinding: [],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
tracker.action.NewSubIssue
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.SetParentIssueActionPopup,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_objects: 'value'
|
||||
}
|
||||
},
|
||||
label: tracker.string.SetParent,
|
||||
icon: tracker.icon.Parent,
|
||||
keyBinding: [],
|
||||
input: 'none',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
tracker.action.SetParent
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.CreateIssue,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_object: 'relatedTo',
|
||||
space: 'space'
|
||||
}
|
||||
},
|
||||
label: tracker.string.NewRelatedIssue,
|
||||
icon: tracker.icon.NewIssue,
|
||||
keyBinding: [],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['context', 'browser', 'editor'],
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
tracker.action.NewRelatedIssue
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionPopup: tracker.component.SetParentIssueActionPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.SetParentIssueActionPopup,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_object: 'value'
|
||||
}
|
||||
},
|
||||
label: tracker.string.SetParent,
|
||||
icon: tracker.icon.Parent,
|
||||
keyBinding: [],
|
||||
input: 'none',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
override: [tracker.action.SetParent],
|
||||
context: {
|
||||
mode: ['browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'associate'
|
||||
}
|
||||
})
|
||||
|
||||
createAction(builder, {
|
||||
...actionTemplates.open,
|
||||
actionProps: {
|
||||
component: tracker.component.EditIssue
|
||||
},
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['browser', 'context'],
|
||||
group: 'create'
|
||||
},
|
||||
override: [view.action.Open]
|
||||
})
|
||||
|
||||
createAction(builder, {
|
||||
...actionTemplates.open,
|
||||
actionProps: {
|
||||
component: tracker.component.EditIssueTemplate
|
||||
},
|
||||
target: tracker.class.IssueTemplate,
|
||||
context: {
|
||||
mode: ['browser', 'context'],
|
||||
group: 'create'
|
||||
},
|
||||
override: [view.action.Open]
|
||||
})
|
||||
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.TimeSpendReportPopup,
|
||||
fillProps: {
|
||||
_object: 'issue'
|
||||
}
|
||||
},
|
||||
label: tracker.string.TimeSpendReportAdd,
|
||||
icon: tracker.icon.TimeReport,
|
||||
input: 'focus',
|
||||
keyBinding: ['keyT'],
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: task.actionImpl.SelectStatus,
|
||||
actionPopup: task.component.StatusSelector,
|
||||
actionProps: {
|
||||
_class: tracker.class.IssueStatus,
|
||||
ofAttribute: tracker.attribute.IssueStatus,
|
||||
placeholder: tracker.string.Status
|
||||
},
|
||||
label: tracker.string.Status,
|
||||
icon: tracker.icon.CategoryBacklog,
|
||||
keyBinding: ['keyS->keyS'],
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.SetStatus
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ValueSelector,
|
||||
actionPopup: view.component.ValueSelector,
|
||||
actionProps: {
|
||||
attribute: 'priority',
|
||||
values: defaultPriorities.map((p) => ({ id: p, ...issuePriorities[p] })),
|
||||
placeholder: tracker.string.SetPriority
|
||||
},
|
||||
label: tracker.string.Priority,
|
||||
icon: tracker.icon.PriorityHigh,
|
||||
keyBinding: ['keyP->keyR'],
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.SetPriority
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ValueSelector,
|
||||
actionPopup: view.component.ValueSelector,
|
||||
actionProps: {
|
||||
attribute: 'assignee',
|
||||
_class: contact.mixin.Employee,
|
||||
query: {},
|
||||
placeholder: tracker.string.AssignTo
|
||||
},
|
||||
label: tracker.string.Assignee,
|
||||
icon: contact.icon.Person,
|
||||
keyBinding: ['keyA'],
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.SetAssignee
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ValueSelector,
|
||||
actionPopup: view.component.ValueSelector,
|
||||
actionProps: {
|
||||
attribute: 'component',
|
||||
_class: tracker.class.Component,
|
||||
query: {},
|
||||
fillQuery: { space: 'space' },
|
||||
docMatches: ['space'],
|
||||
searchField: 'label',
|
||||
placeholder: tracker.string.Component
|
||||
},
|
||||
label: tracker.string.Component,
|
||||
icon: tracker.icon.Component,
|
||||
keyBinding: ['keyM->keyT'],
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.SetComponent
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.AttributeSelector,
|
||||
actionPopup: tracker.component.MilestoneEditor,
|
||||
actionProps: {
|
||||
attribute: 'milestone',
|
||||
isAction: true
|
||||
},
|
||||
label: tracker.string.Milestone,
|
||||
icon: tracker.icon.Milestone,
|
||||
keyBinding: ['keyS->keyP'],
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.SetMilestone
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tags.component.TagsEditorPopup,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_object: 'object'
|
||||
}
|
||||
},
|
||||
label: tracker.string.Labels,
|
||||
icon: tags.icon.Tags,
|
||||
keyBinding: ['keyL'],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.SetLabels
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tags.component.ObjectsTagsEditorPopup,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_objects: 'value'
|
||||
}
|
||||
},
|
||||
label: tracker.string.Labels,
|
||||
icon: tags.icon.Tags,
|
||||
keyBinding: ['keyL'],
|
||||
input: 'selection',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.SetLabels
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.SetDueDateActionPopup,
|
||||
props: { mondayStart: true, withTime: false },
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_objects: 'value'
|
||||
}
|
||||
},
|
||||
label: tracker.string.SetDueDate,
|
||||
icon: tracker.icon.DueDate,
|
||||
keyBinding: ['keyD'],
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
tracker.action.SetDueDate
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.CopyTextToClipboard,
|
||||
actionProps: {
|
||||
textProvider: tracker.function.GetIssueId
|
||||
},
|
||||
label: tracker.string.CopyIssueId,
|
||||
icon: view.icon.CopyId,
|
||||
keyBinding: [],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'copy'
|
||||
}
|
||||
},
|
||||
tracker.action.CopyIssueId
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.CopyTextToClipboard,
|
||||
actionProps: {
|
||||
textProvider: tracker.function.GetIssueTitle
|
||||
},
|
||||
label: tracker.string.CopyIssueTitle,
|
||||
icon: tracker.icon.CopyBranch,
|
||||
keyBinding: [],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'copy'
|
||||
}
|
||||
},
|
||||
tracker.action.CopyIssueTitle
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.CopyTextToClipboard,
|
||||
actionProps: {
|
||||
textProvider: tracker.function.GetIssueLink
|
||||
},
|
||||
label: tracker.string.CopyIssueUrl,
|
||||
icon: view.icon.CopyLink,
|
||||
keyBinding: [],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'copy'
|
||||
}
|
||||
},
|
||||
tracker.action.CopyIssueLink
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: tracker.actionImpl.Move,
|
||||
label: tracker.string.MoveToProject,
|
||||
icon: view.icon.Move,
|
||||
keyBinding: [],
|
||||
input: 'any',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'associate'
|
||||
},
|
||||
override: [task.action.Move]
|
||||
},
|
||||
tracker.action.MoveToProject
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ValueSelector,
|
||||
actionPopup: tracker.component.RelationsPopup,
|
||||
actionProps: {
|
||||
attribute: ''
|
||||
},
|
||||
label: tracker.string.Relations,
|
||||
icon: tracker.icon.Relations,
|
||||
keyBinding: [],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
tracker.action.Relations
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.CreateIssue,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_object: 'originalIssue',
|
||||
space: 'space'
|
||||
}
|
||||
},
|
||||
label: tracker.string.Duplicate,
|
||||
icon: tracker.icon.Duplicate,
|
||||
keyBinding: [],
|
||||
input: 'focus',
|
||||
category: tracker.category.Tracker,
|
||||
target: tracker.class.Issue,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
tracker.action.Duplicate
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: tracker.actionImpl.DeleteMilestone,
|
||||
label: view.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
keyBinding: ['Meta + Backspace'],
|
||||
category: tracker.category.Tracker,
|
||||
input: 'any',
|
||||
target: tracker.class.Milestone,
|
||||
context: { mode: ['context', 'browser'], group: 'remove' }
|
||||
},
|
||||
tracker.action.DeleteMilestone
|
||||
)
|
||||
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [view.action.Open]
|
||||
})
|
||||
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [view.action.Delete]
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: tracker.component.EditRelatedTargetsPopup,
|
||||
element: 'top',
|
||||
fillProps: {
|
||||
_objects: 'value'
|
||||
}
|
||||
},
|
||||
label: tracker.string.MapRelatedIssues,
|
||||
icon: tracker.icon.Relations,
|
||||
keyBinding: [],
|
||||
input: 'none',
|
||||
category: tracker.category.Tracker,
|
||||
target: core.class.Space,
|
||||
query: {
|
||||
_class: { $nin: [tracker.class.Project] }
|
||||
},
|
||||
context: {
|
||||
mode: ['context'],
|
||||
application: tracker.app.Tracker,
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
tracker.action.EditRelatedTargets
|
||||
)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -34,7 +34,7 @@ import {
|
||||
import { DOMAIN_TASK } from '@hcengineering/model-task'
|
||||
import tags from '@hcengineering/tags'
|
||||
import { Issue, Project, TimeReportDayType, TimeSpendReport, createStatuses } from '@hcengineering/tracker'
|
||||
import { DOMAIN_TRACKER } from '.'
|
||||
import { DOMAIN_TRACKER } from './types'
|
||||
import tracker from './plugin'
|
||||
import { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||
import view from '@hcengineering/view'
|
||||
|
@ -43,7 +43,8 @@ export default mergeIds(trackerId, tracker, {
|
||||
Unarchive: '' as IntlString,
|
||||
UnarchiveConfirm: '' as IntlString,
|
||||
AllProjects: '' as IntlString,
|
||||
RemainingTime: '' as IntlString
|
||||
RemainingTime: '' as IntlString,
|
||||
MapRelatedIssues: '' as IntlString
|
||||
},
|
||||
activity: {
|
||||
TxIssueCreated: '' as AnyComponent,
|
||||
@ -55,7 +56,9 @@ export default mergeIds(trackerId, tracker, {
|
||||
IssueStatistics: '' as AnyComponent,
|
||||
TimeSpendReportPopup: '' as AnyComponent,
|
||||
NotificationIssuePresenter: '' as AnyComponent,
|
||||
MilestoneFilter: '' as AnyComponent
|
||||
MilestoneFilter: '' as AnyComponent,
|
||||
EditRelatedTargets: '' as AnyComponent,
|
||||
EditRelatedTargetsPopup: '' as AnyComponent
|
||||
},
|
||||
app: {
|
||||
Tracker: '' as Ref<Application>
|
||||
|
144
models/tracker/src/presenters.ts
Normal file
144
models/tracker/src/presenters.ts
Normal file
@ -0,0 +1,144 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Builder } from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import view, { classPresenter } from '@hcengineering/model-view'
|
||||
import notification from '@hcengineering/notification'
|
||||
import tracker from './plugin'
|
||||
|
||||
/**
|
||||
* Define presenters
|
||||
*/
|
||||
export function definePresenters (builder: Builder): void {
|
||||
//
|
||||
// Issue
|
||||
//
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: tracker.component.IssuePresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.NotificationObjectPresenter, {
|
||||
presenter: tracker.component.NotificationIssuePresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.PreviewPresenter, {
|
||||
presenter: tracker.component.IssuePreview
|
||||
})
|
||||
|
||||
//
|
||||
// Issue Template
|
||||
//
|
||||
builder.mixin(tracker.class.IssueTemplate, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: tracker.component.IssueTemplatePresenter
|
||||
})
|
||||
|
||||
//
|
||||
// Issue Status
|
||||
//
|
||||
builder.mixin(tracker.class.IssueStatus, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: tracker.component.StatusPresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.IssueStatus, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: tracker.component.StatusRefPresenter
|
||||
})
|
||||
|
||||
//
|
||||
// Time Spend Report
|
||||
//
|
||||
builder.mixin(tracker.class.TimeSpendReport, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: tracker.component.TimeSpendReport
|
||||
})
|
||||
|
||||
//
|
||||
// Type Milestone Status
|
||||
//
|
||||
builder.mixin(tracker.class.TypeMilestoneStatus, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: tracker.component.MilestoneStatusPresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.TypeMilestoneStatus, core.class.Class, view.mixin.AttributeEditor, {
|
||||
inlineEditor: tracker.component.MilestoneStatusEditor
|
||||
})
|
||||
|
||||
//
|
||||
// Type Issue Priority
|
||||
//
|
||||
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: tracker.component.PriorityPresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: tracker.component.PriorityRefPresenter
|
||||
})
|
||||
|
||||
//
|
||||
// Project
|
||||
//
|
||||
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: tracker.component.ProjectPresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.SpacePresenter, {
|
||||
presenter: tracker.component.ProjectSpacePresenter
|
||||
})
|
||||
|
||||
//
|
||||
// Component
|
||||
//
|
||||
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: tracker.component.EditComponent
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: tracker.component.ComponentPresenter
|
||||
})
|
||||
|
||||
classPresenter(
|
||||
builder,
|
||||
tracker.class.Component,
|
||||
tracker.component.ComponentSelector,
|
||||
tracker.component.ComponentSelector
|
||||
)
|
||||
|
||||
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.AttributeEditor, {
|
||||
inlineEditor: tracker.component.ComponentSelector
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Component, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: tracker.component.ComponentRefPresenter
|
||||
})
|
||||
|
||||
/// Milestones
|
||||
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: tracker.component.MilestonePresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: tracker.component.MilestoneRefPresenter
|
||||
})
|
||||
|
||||
builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: tracker.component.EditMilestone
|
||||
})
|
||||
|
||||
classPresenter(
|
||||
builder,
|
||||
tracker.class.TypeReportedTime,
|
||||
view.component.NumberPresenter,
|
||||
tracker.component.ReportedTimeEditor
|
||||
)
|
||||
}
|
388
models/tracker/src/types.ts
Normal file
388
models/tracker/src/types.ts
Normal file
@ -0,0 +1,388 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact, { Employee, Person } from '@hcengineering/contact'
|
||||
import {
|
||||
DOMAIN_MODEL,
|
||||
DateRangeMode,
|
||||
Domain,
|
||||
IndexKind,
|
||||
Markup,
|
||||
Ref,
|
||||
RelatedDocument,
|
||||
Timestamp,
|
||||
Type
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
ArrOf,
|
||||
Collection,
|
||||
Hidden,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
ReadOnly,
|
||||
TypeDate,
|
||||
TypeMarkup,
|
||||
TypeNumber,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import chunter from '@hcengineering/model-chunter'
|
||||
import core, { TAttachedDoc, TClass, TDoc, TStatus, TType } from '@hcengineering/model-core'
|
||||
import task, { TSpaceWithStates, TTask } from '@hcengineering/model-task'
|
||||
import { IntlString, Resource } from '@hcengineering/platform'
|
||||
import tags, { TagElement } from '@hcengineering/tags'
|
||||
import { DoneState } from '@hcengineering/task'
|
||||
import {
|
||||
Component,
|
||||
Issue,
|
||||
IssueChildInfo,
|
||||
IssueParentInfo,
|
||||
IssuePriority,
|
||||
IssueStatus,
|
||||
IssueTemplate,
|
||||
IssueTemplateChild,
|
||||
IssueUpdateFunction,
|
||||
Milestone,
|
||||
MilestoneStatus,
|
||||
Project,
|
||||
ProjectIssueTargetOptions,
|
||||
RelatedClassRule,
|
||||
RelatedIssueTarget,
|
||||
RelatedSpaceRule,
|
||||
TimeReportDayType,
|
||||
TimeSpendReport
|
||||
} from '@hcengineering/tracker'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import tracker from './plugin'
|
||||
|
||||
export const DOMAIN_TRACKER = 'tracker' as Domain
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.IssueStatus, core.class.Status)
|
||||
@UX(tracker.string.IssueStatuses, undefined, undefined, 'rank', 'name')
|
||||
export class TIssueStatus extends TStatus implements IssueStatus {}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
export function TypeIssuePriority (): Type<IssuePriority> {
|
||||
return { _class: tracker.class.TypeIssuePriority, label: tracker.string.TypeIssuePriority }
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.TypeIssuePriority, core.class.Type, DOMAIN_MODEL)
|
||||
export class TTypeIssuePriority extends TType {}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
export function TypeMilestoneStatus (): Type<MilestoneStatus> {
|
||||
return { _class: tracker.class.TypeMilestoneStatus, label: 'TypeMilestoneStatus' as IntlString }
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.TypeMilestoneStatus, core.class.Type, DOMAIN_MODEL)
|
||||
export class TTypeMilestoneStatus extends TType {}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.Project, task.class.SpaceWithStates)
|
||||
@UX(tracker.string.Project, tracker.icon.Issues, 'Project', 'name')
|
||||
export class TProject extends TSpaceWithStates implements Project {
|
||||
@Prop(TypeString(), tracker.string.ProjectIdentifier)
|
||||
@Index(IndexKind.FullText)
|
||||
identifier!: IntlString
|
||||
|
||||
@Prop(TypeNumber(), tracker.string.Number)
|
||||
@Hidden()
|
||||
sequence!: number
|
||||
|
||||
@Prop(TypeRef(tracker.class.IssueStatus), tracker.string.DefaultIssueStatus)
|
||||
defaultIssueStatus!: Ref<IssueStatus>
|
||||
|
||||
@Prop(TypeRef(contact.mixin.Employee), tracker.string.DefaultAssignee)
|
||||
defaultAssignee!: Ref<Employee>
|
||||
|
||||
declare defaultTimeReportDay: TimeReportDayType
|
||||
|
||||
@Prop(Collection(tracker.class.RelatedIssueTarget), tracker.string.RelatedIssue)
|
||||
relatedIssueTargets!: number
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.RelatedIssueTarget, core.class.Doc, DOMAIN_TRACKER)
|
||||
@UX(tracker.string.RelatedIssues)
|
||||
export class TRelatedIssueTarget extends TDoc implements RelatedIssueTarget {
|
||||
@Prop(TypeRef(tracker.class.Project), tracker.string.Project)
|
||||
target!: Ref<Project>
|
||||
|
||||
rule!: RelatedClassRule | RelatedSpaceRule
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
export function TypeReportedTime (): Type<number> {
|
||||
return { _class: tracker.class.TypeReportedTime, label: core.string.Number }
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.Issue, task.class.Task)
|
||||
@UX(tracker.string.Issue, tracker.icon.Issue, 'TSK', 'title')
|
||||
export class TIssue extends TTask implements Issue {
|
||||
@Prop(TypeRef(tracker.class.Issue), tracker.string.Parent)
|
||||
declare attachedTo: Ref<Issue>
|
||||
|
||||
@Prop(TypeString(), tracker.string.Title)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
|
||||
@Prop(TypeMarkup(), tracker.string.Description)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: Markup
|
||||
|
||||
@Prop(TypeRef(tracker.class.IssueStatus), tracker.string.Status, {
|
||||
_id: tracker.attribute.IssueStatus,
|
||||
iconComponent: tracker.activity.StatusIcon
|
||||
})
|
||||
@Index(IndexKind.Indexed)
|
||||
declare status: Ref<IssueStatus>
|
||||
|
||||
@Prop(TypeIssuePriority(), tracker.string.Priority, {
|
||||
iconComponent: tracker.activity.PriorityIcon
|
||||
})
|
||||
@Index(IndexKind.Indexed)
|
||||
priority!: IssuePriority
|
||||
|
||||
@Prop(TypeNumber(), tracker.string.Number)
|
||||
@Index(IndexKind.FullText)
|
||||
@ReadOnly()
|
||||
declare number: number
|
||||
|
||||
@Prop(TypeRef(contact.class.Person), tracker.string.Assignee)
|
||||
@Index(IndexKind.Indexed)
|
||||
declare assignee: Ref<Person> | null
|
||||
|
||||
@Prop(TypeRef(tracker.class.Component), tracker.string.Component, { icon: tracker.icon.Component })
|
||||
@Index(IndexKind.Indexed)
|
||||
component!: Ref<Component> | null
|
||||
|
||||
@Prop(Collection(tracker.class.Issue), tracker.string.SubIssues)
|
||||
subIssues!: number
|
||||
|
||||
@Prop(ArrOf(TypeRef(core.class.TypeRelatedDocument)), tracker.string.BlockedBy)
|
||||
blockedBy!: RelatedDocument[]
|
||||
|
||||
@Prop(ArrOf(TypeRef(core.class.TypeRelatedDocument)), tracker.string.RelatedTo)
|
||||
@Index(IndexKind.Indexed)
|
||||
relations!: RelatedDocument[]
|
||||
|
||||
parents!: IssueParentInfo[]
|
||||
|
||||
@Prop(Collection(tags.class.TagReference), tracker.string.Labels)
|
||||
declare labels: number
|
||||
|
||||
@Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: task.attribute.DoneState })
|
||||
@Hidden()
|
||||
declare doneState: Ref<DoneState> | null
|
||||
|
||||
@Prop(TypeRef(tracker.class.Project), tracker.string.Project, { icon: tracker.icon.Issues })
|
||||
@Index(IndexKind.Indexed)
|
||||
@ReadOnly()
|
||||
declare space: Ref<Project>
|
||||
|
||||
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.DueDate)
|
||||
declare dueDate: Timestamp | null
|
||||
|
||||
@Prop(TypeString(), tracker.string.Rank)
|
||||
@Hidden()
|
||||
declare rank: string
|
||||
|
||||
@Prop(TypeRef(tracker.class.Milestone), tracker.string.Milestone, { icon: tracker.icon.Milestone })
|
||||
@Index(IndexKind.Indexed)
|
||||
milestone!: Ref<Milestone> | null
|
||||
|
||||
@Prop(TypeNumber(), tracker.string.Estimation)
|
||||
estimation!: number
|
||||
|
||||
@Prop(TypeReportedTime(), tracker.string.ReportedTime)
|
||||
@ReadOnly()
|
||||
reportedTime!: number
|
||||
|
||||
// A fully virtual property with calculated content.
|
||||
// TODO: Add proper support for this kind of fields
|
||||
@Prop(TypeNumber(), tracker.string.RemainingTime)
|
||||
@ReadOnly()
|
||||
@Hidden()
|
||||
remainingTime!: number
|
||||
|
||||
@Prop(Collection(tracker.class.TimeSpendReport), tracker.string.TimeSpendReports)
|
||||
reports!: number
|
||||
|
||||
declare childInfo: IssueChildInfo[]
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.IssueTemplate, core.class.Doc, DOMAIN_TRACKER)
|
||||
@UX(tracker.string.IssueTemplate, tracker.icon.Issue, 'PROCESS')
|
||||
export class TIssueTemplate extends TDoc implements IssueTemplate {
|
||||
@Prop(TypeString(), tracker.string.Title)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
|
||||
@Prop(TypeMarkup(), tracker.string.Description)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: Markup
|
||||
|
||||
@Prop(TypeIssuePriority(), tracker.string.Priority)
|
||||
priority!: IssuePriority
|
||||
|
||||
@Prop(TypeRef(contact.class.Person), tracker.string.Assignee)
|
||||
assignee!: Ref<Person> | null
|
||||
|
||||
@Prop(TypeRef(tracker.class.Component), tracker.string.Component)
|
||||
component!: Ref<Component> | null
|
||||
|
||||
@Prop(ArrOf(TypeRef(tags.class.TagElement)), tracker.string.Labels)
|
||||
labels?: Ref<TagElement>[]
|
||||
|
||||
declare space: Ref<Project>
|
||||
|
||||
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.DueDate)
|
||||
dueDate!: Timestamp | null
|
||||
|
||||
@Prop(TypeRef(tracker.class.Milestone), tracker.string.Milestone)
|
||||
milestone!: Ref<Milestone> | null
|
||||
|
||||
@Prop(TypeNumber(), tracker.string.Estimation)
|
||||
estimation!: number
|
||||
|
||||
@Prop(ArrOf(TypeRef(tracker.class.IssueTemplate)), tracker.string.IssueTemplate)
|
||||
children!: IssueTemplateChild[]
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), tracker.string.Comments)
|
||||
comments!: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), tracker.string.Attachments)
|
||||
attachments!: number
|
||||
|
||||
@Prop(ArrOf(TypeRef(core.class.TypeRelatedDocument)), tracker.string.RelatedTo)
|
||||
relations!: RelatedDocument[]
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.TimeSpendReport, core.class.AttachedDoc, DOMAIN_TRACKER)
|
||||
@UX(tracker.string.TimeSpendReport, tracker.icon.TimeReport)
|
||||
export class TTimeSpendReport extends TAttachedDoc implements TimeSpendReport {
|
||||
@Prop(TypeRef(tracker.class.Issue), tracker.string.Issue)
|
||||
declare attachedTo: Ref<Issue>
|
||||
|
||||
@Prop(TypeRef(contact.mixin.Employee), contact.string.Employee)
|
||||
employee!: Ref<Employee>
|
||||
|
||||
@Prop(TypeDate(), tracker.string.TimeSpendReportDate)
|
||||
date!: Timestamp | null
|
||||
|
||||
@Prop(TypeNumber(), tracker.string.TimeSpendReportValue)
|
||||
value!: number
|
||||
|
||||
@Prop(TypeString(), tracker.string.TimeSpendReportDescription)
|
||||
description!: string
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@Model(tracker.class.Component, core.class.Doc, DOMAIN_TRACKER)
|
||||
@UX(tracker.string.Component, tracker.icon.Component, 'COMPONENT', 'label')
|
||||
export class TComponent extends TDoc implements Component {
|
||||
@Prop(TypeString(), tracker.string.Title)
|
||||
@Index(IndexKind.FullText)
|
||||
label!: string
|
||||
|
||||
@Prop(TypeMarkup(), tracker.string.Description)
|
||||
description?: Markup
|
||||
|
||||
@Prop(TypeRef(contact.mixin.Employee), tracker.string.ComponentLead)
|
||||
lead!: Ref<Employee> | null
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
comments!: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
declare space: Ref<Project>
|
||||
}
|
||||
|
||||
@Mixin(tracker.mixin.ProjectIssueTargetOptions, core.class.Class)
|
||||
export class TProjectIssueTargetOptions extends TClass implements ProjectIssueTargetOptions {
|
||||
headerComponent!: AnyComponent
|
||||
bodyComponent!: AnyComponent
|
||||
footerComponent!: AnyComponent
|
||||
|
||||
update!: Resource<IssueUpdateFunction>
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@Model(tracker.class.Milestone, core.class.Doc, DOMAIN_TRACKER)
|
||||
@UX(tracker.string.Milestone, tracker.icon.Milestone, '', 'label')
|
||||
export class TMilestone extends TDoc implements Milestone {
|
||||
@Prop(TypeString(), tracker.string.Title)
|
||||
// @Index(IndexKind.FullText)
|
||||
label!: string
|
||||
|
||||
@Prop(TypeMarkup(), tracker.string.Description)
|
||||
description?: Markup
|
||||
|
||||
@Prop(TypeMilestoneStatus(), tracker.string.Status)
|
||||
@Index(IndexKind.Indexed)
|
||||
status!: MilestoneStatus
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||
comments!: number
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
@Prop(TypeDate(), tracker.string.TargetDate)
|
||||
targetDate!: Timestamp
|
||||
|
||||
declare space: Ref<Project>
|
||||
}
|
||||
|
||||
@UX(core.string.Number)
|
||||
@Model(tracker.class.TypeReportedTime, core.class.Type)
|
||||
export class TTypeReportedTime extends TType {}
|
542
models/tracker/src/viewlets.ts
Normal file
542
models/tracker/src/viewlets.ts
Normal file
@ -0,0 +1,542 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact from '@hcengineering/contact'
|
||||
import { SortingOrder } from '@hcengineering/core'
|
||||
import { Builder } from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import task from '@hcengineering/model-task'
|
||||
import view, { showColorsViewOption } from '@hcengineering/model-view'
|
||||
import { BuildModelKey, ViewOptionsModel } from '@hcengineering/view'
|
||||
import tracker from './plugin'
|
||||
import tags from '@hcengineering/tags'
|
||||
|
||||
export const issuesOptions = (kanban: boolean): ViewOptionsModel => ({
|
||||
groupBy: ['status', 'assignee', 'priority', 'component', 'milestone', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['status', SortingOrder.Ascending],
|
||||
['priority', SortingOrder.Descending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending],
|
||||
['dueDate', SortingOrder.Ascending],
|
||||
['rank', SortingOrder.Ascending]
|
||||
],
|
||||
other: [
|
||||
{
|
||||
key: 'shouldShowSubIssues',
|
||||
type: 'toggle',
|
||||
defaultValue: true,
|
||||
actionTarget: 'query',
|
||||
action: tracker.function.SubIssueQuery,
|
||||
label: tracker.string.SubIssues
|
||||
},
|
||||
{
|
||||
key: 'shouldShowAll',
|
||||
type: 'toggle',
|
||||
defaultValue: false,
|
||||
actionTarget: 'category',
|
||||
action: view.function.ShowEmptyGroups,
|
||||
label: view.string.ShowEmptyGroups
|
||||
},
|
||||
...(!kanban ? [showColorsViewOption] : [])
|
||||
]
|
||||
})
|
||||
|
||||
export function issueConfig (
|
||||
key: string = '',
|
||||
compact: boolean = false,
|
||||
milestone: boolean = true,
|
||||
component: boolean = true
|
||||
): (BuildModelKey | string)[] {
|
||||
return [
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Priority,
|
||||
presenter: tracker.component.PriorityEditor,
|
||||
props: { type: 'priority', kind: 'list', size: 'small' },
|
||||
displayProps: { key: 'priority' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Identifier,
|
||||
presenter: tracker.component.IssuePresenter,
|
||||
displayProps: { key: key + 'issue', fixed: 'left' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Status,
|
||||
presenter: tracker.component.StatusEditor,
|
||||
props: { kind: 'list', size: 'small', justify: 'center' },
|
||||
displayProps: { key: key + 'status' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Title,
|
||||
presenter: tracker.component.TitlePresenter,
|
||||
props: compact ? { shouldUseMargin: true, showParent: false } : {},
|
||||
displayProps: { key: key + 'title' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.SubIssues,
|
||||
presenter: tracker.component.SubIssuesSelector,
|
||||
props: {}
|
||||
},
|
||||
{ key: 'comments', displayProps: { key: key + 'comments', suffix: true } },
|
||||
{ key: 'attachments', displayProps: { key: key + 'attachments', suffix: true } },
|
||||
{ key: '', displayProps: { grow: true } },
|
||||
{
|
||||
key: 'labels',
|
||||
presenter: tags.component.LabelsPresenter,
|
||||
displayProps: { compression: true },
|
||||
props: { kind: 'list', full: false }
|
||||
},
|
||||
...(milestone
|
||||
? [
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Milestone,
|
||||
presenter: tracker.component.MilestoneEditor,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: {
|
||||
key: key + 'milestone',
|
||||
excludeByKey: 'milestone',
|
||||
compression: true
|
||||
}
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(component
|
||||
? [
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Component,
|
||||
presenter: tracker.component.ComponentEditor,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: {
|
||||
key: key + 'component',
|
||||
excludeByKey: 'component',
|
||||
compression: true
|
||||
}
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.DueDate,
|
||||
presenter: tracker.component.DueDatePresenter,
|
||||
displayProps: { key: key + 'dueDate', compression: true },
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Estimation,
|
||||
presenter: tracker.component.EstimationEditor,
|
||||
props: { kind: 'list', size: 'small' },
|
||||
displayProps: { key: key + 'estimation', fixed: 'left', dividerBefore: true, optional: true }
|
||||
},
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
presenter: tracker.component.ModificationDatePresenter,
|
||||
displayProps: { key: key + 'modified', fixed: 'left', dividerBefore: true }
|
||||
},
|
||||
{
|
||||
key: 'assignee',
|
||||
presenter: tracker.component.AssigneeEditor,
|
||||
displayProps: { key: 'assignee', fixed: 'right' },
|
||||
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function defineViewlets (builder: Builder): void {
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
label: tracker.string.Board,
|
||||
icon: task.icon.Kanban,
|
||||
component: tracker.component.KanbanView
|
||||
},
|
||||
tracker.viewlet.Kanban
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.Issue,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: issuesOptions(false),
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: [
|
||||
'title',
|
||||
'blockedBy',
|
||||
'relations',
|
||||
'description',
|
||||
'number',
|
||||
'reportedTime',
|
||||
'reports',
|
||||
'priority',
|
||||
'component',
|
||||
'milestone',
|
||||
'estimation',
|
||||
'status',
|
||||
'dueDate',
|
||||
'attachedTo',
|
||||
'createdBy',
|
||||
'modifiedBy'
|
||||
]
|
||||
},
|
||||
config: issueConfig()
|
||||
},
|
||||
tracker.viewlet.IssueList
|
||||
)
|
||||
|
||||
const subIssuesOptions: ViewOptionsModel = {
|
||||
groupBy: ['status', 'assignee', 'priority', 'milestone', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['rank', SortingOrder.Ascending],
|
||||
['status', SortingOrder.Ascending],
|
||||
['priority', SortingOrder.Ascending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending],
|
||||
['dueDate', SortingOrder.Ascending]
|
||||
],
|
||||
groupDepth: 1,
|
||||
other: [showColorsViewOption]
|
||||
}
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.Issue,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: subIssuesOptions,
|
||||
variant: 'subissue',
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: [
|
||||
'priority',
|
||||
'number',
|
||||
'status',
|
||||
'title',
|
||||
'dueDate',
|
||||
'milestone',
|
||||
'estimation',
|
||||
'createdBy',
|
||||
'modifiedBy'
|
||||
]
|
||||
},
|
||||
config: issueConfig('sub', true, true)
|
||||
},
|
||||
tracker.viewlet.SubIssues
|
||||
)
|
||||
|
||||
const milestoneIssueOptions: ViewOptionsModel = {
|
||||
groupBy: ['status', 'assignee', 'priority', 'component', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['rank', SortingOrder.Ascending],
|
||||
['status', SortingOrder.Ascending],
|
||||
['priority', SortingOrder.Ascending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending],
|
||||
['dueDate', SortingOrder.Ascending]
|
||||
],
|
||||
groupDepth: 1,
|
||||
other: [showColorsViewOption]
|
||||
}
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.Issue,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: milestoneIssueOptions,
|
||||
variant: 'milestone',
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: [
|
||||
'priority',
|
||||
'number',
|
||||
'status',
|
||||
'title',
|
||||
'dueDate',
|
||||
'milestone',
|
||||
'estimation',
|
||||
'createdBy',
|
||||
'modifiedBy'
|
||||
]
|
||||
},
|
||||
config: issueConfig('sub', true, false, true)
|
||||
},
|
||||
tracker.viewlet.MilestoneIssuesList
|
||||
)
|
||||
|
||||
const componentIssueOptions: ViewOptionsModel = {
|
||||
groupBy: ['status', 'assignee', 'priority', 'milestone', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['rank', SortingOrder.Ascending],
|
||||
['status', SortingOrder.Ascending],
|
||||
['priority', SortingOrder.Ascending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending],
|
||||
['dueDate', SortingOrder.Ascending]
|
||||
],
|
||||
groupDepth: 1,
|
||||
other: [showColorsViewOption]
|
||||
}
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.Issue,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: componentIssueOptions,
|
||||
variant: 'component',
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: [
|
||||
'priority',
|
||||
'number',
|
||||
'status',
|
||||
'title',
|
||||
'dueDate',
|
||||
'component',
|
||||
'estimation',
|
||||
'createdBy',
|
||||
'modifiedBy'
|
||||
]
|
||||
},
|
||||
config: issueConfig('sub', true, true, false)
|
||||
},
|
||||
tracker.viewlet.ComponentIssuesList
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.IssueTemplate,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: {
|
||||
groupBy: ['assignee', 'priority', 'component', 'milestone', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['priority', SortingOrder.Ascending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['dueDate', SortingOrder.Ascending],
|
||||
['rank', SortingOrder.Ascending]
|
||||
],
|
||||
other: [showColorsViewOption]
|
||||
},
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: ['milestone', 'estimation', 'component', 'title', 'description', 'createdBy', 'modifiedBy']
|
||||
},
|
||||
config: [
|
||||
// { key: '', presenter: tracker.component.PriorityEditor, props: { kind: 'list', size: 'small' } },
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.IssueTemplatePresenter,
|
||||
props: { type: 'issue', shouldUseMargin: true }
|
||||
},
|
||||
// { key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list' } },
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.ComponentEditor,
|
||||
label: tracker.string.Component,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: { key: 'component', compression: true }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Milestone,
|
||||
presenter: tracker.component.MilestoneEditor,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small',
|
||||
shouldShowPlaceholder: false
|
||||
},
|
||||
displayProps: { key: 'milestone', compression: true }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.Estimation,
|
||||
presenter: tracker.component.TemplateEstimationEditor,
|
||||
props: {
|
||||
kind: 'list',
|
||||
size: 'small'
|
||||
},
|
||||
displayProps: { key: 'estimation', compression: true }
|
||||
},
|
||||
{ key: '', displayProps: { grow: true } },
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
presenter: tracker.component.ModificationDatePresenter,
|
||||
displayProps: { fixed: 'right', dividerBefore: true }
|
||||
},
|
||||
{
|
||||
key: 'assignee',
|
||||
presenter: tracker.component.AssigneeEditor,
|
||||
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
||||
}
|
||||
]
|
||||
},
|
||||
tracker.viewlet.IssueTemplateList
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.Issue,
|
||||
descriptor: tracker.viewlet.Kanban,
|
||||
viewOptions: {
|
||||
...issuesOptions(true),
|
||||
groupDepth: 1
|
||||
},
|
||||
configOptions: {
|
||||
strict: true
|
||||
},
|
||||
config: ['subIssues', 'priority', 'component', 'dueDate', 'labels', 'estimation', 'attachments', 'comments']
|
||||
},
|
||||
tracker.viewlet.IssueKanban
|
||||
)
|
||||
|
||||
const componentListViewOptions: ViewOptionsModel = {
|
||||
groupBy: ['lead', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending]
|
||||
],
|
||||
other: [showColorsViewOption]
|
||||
}
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.Component,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: componentListViewOptions,
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: ['label', 'description']
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: '',
|
||||
presenter: tracker.component.ComponentPresenter,
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{ key: '', displayProps: { grow: true } },
|
||||
{
|
||||
key: '$lookup.lead',
|
||||
presenter: tracker.component.LeadPresenter,
|
||||
displayProps: {
|
||||
dividerBefore: true,
|
||||
key: 'lead'
|
||||
},
|
||||
props: { _class: tracker.class.Component, defaultClass: contact.mixin.Employee, shouldShowLabel: false }
|
||||
}
|
||||
]
|
||||
},
|
||||
tracker.viewlet.ComponentList
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.Project,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: {
|
||||
groupBy: ['createdBy'],
|
||||
orderBy: [
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending]
|
||||
],
|
||||
other: [showColorsViewOption]
|
||||
},
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: ['label', 'description']
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: '',
|
||||
props: { kind: 'list' }
|
||||
},
|
||||
{ key: '', displayProps: { grow: true } }
|
||||
]
|
||||
},
|
||||
tracker.viewlet.ProjectList
|
||||
)
|
||||
|
||||
const milestoneOptions: ViewOptionsModel = {
|
||||
groupBy: ['status', 'createdBy', 'modifiedBy'],
|
||||
orderBy: [
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['targetDate', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending]
|
||||
],
|
||||
other: [showColorsViewOption]
|
||||
}
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: tracker.class.Milestone,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: milestoneOptions,
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: ['targetDate', 'label', 'description']
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: 'status',
|
||||
props: { width: '1rem', kind: 'list', size: 'small', justify: 'center' }
|
||||
},
|
||||
{ key: '', presenter: tracker.component.MilestonePresenter, props: { shouldUseMargin: true } },
|
||||
{ key: '', displayProps: { grow: true } },
|
||||
{
|
||||
key: '',
|
||||
label: tracker.string.TargetDate,
|
||||
presenter: tracker.component.MilestoneDatePresenter,
|
||||
props: { field: 'targetDate' }
|
||||
}
|
||||
]
|
||||
},
|
||||
tracker.viewlet.MilestoneList
|
||||
)
|
||||
}
|
@ -296,10 +296,15 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
return new ApplyOperations(this, scope)
|
||||
}
|
||||
|
||||
async diffUpdate (doc: Doc, raw: Doc | Data<Doc>, date: Timestamp, account?: Ref<Account>): Promise<Doc> {
|
||||
async diffUpdate<T extends Doc = Doc>(
|
||||
doc: T,
|
||||
update: T | Data<T> | DocumentUpdate<T>,
|
||||
date?: Timestamp,
|
||||
account?: Ref<Account>
|
||||
): Promise<T> {
|
||||
// We need to update fields if they are different.
|
||||
const documentUpdate: DocumentUpdate<Doc> = {}
|
||||
for (const [k, v] of Object.entries(raw)) {
|
||||
const documentUpdate: DocumentUpdate<T> = {}
|
||||
for (const [k, v] of Object.entries(update)) {
|
||||
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
||||
continue
|
||||
}
|
||||
@ -309,7 +314,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
}
|
||||
}
|
||||
if (Object.keys(documentUpdate).length > 0) {
|
||||
await this.update(doc, documentUpdate, false, date, account ?? doc.modifiedBy)
|
||||
await this.update(doc, documentUpdate, false, date ?? Date.now(), account)
|
||||
TxProcessor.applyUpdate(doc, documentUpdate)
|
||||
}
|
||||
return doc
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
export let label: IntlString
|
||||
export let okAction: () => void
|
||||
export let okLabel: IntlString | undefined = undefined
|
||||
export let canSave: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -40,7 +41,7 @@
|
||||
<div class="antiCard-footer">
|
||||
<Button
|
||||
disabled={!canSave}
|
||||
label={presentation.string.Create}
|
||||
label={okLabel ?? presentation.string.Create}
|
||||
kind={'accented'}
|
||||
on:click={() => {
|
||||
okAction()
|
||||
|
@ -60,6 +60,7 @@
|
||||
export let readonly = false
|
||||
export let iconWithEmoji: AnySvelteComponent | Asset | ComponentType | undefined = view.ids.IconWithEmoji
|
||||
export let defaultIcon: AnySvelteComponent | Asset | ComponentType = IconFolder
|
||||
export let findDefaultSpace: (() => Promise<Space | undefined>) | undefined = undefined
|
||||
|
||||
let selected: (Space & IconProps) | undefined
|
||||
|
||||
@ -71,7 +72,7 @@
|
||||
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
|
||||
|
||||
if (selected === undefined && autoSelect) {
|
||||
selected = await client.findOne(_class, { ...(spaceQuery ?? {}) })
|
||||
selected = (await findDefaultSpace?.()) ?? (await client.findOne(_class, { ...(spaceQuery ?? {}) }))
|
||||
if (selected !== undefined) {
|
||||
value = selected._id ?? undefined
|
||||
dispatch('change', value)
|
||||
|
@ -39,6 +39,7 @@
|
||||
export let iconWithEmoji: AnySvelteComponent | Asset | ComponentType | undefined = undefined
|
||||
export let defaultIcon: AnySvelteComponent | Asset | ComponentType | undefined = undefined
|
||||
export let readonly: boolean = false
|
||||
export let findDefaultSpace: (() => Promise<Space | undefined>) | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -71,4 +72,5 @@
|
||||
dispatch('change', space)
|
||||
}}
|
||||
on:space
|
||||
{findDefaultSpace}
|
||||
/>
|
||||
|
@ -31,6 +31,7 @@
|
||||
"Members": "Members",
|
||||
"UnAssign": "Unassign",
|
||||
"ConfigLabel": "CRM",
|
||||
"ConfigDescription": "Extension for Customer relation management"
|
||||
"ConfigDescription": "Extension for Customer relation management",
|
||||
"EditFunnel": "Edit Funnel"
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@
|
||||
"Members": "Пользователи",
|
||||
"UnAssign": "Отменить назначение",
|
||||
"ConfigLabel": "CRM",
|
||||
"ConfigDescription": "Расширение по работе с клиентами"
|
||||
"ConfigDescription": "Расширение по работе с клиентами",
|
||||
"EditFunnel": "Редактировать воронку"
|
||||
}
|
||||
}
|
@ -14,26 +14,35 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { AccountArrayEditor } from '@hcengineering/contact-resources'
|
||||
import core, { Account, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { Funnel } from '@hcengineering/lead'
|
||||
import presentation, { getClient, SpaceCreateCard } from '@hcengineering/presentation'
|
||||
import task, { createStates, KanbanTemplate } from '@hcengineering/task'
|
||||
import { Component, EditBox, Grid, IconFolder, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import ui, { Component, EditBox, Grid, IconFolder, Label, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import lead from '../plugin'
|
||||
|
||||
export let funnel: Funnel | undefined = undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let name: string = ''
|
||||
const description: string = ''
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: isNew = !funnel
|
||||
|
||||
let name: string = funnel?.name ?? ''
|
||||
const description: string = funnel?.description ?? ''
|
||||
let templateId: Ref<KanbanTemplate> | undefined
|
||||
let isPrivate: boolean = false
|
||||
let isPrivate: boolean = funnel?.private ?? false
|
||||
|
||||
let members: Ref<Account>[] =
|
||||
funnel?.members !== undefined ? hierarchy.clone(funnel.members) : [getCurrentAccount()._id]
|
||||
|
||||
export function canClose (): boolean {
|
||||
return name === '' && templateId !== undefined
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function createFunnel (): Promise<void> {
|
||||
if (
|
||||
templateId !== undefined &&
|
||||
@ -49,17 +58,25 @@
|
||||
description,
|
||||
private: isPrivate,
|
||||
archived: false,
|
||||
members: [getCurrentAccount()._id],
|
||||
members,
|
||||
templateId,
|
||||
states,
|
||||
doneStates
|
||||
})
|
||||
}
|
||||
async function save (): Promise<void> {
|
||||
if (isNew) {
|
||||
await createFunnel()
|
||||
} else if (funnel !== undefined) {
|
||||
await client.diffUpdate<Funnel>(funnel, { name, description, members, private: isPrivate }, Date.now())
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SpaceCreateCard
|
||||
label={lead.string.CreateFunnel}
|
||||
okAction={createFunnel}
|
||||
okAction={save}
|
||||
okLabel={!isNew ? ui.string.Save : undefined}
|
||||
canSave={name.length > 0}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
@ -89,5 +106,17 @@
|
||||
templateId = evt.detail
|
||||
}}
|
||||
/>
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={lead.string.Members} />
|
||||
</div>
|
||||
<AccountArrayEditor
|
||||
value={members}
|
||||
label={lead.string.Members}
|
||||
onChange={(refs) => (members = refs)}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</SpaceCreateCard>
|
||||
|
@ -60,7 +60,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
$: if (_space === undefined) {
|
||||
if (funnels.find((it) => it._id === _space) === undefined) {
|
||||
_space = funnels[0]?._id
|
||||
}
|
||||
|
@ -66,7 +66,6 @@ export default mergeIds(taskId, task, {
|
||||
CantStatusDeleteError: '' as IntlString,
|
||||
Archive: '' as IntlString,
|
||||
Unarchive: '' as IntlString,
|
||||
RelatedIssues: '' as IntlString,
|
||||
|
||||
Tasks: '' as IntlString,
|
||||
Task: '' as IntlString,
|
||||
|
@ -283,7 +283,9 @@
|
||||
"IssueNotificationChangedProperty": "{senderName} changed {property} to \"{newValue}\"",
|
||||
"IssueNotificationMessage": "{senderName}: {message}",
|
||||
"PreviousAssigned": "Previously assigned",
|
||||
"IssueAssigneedToYou": "Assigned to you"
|
||||
"IssueAssigneedToYou": "Assigned to you",
|
||||
"RelatedIssueTargetDescription": "Related issue project target for Class or Space",
|
||||
"MapRelatedIssues": "Configure Related issue default projects"
|
||||
},
|
||||
"status": {}
|
||||
}
|
||||
|
@ -283,7 +283,9 @@
|
||||
"IssueNotificationChangedProperty": "{senderName} изменил {property} на \"{newValue}\"",
|
||||
"IssueNotificationMessage": "{senderName}: {message}",
|
||||
"PreviousAssigned": "Ранее назначенные",
|
||||
"IssueAssigneedToYou": "Назначено вам"
|
||||
"IssueAssigneedToYou": "Назначено вам",
|
||||
"RelatedIssueTargetDescription": "Настройка проекта по умолчанию для Класса или пространства",
|
||||
"MapRelatedIssues": "Настроить проекты по умолчанию для связанных задач"
|
||||
},
|
||||
"status": {}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@
|
||||
"@hcengineering/workbench-resources": "^0.6.1",
|
||||
"@hcengineering/activity-resources": "^0.6.1",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/query": "^0.6.7"
|
||||
"@hcengineering/query": "^0.6.7",
|
||||
"@hcengineering/preference": "^0.6.8"
|
||||
}
|
||||
}
|
||||
|
@ -13,24 +13,25 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentPresenter, AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import core, { Account, DocData, Class, Doc, fillDefaults, generateId, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import core, { Account, Class, Doc, DocData, Ref, SortingOrder, fillDefaults, generateId } from '@hcengineering/core'
|
||||
import { getResource, translate } from '@hcengineering/platform'
|
||||
import preference, { SpacePreference } from '@hcengineering/preference'
|
||||
import {
|
||||
Card,
|
||||
createQuery,
|
||||
DraftController,
|
||||
getClient,
|
||||
KeyedAttribute,
|
||||
MessageBox,
|
||||
MultipleDraftController,
|
||||
SpaceSelector
|
||||
SpaceSelector,
|
||||
createQuery,
|
||||
getClient
|
||||
} from '@hcengineering/presentation'
|
||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||
import {
|
||||
calcRank,
|
||||
Component as ComponentType,
|
||||
Issue,
|
||||
IssueDraft,
|
||||
@ -39,29 +40,30 @@
|
||||
IssueTemplate,
|
||||
Milestone,
|
||||
Project,
|
||||
ProjectIssueTargetOptions
|
||||
ProjectIssueTargetOptions,
|
||||
calcRank
|
||||
} from '@hcengineering/tracker'
|
||||
import {
|
||||
addNotification,
|
||||
Button,
|
||||
Component,
|
||||
createFocusManager,
|
||||
DatePresenter,
|
||||
EditBox,
|
||||
FocusHandler,
|
||||
IconAttachment,
|
||||
Label,
|
||||
addNotification,
|
||||
createFocusManager,
|
||||
showPopup,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentPresenter } from '@hcengineering/attachment-resources'
|
||||
import view from '@hcengineering/view'
|
||||
import { ObjectBox } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { activeComponent, activeMilestone, generateIssueShortLink, getIssueId, updateIssueRelation } from '../issues'
|
||||
import tracker from '../plugin'
|
||||
import ComponentSelector from './ComponentSelector.svelte'
|
||||
import SetParentIssueActionPopup from './SetParentIssueActionPopup.svelte'
|
||||
import SubIssues from './SubIssues.svelte'
|
||||
import AssigneeEditor from './issues/AssigneeEditor.svelte'
|
||||
import IssueNotification from './issues/IssueNotification.svelte'
|
||||
import ParentIssue from './issues/ParentIssue.svelte'
|
||||
@ -69,11 +71,9 @@
|
||||
import StatusEditor from './issues/StatusEditor.svelte'
|
||||
import EstimationEditor from './issues/timereport/EstimationEditor.svelte'
|
||||
import MilestoneSelector from './milestones/MilestoneSelector.svelte'
|
||||
import SetParentIssueActionPopup from './SetParentIssueActionPopup.svelte'
|
||||
import SubIssues from './SubIssues.svelte'
|
||||
import ProjectPresenter from './projects/ProjectPresenter.svelte'
|
||||
|
||||
export let space: Ref<Project>
|
||||
export let space: Ref<Project> | undefined
|
||||
export let status: Ref<IssueStatus> | undefined = undefined
|
||||
export let priority: IssuePriority | undefined = undefined
|
||||
export let assignee: Ref<Employee> | null = null
|
||||
@ -150,7 +150,7 @@
|
||||
title: '',
|
||||
description: '',
|
||||
priority: priority ?? IssuePriority.NoPriority,
|
||||
space: _space,
|
||||
space: _space as Ref<Project>,
|
||||
component: component ?? $activeComponent ?? null,
|
||||
dueDate: null,
|
||||
attachments: 0,
|
||||
@ -209,7 +209,7 @@
|
||||
space: _space
|
||||
}
|
||||
|
||||
$: if (object.space !== _space) {
|
||||
$: if (_space !== undefined && object.space !== _space) {
|
||||
object.space = _space
|
||||
}
|
||||
|
||||
@ -261,7 +261,7 @@
|
||||
return {
|
||||
...p,
|
||||
_id: p.id,
|
||||
space: _space,
|
||||
space: _space as Ref<Project>,
|
||||
subIssues: [],
|
||||
dueDate: null,
|
||||
labels: [],
|
||||
@ -355,7 +355,7 @@
|
||||
|
||||
async function createIssue (): Promise<void> {
|
||||
const _id: Ref<Issue> = generateId()
|
||||
if (!canSave || object.status === undefined) {
|
||||
if (!canSave || object.status === undefined || _space === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -363,7 +363,7 @@
|
||||
const incResult = await client.updateDoc(
|
||||
tracker.class.Project,
|
||||
core.space.Space,
|
||||
_space,
|
||||
_space as Ref<Project>,
|
||||
{
|
||||
$inc: { sequence: 1 }
|
||||
},
|
||||
@ -396,7 +396,7 @@
|
||||
|
||||
if (targetSettings !== undefined) {
|
||||
const updateOp = await getResource(targetSettings.update)
|
||||
updateOp?.(_id, _space, value, targetSettingOptions)
|
||||
updateOp?.(_id, _space as Ref<Project>, value, targetSettingOptions)
|
||||
}
|
||||
|
||||
await client.addCollection(
|
||||
@ -543,6 +543,47 @@
|
||||
const manager = createFocusManager()
|
||||
|
||||
let attachments: Map<Ref<Attachment>, Attachment> = new Map<Ref<Attachment>, Attachment>()
|
||||
|
||||
async function findDefaultSpace (): Promise<Project | undefined> {
|
||||
let targetRef: Ref<Project> | undefined
|
||||
if (relatedTo !== undefined) {
|
||||
const targets = await client.findAll(tracker.class.RelatedIssueTarget, {})
|
||||
// Find a space target first
|
||||
targetRef =
|
||||
targets.find((t) => t.rule.kind === 'spaceRule' && t.rule.space === relatedTo?.space && t.target !== undefined)
|
||||
?.target ?? undefined
|
||||
// Find a class target as second
|
||||
targetRef =
|
||||
targetRef ??
|
||||
targets.find(
|
||||
(t) =>
|
||||
t.rule.kind === 'classRule' &&
|
||||
client.getHierarchy().isDerived(relatedTo?._class as Ref<Class<Doc>>, t.rule.ofClass)
|
||||
)?.target ??
|
||||
undefined
|
||||
}
|
||||
|
||||
// Find first starred project
|
||||
if (targetRef === undefined) {
|
||||
const prefs = await client.findAll<SpacePreference>(
|
||||
preference.class.SpacePreference,
|
||||
{},
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending } }
|
||||
)
|
||||
const projects = await client.findAll<Project>(tracker.class.Project, {
|
||||
_id: {
|
||||
$in: Array.from(prefs.map((it) => it.attachedTo as Ref<Project>).filter((it) => it != null))
|
||||
}
|
||||
})
|
||||
if (projects.length > 0) {
|
||||
return projects[0]
|
||||
}
|
||||
}
|
||||
|
||||
if (targetRef !== undefined) {
|
||||
return client.findOne(tracker.class.Project, { _id: targetRef })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<FocusHandler {manager} />
|
||||
@ -568,6 +609,7 @@
|
||||
size={'small'}
|
||||
component={ProjectPresenter}
|
||||
defaultIcon={tracker.icon.Home}
|
||||
{findDefaultSpace}
|
||||
/>
|
||||
<ObjectBox
|
||||
_class={tracker.class.IssueTemplate}
|
||||
@ -667,6 +709,7 @@
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
{#if _space}
|
||||
<SubIssues
|
||||
bind:this={subIssuesComponent}
|
||||
projectId={_space}
|
||||
@ -675,6 +718,7 @@
|
||||
component={object.component}
|
||||
bind:subIssues={object.subIssues}
|
||||
/>
|
||||
{/if}
|
||||
{#if targetSettings?.bodyComponent && currentProject}
|
||||
<Component
|
||||
is={targetSettings.bodyComponent}
|
||||
|
@ -0,0 +1,137 @@
|
||||
<script lang="ts">
|
||||
import core, { Space, WithLookup } from '@hcengineering/core'
|
||||
import { SpaceSelector, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { RelatedIssueTarget } from '@hcengineering/tracker'
|
||||
import { Button, Icon, IconArrowRight, IconDelete, Label } from '@hcengineering/ui'
|
||||
import { FixedColumn } from '@hcengineering/view-resources'
|
||||
import tracker from '../plugin'
|
||||
|
||||
export let value: Space | undefined
|
||||
|
||||
const targetQuery = createQuery()
|
||||
|
||||
let targets: WithLookup<RelatedIssueTarget>[] = []
|
||||
$: targetQuery.query(
|
||||
tracker.class.RelatedIssueTarget,
|
||||
{},
|
||||
(res) => {
|
||||
targets = res.toSorted((a, b) => a.rule.kind.localeCompare(b.rule.kind))
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
target: tracker.class.Project
|
||||
}
|
||||
}
|
||||
)
|
||||
const client = getClient()
|
||||
|
||||
$: showCreate =
|
||||
value !== undefined &&
|
||||
targets.find((it) => it.rule.kind === 'spaceRule' && it.rule.space === value?._id) === undefined
|
||||
</script>
|
||||
|
||||
<div class="flex-col" class:p-4={value === undefined}>
|
||||
<div class="p-3">
|
||||
<Label label={tracker.string.RelatedIssueTargetDescription} />
|
||||
</div>
|
||||
{#each targets as target, i}
|
||||
<div class="flex-row p-1">
|
||||
<div class="flex-row-center bordered">
|
||||
<FixedColumn key={'rule-name'}>
|
||||
{#if target.rule.kind === 'classRule'}
|
||||
{@const documentClass = client.getHierarchy().getClass(target.rule.ofClass)}
|
||||
|
||||
<div class="flex-row-center">
|
||||
<Button
|
||||
label={documentClass.label}
|
||||
icon={documentClass.icon}
|
||||
disabled={true}
|
||||
size={'medium'}
|
||||
kind={'link'}
|
||||
/>
|
||||
</div>
|
||||
{:else if target.rule.kind === 'spaceRule'}
|
||||
<SpaceSelector
|
||||
label={core.string.Space}
|
||||
_class={core.class.Space}
|
||||
space={target.rule.space}
|
||||
readonly={true}
|
||||
kind={'link'}
|
||||
size={'medium'}
|
||||
/>
|
||||
{/if}
|
||||
</FixedColumn>
|
||||
<span class="p-1 mr-2"> <Icon icon={IconArrowRight} size={'medium'} /> </span>
|
||||
|
||||
<FixedColumn key={'space-value'}>
|
||||
<SpaceSelector
|
||||
label={tracker.string.Project}
|
||||
_class={tracker.class.Project}
|
||||
space={target.target ?? undefined}
|
||||
autoSelect={false}
|
||||
allowDeselect
|
||||
kind={'list'}
|
||||
on:change={(evt) => {
|
||||
client.update(target, { target: evt.detail || null })
|
||||
}}
|
||||
/>
|
||||
</FixedColumn>
|
||||
{#if target.rule.kind === 'spaceRule'}
|
||||
<div class="flex-grow flex flex-reverse">
|
||||
<Button
|
||||
icon={IconDelete}
|
||||
on:click={() => {
|
||||
client.remove(target)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{#if showCreate && value !== undefined}
|
||||
{@const space = value._id}
|
||||
<div class="flex-row-center bordered">
|
||||
<FixedColumn key={'rule-name'}>
|
||||
<SpaceSelector
|
||||
label={core.string.Space}
|
||||
_class={core.class.Space}
|
||||
space={value._id}
|
||||
readonly={true}
|
||||
kind={'link'}
|
||||
size={'medium'}
|
||||
/>
|
||||
</FixedColumn>
|
||||
<span class="p-1"> => </span>
|
||||
|
||||
<FixedColumn key={'space-value'}>
|
||||
<SpaceSelector
|
||||
label={tracker.string.Project}
|
||||
_class={tracker.class.Project}
|
||||
space={undefined}
|
||||
autoSelect={false}
|
||||
allowDeselect
|
||||
kind={'list'}
|
||||
on:change={(evt) => {
|
||||
client.createDoc(tracker.class.RelatedIssueTarget, space, {
|
||||
target: evt.detail || null,
|
||||
rule: {
|
||||
kind: 'spaceRule',
|
||||
space
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</FixedColumn>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.bordered {
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: var(--theme-comp-header-color);
|
||||
border: 1px solid var(--theme-divider-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { Space } from '@hcengineering/core'
|
||||
import { Card } from '@hcengineering/presentation'
|
||||
import ui from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tracker from '../plugin'
|
||||
import EditRelatedTargets from './EditRelatedTargets.svelte'
|
||||
|
||||
export let value: Space
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={tracker.string.RelatedIssues}
|
||||
canSave={true}
|
||||
okAction={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
okLabel={ui.string.Ok}
|
||||
on:close
|
||||
>
|
||||
<EditRelatedTargets {value} on:close />
|
||||
</Card>
|
@ -75,6 +75,8 @@ import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
|
||||
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
||||
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
||||
import Statuses from './components/workflow/Statuses.svelte'
|
||||
import EditRelatedTargets from './components/EditRelatedTargets.svelte'
|
||||
import EditRelatedTargetsPopup from './components/EditRelatedTargetsPopup.svelte'
|
||||
import {
|
||||
getIssueId,
|
||||
getIssueTitle,
|
||||
@ -475,7 +477,9 @@ export default async (): Promise<Resources> => ({
|
||||
PriorityFilterValuePresenter,
|
||||
StatusFilterValuePresenter,
|
||||
ProjectFilterValuePresenter,
|
||||
ComponentFilterValuePresenter
|
||||
ComponentFilterValuePresenter,
|
||||
EditRelatedTargets,
|
||||
EditRelatedTargetsPopup
|
||||
},
|
||||
completion: {
|
||||
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
|
||||
|
@ -226,7 +226,6 @@ export default mergeIds(trackerId, tracker, {
|
||||
AddRelatedIssue: '' as IntlString,
|
||||
RelatedIssuesNotFound: '' as IntlString,
|
||||
RelatedIssue: '' as IntlString,
|
||||
RelatedIssues: '' as IntlString,
|
||||
BlockedIssue: '' as IntlString,
|
||||
BlockingIssue: '' as IntlString,
|
||||
BlockedBySearchPlaceholder: '' as IntlString,
|
||||
@ -301,7 +300,9 @@ export default mergeIds(trackerId, tracker, {
|
||||
NoStatusFound: '' as IntlString,
|
||||
CreateMissingStatus: '' as IntlString,
|
||||
UnsetParent: '' as IntlString,
|
||||
PreviousAssigned: '' as IntlString
|
||||
PreviousAssigned: '' as IntlString,
|
||||
EditRelatedTargets: '' as IntlString,
|
||||
RelatedIssueTargetDescription: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
NopeComponent: '' as AnyComponent,
|
||||
|
@ -55,6 +55,29 @@ export interface Project extends SpaceWithStates, IconProps {
|
||||
defaultTimeReportDay: TimeReportDayType
|
||||
}
|
||||
|
||||
export type RelatedIssueKind = 'classRule' | 'spaceRule'
|
||||
|
||||
export interface RelatedClassRule {
|
||||
kind: 'classRule'
|
||||
ofClass: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
export interface RelatedSpaceRule {
|
||||
kind: 'spaceRule'
|
||||
space: Ref<Space>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* If defined, will be used to set a default project for this kind of document's related issues.
|
||||
*/
|
||||
export interface RelatedIssueTarget extends Doc {
|
||||
// Attached to project.
|
||||
target?: Ref<Project> | null
|
||||
rule: RelatedClassRule | RelatedSpaceRule
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -369,7 +392,8 @@ export default plugin(trackerId, {
|
||||
Milestone: '' as Ref<Class<Milestone>>,
|
||||
TypeMilestoneStatus: '' as Ref<Class<Type<MilestoneStatus>>>,
|
||||
TimeSpendReport: '' as Ref<Class<TimeSpendReport>>,
|
||||
TypeReportedTime: '' as Ref<Class<Type<number>>>
|
||||
TypeReportedTime: '' as Ref<Class<Type<number>>>,
|
||||
RelatedIssueTarget: '' as Ref<Class<RelatedIssueTarget>>
|
||||
},
|
||||
ids: {
|
||||
NoParent: '' as Ref<Issue>,
|
||||
@ -471,7 +495,8 @@ export default plugin(trackerId, {
|
||||
EditWorkflowStatuses: '' as Ref<Action>,
|
||||
EditProject: '' as Ref<Action>,
|
||||
SetMilestone: '' as Ref<Action>,
|
||||
SetLabels: '' as Ref<Action>
|
||||
SetLabels: '' as Ref<Action>,
|
||||
EditRelatedTargets: '' as Ref<Action>
|
||||
},
|
||||
project: {
|
||||
DefaultProject: '' as Ref<Project>
|
||||
@ -487,7 +512,8 @@ export default plugin(trackerId, {
|
||||
IssueNotificationChanged: '' as IntlString,
|
||||
IssueNotificationChangedProperty: '' as IntlString,
|
||||
IssueNotificationMessage: '' as IntlString,
|
||||
IssueAssigneedToYou: '' as IntlString
|
||||
IssueAssigneedToYou: '' as IntlString,
|
||||
RelatedIssues: '' as IntlString
|
||||
},
|
||||
mixin: {
|
||||
ProjectIssueTargetOptions: '' as Ref<Mixin<ProjectIssueTargetOptions>>
|
||||
|
Loading…
Reference in New Issue
Block a user