mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-27 10:49:44 +00:00
Tracker: change "Issue" type to "AttachedDoc" (#1875)
Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@xored.com>
This commit is contained in:
parent
6fefb8c739
commit
883393788b
@ -125,9 +125,9 @@ export class TTeam extends TSpace implements Team {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@Model(tracker.class.Issue, core.class.Doc, DOMAIN_TRACKER)
|
||||
@Model(tracker.class.Issue, core.class.AttachedDoc, DOMAIN_TRACKER)
|
||||
@UX(tracker.string.Issue, tracker.icon.Issue, tracker.string.Issue)
|
||||
export class TIssue extends TDoc implements Issue {
|
||||
export class TIssue extends TAttachedDoc implements Issue {
|
||||
@Prop(TypeString(), tracker.string.Title)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
@ -151,8 +151,8 @@ export class TIssue extends TDoc implements Issue {
|
||||
@Prop(TypeRef(tracker.class.Project), tracker.string.Project)
|
||||
project!: Ref<Project> | null
|
||||
|
||||
@Prop(TypeRef(tracker.class.Issue), tracker.string.Parent)
|
||||
parentIssue!: Ref<Issue>
|
||||
@Prop(Collection(tracker.class.Issue), tracker.string.SubIssues)
|
||||
subIssues!: number
|
||||
|
||||
@Prop(ArrOf(TypeRef(tracker.class.Issue)), tracker.string.BlockedBy)
|
||||
blockedBy!: Ref<Issue>[]
|
||||
|
@ -13,9 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { generateId, Ref, TxOperations } from '@anticrm/core'
|
||||
import core, { Doc, generateId, Ref, TxOperations } from '@anticrm/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import { IssueStatus, IssueStatusCategory, Team, genRanks } from '@anticrm/tracker'
|
||||
import { IssueStatus, IssueStatusCategory, Team, genRanks, Issue } from '@anticrm/tracker'
|
||||
import { DOMAIN_TRACKER } from '.'
|
||||
import tracker from './plugin'
|
||||
|
||||
@ -149,6 +149,53 @@ async function upgradeIssueStatuses (tx: TxOperations): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateParentIssues (client: MigrationClient): Promise<void> {
|
||||
let { updated } = await client.update(
|
||||
DOMAIN_TRACKER,
|
||||
{ _class: tracker.class.Issue, attachedToClass: { $exists: false } },
|
||||
{
|
||||
subIssues: 0,
|
||||
collection: 'subIssues',
|
||||
attachedToClass: tracker.class.Issue
|
||||
}
|
||||
)
|
||||
updated += (
|
||||
await client.update(
|
||||
DOMAIN_TRACKER,
|
||||
{ _class: tracker.class.Issue, parentIssue: { $exists: true } },
|
||||
{ $rename: { parentIssue: 'attachedTo' } }
|
||||
)
|
||||
).updated
|
||||
updated += (
|
||||
await client.update(
|
||||
DOMAIN_TRACKER,
|
||||
{ _class: tracker.class.Issue, attachedTo: { $in: [null, undefined] } },
|
||||
{ attachedTo: tracker.ids.NoParent }
|
||||
)
|
||||
).updated
|
||||
|
||||
if (updated === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const childrenCountById = new Map<Ref<Doc>, number>()
|
||||
const parentIssueIds = (
|
||||
await client.find<Issue>(DOMAIN_TRACKER, {
|
||||
_class: tracker.class.Issue,
|
||||
attachedTo: { $nin: [tracker.ids.NoParent] }
|
||||
})
|
||||
).map((issue) => issue.attachedTo)
|
||||
|
||||
for (const issueId of parentIssueIds) {
|
||||
const count = childrenCountById.get(issueId) ?? 0
|
||||
childrenCountById.set(issueId, count + 1)
|
||||
}
|
||||
|
||||
for (const [_id, childrenCount] of childrenCountById) {
|
||||
await client.update(DOMAIN_TRACKER, { _id }, { subIssues: childrenCount })
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateIssueProjects (client: MigrationClient): Promise<void> {
|
||||
const issues = await client.find(DOMAIN_TRACKER, { _class: tracker.class.Issue, project: { $exists: false } })
|
||||
|
||||
@ -197,7 +244,7 @@ async function upgradeProjects (tx: TxOperations): Promise<void> {
|
||||
|
||||
export const trackerOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await Promise.all([migrateIssueProjects(client)])
|
||||
await Promise.all([migrateIssueProjects(client), migrateParentIssues(client)])
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import core, { Data, generateId, Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
||||
import core, { AttachedData, Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
||||
import presentation, { Card, createQuery, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { calcRank, Issue, IssuePriority, IssueStatus, Project, Team } from '@anticrm/tracker'
|
||||
@ -48,7 +48,7 @@
|
||||
let issueStatuses: WithLookup<IssueStatus>[] | undefined
|
||||
let parentIssue: Issue | undefined
|
||||
|
||||
let object: Data<Issue> = {
|
||||
let object: AttachedData<Issue> = {
|
||||
title: '',
|
||||
description: '',
|
||||
assignee: null,
|
||||
@ -58,13 +58,13 @@
|
||||
status: '' as Ref<IssueStatus>,
|
||||
priority: priority,
|
||||
dueDate: null,
|
||||
comments: 0
|
||||
comments: 0,
|
||||
subIssues: 0
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const statusesQuery = createQuery()
|
||||
const taskId: Ref<Issue> = generateId()
|
||||
|
||||
$: _space = space
|
||||
$: updateIssueStatusId(space, status)
|
||||
@ -95,7 +95,6 @@
|
||||
|
||||
function clearParentIssue () {
|
||||
parentIssue = undefined
|
||||
object.parentIssue = undefined
|
||||
}
|
||||
|
||||
function getTitle (value: string) {
|
||||
@ -126,7 +125,7 @@
|
||||
true
|
||||
)
|
||||
|
||||
const value: Data<Issue> = {
|
||||
const value: AttachedData<Issue> = {
|
||||
title: getTitle(object.title),
|
||||
description: object.description,
|
||||
assignee: currentAssignee,
|
||||
@ -135,12 +134,19 @@
|
||||
status: object.status,
|
||||
priority: object.priority,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
parentIssue: object.parentIssue,
|
||||
comments: 0,
|
||||
subIssues: 0,
|
||||
dueDate: object.dueDate
|
||||
}
|
||||
|
||||
await client.createDoc(tracker.class.Issue, _space, value, taskId)
|
||||
await client.addCollection(
|
||||
tracker.class.Issue,
|
||||
_space,
|
||||
parentIssue?._id ?? tracker.ids.NoParent,
|
||||
parentIssue?._class ?? tracker.class.Issue,
|
||||
'subIssues',
|
||||
value
|
||||
)
|
||||
}
|
||||
|
||||
async function showMoreActions (ev: Event) {
|
||||
@ -160,23 +166,18 @@
|
||||
}
|
||||
|
||||
const setParentIssue = {
|
||||
label: object.parentIssue ? tracker.string.ChangeParent : tracker.string.SetParent,
|
||||
label: parentIssue ? tracker.string.ChangeParent : tracker.string.SetParent,
|
||||
icon: tracker.icon.Parent,
|
||||
action: async () =>
|
||||
showPopup(
|
||||
SetParentIssueActionPopup,
|
||||
{ value: { ...object, space }, shouldSaveOnChange: false },
|
||||
{ value: { ...object, space, attachedTo: parentIssue?._id }, shouldSaveOnChange: false },
|
||||
'top',
|
||||
(selectedIssue) => {
|
||||
if (selectedIssue !== undefined) {
|
||||
parentIssue = selectedIssue
|
||||
object.parentIssue = parentIssue?._id
|
||||
}
|
||||
}
|
||||
(selectedIssue) => selectedIssue !== undefined && (parentIssue = selectedIssue)
|
||||
)
|
||||
}
|
||||
|
||||
const removeParentIssue = object.parentIssue && {
|
||||
const removeParentIssue = parentIssue && {
|
||||
label: tracker.string.RemoveParent,
|
||||
icon: tracker.icon.Parent,
|
||||
action: clearParentIssue
|
||||
|
@ -30,7 +30,15 @@
|
||||
const newDueDate = detail && detail?.getTime()
|
||||
|
||||
if (shouldSaveOnChange && newDueDate !== undefined && newDueDate !== value.dueDate) {
|
||||
await client.update(value, { dueDate: newDueDate })
|
||||
await client.updateCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection,
|
||||
{ dueDate: newDueDate }
|
||||
)
|
||||
}
|
||||
|
||||
dispatch('update', newDueDate)
|
||||
|
@ -42,8 +42,16 @@
|
||||
}
|
||||
|
||||
async function onClose ({ detail: parentIssue }: CustomEvent<Issue | undefined | null>) {
|
||||
if (shouldSaveOnChange && parentIssue !== undefined && parentIssue?._id !== value.parentIssue) {
|
||||
await client.update(value, { parentIssue: parentIssue?._id })
|
||||
if (shouldSaveOnChange && parentIssue !== undefined && parentIssue?._id !== value.attachedTo) {
|
||||
await client.updateCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
'subIssues',
|
||||
{ attachedTo: parentIssue === null ? tracker.ids.NoParent : parentIssue._id }
|
||||
)
|
||||
}
|
||||
|
||||
dispatch('close', parentIssue)
|
||||
@ -59,7 +67,7 @@
|
||||
<ObjectPopup
|
||||
_class={tracker.class.Issue}
|
||||
{options}
|
||||
selected={value.parentIssue}
|
||||
selected={value.attachedTo}
|
||||
multiSelect={false}
|
||||
allowDeselect={true}
|
||||
placeholder={tracker.string.SetParent}
|
||||
|
@ -30,7 +30,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(value, { assignee: newAssignee })
|
||||
await client.updateCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection,
|
||||
{ assignee: newAssignee }
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -60,7 +60,15 @@
|
||||
|
||||
const newAssignee = result === null ? null : result._id
|
||||
|
||||
await client.update(currentIssue, { assignee: newAssignee })
|
||||
await client.updateCollection(
|
||||
currentIssue._class,
|
||||
currentIssue.space,
|
||||
currentIssue._id,
|
||||
currentIssue.attachedTo,
|
||||
currentIssue.attachedToClass,
|
||||
currentIssue.collection,
|
||||
{ assignee: newAssignee }
|
||||
)
|
||||
}
|
||||
|
||||
const handleAssigneeEditorOpened = async (event: MouseEvent) => {
|
||||
|
@ -27,7 +27,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(value, { dueDate: newDueDate })
|
||||
await client.updateCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection,
|
||||
{ dueDate: newDueDate }
|
||||
)
|
||||
}
|
||||
|
||||
$: today = new Date(new Date(Date.now()).setHours(0, 0, 0, 0))
|
||||
|
@ -26,7 +26,15 @@
|
||||
$: dueDateMs = value.dueDate
|
||||
|
||||
const handleDueDateChanged = async (newDate: number | null) => {
|
||||
await client.update(value, { dueDate: newDate })
|
||||
await client.updateCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection,
|
||||
{ dueDate: newDate }
|
||||
)
|
||||
}
|
||||
|
||||
$: shouldRenderPresenter =
|
||||
|
@ -36,7 +36,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(value, { priority: newPriority })
|
||||
await client.updateCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection,
|
||||
{ priority: newPriority }
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -38,7 +38,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(value, { status: newStatus })
|
||||
await client.updateCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection,
|
||||
{ status: newStatus }
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
|
||||
let issue: Issue | undefined
|
||||
let issue: WithLookup<Issue> | undefined
|
||||
let currentTeam: Team | undefined
|
||||
let issueStatuses: WithLookup<IssueStatus>[] | undefined
|
||||
let title = ''
|
||||
@ -65,7 +65,7 @@
|
||||
title = issue.title
|
||||
description = issue.description
|
||||
},
|
||||
{ lookup: { parentIssue: _class } }
|
||||
{ lookup: { attachedTo: tracker.class.Issue } }
|
||||
)
|
||||
|
||||
$: if (issue) {
|
||||
@ -123,7 +123,15 @@
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await client.update(issue, updates)
|
||||
await client.updateCollection(
|
||||
issue._class,
|
||||
issue.space,
|
||||
issue._id,
|
||||
issue.attachedTo,
|
||||
issue.attachedToClass,
|
||||
issue.collection,
|
||||
updates
|
||||
)
|
||||
}
|
||||
|
||||
isEditing = false
|
||||
@ -152,6 +160,7 @@
|
||||
on:fullsize
|
||||
on:close={() => dispatch('close')}
|
||||
>
|
||||
{@const { attachedTo: parentIssue } = issue?.$lookup ?? {}}
|
||||
<svelte:fragment slot="subtitle">
|
||||
<div class="flex-between flex-grow">
|
||||
<div class="buttons-group xsmall-gap">
|
||||
@ -184,7 +193,7 @@
|
||||
{#if isEditing}
|
||||
<Scroller>
|
||||
<div class="popupPanel-body__main-content py-10 clear-mins content">
|
||||
{#if issue?.parentIssue}
|
||||
{#if parentIssue}
|
||||
<div class="mb-6">
|
||||
{#if currentTeam && issueStatuses}
|
||||
<SubIssueSelector {issue} {issueStatuses} team={currentTeam} />
|
||||
@ -211,7 +220,7 @@
|
||||
</div>
|
||||
</Scroller>
|
||||
{:else}
|
||||
{#if issue?.parentIssue}
|
||||
{#if parentIssue}
|
||||
<div class="mb-6">
|
||||
{#if currentTeam && issueStatuses}
|
||||
<SubIssueSelector {issue} {issueStatuses} team={currentTeam} />
|
||||
@ -261,6 +270,7 @@
|
||||
|
||||
.description-preview {
|
||||
color: var(--theme-content-color);
|
||||
line-height: 150%;
|
||||
|
||||
.placeholder {
|
||||
color: var(--theme-content-trans-color);
|
||||
|
@ -16,7 +16,17 @@
|
||||
import { Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Team, Issue, IssueStatus } from '@anticrm/tracker'
|
||||
import { Icon, Tooltip, showPanel, IconForward, IconDetails, showPopup, SelectPopup, closeTooltip } from '@anticrm/ui'
|
||||
import {
|
||||
Icon,
|
||||
Tooltip,
|
||||
showPanel,
|
||||
IconForward,
|
||||
IconDetails,
|
||||
showPopup,
|
||||
SelectPopup,
|
||||
closeTooltip,
|
||||
Spinner
|
||||
} from '@anticrm/ui'
|
||||
import tracker from '../../../plugin'
|
||||
|
||||
export let issue: WithLookup<Issue>
|
||||
@ -43,9 +53,9 @@
|
||||
}
|
||||
|
||||
function openParentIssue () {
|
||||
if (issue.parentIssue) {
|
||||
if (parentIssue) {
|
||||
closeTooltip()
|
||||
openIssue(issue.parentIssue)
|
||||
openIssue(parentIssue._id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,21 +87,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: subIssuesQeury.query(
|
||||
$: areSubIssuesLoading = !subIssues
|
||||
$: parentIssue = issue.$lookup?.attachedTo ? (issue.$lookup?.attachedTo as Issue) : null
|
||||
$: parentIssue &&
|
||||
subIssuesQeury.query(
|
||||
tracker.class.Issue,
|
||||
{ space: issue.space, parentIssue: issue.parentIssue },
|
||||
{ space: issue.space, attachedTo: parentIssue._id },
|
||||
(res) => (subIssues = res),
|
||||
{ sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
$: parentIssue = issue.$lookup?.parentIssue ?? null
|
||||
</script>
|
||||
|
||||
<div class="flex root">
|
||||
<div class="clear-mins parent-issue item">
|
||||
{#if parentIssue}
|
||||
{#if parentIssue}
|
||||
<div class="flex root">
|
||||
<div class="item clear-mins">
|
||||
<Tooltip label={tracker.string.OpenParent} direction="bottom" fill>
|
||||
{@const icon = getIssueStatusIcon(issue)}
|
||||
<div class="flex-center" on:click={openParentIssue}>
|
||||
<div class="flex-center parent-issue" on:click={openParentIssue}>
|
||||
{#if icon}
|
||||
<div class="pr-2">
|
||||
<Icon {icon} size="small" />
|
||||
@ -101,14 +113,21 @@
|
||||
<span class="overflow-label issue-title">{parentIssue.title}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="item">
|
||||
{#if areSubIssuesLoading}
|
||||
<div class="flex-center spinner">
|
||||
<Spinner size="small" />
|
||||
</div>
|
||||
{:else}
|
||||
<Tooltip label={tracker.string.OpenSub} direction="bottom">
|
||||
<div bind:this={subIssuesElement} class="flex-center sub-issues item" on:click|preventDefault={showSubIssues}>
|
||||
{#if subIssues?.length !== undefined}
|
||||
<span class="overflow-label">{subIssues.length}</span>
|
||||
{/if}
|
||||
<div
|
||||
bind:this={subIssuesElement}
|
||||
class="flex-center sub-issues"
|
||||
on:click|preventDefault={areSubIssuesLoading ? undefined : showSubIssues}
|
||||
>
|
||||
<span class="overflow-label">{subIssues?.length}</span>
|
||||
<div class="ml-2">
|
||||
<!-- TODO: fix icon -->
|
||||
<Icon icon={IconDetails} size="small" />
|
||||
@ -118,23 +137,41 @@
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
$padding: 0.375rem 0.75rem;
|
||||
$border: 1px solid var(--button-border-color);
|
||||
|
||||
.root {
|
||||
max-width: fit-content;
|
||||
line-height: 150%;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border: $border;
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: var(--primary-shadow);
|
||||
|
||||
.item {
|
||||
padding: 0.375rem 0.75rem;
|
||||
position: relative;
|
||||
|
||||
&:not(:first-child)::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
border-left: $border;
|
||||
inset: 0.375rem auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
padding: $padding;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.parent-issue {
|
||||
border-right: 1px solid var(--button-border-color);
|
||||
padding: $padding;
|
||||
|
||||
.issue-title {
|
||||
color: var(--theme-content-accent-color);
|
||||
@ -154,6 +191,7 @@
|
||||
}
|
||||
|
||||
.sub-issues {
|
||||
padding: $padding;
|
||||
color: var(--theme-content-color);
|
||||
transition: color 0.15s;
|
||||
|
||||
|
@ -39,7 +39,15 @@
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(value, { project: newProjectId })
|
||||
await client.updateCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection,
|
||||
{ project: newProjectId }
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -86,7 +86,7 @@ export default mergeIds(trackerId, tracker, {
|
||||
Assignee: '' as IntlString,
|
||||
AssignTo: '' as IntlString,
|
||||
AssignedTo: '' as IntlString,
|
||||
Parent: '' as IntlString,
|
||||
SubIssues: '' as IntlString,
|
||||
SetParent: '' as IntlString,
|
||||
ChangeParent: '' as IntlString,
|
||||
RemoveParent: '' as IntlString,
|
||||
|
@ -97,7 +97,7 @@ export enum IssuesDateModificationPeriod {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Issue extends Doc {
|
||||
export interface Issue extends AttachedDoc {
|
||||
title: string
|
||||
description: Markup
|
||||
status: Ref<IssueStatus>
|
||||
@ -108,7 +108,7 @@ export interface Issue extends Doc {
|
||||
project: Ref<Project> | null
|
||||
|
||||
// For subtasks
|
||||
parentIssue?: Ref<Issue>
|
||||
subIssues: number
|
||||
blockedBy?: Ref<Issue>[]
|
||||
relatedIssue?: Ref<Issue>[]
|
||||
|
||||
@ -189,6 +189,9 @@ export default plugin(trackerId, {
|
||||
IssueStatusCategory: '' as Ref<Class<IssueStatusCategory>>,
|
||||
TypeIssuePriority: '' as Ref<Class<Type<IssuePriority>>>
|
||||
},
|
||||
ids: {
|
||||
NoParent: '' as Ref<Issue>
|
||||
},
|
||||
component: {
|
||||
Tracker: '' as AnyComponent,
|
||||
TrackerApp: '' as AnyComponent
|
||||
|
@ -15,12 +15,14 @@
|
||||
//
|
||||
|
||||
import core, {
|
||||
Account,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
ClassifierKind,
|
||||
Collection,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
DocumentUpdate,
|
||||
Domain,
|
||||
DOMAIN_MODEL,
|
||||
DOMAIN_TX,
|
||||
@ -125,12 +127,82 @@ class TServerStorage implements ServerStorage {
|
||||
}
|
||||
}
|
||||
|
||||
private async getCollectionUpdateTx<D extends Doc>(
|
||||
_id: Ref<D>,
|
||||
_class: Ref<Class<D>>,
|
||||
modifiedBy: Ref<Account>,
|
||||
modifiedOn: number,
|
||||
attachedTo: D,
|
||||
update: DocumentUpdate<D>
|
||||
): Promise<Tx> {
|
||||
const txFactory = new TxFactory(modifiedBy)
|
||||
const baseClass = this.hierarchy.getBaseClass(_class)
|
||||
if (baseClass !== _class) {
|
||||
// Mixin operation is required.
|
||||
const tx = txFactory.createTxMixin(_id, attachedTo._class, attachedTo.space, _class, update)
|
||||
tx.modifiedOn = modifiedOn
|
||||
|
||||
return tx
|
||||
} else {
|
||||
const tx = txFactory.createTxUpdateDoc(_class, attachedTo.space, _id, update)
|
||||
tx.modifiedOn = modifiedOn
|
||||
|
||||
return tx
|
||||
}
|
||||
}
|
||||
|
||||
private async updateCollection (ctx: MeasureContext, tx: Tx): Promise<Tx[]> {
|
||||
if (tx._class !== core.class.TxCollectionCUD) {
|
||||
return []
|
||||
}
|
||||
|
||||
const colTx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||
const _id = colTx.objectId
|
||||
const _class = colTx.objectClass
|
||||
const { operations } = colTx.tx as TxUpdateDoc<AttachedDoc>
|
||||
|
||||
if (
|
||||
colTx.tx._class !== core.class.TxUpdateDoc ||
|
||||
this.hierarchy.getDomain(_class) === DOMAIN_MODEL // We could not update increments for model classes
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (operations?.attachedTo === undefined || operations.attachedTo === _id) {
|
||||
return []
|
||||
}
|
||||
|
||||
const oldAttachedTo = (await this.findAll(ctx, _class, { _id }, { limit: 1 }))[0]
|
||||
let oldTx: Tx | null = null
|
||||
if (oldAttachedTo !== undefined) {
|
||||
oldTx = await this.getCollectionUpdateTx(_id, _class, tx.modifiedBy, colTx.modifiedOn, oldAttachedTo, {
|
||||
$inc: { [colTx.collection]: -1 }
|
||||
})
|
||||
}
|
||||
|
||||
const newAttachedToClass = operations.attachedToClass ?? _class
|
||||
const newAttachedToCollection = operations.collection ?? colTx.collection
|
||||
const newAttachedTo = (await this.findAll(ctx, newAttachedToClass, { _id: operations.attachedTo }, { limit: 1 }))[0]
|
||||
let newTx: Tx | null = null
|
||||
if (newAttachedTo !== undefined) {
|
||||
newTx = await this.getCollectionUpdateTx(
|
||||
newAttachedTo._id,
|
||||
newAttachedTo._class,
|
||||
tx.modifiedBy,
|
||||
colTx.modifiedOn,
|
||||
newAttachedTo,
|
||||
{ $inc: { [newAttachedToCollection]: 1 } }
|
||||
)
|
||||
}
|
||||
|
||||
return [...(oldTx !== null ? [oldTx] : []), ...(newTx !== null ? [newTx] : [])]
|
||||
}
|
||||
|
||||
async processCollection (ctx: MeasureContext, tx: Tx): Promise<Tx[]> {
|
||||
if (tx._class === core.class.TxCollectionCUD) {
|
||||
const colTx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||
const _id = colTx.objectId
|
||||
const _class = colTx.objectClass
|
||||
let attachedTo: Doc | undefined
|
||||
|
||||
// Skip model operations
|
||||
if (this.hierarchy.getDomain(_class) === DOMAIN_MODEL) {
|
||||
@ -139,26 +211,20 @@ class TServerStorage implements ServerStorage {
|
||||
}
|
||||
|
||||
const isCreateTx = colTx.tx._class === core.class.TxCreateDoc
|
||||
if (isCreateTx || colTx.tx._class === core.class.TxRemoveDoc) {
|
||||
attachedTo = (await this.findAll(ctx, _class, { _id }, { limit: 1 }))[0]
|
||||
if (attachedTo !== undefined) {
|
||||
const txFactory = new TxFactory(tx.modifiedBy)
|
||||
const baseClass = this.hierarchy.getBaseClass(_class)
|
||||
if (baseClass !== _class) {
|
||||
// Mixin opeeration is required.
|
||||
const tx = txFactory.createTxMixin(_id, attachedTo._class, attachedTo.space, _class, {
|
||||
$inc: { [colTx.collection]: isCreateTx ? 1 : -1 }
|
||||
})
|
||||
tx.modifiedOn = colTx.modifiedOn
|
||||
const isDeleteTx = colTx.tx._class === core.class.TxRemoveDoc
|
||||
const isUpdateTx = colTx.tx._class === core.class.TxUpdateDoc
|
||||
|
||||
return [tx]
|
||||
if (isCreateTx || isDeleteTx || isUpdateTx) {
|
||||
if (isUpdateTx) {
|
||||
return await this.updateCollection(ctx, tx)
|
||||
} else {
|
||||
const tx = txFactory.createTxUpdateDoc(_class, attachedTo.space, _id, {
|
||||
const attachedTo = (await this.findAll(ctx, _class, { _id }, { limit: 1 }))[0]
|
||||
if (attachedTo !== undefined) {
|
||||
return [
|
||||
await this.getCollectionUpdateTx(_id, _class, tx.modifiedBy, colTx.modifiedOn, attachedTo, {
|
||||
$inc: { [colTx.collection]: isCreateTx ? 1 : -1 }
|
||||
})
|
||||
tx.modifiedOn = colTx.modifiedOn
|
||||
|
||||
return [tx]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user