States fix (#3690)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-09-14 06:25:26 +03:00 committed by GitHub
parent 2df68a46b1
commit 8b1a488725
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 95 additions and 68 deletions

View File

@ -41,7 +41,7 @@ import workbench, { Application } from '@hcengineering/model-workbench'
import { IntlString } from '@hcengineering/platform' import { IntlString } from '@hcengineering/platform'
import type { AnyComponent } from '@hcengineering/ui' import type { AnyComponent } from '@hcengineering/ui'
import board from './plugin' import board from './plugin'
import { State } from '@hcengineering/task' import { DoneState, State } from '@hcengineering/task'
export { boardId } from '@hcengineering/board' export { boardId } from '@hcengineering/board'
export { boardOperation } from './migration' export { boardOperation } from './migration'
@ -103,6 +103,9 @@ export class TCard extends TTask implements Card {
@Prop(TypeRef(task.class.State), task.string.TaskState, { _id: board.attribute.State }) @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: board.attribute.State })
declare status: Ref<State> declare status: Ref<State>
@Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: board.attribute.DoneState })
declare doneState: Ref<DoneState>
} }
@Model(board.class.MenuPage, core.class.Doc, DOMAIN_MODEL) @Model(board.class.MenuPage, core.class.Doc, DOMAIN_MODEL)

View File

@ -42,7 +42,7 @@ import view, { createAction, actionTemplates as viewTemplates } from '@hcenginee
import workbench from '@hcengineering/model-workbench' import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification' import notification from '@hcengineering/notification'
import setting from '@hcengineering/setting' import setting from '@hcengineering/setting'
import { State } from '@hcengineering/task' import { DoneState, State } from '@hcengineering/task'
import { ViewOptionsModel } from '@hcengineering/view' import { ViewOptionsModel } from '@hcengineering/view'
import lead from './plugin' import lead from './plugin'
@ -84,6 +84,9 @@ export class TLead extends TTask implements Lead {
@Prop(TypeRef(task.class.State), task.string.TaskState, { _id: lead.attribute.State }) @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: lead.attribute.State })
declare status: Ref<State> declare status: Ref<State>
@Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: lead.attribute.DoneState })
declare doneState: Ref<DoneState>
declare space: Ref<Funnel> declare space: Ref<Funnel>
} }

View File

@ -55,7 +55,7 @@ import {
recruitId recruitId
} from '@hcengineering/recruit' } from '@hcengineering/recruit'
import setting from '@hcengineering/setting' import setting from '@hcengineering/setting'
import { State } from '@hcengineering/task' import { DoneState, State } from '@hcengineering/task'
import { KeyBinding, ViewOptionsModel } from '@hcengineering/view' import { KeyBinding, ViewOptionsModel } from '@hcengineering/view'
import recruit from './plugin' import recruit from './plugin'
import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review' import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
@ -167,6 +167,9 @@ export class TApplicant extends TTask implements Applicant {
@Prop(TypeRef(task.class.State), task.string.TaskState, { _id: recruit.attribute.State }) @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: recruit.attribute.State })
declare status: Ref<State> declare status: Ref<State>
@Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: recruit.attribute.DoneState })
declare doneState: Ref<DoneState>
} }
@Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK) @Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK)
@ -1196,23 +1199,17 @@ export function createModel (builder: Builder): void {
}) })
createAction(builder, { createAction(builder, {
action: view.actionImpl.ValueSelector, action: task.actionImpl.SelectStatus,
actionPopup: view.component.ValueSelector, actionPopup: task.component.StatusSelector,
actionProps: { actionProps: {
attribute: 'status',
_class: task.class.State, _class: task.class.State,
query: {}, ofAttribute: recruit.attribute.State,
searchField: 'name',
// should match space
fillQuery: { space: 'space' },
// Only apply for same vacancy
docMatches: ['space'],
placeholder: task.string.TaskState placeholder: task.string.TaskState
}, },
label: task.string.TaskState, label: task.string.TaskState,
icon: task.icon.TaskState, icon: task.icon.TaskState,
keyBinding: [], keyBinding: ['keyS->keyS'],
input: 'none', input: 'any',
category: recruit.category.Recruit, category: recruit.category.Recruit,
target: recruit.class.Applicant, target: recruit.class.Applicant,
context: { context: {
@ -1221,24 +1218,19 @@ export function createModel (builder: Builder): void {
group: 'edit' group: 'edit'
} }
}) })
createAction(builder, { createAction(builder, {
action: view.actionImpl.ValueSelector, action: task.actionImpl.SelectStatus,
actionPopup: view.component.ValueSelector, actionPopup: task.component.StatusSelector,
actionProps: { actionProps: {
attribute: 'doneState',
_class: task.class.DoneState, _class: task.class.DoneState,
query: {}, ofAttribute: recruit.attribute.DoneState,
searchField: 'name',
// should match space
fillQuery: { space: 'space' },
// Only apply for same vacancy
docMatches: ['space'],
placeholder: task.string.DoneState placeholder: task.string.DoneState
}, },
label: task.string.DoneState, label: task.string.DoneState,
icon: task.icon.TaskState, icon: task.icon.TaskState,
keyBinding: [], keyBinding: ['keyS->keyD'],
input: 'none', input: 'any',
category: recruit.category.Recruit, category: recruit.category.Recruit,
target: recruit.class.Applicant, target: recruit.class.Applicant,
context: { context: {
@ -1247,6 +1239,7 @@ export function createModel (builder: Builder): void {
group: 'edit' group: 'edit'
} }
}) })
createAction( createAction(
builder, builder,
{ {

View File

@ -240,21 +240,21 @@ async function fixStatusAttributes (client: MigrationClient): Promise<void> {
const space = map.get(oldStatus.space) const space = map.get(oldStatus.space)
if (space !== undefined) { if (space !== undefined) {
try { try {
const isDone = oldStatus._class === task.class.DoneState const isDone = client.hierarchy.isDerived(oldStatus._class, task.class.DoneState)
let ofAttribute = task.attribute.State let ofAttribute = task.attribute.State
if (space._class === ('recruit:class:Vacancy' as Ref<Class<Space>>)) { if (space._class === ('recruit:class:Vacancy' as Ref<Class<Space>>)) {
ofAttribute = isDone ofAttribute = isDone
? ('recruit.attribute.DoneState' as Ref<Attribute<State>>) ? ('recruit:attribute:DoneState' as Ref<Attribute<State>>)
: ('recruit:attribute:State' as Ref<Attribute<State>>) : ('recruit:attribute:State' as Ref<Attribute<State>>)
} }
if (space._class === ('lead:class:Funnel' as Ref<Class<Space>>)) { if (space._class === ('lead:class:Funnel' as Ref<Class<Space>>)) {
ofAttribute = isDone ofAttribute = isDone
? ('lead.attribute.DoneState' as Ref<Attribute<State>>) ? ('lead:attribute:DoneState' as Ref<Attribute<State>>)
: ('lead:attribute:State' as Ref<Attribute<State>>) : ('lead:attribute:State' as Ref<Attribute<State>>)
} }
if (space._class === ('board:class:Board' as Ref<Class<Space>>)) { if (space._class === ('board:class:Board' as Ref<Class<Space>>)) {
ofAttribute = isDone ofAttribute = isDone
? ('board.attribute.DoneState' as Ref<Attribute<State>>) ? ('board:attribute:DoneState' as Ref<Attribute<State>>)
: ('board:attribute:State' as Ref<Attribute<State>>) : ('board:attribute:State' as Ref<Attribute<State>>)
} }
if (space._class === ('tracker:class:Project' as Ref<Class<Space>>)) { if (space._class === ('tracker:class:Project' as Ref<Class<Space>>)) {

View File

@ -34,7 +34,8 @@ export default mergeIds(taskId, task, {
TodoItemMarkDone: '' as ViewAction, TodoItemMarkDone: '' as ViewAction,
TodoItemMarkUnDone: '' as ViewAction, TodoItemMarkUnDone: '' as ViewAction,
ArchiveSpace: '' as ViewAction, ArchiveSpace: '' as ViewAction,
UnarchiveSpace: '' as ViewAction UnarchiveSpace: '' as ViewAction,
SelectStatus: '' as ViewAction
}, },
category: { category: {
Task: '' as Ref<ActionCategory>, Task: '' as Ref<ActionCategory>,
@ -57,7 +58,8 @@ export default mergeIds(taskId, task, {
TaskHeader: '' as AnyComponent, TaskHeader: '' as AnyComponent,
Dashboard: '' as AnyComponent, Dashboard: '' as AnyComponent,
StateRefPresenter: '' as AnyComponent, StateRefPresenter: '' as AnyComponent,
DoneStateRefPresenter: '' as AnyComponent DoneStateRefPresenter: '' as AnyComponent,
StatusSelector: '' as AnyComponent
}, },
space: { space: {
TasksPublic: '' as Ref<Space> TasksPublic: '' as Ref<Space>

View File

@ -1541,8 +1541,13 @@ export function createModel (builder: Builder): void {
createAction( createAction(
builder, builder,
{ {
action: tracker.actionImpl.SelectStatus, action: task.actionImpl.SelectStatus,
actionPopup: tracker.component.StatusSelector, actionPopup: task.component.StatusSelector,
actionProps: {
_class: tracker.class.IssueStatus,
ofAttribute: tracker.attribute.IssueStatus,
placeholder: tracker.string.Status
},
label: tracker.string.Status, label: tracker.string.Status,
icon: tracker.icon.CategoryBacklog, icon: tracker.icon.CategoryBacklog,
keyBinding: ['keyS->keyS'], keyBinding: ['keyS->keyS'],

View File

@ -54,8 +54,7 @@ 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
StatusSelector: '' as AnyComponent
}, },
app: { app: {
Tracker: '' as Ref<Application> Tracker: '' as Ref<Application>
@ -78,7 +77,6 @@ export default mergeIds(trackerId, tracker, {
IssueCategory: '' as Ref<ObjectSearchCategory> IssueCategory: '' as Ref<ObjectSearchCategory>
}, },
actionImpl: { actionImpl: {
SelectStatus: '' as ViewAction,
Move: '' as ViewAction, Move: '' as ViewAction,
CopyToClipboard: '' as ViewAction, CopyToClipboard: '' as ViewAction,
EditWorkflowStatuses: '' as ViewAction, EditWorkflowStatuses: '' as ViewAction,

View File

@ -1,24 +1,29 @@
<script lang="ts"> <script lang="ts">
import core, { DocumentQuery, FindOptions, SortingOrder } from '@hcengineering/core' import core, { Attribute, Class, DocumentQuery, FindOptions, Ref, SortingOrder, Status } from '@hcengineering/core'
import { ObjectPopup, createQuery, getClient } from '@hcengineering/presentation' import { ObjectPopup, createQuery, getClient } from '@hcengineering/presentation'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { Label, resizeObserver } from '@hcengineering/ui' import { Label, resizeObserver } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources' import { ObjectPresenter } from '@hcengineering/view-resources'
import view from '@hcengineering/view-resources/src/plugin' import view from '@hcengineering/view-resources/src/plugin'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin' import task from '../plugin'
import { SpaceWithStates, Task } from '@hcengineering/task'
import { IntlString } from '@hcengineering/platform'
export let value: Issue | Issue[] export let value: Task | Task[]
const queryOptions: FindOptions<IssueStatus> = { export let placeholder: IntlString
export let ofAttribute: Ref<Attribute<Status>>
export let _class: Ref<Class<Status>>
const queryOptions: FindOptions<Status> = {
lookup: { lookup: {
category: core.class.StatusCategory category: core.class.StatusCategory
}, },
sort: { category: SortingOrder.Ascending, name: SortingOrder.Ascending } sort: { category: SortingOrder.Ascending, name: SortingOrder.Ascending }
} }
const placeholder = tracker.string.SetStatus
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const client = getClient() const client = getClient()
const h = client.getHierarchy()
const changeStatus = async (newStatus: any) => { const changeStatus = async (newStatus: any) => {
if (newStatus === undefined) { if (newStatus === undefined) {
dispatch('close', undefined) dispatch('close', undefined)
@ -26,10 +31,11 @@
} }
const docs = Array.isArray(value) ? value : [value] const docs = Array.isArray(value) ? value : [value]
const changed = (d: Issue) => d.status !== newStatus const changed = (d: Task) => d.status !== newStatus
const field = h.isDerived(_class, task.class.DoneState) ? 'doneState' : 'status'
await Promise.all( await Promise.all(
docs.filter(changed).map((it) => { docs.filter(changed).map((it) => {
return client.update(it, { status: newStatus }) return client.update(it, { [field]: newStatus })
}) })
) )
@ -37,31 +43,36 @@
} }
$: current = Array.isArray(value) $: current = Array.isArray(value)
? value.every((v) => v.status === (value as Array<Issue>)[0].status) ? value.every((v) => v.status === (value as Array<Task>)[0].status)
? (value as Array<Issue>)[0].status ? (value as Array<Task>)[0].status
: undefined : undefined
: value.status : value.status
let finalQuery: DocumentQuery<IssueStatus> = {} let finalQuery: DocumentQuery<Status> = {}
let docMatch = true let docMatch = true
$: _space = Array.isArray(value) $: _space = Array.isArray(value)
? value.every((v) => v.space === (value as Array<Issue>)[0].space) ? value.every((v) => v.space === (value as Array<Task>)[0].space)
? (value as Array<Issue>)[0].space ? (value as Array<Task>)[0].space
: undefined : undefined
: value.space : value.space
let project: Project | undefined let project: SpaceWithStates | undefined
const query = createQuery() const query = createQuery()
$: _space ? query.query(tracker.class.Project, { _id: _space }, (res) => (project = res[0])) : (project = undefined) $: _space
? query.query(task.class.SpaceWithStates, { _id: _space as Ref<SpaceWithStates> }, (res) => (project = res[0]))
: (project = undefined)
function updateQuery (space: Project | undefined): void { function updateQuery (space: SpaceWithStates | undefined): void {
if (space === undefined) { if (space === undefined) {
finalQuery = { ofAttribute: tracker.attribute.IssueStatus } finalQuery = { ofAttribute }
} else { } else {
finalQuery = { ofAttribute: tracker.attribute.IssueStatus, _id: { $in: space.states } } finalQuery = {
ofAttribute,
_id: { $in: !h.isDerived(_class, task.class.DoneState) ? space.states : space?.doneStates }
}
} }
docMatch = true docMatch = true
} }
@ -71,7 +82,7 @@
{#if docMatch} {#if docMatch}
<ObjectPopup <ObjectPopup
_class={tracker.class.IssueStatus} {_class}
docQuery={finalQuery} docQuery={finalQuery}
options={queryOptions} options={queryOptions}
allowDeselect={true} allowDeselect={true}

View File

@ -14,9 +14,9 @@
// limitations under the License. // limitations under the License.
// //
import { Attribute, Ref, Status } from '@hcengineering/core' import { Attribute, Class, Ref, Status } from '@hcengineering/core'
import { Resources } from '@hcengineering/platform' import { IntlString, Resources } from '@hcengineering/platform'
import { SpaceWithStates } from '@hcengineering/task' import { SpaceWithStates, Task } from '@hcengineering/task'
import { showPopup } from '@hcengineering/ui' import { showPopup } from '@hcengineering/ui'
import AssignedTasks from './components/AssignedTasks.svelte' import AssignedTasks from './components/AssignedTasks.svelte'
import CreateStatePopup from './components/CreateStatePopup.svelte' import CreateStatePopup from './components/CreateStatePopup.svelte'
@ -41,6 +41,7 @@ import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte' import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte' import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
import Todos from './components/todos/Todos.svelte' import Todos from './components/todos/Todos.svelte'
import StatusSelector from './components/StatusSelector.svelte'
export { default as AssigneePresenter } from './components/AssigneePresenter.svelte' export { default as AssigneePresenter } from './components/AssigneePresenter.svelte'
export { StateRefPresenter } export { StateRefPresenter }
@ -60,6 +61,22 @@ async function editStatuses (
) )
} }
async function selectStatus (
doc: Task | Task[],
ev: any,
props: {
ofAttribute: Ref<Attribute<Status>>
placeholder: IntlString
_class: Ref<Class<Status>>
}
): Promise<void> {
showPopup(
StatusSelector,
{ value: doc, ofAttribute: props.ofAttribute, _class: props._class, placeholder: props.placeholder },
'top'
)
}
export type StatesBarPosition = 'start' | 'middle' | 'end' | undefined export type StatesBarPosition = 'start' | 'middle' | 'end' | undefined
export default async (): Promise<Resources> => ({ export default async (): Promise<Resources> => ({
@ -85,9 +102,11 @@ export default async (): Promise<Resources> => ({
TodoItemsPopup, TodoItemsPopup,
DueDateEditor, DueDateEditor,
CreateStatePopup, CreateStatePopup,
CreateStateTemplatePopup CreateStateTemplatePopup,
StatusSelector
}, },
actionImpl: { actionImpl: {
EditStatuses: editStatuses EditStatuses: editStatuses,
SelectStatus: selectStatus
} }
}) })

View File

@ -75,7 +75,6 @@ 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 StatusSelector from './components/issues/StatusSelector.svelte'
import { import {
getIssueId, getIssueId,
getIssueTitle, getIssueTitle,
@ -208,10 +207,6 @@ async function move (issues: Issue | Issue[]): Promise<void> {
showPopup(MoveIssues, { selected: issues }, 'top') showPopup(MoveIssues, { selected: issues }, 'top')
} }
async function selectStatus (doc: Issue | Issue[]): Promise<void> {
showPopup(StatusSelector, { value: doc }, 'top')
}
async function editWorkflowStatuses (project: Project | undefined): Promise<void> { async function editWorkflowStatuses (project: Project | undefined): Promise<void> {
if (project !== undefined) { if (project !== undefined) {
showPopup(Statuses, { projectId: project._id, projectClass: project._class }, 'top') showPopup(Statuses, { projectId: project._id, projectClass: project._class }, 'top')
@ -478,8 +473,7 @@ export default async (): Promise<Resources> => ({
PriorityFilterValuePresenter, PriorityFilterValuePresenter,
StatusFilterValuePresenter, StatusFilterValuePresenter,
ProjectFilterValuePresenter, ProjectFilterValuePresenter,
ComponentFilterValuePresenter, ComponentFilterValuePresenter
StatusSelector
}, },
completion: { completion: {
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) => IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
@ -502,7 +496,6 @@ export default async (): Promise<Resources> => ({
IsProjectJoined: async (project: Project) => !project.private || project.members.includes(getCurrentAccount()._id) IsProjectJoined: async (project: Project) => !project.private || project.members.includes(getCurrentAccount()._id)
}, },
actionImpl: { actionImpl: {
SelectStatus: selectStatus,
Move: move, Move: move,
EditWorkflowStatuses: editWorkflowStatuses, EditWorkflowStatuses: editWorkflowStatuses,
EditProject: editProject, EditProject: editProject,