diff --git a/dev/generator/src/issues.ts b/dev/generator/src/issues.ts index 686772ebb5..d2c294b703 100644 --- a/dev/generator/src/issues.ts +++ b/dev/generator/src/issues.ts @@ -34,6 +34,7 @@ const object: AttachedData = { subIssues: 0, parents: [], reportedTime: 0, + remainingTime: 0, estimation: 0, reports: 0, childInfo: [] @@ -96,6 +97,7 @@ async function genIssue (client: TxOperations, statuses: Ref[]): Pr dueDate: object.dueDate, parents: [], reportedTime: 0, + remainingTime: 0, estimation: object.estimation, reports: 0, relations: [], diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 08bcb6fcf2..f4d732656e 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -39,8 +39,10 @@ import { TProjectIssueTargetOptions, TRelatedIssueTarget, TTimeSpendReport, + TTypeEstimation, TTypeIssuePriority, TTypeMilestoneStatus, + TTypeRemainingTime, TTypeReportedTime } from './types' import { defineViewlets } from './viewlets' @@ -430,7 +432,9 @@ export function createModel (builder: Builder): void { TTimeSpendReport, TTypeReportedTime, TProjectIssueTargetOptions, - TRelatedIssueTarget + TRelatedIssueTarget, + TTypeEstimation, + TTypeRemainingTime ) defineViewlets(builder) diff --git a/models/tracker/src/migration.ts b/models/tracker/src/migration.ts index 2a62558162..06730b11b1 100644 --- a/models/tracker/src/migration.ts +++ b/models/tracker/src/migration.ts @@ -171,6 +171,22 @@ async function fixEstimation (client: MigrationClient): Promise { } } +async function fixRemainingTime (client: MigrationClient): Promise { + while (true) { + const issues = await client.find(DOMAIN_TASK, { remainingTime: { $exists: false } }, { limit: 1000 }) + for (const issue of issues) { + await client.update( + DOMAIN_TASK, + { _id: issue._id }, + { remainingTime: Math.max(0, issue.estimation - issue.reportedTime) } + ) + } + if (issues.length === 0) { + break + } + } +} + async function moveIssues (client: MigrationClient): Promise { const docs = await client.find(DOMAIN_TRACKER, { _class: tracker.class.Issue }) if (docs.length > 0) { @@ -218,6 +234,10 @@ export const trackerOperation: MigrateOperation = { { state: 'estimationDayToHour', func: fixEstimation + }, + { + state: 'fixRemainingTime', + func: fixRemainingTime } ]) }, diff --git a/models/tracker/src/plugin.ts b/models/tracker/src/plugin.ts index 94f811b58e..0ebe5644d6 100644 --- a/models/tracker/src/plugin.ts +++ b/models/tracker/src/plugin.ts @@ -43,7 +43,6 @@ export default mergeIds(trackerId, tracker, { Unarchive: '' as IntlString, UnarchiveConfirm: '' as IntlString, AllProjects: '' as IntlString, - RemainingTime: '' as IntlString, MapRelatedIssues: '' as IntlString }, activity: { diff --git a/models/tracker/src/presenters.ts b/models/tracker/src/presenters.ts index 519c1ba085..4317a7b584 100644 --- a/models/tracker/src/presenters.ts +++ b/models/tracker/src/presenters.ts @@ -138,7 +138,21 @@ export function definePresenters (builder: Builder): void { classPresenter( builder, tracker.class.TypeReportedTime, - view.component.NumberPresenter, + tracker.component.TimePresenter, tracker.component.ReportedTimeEditor ) + + classPresenter( + builder, + tracker.class.TypeEstimation, + tracker.component.TimePresenter, + tracker.component.EstimationValueEditor + ) + + classPresenter( + builder, + tracker.class.TypeRemainingTime, + tracker.component.TimePresenter, + tracker.component.EstimationValueEditor + ) } diff --git a/models/tracker/src/types.ts b/models/tracker/src/types.ts index 42cb4f6139..d0626fb764 100644 --- a/models/tracker/src/types.ts +++ b/models/tracker/src/types.ts @@ -143,17 +143,31 @@ export class TRelatedIssueTarget extends TDoc implements RelatedIssueTarget { rule!: RelatedClassRule | RelatedSpaceRule } + /** * @public */ - export function TypeReportedTime (): Type { - return { _class: tracker.class.TypeReportedTime, label: core.string.Number } + return { _class: tracker.class.TypeReportedTime, label: tracker.string.ReportedTime } } + /** * @public */ +export function TypeRemainingTime (): Type { + return { _class: tracker.class.TypeRemainingTime, label: tracker.string.RemainingTime } +} +/** + * @public + */ +export function TypeEstimation (): Type { + return { _class: tracker.class.TypeEstimation, label: tracker.string.Estimation } +} + +/** + * @public + */ @Model(tracker.class.Issue, task.class.Task) @UX(tracker.string.Issue, tracker.icon.Issue, 'TSK', 'title') export class TIssue extends TTask implements Issue { @@ -229,18 +243,15 @@ export class TIssue extends TTask implements Issue { @Index(IndexKind.Indexed) milestone!: Ref | null - @Prop(TypeNumber(), tracker.string.Estimation) + @Prop(TypeEstimation(), 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) + @Prop(TypeRemainingTime(), tracker.string.RemainingTime) @ReadOnly() - @Hidden() remainingTime!: number @Prop(Collection(tracker.class.TimeSpendReport), tracker.string.TimeSpendReports) @@ -283,7 +294,7 @@ export class TIssueTemplate extends TDoc implements IssueTemplate { @Prop(TypeRef(tracker.class.Milestone), tracker.string.Milestone) milestone!: Ref | null - @Prop(TypeNumber(), tracker.string.Estimation) + @Prop(TypeEstimation(), tracker.string.Estimation) estimation!: number @Prop(ArrOf(TypeRef(tracker.class.IssueTemplate)), tracker.string.IssueTemplate) @@ -386,3 +397,11 @@ export class TMilestone extends TDoc implements Milestone { @UX(core.string.Number) @Model(tracker.class.TypeReportedTime, core.class.Type) export class TTypeReportedTime extends TType {} + +@UX(core.string.Number) +@Model(tracker.class.TypeEstimation, core.class.Type) +export class TTypeEstimation extends TType {} + +@UX(core.string.Number) +@Model(tracker.class.TypeRemainingTime, core.class.Type) +export class TTypeRemainingTime extends TType {} diff --git a/models/tracker/src/viewlets.ts b/models/tracker/src/viewlets.ts index add265f0d1..5e0393431f 100644 --- a/models/tracker/src/viewlets.ts +++ b/models/tracker/src/viewlets.ts @@ -24,14 +24,28 @@ import tracker from './plugin' import tags from '@hcengineering/tags' export const issuesOptions = (kanban: boolean): ViewOptionsModel => ({ - groupBy: ['status', 'assignee', 'priority', 'component', 'milestone', 'createdBy', 'modifiedBy'], + groupBy: [ + 'status', + 'assignee', + 'priority', + 'component', + 'milestone', + 'createdBy', + 'modifiedBy', + 'estimation', + 'remainingTime', + 'reportedTime' + ], orderBy: [ ['status', SortingOrder.Ascending], ['priority', SortingOrder.Descending], ['modifiedOn', SortingOrder.Descending], ['createdOn', SortingOrder.Descending], ['dueDate', SortingOrder.Ascending], - ['rank', SortingOrder.Ascending] + ['rank', SortingOrder.Ascending], + ['estimation', SortingOrder.Descending], + ['remainingTime', SortingOrder.Descending], + ['reportedTime', SortingOrder.Descending] ], other: [ { @@ -202,6 +216,7 @@ export function defineViewlets (builder: Builder): void { 'component', 'milestone', 'estimation', + 'remainingTime', 'status', 'dueDate', 'attachedTo', @@ -246,6 +261,7 @@ export function defineViewlets (builder: Builder): void { 'dueDate', 'milestone', 'estimation', + 'remainingTime', 'createdBy', 'modifiedBy' ] @@ -287,6 +303,7 @@ export function defineViewlets (builder: Builder): void { 'dueDate', 'milestone', 'estimation', + 'remainingTime', 'createdBy', 'modifiedBy' ] @@ -328,6 +345,7 @@ export function defineViewlets (builder: Builder): void { 'dueDate', 'component', 'estimation', + 'remainingTime', 'createdBy', 'modifiedBy' ] @@ -355,7 +373,17 @@ export function defineViewlets (builder: Builder): void { }, configOptions: { strict: true, - hiddenKeys: ['milestone', 'estimation', 'component', 'title', 'description', 'createdBy', 'modifiedBy'] + hiddenKeys: [ + 'milestone', + 'estimation', + 'remainingTime', + 'reportedTime', + 'component', + 'title', + 'description', + 'createdBy', + 'modifiedBy' + ] }, config: [ // { key: '', presenter: tracker.component.PriorityEditor, props: { kind: 'list', size: 'small' } }, diff --git a/plugins/recruit-resources/src/components/CreateVacancy.svelte b/plugins/recruit-resources/src/components/CreateVacancy.svelte index 12564c658c..c596f2ad2e 100644 --- a/plugins/recruit-resources/src/components/CreateVacancy.svelte +++ b/plugins/recruit-resources/src/components/CreateVacancy.svelte @@ -140,6 +140,7 @@ dueDate: null, parents: [], reportedTime: 0, + remainingTime: 0, estimation: template.estimation, reports: 0, relations: [{ _id: id, _class: recruit.class.Vacancy }], diff --git a/plugins/tracker-resources/src/components/CreateIssue.svelte b/plugins/tracker-resources/src/components/CreateIssue.svelte index 161eb52900..d7d16e19c6 100644 --- a/plugins/tracker-resources/src/components/CreateIssue.svelte +++ b/plugins/tracker-resources/src/components/CreateIssue.svelte @@ -388,6 +388,7 @@ ? [{ parentId: parentIssue._id, parentTitle: parentIssue.title }, ...parentIssue.parents] : [], reportedTime: 0, + remainingTime: 0, estimation: object.estimation, reports: 0, relations: relatedTo !== undefined ? [{ _id: relatedTo._id, _class: relatedTo._class }] : [], diff --git a/plugins/tracker-resources/src/components/SubIssues.svelte b/plugins/tracker-resources/src/components/SubIssues.svelte index 7afb4805f4..33aca52856 100644 --- a/plugins/tracker-resources/src/components/SubIssues.svelte +++ b/plugins/tracker-resources/src/components/SubIssues.svelte @@ -92,6 +92,7 @@ dueDate: null, parents, reportedTime: 0, + remainingTime: 0, estimation: subIssue.estimation, reports: 0, relations: [], diff --git a/plugins/tracker-resources/src/components/issues/timereport/EstimationPopup.svelte b/plugins/tracker-resources/src/components/issues/timereport/EstimationPopup.svelte index 6e4a3dcf9d..0cf4eacf96 100644 --- a/plugins/tracker-resources/src/components/issues/timereport/EstimationPopup.svelte +++ b/plugins/tracker-resources/src/components/issues/timereport/EstimationPopup.svelte @@ -25,6 +25,7 @@ import SubIssuesEstimations from './SubIssuesEstimations.svelte' import TimeSpendReportPopup from './TimeSpendReportPopup.svelte' import TimeSpendReports from './TimeSpendReports.svelte' + import TimePresenter from './TimePresenter.svelte' export let format: 'text' | 'password' | 'number' export let kind: EditStyle = 'search-style' @@ -104,6 +105,10 @@ > +