From 769c9bd3413f7ffd341c61548d2d10b1017b97c3 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Wed, 8 Feb 2023 15:30:54 +0700 Subject: [PATCH] Sprints List and Reports view (#2602) Signed-off-by: Andrey Sobolev --- models/tracker/src/index.ts | 78 +++++ models/tracker/src/plugin.ts | 3 +- .../src/components/ScheduleRequests.svelte | 1 + .../src/components/ScheduleView.svelte | 19 +- .../components/schedule/MonthTableView.svelte | 48 ++- .../src/components/schedule/MonthView.svelte | 16 +- .../components/schedule/ReportsPopup.svelte | 59 ++++ plugins/hr-resources/src/utils.ts | 3 +- .../issues/timereport/ReportsPopup.svelte | 2 +- .../components/projects/LeadPresenter.svelte | 4 +- .../components/sprints/SprintBrowser.svelte | 184 +++++------ .../components/sprints/SprintContent.svelte | 51 +++ .../sprints/SprintLeadPresenter.svelte | 105 +++++++ .../src/components/sprints/SprintList.svelte | 292 ------------------ .../sprints/SprintListBrowser.svelte | 72 ----- plugins/tracker-resources/src/index.ts | 9 +- plugins/tracker-resources/src/plugin.ts | 3 + plugins/view-resources/src/index.ts | 1 + 18 files changed, 467 insertions(+), 483 deletions(-) create mode 100644 plugins/hr-resources/src/components/schedule/ReportsPopup.svelte create mode 100644 plugins/tracker-resources/src/components/sprints/SprintContent.svelte create mode 100644 plugins/tracker-resources/src/components/sprints/SprintLeadPresenter.svelte delete mode 100644 plugins/tracker-resources/src/components/sprints/SprintList.svelte delete mode 100644 plugins/tracker-resources/src/components/sprints/SprintListBrowser.svelte diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index ea5e3e99d9..e8678e0cb8 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -1241,6 +1241,10 @@ export function createModel (builder: Builder): void { filters: ['priority', 'assignee', 'project', 'sprint', 'modifiedOn'] }) + builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.ClassFilters, { + filters: ['status', 'project', 'lead', 'startDate', 'targetDate', 'modifiedOn', 'capacity'] + }) + builder.createDoc( presentation.class.ObjectSearchCategory, core.space.Model, @@ -1600,4 +1604,78 @@ export function createModel (builder: Builder): void { view.component.NumberPresenter, tracker.component.ReportedTimeEditor ) + + const sprintOptions: ViewOptionsModel = { + groupBy: ['project', 'lead'], + orderBy: [ + ['startDate', SortingOrder.Descending], + ['modifiedOn', SortingOrder.Descending], + ['targetDate', SortingOrder.Descending], + ['capacity', SortingOrder.Ascending] + ], + other: [] + } + + builder.createDoc(view.class.Viewlet, core.space.Model, { + attachTo: tracker.class.Sprint, + descriptor: view.viewlet.List, + viewOptions: sprintOptions, + config: [ + { + key: '', + presenter: tracker.component.SprintStatusPresenter, + props: { width: '1rem', kind: 'list', size: 'small', justify: 'center' } + }, + { key: '', presenter: tracker.component.SprintPresenter, props: { shouldUseMargin: true } }, + { key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } }, + { key: '', presenter: tracker.component.SprintProjectEditor, props: { kind: 'list' } }, + { + key: '', + presenter: contact.component.MembersPresenter, + props: { + kind: 'link', + intlTitle: tracker.string.SprintMembersTitle, + intlSearchPh: tracker.string.SprintMembersSearchPlaceholder + } + }, + { key: '', presenter: tracker.component.SprintDatePresenter, props: { field: 'startDate' } }, + { key: '', presenter: tracker.component.SprintDatePresenter, props: { field: 'targetDate' } }, + { + key: '$lookup.lead', + presenter: tracker.component.SprintLeadPresenter, + props: { + _class: tracker.class.Sprint, + defaultClass: contact.class.Employee, + shouldShowLabel: false, + size: 'x-small' + } + } + ] + }) + + createAction( + builder, + { + action: view.actionImpl.ValueSelector, + actionPopup: view.component.ValueSelector, + actionProps: { + attribute: 'lead', + _class: contact.class.Employee, + query: {}, + placeholder: tracker.string.SprintLead + }, + label: tracker.string.SprintLead, + icon: contact.icon.Person, + keyBinding: [], + input: 'none', + category: tracker.category.Tracker, + target: tracker.class.Sprint, + context: { + mode: ['context'], + application: tracker.app.Tracker, + group: 'edit' + } + }, + tracker.action.SetSprintLead + ) } diff --git a/models/tracker/src/plugin.ts b/models/tracker/src/plugin.ts index 1f69e517be..dd4737dd0d 100644 --- a/models/tracker/src/plugin.ts +++ b/models/tracker/src/plugin.ts @@ -62,6 +62,7 @@ export default mergeIds(trackerId, tracker, { }, action: { NewRelatedIssue: '' as Ref>>, - DeleteSprint: '' as Ref>> + DeleteSprint: '' as Ref>>, + SetSprintLead: '' as Ref>> } }) diff --git a/plugins/hr-resources/src/components/ScheduleRequests.svelte b/plugins/hr-resources/src/components/ScheduleRequests.svelte index 47206a36e4..00adb3d12d 100644 --- a/plugins/hr-resources/src/components/ScheduleRequests.svelte +++ b/plugins/hr-resources/src/components/ScheduleRequests.svelte @@ -51,6 +51,7 @@ {#each requests as request} {#await getType(request) then type} {#if type} +
, EmployeeReports> = new Map() @@ -163,17 +163,24 @@ const newMap = new Map, EmployeeReports>() for (const r of res) { if (r.employee != null) { - const or = newMap.get(r.employee) - newMap.set(r.employee, { value: (or?.value ?? 0) + r.value, reports: [...(or?.reports ?? []), r] }) + const or = newMap.get(r.employee) ?? { + value: 0, + reports: [], + tasks: new Map() + } + const tsk = r.$lookup?.attachedTo as Issue + newMap.set(r.employee, { + value: or.value + r.value, + reports: [...or.reports, r], + tasks: or.tasks.set(tsk._id, tsk) + }) } } timeReports = newMap }, { lookup: { - _id: { - attachedTo: tracker.class.Issue - } + attachedTo: tracker.class.Issue } } ) diff --git a/plugins/hr-resources/src/components/schedule/MonthTableView.svelte b/plugins/hr-resources/src/components/schedule/MonthTableView.svelte index 971aaf0d69..5e13b1ae84 100644 --- a/plugins/hr-resources/src/components/schedule/MonthTableView.svelte +++ b/plugins/hr-resources/src/components/schedule/MonthTableView.svelte @@ -83,6 +83,18 @@ function getOverrideConfig (startDate: Date): Map { const typevals = getTypeVals(startDate) const endDate = getEndDate(startDate.getFullYear(), startDate.getMonth()) + + const getReport = (id: Ref): EmployeeReports => { + return timeReports.get(id as Ref) ?? { value: 0, reports: [], tasks: new Map() } + } + const getTPD = (id: Ref): number => { + const rr = getReport(id) + if (rr.value === 0) { + return 0 + } + return rr.tasks.size / rr.value + } + return new Map([ [ '@wdCount', @@ -109,12 +121,38 @@ presenter: ReportPresenter, props: { month: startDate ?? getStartDate(currentDate.getFullYear(), currentDate.getMonth()), - display: (staff: Staff) => (timeReports.get(staff._id) ?? { value: 0 }).value + display: (staff: Staff) => getReport(staff._id).value }, - sortingKey: '@wdCount', - sortingFunction: (a: Doc, b: Doc) => - getTotal(getStatRequests(b._id as Ref, startDate), startDate, endDate, types) - - getTotal(getStatRequests(a._id as Ref, startDate), startDate, endDate, types) + sortingKey: '@wdCountReported', + sortingFunction: (a: Doc, b: Doc) => getReport(b._id).value - getReport(a._id).value + } + ], + [ + '@wdTaskCountReported', + { + key: '', + label: getEmbeddedLabel('Tasks'), + presenter: ReportPresenter, + props: { + month: startDate ?? getStartDate(currentDate.getFullYear(), currentDate.getMonth()), + display: (staff: Staff) => getReport(staff._id).tasks.size + }, + sortingKey: '@wdTaskCountReported', + sortingFunction: (a: Doc, b: Doc) => getReport(b._id).tasks.size - getReport(a._id).tasks.size + } + ], + [ + '@wdTaskPerDayReported', + { + key: '', + label: getEmbeddedLabel('TPD'), + presenter: ReportPresenter, + props: { + month: startDate ?? getStartDate(currentDate.getFullYear(), currentDate.getMonth()), + display: (staff: Staff) => getTPD(staff._id) + }, + sortingKey: '@wdTaskPerDayReported', + sortingFunction: (a: Doc, b: Doc) => getTPD(b._id) - getTPD(a._id) } ], [ diff --git a/plugins/hr-resources/src/components/schedule/MonthView.svelte b/plugins/hr-resources/src/components/schedule/MonthView.svelte index 73c6d926e5..a2aad648bb 100644 --- a/plugins/hr-resources/src/components/schedule/MonthView.svelte +++ b/plugins/hr-resources/src/components/schedule/MonthView.svelte @@ -39,6 +39,7 @@ import CreateRequest from '../CreateRequest.svelte' import RequestsPopup from '../RequestsPopup.svelte' import ScheduleRequests from '../ScheduleRequests.svelte' + import ReportsPopup from './ReportsPopup.svelte' export let currentDate: Date = new Date() @@ -106,6 +107,13 @@ bottom: 3.5 } } + + function showReportInfo (employee: Staff, rTime: EmployeeReports | undefined): void { + if (rTime === undefined) { + return + } + showPopup(ReportsPopup, { employee, reports: rTime.reports }, 'top') + } {#if departmentStaff.length} @@ -152,9 +160,14 @@ > {getTotal(requests, startDate, endDate, types)} - + + showReportInfo(employee, rTime)} + > {#if rTime !== undefined} {floorFractionDigits(rTime.value, 3)} + ({rTime.tasks.size}) {:else} 0 {/if} @@ -166,6 +179,7 @@ {@const tooltipValue = getTooltip(requests)} {@const ww = findReports(employee, day, timeReports)} {#key [tooltipValue, editable]} + + + + {}} + okLabel={presentation.string.Ok} +> + + + + it._id) } }} + config={['$lookup.attachedTo', '$lookup.attachedTo.title', '', 'employee', 'date']} + {options} + /> + diff --git a/plugins/hr-resources/src/utils.ts b/plugins/hr-resources/src/utils.ts index 3a07d3fd0f..d27bdd122c 100644 --- a/plugins/hr-resources/src/utils.ts +++ b/plugins/hr-resources/src/utils.ts @@ -2,7 +2,7 @@ import { Employee, formatName } from '@hcengineering/contact' import { Ref, TxOperations } from '@hcengineering/core' import { Department, Request, RequestType, Staff, TzDate } from '@hcengineering/hr' import { MessageBox } from '@hcengineering/presentation' -import { TimeSpendReport } from '@hcengineering/tracker' +import { Issue, TimeSpendReport } from '@hcengineering/tracker' import { isWeekend, MILLISECONDS_IN_DAY, showPopup } from '@hcengineering/ui' import hr from './plugin' @@ -235,5 +235,6 @@ export function tableToCSV (tableId: string, separator = ','): string { export interface EmployeeReports { reports: TimeSpendReport[] + tasks: Map, Issue> value: number } diff --git a/plugins/tracker-resources/src/components/issues/timereport/ReportsPopup.svelte b/plugins/tracker-resources/src/components/issues/timereport/ReportsPopup.svelte index b43c143604..7328c21f18 100644 --- a/plugins/tracker-resources/src/components/issues/timereport/ReportsPopup.svelte +++ b/plugins/tracker-resources/src/components/issues/timereport/ReportsPopup.svelte @@ -66,7 +66,7 @@ it.childId)] } }} + query={{ attachedTo: { $in: [issue._id, ...(issue.childInfo?.map((it) => it.childId) ?? [])] } }} config={[ '$lookup.attachedTo', '', diff --git a/plugins/tracker-resources/src/components/projects/LeadPresenter.svelte b/plugins/tracker-resources/src/components/projects/LeadPresenter.svelte index c3828480e2..7dc4b509fb 100644 --- a/plugins/tracker-resources/src/components/projects/LeadPresenter.svelte +++ b/plugins/tracker-resources/src/components/projects/LeadPresenter.svelte @@ -27,7 +27,7 @@ export let value: Employee | null export let _class: Ref> export let size: IconSize = 'x-small' - export let parentId: Ref + export let parentId: Ref export let defaultClass: Ref> | undefined = undefined export let isEditable: boolean = true export let shouldShowLabel: boolean = false @@ -54,7 +54,7 @@ return } - const currentParent = await client.findOne(_class, { _id: parentId }) + const currentParent = await client.findOne(_class, { _id: parentId as Ref }) if (currentParent === undefined) { return diff --git a/plugins/tracker-resources/src/components/sprints/SprintBrowser.svelte b/plugins/tracker-resources/src/components/sprints/SprintBrowser.svelte index 8057f8f4de..173b72f911 100644 --- a/plugins/tracker-resources/src/components/sprints/SprintBrowser.svelte +++ b/plugins/tracker-resources/src/components/sprints/SprintBrowser.svelte @@ -13,62 +13,84 @@ // limitations under the License. -->
-
+
+
+ {}} /> +
-
@@ -130,66 +159,23 @@ />
-
-
-
- - - + (resultQuery = e.detail)} +/> +
+ {#if viewlet} + + {/if} + {#if $$slots.aside !== undefined && asideShown} +
+ +
+ {/if}
diff --git a/plugins/tracker-resources/src/components/sprints/SprintContent.svelte b/plugins/tracker-resources/src/components/sprints/SprintContent.svelte new file mode 100644 index 0000000000..f8122b1c33 --- /dev/null +++ b/plugins/tracker-resources/src/components/sprints/SprintContent.svelte @@ -0,0 +1,51 @@ + + +{#if viewlet?.$lookup?.descriptor?.component} + +{/if} diff --git a/plugins/tracker-resources/src/components/sprints/SprintLeadPresenter.svelte b/plugins/tracker-resources/src/components/sprints/SprintLeadPresenter.svelte new file mode 100644 index 0000000000..96d6a5298b --- /dev/null +++ b/plugins/tracker-resources/src/components/sprints/SprintLeadPresenter.svelte @@ -0,0 +1,105 @@ + + + +{#if value && presenter} + +{:else if presenter} + +{/if} diff --git a/plugins/tracker-resources/src/components/sprints/SprintList.svelte b/plugins/tracker-resources/src/components/sprints/SprintList.svelte deleted file mode 100644 index 26bd0320b5..0000000000 --- a/plugins/tracker-resources/src/components/sprints/SprintList.svelte +++ /dev/null @@ -1,292 +0,0 @@ - - - -{#await buildModel({ client, _class, keys: itemsConfig, lookup: options.lookup }) then itemModels} -
- {#if sprints} - {#each Array.from(byProject?.entries() ?? []) as e} - -
handleCollapseCategory(e[0])}> -
- -
-
- - {#each e[1] as docObject (docObject._id)} -
x === docObject)]} - class="listGrid" - class:mListGridChecked={selectedObjectIdsSet.has(docObject._id)} - class:mListGridFixed={selectedRowIndex === sprints.findIndex((x) => x === docObject)} - class:mListGridSelected={selectedRowIndex === sprints.findIndex((x) => x === docObject)} - on:focus={() => {}} - on:mouseover={() => handleRowFocused(docObject)} - > -
- {#each itemModels as attributeModel, attributeModelIndex} - {#if attributeModelIndex === 0} -
-
- { - onObjectChecked([docObject], event.detail) - }} - /> -
-
- -
-
- {:else if attributeModelIndex === 1} -
- -
-
- {:else} -
- -
- {/if} - {/each} -
-
- {/each} - - {/each} - {:else if loadingProps !== undefined} - {#each Array(getLoadingElementsLength(loadingProps, options)) as _, rowIndex} -
-
-
- -
- -
-
-
-
- {/each} - {/if} -
-{/await} - - diff --git a/plugins/tracker-resources/src/components/sprints/SprintListBrowser.svelte b/plugins/tracker-resources/src/components/sprints/SprintListBrowser.svelte deleted file mode 100644 index f40b1f5478..0000000000 --- a/plugins/tracker-resources/src/components/sprints/SprintListBrowser.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - { - listProvider.updateFocus(event.detail ?? undefined) - }} - on:check={(event) => { - listProvider.updateSelection(event.detail.docs, event.detail.value) - }} -/> diff --git a/plugins/tracker-resources/src/index.ts b/plugins/tracker-resources/src/index.ts index 0a47de049b..95c61a8c82 100644 --- a/plugins/tracker-resources/src/index.ts +++ b/plugins/tracker-resources/src/index.ts @@ -70,6 +70,10 @@ import Views from './components/views/Views.svelte' import Statuses from './components/workflow/Statuses.svelte' import RelatedIssuesSection from './components/issues/related/RelatedIssuesSection.svelte' import RelatedIssueSelector from './components/issues/related/RelatedIssueSelector.svelte' +import SprintProjectEditor from './components/sprints/SprintProjectEditor.svelte' +import SprintDatePresenter from './components/sprints/SprintDatePresenter.svelte' +import SprintLeadPresenter from './components/sprints/SprintLeadPresenter.svelte' + import { getIssueId, getIssueTitle, @@ -380,7 +384,10 @@ export default async (): Promise => ({ RelatedIssuesSection, RelatedIssueSelector, DeleteProjectPresenter, - TimeSpendReportPopup + TimeSpendReportPopup, + SprintProjectEditor, + SprintDatePresenter, + SprintLeadPresenter }, completion: { IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) => diff --git a/plugins/tracker-resources/src/plugin.ts b/plugins/tracker-resources/src/plugin.ts index ab5cb6b03b..5376b0f253 100644 --- a/plugins/tracker-resources/src/plugin.ts +++ b/plugins/tracker-resources/src/plugin.ts @@ -350,6 +350,9 @@ export default mergeIds(trackerId, tracker, { SprintPresenter: '' as AnyComponent, SprintStatusPresenter: '' as AnyComponent, SprintTitlePresenter: '' as AnyComponent, + SprintProjectEditor: '' as AnyComponent, + SprintDatePresenter: '' as AnyComponent, + SprintLeadPresenter: '' as AnyComponent, ReportedTimeEditor: '' as AnyComponent, TimeSpendReport: '' as AnyComponent, EstimationEditor: '' as AnyComponent, diff --git a/plugins/view-resources/src/index.ts b/plugins/view-resources/src/index.ts index c2a8bc31bc..2b2a9b86cb 100644 --- a/plugins/view-resources/src/index.ts +++ b/plugins/view-resources/src/index.ts @@ -98,6 +98,7 @@ export { default as ObjectPresenter } from './components/ObjectPresenter.svelte' export { default as TableBrowser } from './components/TableBrowser.svelte' export { default as ValueSelector } from './components/ValueSelector.svelte' export { default as MarkupPreviewPopup } from './components/MarkupPreviewPopup.svelte' +export { default as MarkupPresenter } from './components/MarkupPresenter.svelte' export * from './context' export * from './filter' export * from './selection'