mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +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, {
|
createAction(builder, {
|
||||||
...viewTemplates.open,
|
...viewTemplates.open,
|
||||||
target: lead.class.Funnel,
|
target: lead.class.Funnel,
|
||||||
|
query: {
|
||||||
|
archived: true
|
||||||
|
},
|
||||||
context: {
|
context: {
|
||||||
mode: ['browser', 'context'],
|
mode: ['browser', 'context'],
|
||||||
group: 'create'
|
group: 'create'
|
||||||
@ -645,4 +648,26 @@ export function createModel (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
lead.action.CreateGlobalLead
|
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 { 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 core from '@hcengineering/model-core'
|
||||||
import { createKanbanTemplate, createSequence } from '@hcengineering/model-task'
|
import { createKanbanTemplate, createSequence } from '@hcengineering/model-task'
|
||||||
|
import tracker from '@hcengineering/model-tracker'
|
||||||
import task, { KanbanTemplate, createStates } from '@hcengineering/task'
|
import task, { KanbanTemplate, createStates } from '@hcengineering/task'
|
||||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||||
import lead from './plugin'
|
import lead from './plugin'
|
||||||
@ -120,5 +122,20 @@ export const leadOperation: MigrateOperation = {
|
|||||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||||
const ops = new TxOperations(client, core.account.System)
|
const ops = new TxOperations(client, core.account.System)
|
||||||
await createDefaults(ops)
|
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,
|
Title: '' as IntlString,
|
||||||
ManageFunnelStatuses: '' as IntlString,
|
ManageFunnelStatuses: '' as IntlString,
|
||||||
GotoLeadApplication: '' as IntlString,
|
GotoLeadApplication: '' as IntlString,
|
||||||
ConfigDescription: '' as IntlString
|
ConfigDescription: '' as IntlString,
|
||||||
|
EditFunnel: '' as IntlString
|
||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
CreateLead: '' as AnyComponent,
|
CreateLead: '' as AnyComponent,
|
||||||
|
@ -88,8 +88,6 @@ export class TVacancy extends TSpaceWithStates implements Vacancy {
|
|||||||
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
|
||||||
comments?: number
|
comments?: number
|
||||||
|
|
||||||
relations!: number
|
|
||||||
|
|
||||||
@Prop(TypeString(), recruit.string.Vacancy)
|
@Prop(TypeString(), recruit.string.Vacancy)
|
||||||
@Index(IndexKind.FullText)
|
@Index(IndexKind.FullText)
|
||||||
@Hidden()
|
@Hidden()
|
||||||
|
@ -15,12 +15,20 @@
|
|||||||
|
|
||||||
import { getCategories } from '@anticrm/skillset'
|
import { getCategories } from '@anticrm/skillset'
|
||||||
import core, { Doc, Ref, Space, TxOperations } from '@hcengineering/core'
|
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 tags, { TagCategory } from '@hcengineering/model-tags'
|
||||||
import { createKanbanTemplate, createSequence } from '@hcengineering/model-task'
|
import { createKanbanTemplate, createSequence } from '@hcengineering/model-task'
|
||||||
import task, { KanbanTemplate } from '@hcengineering/task'
|
import task, { KanbanTemplate } from '@hcengineering/task'
|
||||||
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
|
||||||
import recruit from './plugin'
|
import recruit from './plugin'
|
||||||
|
import { recruitId } from '@hcengineering/recruit'
|
||||||
|
import tracker from '@hcengineering/model-tracker'
|
||||||
|
|
||||||
export const recruitOperation: MigrateOperation = {
|
export const recruitOperation: MigrateOperation = {
|
||||||
async migrate (client: MigrationClient): Promise<void> {},
|
async migrate (client: MigrationClient): Promise<void> {},
|
||||||
@ -28,6 +36,28 @@ export const recruitOperation: MigrateOperation = {
|
|||||||
const tx = new TxOperations(client, core.account.System)
|
const tx = new TxOperations(client, core.account.System)
|
||||||
await createDefaults(tx)
|
await createDefaults(tx)
|
||||||
await fixTemplateSpace(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 { DOMAIN_TASK } from '@hcengineering/model-task'
|
||||||
import tags from '@hcengineering/tags'
|
import tags from '@hcengineering/tags'
|
||||||
import { Issue, Project, TimeReportDayType, TimeSpendReport, createStatuses } from '@hcengineering/tracker'
|
import { Issue, Project, TimeReportDayType, TimeSpendReport, createStatuses } from '@hcengineering/tracker'
|
||||||
import { DOMAIN_TRACKER } from '.'
|
import { DOMAIN_TRACKER } from './types'
|
||||||
import tracker from './plugin'
|
import tracker from './plugin'
|
||||||
import { DOMAIN_SPACE } from '@hcengineering/model-core'
|
import { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
|
@ -43,7 +43,8 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
Unarchive: '' as IntlString,
|
Unarchive: '' as IntlString,
|
||||||
UnarchiveConfirm: '' as IntlString,
|
UnarchiveConfirm: '' as IntlString,
|
||||||
AllProjects: '' as IntlString,
|
AllProjects: '' as IntlString,
|
||||||
RemainingTime: '' as IntlString
|
RemainingTime: '' as IntlString,
|
||||||
|
MapRelatedIssues: '' as IntlString
|
||||||
},
|
},
|
||||||
activity: {
|
activity: {
|
||||||
TxIssueCreated: '' as AnyComponent,
|
TxIssueCreated: '' as AnyComponent,
|
||||||
@ -55,7 +56,9 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
IssueStatistics: '' as AnyComponent,
|
IssueStatistics: '' as AnyComponent,
|
||||||
TimeSpendReportPopup: '' as AnyComponent,
|
TimeSpendReportPopup: '' as AnyComponent,
|
||||||
NotificationIssuePresenter: '' as AnyComponent,
|
NotificationIssuePresenter: '' as AnyComponent,
|
||||||
MilestoneFilter: '' as AnyComponent
|
MilestoneFilter: '' as AnyComponent,
|
||||||
|
EditRelatedTargets: '' as AnyComponent,
|
||||||
|
EditRelatedTargetsPopup: '' as AnyComponent
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
Tracker: '' as Ref<Application>
|
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)
|
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.
|
// We need to update fields if they are different.
|
||||||
const documentUpdate: DocumentUpdate<Doc> = {}
|
const documentUpdate: DocumentUpdate<T> = {}
|
||||||
for (const [k, v] of Object.entries(raw)) {
|
for (const [k, v] of Object.entries(update)) {
|
||||||
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -309,7 +314,7 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Object.keys(documentUpdate).length > 0) {
|
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)
|
TxProcessor.applyUpdate(doc, documentUpdate)
|
||||||
}
|
}
|
||||||
return doc
|
return doc
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
export let label: IntlString
|
export let label: IntlString
|
||||||
export let okAction: () => void
|
export let okAction: () => void
|
||||||
|
export let okLabel: IntlString | undefined = undefined
|
||||||
export let canSave: boolean = false
|
export let canSave: boolean = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -40,7 +41,7 @@
|
|||||||
<div class="antiCard-footer">
|
<div class="antiCard-footer">
|
||||||
<Button
|
<Button
|
||||||
disabled={!canSave}
|
disabled={!canSave}
|
||||||
label={presentation.string.Create}
|
label={okLabel ?? presentation.string.Create}
|
||||||
kind={'accented'}
|
kind={'accented'}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
okAction()
|
okAction()
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let iconWithEmoji: AnySvelteComponent | Asset | ComponentType | undefined = view.ids.IconWithEmoji
|
export let iconWithEmoji: AnySvelteComponent | Asset | ComponentType | undefined = view.ids.IconWithEmoji
|
||||||
export let defaultIcon: AnySvelteComponent | Asset | ComponentType = IconFolder
|
export let defaultIcon: AnySvelteComponent | Asset | ComponentType = IconFolder
|
||||||
|
export let findDefaultSpace: (() => Promise<Space | undefined>) | undefined = undefined
|
||||||
|
|
||||||
let selected: (Space & IconProps) | undefined
|
let selected: (Space & IconProps) | undefined
|
||||||
|
|
||||||
@ -71,7 +72,7 @@
|
|||||||
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
|
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
|
||||||
|
|
||||||
if (selected === undefined && autoSelect) {
|
if (selected === undefined && autoSelect) {
|
||||||
selected = await client.findOne(_class, { ...(spaceQuery ?? {}) })
|
selected = (await findDefaultSpace?.()) ?? (await client.findOne(_class, { ...(spaceQuery ?? {}) }))
|
||||||
if (selected !== undefined) {
|
if (selected !== undefined) {
|
||||||
value = selected._id ?? undefined
|
value = selected._id ?? undefined
|
||||||
dispatch('change', value)
|
dispatch('change', value)
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
export let iconWithEmoji: AnySvelteComponent | Asset | ComponentType | undefined = undefined
|
export let iconWithEmoji: AnySvelteComponent | Asset | ComponentType | undefined = undefined
|
||||||
export let defaultIcon: AnySvelteComponent | Asset | ComponentType | undefined = undefined
|
export let defaultIcon: AnySvelteComponent | Asset | ComponentType | undefined = undefined
|
||||||
export let readonly: boolean = false
|
export let readonly: boolean = false
|
||||||
|
export let findDefaultSpace: (() => Promise<Space | undefined>) | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
@ -71,4 +72,5 @@
|
|||||||
dispatch('change', space)
|
dispatch('change', space)
|
||||||
}}
|
}}
|
||||||
on:space
|
on:space
|
||||||
|
{findDefaultSpace}
|
||||||
/>
|
/>
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"Members": "Members",
|
"Members": "Members",
|
||||||
"UnAssign": "Unassign",
|
"UnAssign": "Unassign",
|
||||||
"ConfigLabel": "CRM",
|
"ConfigLabel": "CRM",
|
||||||
"ConfigDescription": "Extension for Customer relation management"
|
"ConfigDescription": "Extension for Customer relation management",
|
||||||
|
"EditFunnel": "Edit Funnel"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,6 +31,7 @@
|
|||||||
"Members": "Пользователи",
|
"Members": "Пользователи",
|
||||||
"UnAssign": "Отменить назначение",
|
"UnAssign": "Отменить назначение",
|
||||||
"ConfigLabel": "CRM",
|
"ConfigLabel": "CRM",
|
||||||
"ConfigDescription": "Расширение по работе с клиентами"
|
"ConfigDescription": "Расширение по работе с клиентами",
|
||||||
|
"EditFunnel": "Редактировать воронку"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,26 +14,35 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<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 presentation, { getClient, SpaceCreateCard } from '@hcengineering/presentation'
|
||||||
import task, { createStates, KanbanTemplate } from '@hcengineering/task'
|
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 { createEventDispatcher } from 'svelte'
|
||||||
import lead from '../plugin'
|
import lead from '../plugin'
|
||||||
|
|
||||||
|
export let funnel: Funnel | undefined = undefined
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let name: string = ''
|
const client = getClient()
|
||||||
const description: string = ''
|
const hierarchy = client.getHierarchy()
|
||||||
|
|
||||||
|
$: isNew = !funnel
|
||||||
|
|
||||||
|
let name: string = funnel?.name ?? ''
|
||||||
|
const description: string = funnel?.description ?? ''
|
||||||
let templateId: Ref<KanbanTemplate> | undefined
|
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 {
|
export function canClose (): boolean {
|
||||||
return name === '' && templateId !== undefined
|
return name === '' && templateId !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = getClient()
|
|
||||||
|
|
||||||
async function createFunnel (): Promise<void> {
|
async function createFunnel (): Promise<void> {
|
||||||
if (
|
if (
|
||||||
templateId !== undefined &&
|
templateId !== undefined &&
|
||||||
@ -49,17 +58,25 @@
|
|||||||
description,
|
description,
|
||||||
private: isPrivate,
|
private: isPrivate,
|
||||||
archived: false,
|
archived: false,
|
||||||
members: [getCurrentAccount()._id],
|
members,
|
||||||
templateId,
|
templateId,
|
||||||
states,
|
states,
|
||||||
doneStates
|
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>
|
</script>
|
||||||
|
|
||||||
<SpaceCreateCard
|
<SpaceCreateCard
|
||||||
label={lead.string.CreateFunnel}
|
label={lead.string.CreateFunnel}
|
||||||
okAction={createFunnel}
|
okAction={save}
|
||||||
|
okLabel={!isNew ? ui.string.Save : undefined}
|
||||||
canSave={name.length > 0}
|
canSave={name.length > 0}
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
@ -89,5 +106,17 @@
|
|||||||
templateId = evt.detail
|
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>
|
</Grid>
|
||||||
</SpaceCreateCard>
|
</SpaceCreateCard>
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
$: if (_space === undefined) {
|
||||||
if (funnels.find((it) => it._id === _space) === undefined) {
|
if (funnels.find((it) => it._id === _space) === undefined) {
|
||||||
_space = funnels[0]?._id
|
_space = funnels[0]?._id
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,6 @@ export default mergeIds(taskId, task, {
|
|||||||
CantStatusDeleteError: '' as IntlString,
|
CantStatusDeleteError: '' as IntlString,
|
||||||
Archive: '' as IntlString,
|
Archive: '' as IntlString,
|
||||||
Unarchive: '' as IntlString,
|
Unarchive: '' as IntlString,
|
||||||
RelatedIssues: '' as IntlString,
|
|
||||||
|
|
||||||
Tasks: '' as IntlString,
|
Tasks: '' as IntlString,
|
||||||
Task: '' as IntlString,
|
Task: '' as IntlString,
|
||||||
|
@ -283,7 +283,9 @@
|
|||||||
"IssueNotificationChangedProperty": "{senderName} changed {property} to \"{newValue}\"",
|
"IssueNotificationChangedProperty": "{senderName} changed {property} to \"{newValue}\"",
|
||||||
"IssueNotificationMessage": "{senderName}: {message}",
|
"IssueNotificationMessage": "{senderName}: {message}",
|
||||||
"PreviousAssigned": "Previously assigned",
|
"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": {}
|
"status": {}
|
||||||
}
|
}
|
||||||
|
@ -283,7 +283,9 @@
|
|||||||
"IssueNotificationChangedProperty": "{senderName} изменил {property} на \"{newValue}\"",
|
"IssueNotificationChangedProperty": "{senderName} изменил {property} на \"{newValue}\"",
|
||||||
"IssueNotificationMessage": "{senderName}: {message}",
|
"IssueNotificationMessage": "{senderName}: {message}",
|
||||||
"PreviousAssigned": "Ранее назначенные",
|
"PreviousAssigned": "Ранее назначенные",
|
||||||
"IssueAssigneedToYou": "Назначено вам"
|
"IssueAssigneedToYou": "Назначено вам",
|
||||||
|
"RelatedIssueTargetDescription": "Настройка проекта по умолчанию для Класса или пространства",
|
||||||
|
"MapRelatedIssues": "Настроить проекты по умолчанию для связанных задач"
|
||||||
},
|
},
|
||||||
"status": {}
|
"status": {}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
"@hcengineering/workbench-resources": "^0.6.1",
|
"@hcengineering/workbench-resources": "^0.6.1",
|
||||||
"@hcengineering/activity-resources": "^0.6.1",
|
"@hcengineering/activity-resources": "^0.6.1",
|
||||||
"@hcengineering/activity": "^0.6.0",
|
"@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.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<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 chunter from '@hcengineering/chunter'
|
||||||
import { Employee } from '@hcengineering/contact'
|
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 { getResource, translate } from '@hcengineering/platform'
|
||||||
|
import preference, { SpacePreference } from '@hcengineering/preference'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
createQuery,
|
|
||||||
DraftController,
|
DraftController,
|
||||||
getClient,
|
|
||||||
KeyedAttribute,
|
KeyedAttribute,
|
||||||
MessageBox,
|
MessageBox,
|
||||||
MultipleDraftController,
|
MultipleDraftController,
|
||||||
SpaceSelector
|
SpaceSelector,
|
||||||
|
createQuery,
|
||||||
|
getClient
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||||
import {
|
import {
|
||||||
calcRank,
|
|
||||||
Component as ComponentType,
|
Component as ComponentType,
|
||||||
Issue,
|
Issue,
|
||||||
IssueDraft,
|
IssueDraft,
|
||||||
@ -39,29 +40,30 @@
|
|||||||
IssueTemplate,
|
IssueTemplate,
|
||||||
Milestone,
|
Milestone,
|
||||||
Project,
|
Project,
|
||||||
ProjectIssueTargetOptions
|
ProjectIssueTargetOptions,
|
||||||
|
calcRank
|
||||||
} from '@hcengineering/tracker'
|
} from '@hcengineering/tracker'
|
||||||
import {
|
import {
|
||||||
addNotification,
|
|
||||||
Button,
|
Button,
|
||||||
Component,
|
Component,
|
||||||
createFocusManager,
|
|
||||||
DatePresenter,
|
DatePresenter,
|
||||||
EditBox,
|
EditBox,
|
||||||
FocusHandler,
|
FocusHandler,
|
||||||
IconAttachment,
|
IconAttachment,
|
||||||
Label,
|
Label,
|
||||||
|
addNotification,
|
||||||
|
createFocusManager,
|
||||||
showPopup,
|
showPopup,
|
||||||
themeStore
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { Attachment } from '@hcengineering/attachment'
|
|
||||||
import { AttachmentPresenter } from '@hcengineering/attachment-resources'
|
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
import { ObjectBox } from '@hcengineering/view-resources'
|
import { ObjectBox } from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||||
import { activeComponent, activeMilestone, generateIssueShortLink, getIssueId, updateIssueRelation } from '../issues'
|
import { activeComponent, activeMilestone, generateIssueShortLink, getIssueId, updateIssueRelation } from '../issues'
|
||||||
import tracker from '../plugin'
|
import tracker from '../plugin'
|
||||||
import ComponentSelector from './ComponentSelector.svelte'
|
import ComponentSelector from './ComponentSelector.svelte'
|
||||||
|
import SetParentIssueActionPopup from './SetParentIssueActionPopup.svelte'
|
||||||
|
import SubIssues from './SubIssues.svelte'
|
||||||
import AssigneeEditor from './issues/AssigneeEditor.svelte'
|
import AssigneeEditor from './issues/AssigneeEditor.svelte'
|
||||||
import IssueNotification from './issues/IssueNotification.svelte'
|
import IssueNotification from './issues/IssueNotification.svelte'
|
||||||
import ParentIssue from './issues/ParentIssue.svelte'
|
import ParentIssue from './issues/ParentIssue.svelte'
|
||||||
@ -69,11 +71,9 @@
|
|||||||
import StatusEditor from './issues/StatusEditor.svelte'
|
import StatusEditor from './issues/StatusEditor.svelte'
|
||||||
import EstimationEditor from './issues/timereport/EstimationEditor.svelte'
|
import EstimationEditor from './issues/timereport/EstimationEditor.svelte'
|
||||||
import MilestoneSelector from './milestones/MilestoneSelector.svelte'
|
import MilestoneSelector from './milestones/MilestoneSelector.svelte'
|
||||||
import SetParentIssueActionPopup from './SetParentIssueActionPopup.svelte'
|
|
||||||
import SubIssues from './SubIssues.svelte'
|
|
||||||
import ProjectPresenter from './projects/ProjectPresenter.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 status: Ref<IssueStatus> | undefined = undefined
|
||||||
export let priority: IssuePriority | undefined = undefined
|
export let priority: IssuePriority | undefined = undefined
|
||||||
export let assignee: Ref<Employee> | null = null
|
export let assignee: Ref<Employee> | null = null
|
||||||
@ -150,7 +150,7 @@
|
|||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
priority: priority ?? IssuePriority.NoPriority,
|
priority: priority ?? IssuePriority.NoPriority,
|
||||||
space: _space,
|
space: _space as Ref<Project>,
|
||||||
component: component ?? $activeComponent ?? null,
|
component: component ?? $activeComponent ?? null,
|
||||||
dueDate: null,
|
dueDate: null,
|
||||||
attachments: 0,
|
attachments: 0,
|
||||||
@ -209,7 +209,7 @@
|
|||||||
space: _space
|
space: _space
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (object.space !== _space) {
|
$: if (_space !== undefined && object.space !== _space) {
|
||||||
object.space = _space
|
object.space = _space
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +261,7 @@
|
|||||||
return {
|
return {
|
||||||
...p,
|
...p,
|
||||||
_id: p.id,
|
_id: p.id,
|
||||||
space: _space,
|
space: _space as Ref<Project>,
|
||||||
subIssues: [],
|
subIssues: [],
|
||||||
dueDate: null,
|
dueDate: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
@ -355,7 +355,7 @@
|
|||||||
|
|
||||||
async function createIssue (): Promise<void> {
|
async function createIssue (): Promise<void> {
|
||||||
const _id: Ref<Issue> = generateId()
|
const _id: Ref<Issue> = generateId()
|
||||||
if (!canSave || object.status === undefined) {
|
if (!canSave || object.status === undefined || _space === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,7 +363,7 @@
|
|||||||
const incResult = await client.updateDoc(
|
const incResult = await client.updateDoc(
|
||||||
tracker.class.Project,
|
tracker.class.Project,
|
||||||
core.space.Space,
|
core.space.Space,
|
||||||
_space,
|
_space as Ref<Project>,
|
||||||
{
|
{
|
||||||
$inc: { sequence: 1 }
|
$inc: { sequence: 1 }
|
||||||
},
|
},
|
||||||
@ -396,7 +396,7 @@
|
|||||||
|
|
||||||
if (targetSettings !== undefined) {
|
if (targetSettings !== undefined) {
|
||||||
const updateOp = await getResource(targetSettings.update)
|
const updateOp = await getResource(targetSettings.update)
|
||||||
updateOp?.(_id, _space, value, targetSettingOptions)
|
updateOp?.(_id, _space as Ref<Project>, value, targetSettingOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.addCollection(
|
await client.addCollection(
|
||||||
@ -543,6 +543,47 @@
|
|||||||
const manager = createFocusManager()
|
const manager = createFocusManager()
|
||||||
|
|
||||||
let attachments: Map<Ref<Attachment>, Attachment> = new Map<Ref<Attachment>, Attachment>()
|
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>
|
</script>
|
||||||
|
|
||||||
<FocusHandler {manager} />
|
<FocusHandler {manager} />
|
||||||
@ -568,6 +609,7 @@
|
|||||||
size={'small'}
|
size={'small'}
|
||||||
component={ProjectPresenter}
|
component={ProjectPresenter}
|
||||||
defaultIcon={tracker.icon.Home}
|
defaultIcon={tracker.icon.Home}
|
||||||
|
{findDefaultSpace}
|
||||||
/>
|
/>
|
||||||
<ObjectBox
|
<ObjectBox
|
||||||
_class={tracker.class.IssueTemplate}
|
_class={tracker.class.IssueTemplate}
|
||||||
@ -667,6 +709,7 @@
|
|||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
{#if _space}
|
||||||
<SubIssues
|
<SubIssues
|
||||||
bind:this={subIssuesComponent}
|
bind:this={subIssuesComponent}
|
||||||
projectId={_space}
|
projectId={_space}
|
||||||
@ -675,6 +718,7 @@
|
|||||||
component={object.component}
|
component={object.component}
|
||||||
bind:subIssues={object.subIssues}
|
bind:subIssues={object.subIssues}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
{#if targetSettings?.bodyComponent && currentProject}
|
{#if targetSettings?.bodyComponent && currentProject}
|
||||||
<Component
|
<Component
|
||||||
is={targetSettings.bodyComponent}
|
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 SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
||||||
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
||||||
import Statuses from './components/workflow/Statuses.svelte'
|
import Statuses from './components/workflow/Statuses.svelte'
|
||||||
|
import EditRelatedTargets from './components/EditRelatedTargets.svelte'
|
||||||
|
import EditRelatedTargetsPopup from './components/EditRelatedTargetsPopup.svelte'
|
||||||
import {
|
import {
|
||||||
getIssueId,
|
getIssueId,
|
||||||
getIssueTitle,
|
getIssueTitle,
|
||||||
@ -475,7 +477,9 @@ export default async (): Promise<Resources> => ({
|
|||||||
PriorityFilterValuePresenter,
|
PriorityFilterValuePresenter,
|
||||||
StatusFilterValuePresenter,
|
StatusFilterValuePresenter,
|
||||||
ProjectFilterValuePresenter,
|
ProjectFilterValuePresenter,
|
||||||
ComponentFilterValuePresenter
|
ComponentFilterValuePresenter,
|
||||||
|
EditRelatedTargets,
|
||||||
|
EditRelatedTargetsPopup
|
||||||
},
|
},
|
||||||
completion: {
|
completion: {
|
||||||
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
|
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
|
||||||
|
@ -226,7 +226,6 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
AddRelatedIssue: '' as IntlString,
|
AddRelatedIssue: '' as IntlString,
|
||||||
RelatedIssuesNotFound: '' as IntlString,
|
RelatedIssuesNotFound: '' as IntlString,
|
||||||
RelatedIssue: '' as IntlString,
|
RelatedIssue: '' as IntlString,
|
||||||
RelatedIssues: '' as IntlString,
|
|
||||||
BlockedIssue: '' as IntlString,
|
BlockedIssue: '' as IntlString,
|
||||||
BlockingIssue: '' as IntlString,
|
BlockingIssue: '' as IntlString,
|
||||||
BlockedBySearchPlaceholder: '' as IntlString,
|
BlockedBySearchPlaceholder: '' as IntlString,
|
||||||
@ -301,7 +300,9 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
NoStatusFound: '' as IntlString,
|
NoStatusFound: '' as IntlString,
|
||||||
CreateMissingStatus: '' as IntlString,
|
CreateMissingStatus: '' as IntlString,
|
||||||
UnsetParent: '' as IntlString,
|
UnsetParent: '' as IntlString,
|
||||||
PreviousAssigned: '' as IntlString
|
PreviousAssigned: '' as IntlString,
|
||||||
|
EditRelatedTargets: '' as IntlString,
|
||||||
|
RelatedIssueTargetDescription: '' as IntlString
|
||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
NopeComponent: '' as AnyComponent,
|
NopeComponent: '' as AnyComponent,
|
||||||
|
@ -55,6 +55,29 @@ export interface Project extends SpaceWithStates, IconProps {
|
|||||||
defaultTimeReportDay: TimeReportDayType
|
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
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -369,7 +392,8 @@ export default plugin(trackerId, {
|
|||||||
Milestone: '' as Ref<Class<Milestone>>,
|
Milestone: '' as Ref<Class<Milestone>>,
|
||||||
TypeMilestoneStatus: '' as Ref<Class<Type<MilestoneStatus>>>,
|
TypeMilestoneStatus: '' as Ref<Class<Type<MilestoneStatus>>>,
|
||||||
TimeSpendReport: '' as Ref<Class<TimeSpendReport>>,
|
TimeSpendReport: '' as Ref<Class<TimeSpendReport>>,
|
||||||
TypeReportedTime: '' as Ref<Class<Type<number>>>
|
TypeReportedTime: '' as Ref<Class<Type<number>>>,
|
||||||
|
RelatedIssueTarget: '' as Ref<Class<RelatedIssueTarget>>
|
||||||
},
|
},
|
||||||
ids: {
|
ids: {
|
||||||
NoParent: '' as Ref<Issue>,
|
NoParent: '' as Ref<Issue>,
|
||||||
@ -471,7 +495,8 @@ export default plugin(trackerId, {
|
|||||||
EditWorkflowStatuses: '' as Ref<Action>,
|
EditWorkflowStatuses: '' as Ref<Action>,
|
||||||
EditProject: '' as Ref<Action>,
|
EditProject: '' as Ref<Action>,
|
||||||
SetMilestone: '' as Ref<Action>,
|
SetMilestone: '' as Ref<Action>,
|
||||||
SetLabels: '' as Ref<Action>
|
SetLabels: '' as Ref<Action>,
|
||||||
|
EditRelatedTargets: '' as Ref<Action>
|
||||||
},
|
},
|
||||||
project: {
|
project: {
|
||||||
DefaultProject: '' as Ref<Project>
|
DefaultProject: '' as Ref<Project>
|
||||||
@ -487,7 +512,8 @@ export default plugin(trackerId, {
|
|||||||
IssueNotificationChanged: '' as IntlString,
|
IssueNotificationChanged: '' as IntlString,
|
||||||
IssueNotificationChangedProperty: '' as IntlString,
|
IssueNotificationChangedProperty: '' as IntlString,
|
||||||
IssueNotificationMessage: '' as IntlString,
|
IssueNotificationMessage: '' as IntlString,
|
||||||
IssueAssigneedToYou: '' as IntlString
|
IssueAssigneedToYou: '' as IntlString,
|
||||||
|
RelatedIssues: '' as IntlString
|
||||||
},
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
ProjectIssueTargetOptions: '' as Ref<Mixin<ProjectIssueTargetOptions>>
|
ProjectIssueTargetOptions: '' as Ref<Mixin<ProjectIssueTargetOptions>>
|
||||||
|
Loading…
Reference in New Issue
Block a user