UBERF-4928: Indexing fixes (#4357)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-01-15 23:10:30 +07:00 committed by GitHub
parent 266b82e284
commit 960c7d9963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 91 additions and 1162 deletions

View File

@ -484,24 +484,6 @@ export function createModel (builder: Builder): void {
actions: [view.action.Delete, task.action.Move]
})
// TODO: update query when nested query is available
createAction(
builder,
{
action: board.actionImpl.ConvertToCard,
label: board.string.ConvertToCard,
icon: board.icon.Card,
category: board.category.Card,
query: {
attachedToClass: task.class.TodoItem
},
input: 'any',
target: task.class.TodoItem,
context: { mode: ['context', 'browser'] }
},
board.action.ConvertToCard
)
createAction(builder, {
...viewTemplates.open,
target: board.class.Board,

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import type { Employee, Person } from '@hcengineering/contact'
import type { Person } from '@hcengineering/contact'
import contact from '@hcengineering/contact'
import {
ClassifierKind,
@ -41,16 +41,15 @@ import {
Mixin,
Model,
Prop,
ReadOnly,
TypeBoolean,
TypeDate,
TypeMarkup,
TypeRecord,
TypeRef,
TypeString,
UX,
type Builder,
type MigrationClient,
ReadOnly
type MigrationClient
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
@ -65,7 +64,6 @@ import { getEmbeddedLabel, type Asset, type IntlString, type Resource } from '@h
import setting from '@hcengineering/setting'
import tags from '@hcengineering/tags'
import {
type TaskStatusFactory,
calculateStatuses,
findStatusAttr,
type KanbanCard,
@ -76,11 +74,11 @@ import {
type ProjectTypeDescriptor,
type Sequence,
type Task,
type TaskStatusFactory,
type TaskType,
type TaskTypeClass,
type TaskTypeDescriptor,
type TaskTypeKind,
type TodoItem
type TaskTypeKind
} from '@hcengineering/task'
import type { AnyComponent } from '@hcengineering/ui'
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
@ -121,7 +119,10 @@ export class TTask extends TAttachedDoc implements Task {
@Prop(TypeDate(), task.string.DueDate, { editor: task.component.DueDateEditor })
dueDate!: Timestamp | null
declare rank: string
@Prop(TypeString(), task.string.Rank)
@Index(IndexKind.IndexedDsc)
@Hidden()
rank!: string
@Prop(Collection(tags.class.TagReference, task.string.TaskLabels), task.string.TaskLabels)
labels?: number
@ -135,28 +136,6 @@ export class TTask extends TAttachedDoc implements Task {
isDone?: boolean
}
@Model(task.class.TodoItem, core.class.AttachedDoc, DOMAIN_TASK)
@UX(task.string.Todo)
export class TTodoItem extends TAttachedDoc implements TodoItem {
@Prop(TypeMarkup(), task.string.TodoName, task.icon.Task)
@Index(IndexKind.FullText)
name!: string
@Prop(TypeRef(contact.mixin.Employee), task.string.TaskAssignee)
assignee!: Ref<Employee> | null
@Prop(TypeBoolean(), task.string.TaskDone)
done!: boolean
@Prop(TypeDate(), task.string.TaskDueTo)
dueTo!: Timestamp | null
@Prop(Collection(task.class.TodoItem), task.string.Todos)
items!: number
declare rank: string
}
@Mixin(task.mixin.KanbanCard, core.class.Class)
export class TKanbanCard extends TClass implements KanbanCard {
card!: AnyComponent
@ -320,7 +299,6 @@ export function createModel (builder: Builder): void {
TKanbanCard,
TSequence,
TTask,
TTodoItem,
TProject,
TProjectType,
TTaskType,
@ -418,14 +396,6 @@ export function createModel (builder: Builder): void {
task.viewlet.Dashboard
)
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.CollectionEditor, {
editor: task.component.Todos
})
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.ObjectPresenter, {
presenter: task.component.TodoItemPresenter
})
builder.mixin(task.class.TaskType, core.class.Class, view.mixin.ObjectPresenter, {
presenter: task.component.TaskTypePresenter
})
@ -436,46 +406,6 @@ export function createModel (builder: Builder): void {
classPresenter(builder, task.class.TaskType, task.component.TaskTypePresenter, task.component.TaskTypePresenter)
createAction(builder, {
label: task.string.MarkAsDone,
icon: task.icon.TodoCheck,
action: view.actionImpl.UpdateDocument,
actionProps: {
key: 'done',
value: true
},
input: 'focus',
category: task.category.Task,
query: {
done: false
},
target: task.class.TodoItem,
context: {
mode: ['context', 'browser'],
group: 'edit'
}
})
createAction(builder, {
label: task.string.MarkAsUndone,
icon: task.icon.TodoUnCheck,
action: view.actionImpl.UpdateDocument,
actionProps: {
key: 'done',
value: false
},
input: 'focus',
category: task.category.Task,
query: {
done: true
},
context: {
mode: ['context', 'browser'],
group: 'edit'
},
target: task.class.TodoItem
})
createAction(
builder,
{

View File

@ -32,8 +32,6 @@ export default mergeIds(taskId, task, {
},
actionImpl: {
EditStatuses: '' as ViewAction,
TodoItemMarkDone: '' as ViewAction,
TodoItemMarkUnDone: '' as ViewAction,
ArchiveSpace: '' as ViewAction,
UnarchiveSpace: '' as ViewAction,
SelectStatus: '' as ViewAction
@ -51,8 +49,6 @@ export default mergeIds(taskId, task, {
StatePresenter: '' as AnyComponent,
StateEditor: '' as AnyComponent,
KanbanView: '' as AnyComponent,
Todos: '' as AnyComponent,
TodoItemPresenter: '' as AnyComponent,
StatusTableView: '' as AnyComponent,
TaskHeader: '' as AnyComponent,
Dashboard: '' as AnyComponent,

View File

@ -224,10 +224,6 @@ export class TIssue extends TTask implements Issue {
@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

View File

@ -114,7 +114,9 @@ export enum IndexKind {
*
* Also mean to include into Elastic search.
*/
Indexed
Indexed,
// Same as indexed but for descending
IndexedDsc
}
/**

View File

@ -184,7 +184,7 @@ export function isFullTextAttribute (attr: AnyAttribute): boolean {
* @public
*/
export function isIndexedAttribute (attr: AnyAttribute): boolean {
return attr.index === IndexKind.Indexed
return attr.index === IndexKind.Indexed || attr.index === IndexKind.IndexedDsc
}
/**

View File

@ -19,27 +19,23 @@
import core, { Class, Doc, Mixin, Ref, Space } from '@hcengineering/core'
import { Panel } from '@hcengineering/panel'
import { createQuery, getClient } from '@hcengineering/presentation'
import type { State, TodoItem } from '@hcengineering/task'
import task from '@hcengineering/task'
import { StyledTextBox } from '@hcengineering/text-editor'
import {
Button,
CircleButton,
EditBox,
getEventPopupPositionElement,
IconAdd,
IconMoreH,
Label,
getEventPopupPositionElement,
showPopup
} from '@hcengineering/ui'
import { ContextMenu, DocAttributeBar, invokeAction, ParentsNavigator } from '@hcengineering/view-resources'
import { ContextMenu, DocAttributeBar, ParentsNavigator, invokeAction } from '@hcengineering/view-resources'
import { createEventDispatcher, onMount } from 'svelte'
import board from '../plugin'
import { getCardActions } from '../utils/CardActionUtils'
import { updateCard } from '../utils/CardUtils'
import CardActions from './editor/CardActions.svelte'
import CardChecklist from './editor/CardChecklist.svelte'
import AddChecklist from './popups/AddChecklist.svelte'
export let _id: Ref<Card>
export let _class: Ref<Class<Card>>
@ -48,13 +44,12 @@
const cardQuery = createQuery()
const stateQuery = createQuery()
const spaceQuery = createQuery()
const checklistsQuery = createQuery()
let object: Card | undefined
let state: State | undefined
let space: Space | undefined
let handleMove: (e: Event) => void
let checklists: TodoItem[] = []
const mixins: Mixin<Doc>[] = []
const allowedCollections = ['labels']
const ignoreKeys = ['isArchived', 'location', 'title', 'description', 'status', 'number', 'assignee']
@ -65,16 +60,12 @@
}
}
function addChecklist (e: Event) {
showPopup(AddChecklist, { value: object }, getEventPopupPositionElement(e))
}
$: cardQuery.query(_class, { _id }, (result) => {
object = result[0]
})
$: object?.status &&
stateQuery.query(task.class.State, { _id: object.status }, (result) => {
stateQuery.query(core.class.Status, { _id: object.status }, (result) => {
state = result[0]
})
@ -83,11 +74,6 @@
space = result[0]
})
$: object &&
checklistsQuery.query(task.class.TodoItem, { space: object.space, attachedTo: object._id }, (result) => {
checklists = result
})
getCardActions(client, { _id: board.action.Move }).then(async (result) => {
if (result[0]) {
handleMove = (e) => {
@ -157,15 +143,6 @@
<div class="mt-6">
<Attachments objectId={_id} {_class} space={object.space} attachments={object.attachments ?? 0} />
</div>
<div class="flex-row-center mt-6">
<span class="text-xl font-medium caption-color mr-3"><Label label={board.string.Checklists} /></span>
<CircleButton icon={IconAdd} size="small" selected on:click={addChecklist} />
</div>
<div class="mr-2 ml-2 mb-4">
{#each checklists as checklist}
<CardChecklist value={checklist} />
{/each}
</div>
<svelte:fragment slot="custom-attributes" let:direction>
{#if direction === 'column'}
<DocAttributeBar {object} {mixins} {ignoreKeys} {allowedCollections} />

View File

@ -19,27 +19,26 @@
import contact, { Employee } from '@hcengineering/contact'
import type { Ref, WithLookup } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import view from '@hcengineering/view'
import tags from '@hcengineering/tags'
import { ChatMessagesPresenter } from '@hcengineering/notification-resources'
import { getClient } from '@hcengineering/presentation'
import tags from '@hcengineering/tags'
import {
Button,
Component,
EditBox,
getPopupPositionElement,
Icon,
IconMoreV,
Label,
getPopupPositionElement,
numberToHexColor,
showPopup
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { ContextMenu } from '@hcengineering/view-resources'
import board from '../plugin'
import DatePresenter from './presenters/DatePresenter.svelte'
import { hasDate, openCardPanel, updateCard, updateCardMembers } from '../utils/CardUtils'
import CheckListsPresenter from './presenters/ChecklistsPresenter.svelte'
import DatePresenter from './presenters/DatePresenter.svelte'
import NotificationPresenter from './presenters/NotificationPresenter.svelte'
import { ChatMessagesPresenter } from '@hcengineering/notification-resources'
export let object: WithLookup<Card>
@ -198,11 +197,6 @@
<ChatMessagesPresenter value={object.comments} {object} size="small" />
</div>
{/if}
{#if (object.todoItems ?? 0) > 0}
<div class="float-left">
<CheckListsPresenter value={object} />
</div>
{/if}
{#if (object.labels ?? 0) > 0}
<div class="float-left">
<Component

View File

@ -1,315 +0,0 @@
<!--
// Copyright © 2022 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.
-->
<script lang="ts">
import { Employee } from '@hcengineering/contact'
import { EmployeeBox } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
import type { TodoItem } from '@hcengineering/task'
import task, { calcRank } from '@hcengineering/task'
import {
Button,
CheckBox,
DateRangePresenter,
IconAdd,
IconDelete,
IconMoreH,
Progress,
TextAreaEditor,
getEventPopupPositionElement,
showPopup
} from '@hcengineering/ui'
import { ContextMenu, HTMLPresenter } from '@hcengineering/view-resources'
import board from '../../plugin'
import { getDateIcon } from '../../utils/BoardUtils'
export let value: TodoItem
const client = getClient()
const checklistItemsQuery = createQuery()
let checklistItems: TodoItem[] = []
let done = 0
let isEditingName: boolean = false
let isAddingItem: boolean = false
let hideDoneItems: boolean = false
let newItemName = ''
let editingItemId: Ref<TodoItem> | undefined = undefined
let hovered: Ref<TodoItem> | undefined = undefined
let dragItem: TodoItem | undefined = undefined
let dragOverItem: TodoItem | undefined = undefined
function deleteChecklist () {
if (!value) {
return
}
showPopup(
MessageBox,
{
label: board.string.DeleteChecklist,
message: board.string.DeleteChecklistConfirm
},
undefined,
(result?: boolean) => {
if (result === true) {
client.removeCollection(
value._class,
value.space,
value._id,
value.attachedTo,
value.attachedToClass,
value.collection
)
}
}
)
}
function startAddingItem () {
isAddingItem = true
}
async function addItem (event: CustomEvent<string>) {
newItemName = ''
const prev = checklistItems && checklistItems.length > 0 ? checklistItems[checklistItems.length - 1] : undefined
const item = {
name: event.detail ?? '',
assignee: null,
dueTo: null,
done: false,
rank: calcRank(prev, undefined)
}
if (item.name.length <= 0) {
return
}
await client.addCollection(task.class.TodoItem, value.space, value._id, value._class, 'items', item)
}
function updateItemName (item: TodoItem, name: string) {
if (name === undefined || name.length === 0 || name === item.name) return
client.update(item, { name })
}
function updateName (event: CustomEvent<string>) {
isEditingName = false
updateItemName(value, event.detail)
}
function updateItemAssignee (item: TodoItem, assignee: Ref<Employee>) {
client.update(item, { assignee })
}
function updateDueDate (item: TodoItem, dueTo: number) {
client.update(item, { dueTo })
}
async function setDoneToChecklistItem (item: TodoItem, event: CustomEvent<boolean>) {
const isDone = event.detail
if (!value) {
return
}
await client.update(item, { done: isDone })
}
function showItemMenu (item: TodoItem, e?: Event) {
showPopup(ContextMenu, { object: item }, getEventPopupPositionElement(e))
}
function itemDrop (): void {
if (dragItem === undefined || dragOverItem === undefined || dragItem._id === dragOverItem._id) {
return
}
const index = checklistItems.findIndex((item) => item._id === dragOverItem?._id)
const dragIndex = checklistItems.findIndex((item) => item._id === dragItem?._id)
if (dragIndex > index) {
const prev = index - 1 >= 0 ? checklistItems[index - 1] : undefined
dragItem.rank = calcRank(prev, dragOverItem)
client.update(dragItem, { rank: dragItem.rank })
} else {
const next = index + 1 < checklistItems.length ? checklistItems[index + 1] : undefined
dragItem.rank = calcRank(dragOverItem, next)
client.update(dragItem, { rank: dragItem.rank })
}
}
$: checklistItemsQuery.query(
task.class.TodoItem,
{ space: value.space, attachedTo: value._id },
(result) => {
checklistItems = result
done = checklistItems.reduce((result: number, current: TodoItem) => {
return current.done ? result + 1 : result
}, 0)
},
{
sort: {
rank: 1
}
}
)
</script>
{#if value !== undefined}
<div
class="flex-col w-full"
on:drop|preventDefault={(ev) => {
itemDrop()
}}
on:dragover|preventDefault
on:dragleave
>
<div class="flex-row-stretch mt-4 mb-2">
{#if isEditingName}
<div class="flex-grow">
<TextAreaEditor
value={value.name}
on:submit={updateName}
on:cancel={() => {
isEditingName = false
}}
/>
</div>
{:else}
<div
class="flex-grow fs-title"
on:click={() => {
isEditingName = true
}}
>
{value.name}
</div>
{#if done > 0}
<Button
label={hideDoneItems ? board.string.ShowDoneChecklistItems : board.string.HideDoneChecklistItems}
labelParams={{ done }}
kind="ghost"
size="small"
on:click={() => {
hideDoneItems = !hideDoneItems
}}
/>
{/if}
<Button icon={IconAdd} kind="ghost" size="small" on:click={startAddingItem} />
<Button icon={IconDelete} kind="ghost" size="small" on:click={deleteChecklist} />
{/if}
</div>
<div class="flex-row-stretch mb-2 mt-1">
<div class="w-9 text-sm pl-1 pr-1">
{checklistItems.length > 0 ? Math.round((done / checklistItems.length) * 100) : 0}%
</div>
<div class="flex-center flex-grow w-full">
<Progress min={0} max={checklistItems?.length ?? 0} value={done} />
</div>
</div>
{#each checklistItems.filter((item) => !hideDoneItems || !item.done) as item}
<div
class="flex-row-stretch mb-1 mt-1 pl-1 min-h-7 border-radius-1"
class:background-button-noborder-bg-hover={hovered === item._id && editingItemId !== item._id}
draggable={true}
on:dragstart={() => {
dragItem = item
}}
on:dragend={() => {
dragItem = undefined
dragOverItem = undefined
}}
on:dragover={() => {
dragOverItem = item
}}
on:mouseover={() => {
hovered = item._id
}}
on:focus={() => {
hovered = item._id
}}
on:mouseout={() => {
hovered = undefined
}}
on:blur={() => {
hovered = undefined
}}
>
<div class="w-9 flex items-center">
<CheckBox bind:checked={item.done} on:value={(event) => setDoneToChecklistItem(item, event)} />
</div>
{#if editingItemId === item._id}
<div class="flex-grow">
<TextAreaEditor
value={item.name}
on:submit={(event) => {
editingItemId = undefined
updateItemName(item, event.detail)
}}
on:cancel={() => {
editingItemId = undefined
}}
/>
</div>
{:else}
<div
class="flex-col justify-center flex-gap-1 w-full"
class:text-line-through={item.done}
on:click={() => {
editingItemId = item._id
}}
>
<HTMLPresenter bind:value={item.name} />
</div>
<div class="flex-center gap-1">
<DateRangePresenter
editable
bind:value={item.dueTo}
icon={getDateIcon(item)}
on:change={(e) => {
updateDueDate(item, e.detail)
}}
noShift
/>
<EmployeeBox
label={board.string.Assignee}
bind:value={item.assignee}
allowDeselect={true}
on:change={(e) => {
updateItemAssignee(item, e.detail)
}}
/>
<Button
icon={IconMoreH}
kind="ghost"
size="small"
on:click={(e) => {
showItemMenu(item, e)
}}
/>
</div>
{/if}
</div>
{/each}
<div class="flex-row-stretch mt-2 mb-2">
<div class="w-9" />
{#if isAddingItem}
<div class="w-full p-1">
<TextAreaEditor
bind:value={newItemName}
on:submit={addItem}
on:cancel={() => {
newItemName = ''
isAddingItem = false
}}
/>
</div>
{/if}
</div>
</div>
{/if}

View File

@ -1,137 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Card } from '@hcengineering/board'
import { WithLookup } from '@hcengineering/core'
import task, { calcRank, TodoItem } from '@hcengineering/task'
import { translate } from '@hcengineering/platform'
import presentation, { Card as Popup, createQuery, getClient } from '@hcengineering/presentation'
import { Label, Dropdown, EditBox, themeStore } from '@hcengineering/ui'
import type { ListItem } from '@hcengineering/ui'
import board from '../../plugin'
export let value: Card
const noneListItem: ListItem = {
_id: 'none',
label: ''
}
let lastCardList: TodoItem | undefined = undefined
let name: string | undefined
let selectedTemplate: ListItem | undefined = undefined
let templateListItems: ListItem[] = [noneListItem]
let templatesMap = new Map<string, TodoItem>()
const client = getClient()
const templatesQuery = createQuery()
const dispatch = createEventDispatcher()
translate(board.string.ChecklistDropdownNone, {}, $themeStore.language).then((result) => {
noneListItem.label = result
})
async function addChecklist () {
if (!name || name.trim().length <= 0) {
return
}
const template = selectedTemplate ? templatesMap.get(selectedTemplate._id) : undefined
const items: TodoItem[] = template ? await client.findAll(task.class.TodoItem, { attachedTo: template._id }) : []
const checklistRef = await client.addCollection(
task.class.TodoItem,
value.space,
value._id,
value._class,
'todoItems',
{
name,
done: false,
dueTo: null,
assignee: null,
rank: calcRank(lastCardList)
}
)
if (items.length > 0) {
await Promise.all(
items.map((item) =>
client.addCollection(task.class.TodoItem, value.space, checklistRef, task.class.TodoItem, 'items', {
name: item.name,
dueTo: item.dueTo,
done: item.done,
assignee: item.assignee,
rank: item.rank
})
)
)
}
dispatch('close')
}
$: templatesQuery.query(
board.class.Card,
{ todoItems: { $gt: 0 } },
(result: WithLookup<Card>[]) => {
templateListItems = [noneListItem]
templatesMap = new Map()
lastCardList = undefined
for (const card of result) {
const todoItems = card.$lookup?.todoItems as TodoItem[]
if (!todoItems) {
continue
}
if (card._id === value?._id && todoItems.length > 0) {
todoItems.sort((a, b) => a.rank?.localeCompare(b.rank))
lastCardList = todoItems[todoItems.length - 1]
}
templateListItems.push({
_id: card._id,
label: card.title,
fontWeight: 'semi-bold',
isSelectable: false
})
for (const todoItem of todoItems) {
templateListItems.push({
_id: todoItem._id,
label: todoItem.name,
paddingLeft: 4
})
templatesMap.set(todoItem._id, todoItem)
}
}
},
{ lookup: { _id: { todoItems: task.class.TodoItem } } }
)
</script>
<Popup
label={board.string.Checklists}
okAction={addChecklist}
okLabel={presentation.string.Add}
canSave={(name?.length ?? 0) > 0}
on:close={() => {
dispatch('close')
}}
>
<div class="flex-col flex-gap-1">
<Label label={board.string.Title} />
<div class="p-2 mt-1 mb-1 border-divider-color border-radius-1">
<EditBox bind:value={name} autoFocus />
</div>
</div>
<div class="flex-col flex-gap-1">
<Label label={board.string.CopyChecklistFrom} />
<div class="mt-1 mb-1 w-full">
<Dropdown
bind:selected={selectedTemplate}
items={templateListItems}
justify="left"
width="100%"
placeholder={board.string.ChecklistDropdownNone}
/>
</div>
</div>
</Popup>

View File

@ -1,42 +0,0 @@
<script lang="ts">
import { DatePresenter, Icon } from '@hcengineering/ui'
import board, { Card } from '@hcengineering/board'
import { createQuery } from '@hcengineering/presentation'
import task, { TodoItem } from '@hcengineering/task'
import { Ref } from '@hcengineering/core'
import { getDateIcon } from '../../utils/BoardUtils'
export let value: Card
export let size: 'small' | 'medium' | 'large' = 'small'
const todoListQuery = createQuery()
let todoLists: Ref<TodoItem>[] = []
$: todoListQuery.query(task.class.TodoItem, { space: value.space, attachedTo: value._id }, (result) => {
todoLists = result.map(({ _id }) => _id)
})
const query = createQuery()
let done: number, total: number, item: TodoItem
$: query.query(
task.class.TodoItem,
{ space: value.space, attachedTo: { $in: todoLists } },
(result) => {
total = result.total
done = result.filter((t) => t.done).length
if (!total) return
item = result.reduce((min, cur) =>
cur.dueTo === null ? min : min.dueTo === null || cur.dueTo < min.dueTo ? cur : min
)
},
{ total: true }
)
</script>
{#if value && (total ?? 0) > 0}
<div class="sm-tool-icon ml-1 mr-1">
<Icon icon={board.icon.Card} {size} />
&nbsp;{done}/{total}
{#if item.dueTo !== null}
&nbsp;<DatePresenter value={item.dueTo} size="x-small" iconModifier={getDateIcon(item)} kind="ghost" />
{/if}
</div>
{/if}

View File

@ -14,10 +14,7 @@
// limitations under the License.
//
import { type Resources } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import task, { type Project, type TodoItem } from '@hcengineering/task'
import { type Ref } from '@hcengineering/core'
import Archive from './components/Archive.svelte'
import BoardHeader from './components/BoardHeader.svelte'
import BoardMenu from './components/BoardMenu.svelte'
@ -39,25 +36,6 @@ import CopyCard from './components/popups/CopyCard.svelte'
import DateRangePicker from './components/popups/DateRangePicker.svelte'
import MoveCard from './components/popups/MoveCard.svelte'
import CardCoverPresenter from './components/presenters/CardCoverPresenter.svelte'
import { createCard, getCardFromTodoItem } from './utils/CardUtils'
async function ConvertToCard (object: TodoItem): Promise<void> {
const client = getClient()
const todoItemCard = await getCardFromTodoItem(client, object)
if (todoItemCard === undefined) return
// TODO: Add filtering if requierd, or pass a type from UI
const project = await client.findOne(task.class.Project, { _id: object.space as Ref<Project> })
const taskTypes = await client.findAll(task.class.TaskType, { parent: project?.type })
await createCard(client, todoItemCard.space, todoItemCard.status, {
title: object.name,
assignee: object.assignee,
dueDate: object.dueTo,
kind: taskTypes[0]._id
})
await client.remove(object)
}
export default async (): Promise<Resources> => ({
component: {
@ -83,8 +61,5 @@ export default async (): Promise<Resources> => ({
CoverActionPopup: CardCoverPicker,
MoveActionPopup: MoveCard,
CopyActionPopup: CopyCard
},
actionImpl: {
ConvertToCard
}
})

View File

@ -56,11 +56,6 @@ export default mergeIds(boardId, board, {
SelectColor: '' as IntlString,
NoColor: '' as IntlString,
NoColorInfo: '' as IntlString,
Checklists: '' as IntlString,
ChecklistDropdownNone: '' as IntlString,
ShowDoneChecklistItems: '' as IntlString,
HideDoneChecklistItems: '' as IntlString,
CopyChecklistFrom: '' as IntlString,
Dates: '' as IntlString,
Attachments: '' as IntlString,
AddAttachment: '' as IntlString,
@ -119,9 +114,7 @@ export default mergeIds(boardId, board, {
SwitchToCards: '' as IntlString,
SearchArchive: '' as IntlString,
Size: '' as IntlString,
RemoveCover: '' as IntlString,
DeleteChecklist: '' as IntlString,
DeleteChecklistConfirm: '' as IntlString
RemoveCover: '' as IntlString
},
statusCategory: {
Completed: '' as Ref<StatusCategory>

View File

@ -1,8 +1,8 @@
import board, { type Board, type CommonBoardPreference } from '@hcengineering/board'
import core, { type Ref, type TxOperations, getCurrentAccount } from '@hcengineering/core'
import core, { getCurrentAccount, type Ref, type TxOperations } from '@hcengineering/core'
import preference from '@hcengineering/preference'
import { createQuery, getClient } from '@hcengineering/presentation'
import type { ProjectType, TodoItem } from '@hcengineering/task'
import type { ProjectType } from '@hcengineering/task'
import {
EastSideColor,
FeijoaColor,
@ -13,8 +13,7 @@ import {
MoodyBlueColor,
SalmonColor,
SeaBuckthornColor,
SeagullColor,
areDatesEqual
SeagullColor
} from '@hcengineering/ui'
import { readable } from 'svelte/store'
@ -50,14 +49,6 @@ export function getBoardAvailableColors (): string[] {
SeagullColor
]
}
export function getDateIcon (item: TodoItem): 'normal' | 'warning' | 'overdue' {
if (item.dueTo === null) return 'normal'
const date = new Date()
const dueDate = new Date(item.dueTo)
return areDatesEqual(date, dueDate) ? 'warning' : dueDate < date ? 'overdue' : 'normal'
}
export const commonBoardPreference = readable<CommonBoardPreference>(undefined, (set) => {
createQuery().query(board.class.CommonBoardPreference, { attachedTo: board.app.Board }, (result) => {
if (result.length > 0) {

View File

@ -11,7 +11,7 @@ import {
type Status
} from '@hcengineering/core'
import { showPanel } from '@hcengineering/ui'
import task, { calcRank, type TodoItem } from '@hcengineering/task'
import task, { calcRank } from '@hcengineering/task'
import board from '../plugin'
export async function createCard (
@ -43,17 +43,6 @@ export async function createCard (
return await client.addCollection(board.class.Card, space, space, board.class.Board, 'cards', value)
}
export async function getCardFromTodoItem (client: Client, todoItem: TodoItem | undefined): Promise<Card | undefined> {
if (todoItem === undefined) return
if (todoItem.attachedToClass === todoItem._class) {
return await getCardFromTodoItem(
client,
await client.findOne(todoItem._class, { _id: todoItem.attachedTo as Ref<TodoItem> })
)
}
return await client.findOne(board.class.Card, { _id: todoItem.attachedTo as Ref<Card> })
}
export function updateCard (client: Client, card: Card, field: string, value: any): Promise<TxResult> | undefined {
if (card === undefined) {
return

View File

@ -41,7 +41,6 @@
"TaskUnAssign": "Unassign",
"NoTaskForObject": "No tasks defined",
"Delete": "Delete",
"NoTodoItems": "No to do's defined",
"TodoName": "Name",
"TodoState": "State",
"DoneState": "Done State",
@ -70,7 +69,6 @@
"CantStatusDeleteError": "There are objects in the given state. Move or delete them first.",
"Tasks": "Tasks",
"AssignedToMe": "Assigned to me",
"TodoItems": "Todos",
"Dashboard": "Dashboard",
"AllTime": "All time",
"RelatedIssues": "Related processes",

View File

@ -41,7 +41,6 @@
"TaskUnAssign": "Сбросить назначение",
"NoTaskForObject": "Задачи не определены",
"Delete": "Удалить",
"NoTodoItems": "Нет to do",
"TodoName": "Название",
"TodoState": "Статус",
"DoneState": "Статус Завершен",
@ -70,7 +69,6 @@
"CantStatusDeleteError": "Есть объекты с данным статусом. Сначала переместите или удалите их. ",
"Tasks": "Задачи",
"AssignedToMe": "Назначено на меня",
"TodoItems": "Todos",
"Dashboard": "Дашборд",
"AllTime": "Все время",
"RelatedIssues": "Связанные процессы",

View File

@ -1,88 +0,0 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
-->
<script lang="ts">
import type { Class, Ref, Space } from '@hcengineering/core'
import { Card, createQuery, getClient } from '@hcengineering/presentation'
import type { Task, TodoItem } from '@hcengineering/task'
import task, { calcRank } from '@hcengineering/task'
import { DatePicker, EditBox, Grid } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../../plugin'
export let objectId: Ref<Task>
export let _class: Ref<Class<Task>>
export let space: Ref<Space>
let name: string
const done = false
let dueTo: number | null = null
let latestItem: TodoItem | undefined = undefined
const dispatch = createEventDispatcher()
const client = getClient()
const todoItemsQuery = createQuery()
export function canClose (): boolean {
return objectId === undefined
}
async function createTodo () {
await client.addCollection(task.class.TodoItem, space, objectId, _class, 'todoItems', {
name,
assignee: null,
done,
dueTo: dueTo ?? null,
rank: calcRank(latestItem)
})
}
$: todoItemsQuery.query(
task.class.TodoItem,
{ attachedTo: objectId },
(result) => {
latestItem = undefined
if (result && result.length > 0) {
latestItem = result[result.length - 1]
}
},
{
sort: {
rank: 1
}
}
)
</script>
<Card
label={plugin.string.TodoCreate}
okAction={createTodo}
canSave={name?.length > 0}
on:close={() => {
dispatch('close')
}}
okLabel={plugin.string.TodoSave}
on:changeContent
>
<Grid column={1} rowGap={1.75}>
<EditBox
label={plugin.string.TodoDescription}
bind:value={name}
icon={task.icon.Task}
placeholder={plugin.string.TodoDescriptionPlaceholder}
autoFocus
/>
<DatePicker title={plugin.string.TodoDueDate} bind:value={dueTo} />
</Grid>
</Card>

View File

@ -1,85 +0,0 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
-->
<script lang="ts">
import type { DocumentUpdate, Ref, Timestamp } from '@hcengineering/core'
import { Card, getClient } from '@hcengineering/presentation'
import type { TodoItem } from '@hcengineering/task'
import task from '@hcengineering/task'
import { DatePicker, EditBox, Grid } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../../plugin'
export let item: TodoItem
let name: string = ''
let dueTo: number | null | undefined = null
let _itemId: Ref<TodoItem>
$: if (_itemId !== item._id) {
_itemId = item._id
name = item.name
if (item.dueTo != null) {
dueTo = item.dueTo
} else {
dueTo = null
}
}
const dispatch = createEventDispatcher()
const client = getClient()
export function canClose (): boolean {
return true
}
async function editTodo () {
const ops: DocumentUpdate<TodoItem> = {}
if (item.name !== name) {
ops.name = name
}
if (item.dueTo !== dueTo) {
ops.dueTo = (dueTo ?? null) as unknown as Timestamp
}
if (Object.keys(ops).length === 0) {
return
}
await client.update(item, ops)
}
</script>
<Card
label={plugin.string.TodoEdit}
okAction={editTodo}
canSave={name.length > 0}
on:close={() => {
dispatch('close')
}}
okLabel={plugin.string.TodoSave}
on:changeContent
>
<Grid column={1} rowGap={1.75}>
<EditBox
label={plugin.string.TodoDescription}
bind:value={name}
icon={task.icon.Task}
placeholder={plugin.string.TodoDescriptionPlaceholder}
autoFocus
/>
<DatePicker title={plugin.string.TodoDueDate} bind:value={dueTo} />
</Grid>
</Card>

View File

@ -1,46 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
-->
<script lang="ts">
import type { TodoItem } from '@hcengineering/task'
import { closeTooltip, Icon, showPopup } from '@hcengineering/ui'
import task from '../../plugin'
import EditTodo from './EditTodo.svelte'
export let value: TodoItem
export let inline: boolean = false
function show (elm: EventTarget | null) {
closeTooltip()
showPopup(EditTodo, { item: value }, elm as HTMLElement)
}
</script>
{#if value}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="flex-presenter"
class:inline-presenter={inline}
on:click={(ev) => {
show(ev.target)
}}
>
<div class="icon">
<Icon icon={task.icon.Task} size={'small'} />
</div>
<span class="label">{value.name}</span>
</div>
{/if}

View File

@ -1,40 +0,0 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
-->
<script lang="ts">
import type { WithLookup } from '@hcengineering/core'
import type { Task, TodoItem } from '@hcengineering/task'
import task from '@hcengineering/task'
import { Table } from '@hcengineering/view-resources'
import plugin from '../../plugin'
export let value: WithLookup<Task>
$: todos = (value.$lookup?.todoItems as TodoItem[]) ?? []
</script>
<div class="flex-col">
{#if todos.length > 0}
<Table
_class={task.class.TodoItem}
config={[
{ key: 'name', label: plugin.string.TodoName },
'dueTo',
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
]}
options={{}}
query={{ attachedTo: value._id }}
/>
{/if}
</div>

View File

@ -1,44 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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.
-->
<script lang="ts">
import { getPlatformColor, Label, themeStore } from '@hcengineering/ui'
import task from '../../plugin'
export let value: boolean
$: color = value ? getPlatformColor(2, $themeStore.dark) : getPlatformColor(16, $themeStore.dark)
$: text = value ? task.string.DoneState : task.string.UndoneState
</script>
{#if value !== undefined}
<div class="overflow-label state-container" style="background-color: {color};">
<Label label={text} />
</div>
{/if}
<style lang="scss">
.state-container {
padding: 0.25rem 0.5rem;
width: 6.25rem;
max-width: 6.25rem;
text-transform: uppercase;
text-align: center;
letter-spacing: 0.5px;
font-size: 0.625rem;
color: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.25rem;
}
</style>

View File

@ -1,93 +0,0 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
-->
<script lang="ts">
import type { Ref, Space, Doc, Class } from '@hcengineering/core'
import type { TodoItem } from '@hcengineering/task'
import { createQuery } from '@hcengineering/presentation'
import { Button, IconAdd, showPopup, Label, resizeObserver, Scroller } from '@hcengineering/ui'
import CreateTodo from './CreateTodo.svelte'
import { Table } from '@hcengineering/view-resources'
import task from '@hcengineering/task'
import plugin from '../../plugin'
export let objectId: Ref<Doc>
export let space: Ref<Space>
export let _class: Ref<Class<Doc>>
let todos: TodoItem[] = []
const query = createQuery()
$: query.query(task.class.TodoItem, { attachedTo: objectId }, (result) => {
todos = result
})
const createApp = (ev: MouseEvent): void => {
showPopup(CreateTodo, { objectId, _class, space }, ev.target as HTMLElement)
}
let wSection: number
</script>
<div class="antiSection" use:resizeObserver={(element) => (wSection = element.clientWidth)}>
<div class="antiSection-header">
<span class="antiSection-header__title">
<Label label={plugin.string.Todos} />
</span>
<Button icon={IconAdd} kind={'ghost'} on:click={createApp} />
</div>
{#if todos.length > 0}
{#if wSection < 640}
<Scroller horizontal>
<Table
_class={task.class.TodoItem}
config={[
{ key: '', label: plugin.string.TodoName },
'dueTo',
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
]}
options={{
sort: {
rank: 1
}
}}
query={{ attachedTo: objectId }}
/>
</Scroller>
{:else}
<Table
_class={task.class.TodoItem}
config={[
{ key: '', label: plugin.string.TodoName },
'dueTo',
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
]}
options={{
sort: {
rank: 1
}
}}
query={{ attachedTo: objectId }}
/>
{/if}
{:else}
<div class="antiSection-empty solid flex-col-center mt-3">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span class="text-sm over-underline" on:click={createApp}>
<Label label={plugin.string.NoTodoItems} />
</span>
</div>
{/if}
</div>

View File

@ -63,10 +63,6 @@ import TaskTypeClassPresenter from './components/taskTypes/TaskTypeClassPresente
import ProjectTypeClassPresenter from './components/taskTypes/ProjectTypeClassPresenter.svelte'
import TaskTypePresenter from './components/taskTypes/TaskTypePresenter.svelte'
import ProjectTypePresenter from './components/projectTypes/ProjectTypePresenter.svelte'
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
import Todos from './components/todos/Todos.svelte'
import StateIconPresenter from './components/state/StateIconPresenter.svelte'
import TaskKindSelector from './components/taskTypes/TaskKindSelector.svelte'
@ -124,16 +120,12 @@ export default async (): Promise<Resources> => ({
KanbanView,
StatePresenter,
StateEditor,
Todos,
TodoItemPresenter,
TodoStatePresenter,
StatusTableView,
TaskHeader,
ProjectEditor,
ProjectTypeSelector,
AssignedTasks,
StateRefPresenter,
TodoItemsPopup,
DueDateEditor,
CreateStatePopup,
StatusSelector,

View File

@ -44,7 +44,6 @@ export default mergeIds(taskId, task, {
UploadDropFilesHere: '' as IntlString,
NoTaskForObject: '' as IntlString,
Delete: '' as IntlString,
NoTodoItems: '' as IntlString,
TodoState: '' as IntlString,
DoneState: '' as IntlString,
UndoneState: '' as IntlString,

View File

@ -19,7 +19,6 @@ import {
Attribute,
Class,
Doc,
Markup,
Mixin,
Ref,
Space,
@ -27,11 +26,11 @@ import {
StatusCategory,
Timestamp
} from '@hcengineering/core'
import { NotificationType } from '@hcengineering/notification'
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import type { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
import { Action, IconProps, ViewletDescriptor } from '@hcengineering/view'
import { NotificationType } from '@hcengineering/notification'
/**
* @public
@ -56,21 +55,9 @@ export interface Task extends AttachedDoc, DocWithRank {
dueDate: Timestamp | null
comments?: number
attachments?: number
todoItems?: number
labels?: number
}
/**
* @public
*/
export interface TodoItem extends AttachedDoc, DocWithRank {
name: Markup
assignee: Ref<Person> | null
done: boolean
dueTo: Timestamp | null
items?: number
}
/**
* @public
*/
@ -252,7 +239,6 @@ const task = plugin(taskId, {
MarkAsUndone: '' as IntlString,
Kanban: '' as IntlString,
ApplicationLabelTask: '' as IntlString,
TodoItems: '' as IntlString,
AssignedToMe: '' as IntlString,
Dashboard: '' as IntlString,
ProjectTypes: '' as IntlString,
@ -261,7 +247,6 @@ const task = plugin(taskId, {
},
class: {
Sequence: '' as Ref<Class<Sequence>>,
TodoItem: '' as Ref<Class<TodoItem>>,
ProjectTypeDescriptor: '' as Ref<Class<ProjectTypeDescriptor>>,
ProjectType: '' as Ref<Class<ProjectType>>,
Project: '' as Ref<Class<Project>>,
@ -302,7 +287,6 @@ const task = plugin(taskId, {
component: {
ProjectEditor: '' as AnyComponent,
ProjectTypeSelector: '' as AnyComponent,
TodoItemsPopup: '' as AnyComponent,
CreateStatePopup: '' as AnyComponent
},
ids: {

View File

@ -627,10 +627,12 @@ export async function restore (
Object.keys(s.domains).forEach((it) => domains.add(it as Domain))
}
console.log('connecting:', transactorUrl, workspaceId.name)
const connection = (await connect(transactorUrl, workspaceId, undefined, {
mode: 'backup',
model: 'upgrade'
})) as unknown as CoreClient & BackupClient
console.log('connected')
// We need to find empty domains and clean them.
const allDomains = connection.getHierarchy().domains()

View File

@ -399,10 +399,7 @@ export class FullTextIndexPipeline implements FullTextPipeline {
removed: false
},
{
limit: globalIndexer.processingSize,
sort: {
_id: 1
}
limit: globalIndexer.processingSize
}
)
)

View File

@ -20,27 +20,27 @@ import core, {
DocumentQuery,
FindOptions,
FindResult,
generateId,
LookupData,
MeasureContext,
ObjQueryType,
Position,
PullArray,
Ref,
SearchOptions,
SearchQuery,
SearchResult,
ServerStorage,
Space,
systemAccountEmail,
Tx,
TxCreateDoc,
TxCUD,
TxCreateDoc,
TxProcessor,
TxRemoveDoc,
TxUpdateDoc,
TxWorkspaceEvent,
WorkspaceEvent,
SearchResult,
SearchQuery,
SearchOptions
generateId,
systemAccountEmail
} from '@hcengineering/core'
import platform, { PlatformError, Severity, Status } from '@hcengineering/platform'
import { BroadcastFunc, Middleware, SessionContext, TxMiddlewareResult } from '@hcengineering/server-core'
@ -117,16 +117,26 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
for (const domain in classesPerDomain) {
for (const _class of classesPerDomain[domain]) {
const field = this.getKey(_class)
const spaces = await this.storage.findAll(
ctx,
_class,
{},
{
projection: { [field]: 1 }
const map = this.domainSpaces[domain] ?? new Set()
this.domainSpaces[domain] = map
while (true) {
const spaces = await this.storage.findAll(
ctx,
_class,
{
[field]: { $nin: Array.from(map.values()) }
},
{
projection: { [field]: 1 },
limit: 1000
}
)
if (spaces.length === 0) {
break
}
)
this.domainSpaces[domain] = this.domainSpaces[domain] ?? new Set()
spaces.forEach((p) => this.domainSpaces[domain].add((p as any)[field] as Ref<Space>))
spaces.forEach((p) => map.add((p as any)[field] as Ref<Space>))
}
}
}
}

View File

@ -594,7 +594,24 @@ abstract class MongoAdapterBase implements DbAdapter {
find (domain: Domain): StorageIterator {
const coll = this.db.collection<Doc>(domain)
const iterator = coll.find({}, { sort: { _id: -1 } })
const iterator = coll.find({}, {})
const bulkUpdate = new Map<Ref<Doc>, string>()
const flush = async (flush = false): Promise<void> => {
if (bulkUpdate.size > 1000 || flush) {
if (bulkUpdate.size > 0) {
await coll.bulkWrite(
Array.from(bulkUpdate.entries()).map((it) => ({
updateOne: {
filter: { _id: it[0] },
update: { $set: { '%hash%': it[1] } }
}
}))
)
}
bulkUpdate.clear()
}
}
return {
next: async () => {
@ -611,7 +628,10 @@ abstract class MongoAdapterBase implements DbAdapter {
const hash = createHash('sha256')
hash.update(doc)
digest = hash.digest('base64')
await coll.updateOne({ _id: d._id }, { $set: { '%hash%': digest } })
bulkUpdate.set(d._id, digest)
// await coll.updateOne({ _id: d._id }, { $set: { '%hash%': digest } })
await flush()
}
return {
id: d._id,
@ -620,6 +640,7 @@ abstract class MongoAdapterBase implements DbAdapter {
}
},
close: async () => {
await flush(true)
await iterator.close()
}
}

View File

@ -24,6 +24,7 @@ import core, {
FieldIndex,
Hierarchy,
IndexKind,
IndexOrder,
ModelDb,
Tx,
WorkspaceId
@ -250,8 +251,12 @@ async function createUpdateIndexes (connection: CoreClient, db: Db, logger: Mode
const attrs = hierarchy.getAllAttributes(c._id)
const domainAttrs = domains.get(domain) ?? new Set<string | FieldIndex<Doc>>()
for (const a of attrs.values()) {
if (a.index !== undefined && a.index === IndexKind.Indexed) {
domainAttrs.add(a.name)
if (a.index !== undefined && (a.index === IndexKind.Indexed || a.index === IndexKind.IndexedDsc)) {
if (a.index === IndexKind.Indexed) {
domainAttrs.add(a.name)
} else {
domainAttrs.add({ [a.name]: IndexOrder.Descending })
}
}
}

View File

@ -154,8 +154,6 @@ class TSessionManager implements SessionManager {
return this.sessionFactory(token, pipeline, this.broadcast.bind(this))
}
upgradeIdMap = new Map<string, string>()
async addSession (
ctx: MeasureContext,
ws: ConnectionSocket,
@ -176,18 +174,14 @@ class TSessionManager implements SessionManager {
}
let pipeline: Pipeline
const upgradeId = this.upgradeIdMap.get(token.workspace.name)
if (token.extra?.model === 'upgrade') {
if (upgradeId !== undefined && sessionId !== upgradeId) {
ws.close()
throw new Error('Another Upgrade in progress....')
if (workspace.upgrade) {
pipeline = await ctx.with('pipeline', {}, async () => await (workspace as Workspace).pipeline)
} else {
pipeline = await this.createUpgradeSession(token, sessionId, ctx, wsString, workspace, pipelineFactory, ws)
}
if (sessionId !== undefined) {
this.upgradeIdMap.set(token.workspace.name, sessionId)
}
pipeline = await this.createUpgradeSession(token, sessionId, ctx, wsString, workspace, pipelineFactory, ws)
} else {
if (workspace.upgrade && sessionId !== upgradeId) {
if (workspace.upgrade) {
ws.close()
throw new Error('Upgrade in progress....')
}
@ -331,10 +325,6 @@ class TSessionManager implements SessionManager {
}
const sessionRef = this.sessions.get(ws.id)
if (sessionRef !== undefined) {
const upgradeId = this.upgradeIdMap.get(workspaceId.name)
if (upgradeId === sessionRef.session.sessionId) {
this.upgradeIdMap.delete(workspaceId.name)
}
this.sessions.delete(ws.id)
workspace.sessions.delete(sessionRef.session.sessionId)
this.reconnectIds.add(sessionRef.session.sessionId)
@ -441,14 +431,12 @@ class TSessionManager implements SessionManager {
if (this.workspaces.get(wsid)?.id === wsUID) {
this.workspaces.delete(wsid)
this.upgradeIdMap.delete(workspaceId.name)
}
if (LOGGING_ENABLED) {
console.timeLog(workspaceId.name, 'Closed workspace', wsUID)
}
} catch (err: any) {
this.workspaces.delete(wsid)
this.upgradeIdMap.delete(workspaceId.name)
if (LOGGING_ENABLED) {
console.error(workspaceId.name, err)
}