mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-11 12:57:59 +00:00
Fix load of applications in tables/lists/kanban (#6231)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
943c01bf8c
commit
1c965209a7
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
@ -100,6 +100,7 @@
|
|||||||
"ACCOUNTS_URL": "http://localhost:3000",
|
"ACCOUNTS_URL": "http://localhost:3000",
|
||||||
"UPLOAD_URL": "/files",
|
"UPLOAD_URL": "/files",
|
||||||
"SERVER_PORT": "8087",
|
"SERVER_PORT": "8087",
|
||||||
|
"VERSION": null,
|
||||||
"COLLABORATOR_URL": "ws://localhost:3078",
|
"COLLABORATOR_URL": "ws://localhost:3078",
|
||||||
"COLLABORATOR_API_URL": "http://localhost:3078",
|
"COLLABORATOR_API_URL": "http://localhost:3078",
|
||||||
"CALENDAR_URL": "http://localhost:8095",
|
"CALENDAR_URL": "http://localhost:8095",
|
||||||
|
@ -1 +1 @@
|
|||||||
"0.6.270"
|
"0.6.271"
|
@ -77,6 +77,7 @@ services:
|
|||||||
- ACCOUNTS_URL=http://account:3000
|
- ACCOUNTS_URL=http://account:3000
|
||||||
- UPLOAD_URL=/files
|
- UPLOAD_URL=/files
|
||||||
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
||||||
|
- 'MONGO_OPTIONS={"appName":"collaborator","maxPoolSize":2}'
|
||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
front:
|
front:
|
||||||
@ -95,6 +96,7 @@ services:
|
|||||||
- SERVER_PORT=8080
|
- SERVER_PORT=8080
|
||||||
- SERVER_SECRET=secret
|
- SERVER_SECRET=secret
|
||||||
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
||||||
|
- 'MONGO_OPTIONS={"appName":"front","maxPoolSize":1}'
|
||||||
- ACCOUNTS_URL=http://localhost:3000
|
- ACCOUNTS_URL=http://localhost:3000
|
||||||
- UPLOAD_URL=/files
|
- UPLOAD_URL=/files
|
||||||
- ELASTIC_URL=http://elastic:9200
|
- ELASTIC_URL=http://elastic:9200
|
||||||
@ -135,6 +137,7 @@ services:
|
|||||||
- ENABLE_COMPRESSION=true
|
- ENABLE_COMPRESSION=true
|
||||||
- ELASTIC_URL=http://elastic:9200
|
- ELASTIC_URL=http://elastic:9200
|
||||||
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
||||||
|
- 'MONGO_OPTIONS={"appName": "transactor", "maxPoolSize": 1}'
|
||||||
- METRICS_CONSOLE=false
|
- METRICS_CONSOLE=false
|
||||||
- METRICS_FILE=metrics.txt
|
- METRICS_FILE=metrics.txt
|
||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
@ -165,6 +168,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- SECRET=secret
|
- SECRET=secret
|
||||||
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
- MONGO_URL=mongodb://mongodb:27017?compressors=snappy
|
||||||
|
- 'MONGO_OPTIONS={"appName":"print","maxPoolSize":1}'
|
||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
@ -181,6 +185,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- SECRET=secret
|
- SECRET=secret
|
||||||
- MONGO_URL=mongodb://mongodb:27017
|
- MONGO_URL=mongodb://mongodb:27017
|
||||||
|
- 'MONGO_OPTIONS={"appName":"sign","maxPoolSize":1}'
|
||||||
- MINIO_ENDPOINT=minio
|
- MINIO_ENDPOINT=minio
|
||||||
- MINIO_ACCESS_KEY=minioadmin
|
- MINIO_ACCESS_KEY=minioadmin
|
||||||
- ACCOUNTS_URL=http://account:3000
|
- ACCOUNTS_URL=http://account:3000
|
||||||
@ -201,6 +206,7 @@ services:
|
|||||||
- SECRET=secret
|
- SECRET=secret
|
||||||
- PORT=4007
|
- PORT=4007
|
||||||
- MONGO_URL=mongodb://mongodb:27017
|
- MONGO_URL=mongodb://mongodb:27017
|
||||||
|
- 'MONGO_OPTIONS={"appName":"analytics","maxPoolSize":1}'
|
||||||
- SERVICE_ID=analytics-collector-service
|
- SERVICE_ID=analytics-collector-service
|
||||||
- ACCOUNTS_URL=http://account:3000
|
- ACCOUNTS_URL=http://account:3000
|
||||||
- SUPPORT_WORKSPACE=support
|
- SUPPORT_WORKSPACE=support
|
||||||
|
@ -70,6 +70,7 @@ export class TSpace extends TDoc implements Space {
|
|||||||
archived!: boolean
|
archived!: boolean
|
||||||
|
|
||||||
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members)
|
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members)
|
||||||
|
@Index(IndexKind.Indexed)
|
||||||
members!: Arr<Ref<Account>>
|
members!: Arr<Ref<Account>>
|
||||||
|
|
||||||
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Owners)
|
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Owners)
|
||||||
|
@ -39,6 +39,6 @@ export function createModel (builder: Builder): void {
|
|||||||
|
|
||||||
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||||
domain: DOMAIN_PREFERENCE,
|
domain: DOMAIN_PREFERENCE,
|
||||||
disabled: [{ modifiedOn: 1 }, { createdOn: 1 }]
|
disabled: [{ modifiedOn: 1 }, { createdOn: 1 }, { attachedTo: 1 }, { createdOn: -1 }, { modifiedBy: 1 }]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,7 @@ export function createModel (builder: Builder): void {
|
|||||||
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
|
||||||
domain: DOMAIN_TAGS,
|
domain: DOMAIN_TAGS,
|
||||||
disabled: [
|
disabled: [
|
||||||
|
{ _class: 1 },
|
||||||
{ modifiedOn: 1 },
|
{ modifiedOn: 1 },
|
||||||
{ modifiedBy: 1 },
|
{ modifiedBy: 1 },
|
||||||
{ createdBy: 1 },
|
{ createdBy: 1 },
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Basic performance metrics suite.
|
// Basic performance metrics suite.
|
||||||
|
|
||||||
import { MetricsData } from '.'
|
import { MetricsData } from '.'
|
||||||
import { cutObjectArray } from '../utils'
|
|
||||||
import { FullParamsType, Metrics, ParamsType } from './types'
|
import { FullParamsType, Metrics, ParamsType } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,7 +34,7 @@ function getUpdatedTopResult (
|
|||||||
|
|
||||||
const newValue = {
|
const newValue = {
|
||||||
value: time,
|
value: time,
|
||||||
params: cutObjectArray(params)
|
params
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.length > 6) {
|
if (result.length > 6) {
|
||||||
|
@ -487,9 +487,13 @@ export class ApplyOperations extends TxOperations {
|
|||||||
extraNotify
|
extraNotify
|
||||||
)
|
)
|
||||||
)) as Promise<TxApplyResult>)
|
)) as Promise<TxApplyResult>)
|
||||||
|
const dnow = Date.now()
|
||||||
|
if (typeof window === 'object' && window !== null) {
|
||||||
|
console.log(`measure ${this.measureName}`, dnow - st, 'server time', result.serverTime)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
result: result.success,
|
result: result.success,
|
||||||
time: Date.now() - st,
|
time: dnow - st,
|
||||||
serverTime: result.serverTime
|
serverTime: result.serverTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
import { getEmbeddedLabel, IntlString, PlatformError, unknownError } from '@hcengineering/platform'
|
||||||
import { deepEqual } from 'fast-equals'
|
import { deepEqual } from 'fast-equals'
|
||||||
|
import { DOMAIN_BENCHMARK } from './benchmark'
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
AccountRole,
|
AccountRole,
|
||||||
@ -46,7 +47,6 @@ import { TxOperations } from './operations'
|
|||||||
import { isPredicate } from './predicate'
|
import { isPredicate } from './predicate'
|
||||||
import { DocumentQuery, FindResult } from './storage'
|
import { DocumentQuery, FindResult } from './storage'
|
||||||
import { DOMAIN_TX } from './tx'
|
import { DOMAIN_TX } from './tx'
|
||||||
import { DOMAIN_BENCHMARK } from './benchmark'
|
|
||||||
|
|
||||||
function toHex (value: number, chars: number): string {
|
function toHex (value: number, chars: number): string {
|
||||||
const result = value.toString(16)
|
const result = value.toString(16)
|
||||||
@ -355,7 +355,6 @@ export class DocManager<T extends Doc> implements IDocManager<T> {
|
|||||||
|
|
||||||
export class RateLimiter {
|
export class RateLimiter {
|
||||||
idCounter: number = 0
|
idCounter: number = 0
|
||||||
processingQueue = new Map<number, Promise<void>>()
|
|
||||||
last: number = 0
|
last: number = 0
|
||||||
rate: number
|
rate: number
|
||||||
|
|
||||||
@ -366,21 +365,21 @@ export class RateLimiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notify: (() => void)[] = []
|
notify: (() => void)[] = []
|
||||||
|
finished: boolean = false
|
||||||
|
|
||||||
async exec<T, B extends Record<string, any> = any>(op: (args?: B) => Promise<T>, args?: B): Promise<T> {
|
async exec<T, B extends Record<string, any> = any>(op: (args?: B) => Promise<T>, args?: B): Promise<T> {
|
||||||
const processingId = this.idCounter++
|
if (this.finished) {
|
||||||
|
throw new PlatformError(unknownError('No Possible to add/exec on finished queue'))
|
||||||
while (this.processingQueue.size >= this.rate) {
|
}
|
||||||
|
while (this.notify.length >= this.rate) {
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
this.notify.push(resolve)
|
this.notify.push(resolve)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const p = op(args)
|
const p = op(args)
|
||||||
this.processingQueue.set(processingId, p as Promise<void>)
|
|
||||||
return await p
|
return await p
|
||||||
} finally {
|
} finally {
|
||||||
this.processingQueue.delete(processingId)
|
|
||||||
const n = this.notify.shift()
|
const n = this.notify.shift()
|
||||||
if (n !== undefined) {
|
if (n !== undefined) {
|
||||||
n()
|
n()
|
||||||
@ -389,7 +388,7 @@ export class RateLimiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async add<T, B extends Record<string, any> = any>(op: (args?: B) => Promise<T>, args?: B): Promise<void> {
|
async add<T, B extends Record<string, any> = any>(op: (args?: B) => Promise<T>, args?: B): Promise<void> {
|
||||||
if (this.processingQueue.size < this.rate) {
|
if (this.notify.length < this.rate) {
|
||||||
void this.exec(op, args)
|
void this.exec(op, args)
|
||||||
} else {
|
} else {
|
||||||
await this.exec(op, args)
|
await this.exec(op, args)
|
||||||
@ -397,7 +396,12 @@ export class RateLimiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitProcessing (): Promise<void> {
|
async waitProcessing (): Promise<void> {
|
||||||
await Promise.all(this.processingQueue.values())
|
this.finished = true
|
||||||
|
while (this.notify.length > 0) {
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
this.notify.push(resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
type FindResult,
|
type FindResult,
|
||||||
type Hierarchy,
|
type Hierarchy,
|
||||||
type ModelDb,
|
type ModelDb,
|
||||||
|
type QuerySelector,
|
||||||
type Ref,
|
type Ref,
|
||||||
type SearchOptions,
|
type SearchOptions,
|
||||||
type SearchQuery,
|
type SearchQuery,
|
||||||
@ -330,6 +331,22 @@ export class OptimizeQueryMiddleware extends BasePresentationMiddleware implemen
|
|||||||
const fQuery = { ...query }
|
const fQuery = { ...query }
|
||||||
const fOptions = { ...options }
|
const fOptions = { ...options }
|
||||||
this.optimizeQuery<T>(fQuery, fOptions)
|
this.optimizeQuery<T>(fQuery, fOptions)
|
||||||
|
|
||||||
|
// Immidiate response queries, if have some $in with empty list.
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(fQuery)) {
|
||||||
|
if (typeof v === 'object' && v != null) {
|
||||||
|
const vobj = v as QuerySelector<any>
|
||||||
|
if (vobj.$in != null && vobj.$in.length === 0) {
|
||||||
|
// Emopty in, will always return []
|
||||||
|
return toFindResult([], 0)
|
||||||
|
} else if (vobj.$in != null && vobj.$in.length === 1 && Object.keys(vobj).length === 1) {
|
||||||
|
;(fQuery as any)[k] = vobj.$in[0]
|
||||||
|
} else if (vobj.$nin != null && vobj.$nin.length === 1 && Object.keys(vobj).length === 1) {
|
||||||
|
;(fQuery as any)[k] = { $ne: vobj.$nin[0] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return await this.provideFindAll(_class, fQuery, fOptions)
|
return await this.provideFindAll(_class, fQuery, fOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,10 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
|
|||||||
if (options === undefined) {
|
if (options === undefined) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
if (options.label === undefined && options.component === undefined) {
|
||||||
|
// No tooltip
|
||||||
|
return {}
|
||||||
|
}
|
||||||
let opt = options
|
let opt = options
|
||||||
const show = (): void => {
|
const show = (): void => {
|
||||||
const shown = !!(storedValue.label !== undefined || storedValue.component !== undefined)
|
const shown = !!(storedValue.label !== undefined || storedValue.component !== undefined)
|
||||||
@ -113,7 +117,7 @@ export function showTooltip (
|
|||||||
props,
|
props,
|
||||||
anchor,
|
anchor,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
kind,
|
kind: kind ?? 'tooltip',
|
||||||
keys,
|
keys,
|
||||||
type: 'tooltip'
|
type: 'tooltip'
|
||||||
}
|
}
|
||||||
|
@ -13,23 +13,23 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
|
||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import core, { Account, Class, Doc, generateId, Markup, Ref, Space, toIdMap, type Blob } from '@hcengineering/core'
|
import { Account, Class, Doc, generateId, Markup, Ref, Space, toIdMap, type Blob } from '@hcengineering/core'
|
||||||
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
import { IntlString, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||||
import {
|
import {
|
||||||
createQuery,
|
createQuery,
|
||||||
DraftController,
|
|
||||||
deleteFile,
|
deleteFile,
|
||||||
|
DraftController,
|
||||||
draftsStore,
|
draftsStore,
|
||||||
getClient,
|
getClient,
|
||||||
getFileMetadata,
|
getFileMetadata,
|
||||||
uploadFile
|
uploadFile
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import textEditor, { type RefAction } from '@hcengineering/text-editor'
|
|
||||||
import { EmptyMarkup } from '@hcengineering/text'
|
import { EmptyMarkup } from '@hcengineering/text'
|
||||||
|
import textEditor, { type RefAction } from '@hcengineering/text-editor'
|
||||||
import { AttachIcon, StyledTextBox } from '@hcengineering/text-editor-resources'
|
import { AttachIcon, StyledTextBox } from '@hcengineering/text-editor-resources'
|
||||||
import { ButtonSize } from '@hcengineering/ui'
|
import { ButtonSize } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||||
|
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
import AttachmentsGrid from './AttachmentsGrid.svelte'
|
||||||
|
@ -76,9 +76,13 @@ const providerSettingsQuery = createQuery(true)
|
|||||||
const typeSettingsQuery = createQuery(true)
|
const typeSettingsQuery = createQuery(true)
|
||||||
|
|
||||||
export function loadNotificationSettings (): void {
|
export function loadNotificationSettings (): void {
|
||||||
providerSettingsQuery.query(notification.class.NotificationProviderSetting, {}, (res) => {
|
providerSettingsQuery.query(
|
||||||
providersSettings.set(res)
|
notification.class.NotificationProviderSetting,
|
||||||
})
|
{ space: core.space.Workspace },
|
||||||
|
(res) => {
|
||||||
|
providersSettings.set(res)
|
||||||
|
}
|
||||||
|
)
|
||||||
typeSettingsQuery.query(notification.class.NotificationTypeSetting, {}, (res) => {
|
typeSettingsQuery.query(notification.class.NotificationTypeSetting, {}, (res) => {
|
||||||
typesSettings.set(res)
|
typesSettings.set(res)
|
||||||
})
|
})
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Product, ProductVersion } from '@hcengineering/products'
|
import type { Product, ProductVersion } from '@hcengineering/products'
|
||||||
import { FindOptions, SortingOrder } from '@hcengineering/core'
|
import core, { FindOptions, SortingOrder } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { Label, Loading } from '@hcengineering/ui'
|
import { Label, Loading } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
@ -45,6 +45,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: viewlet._id
|
attachedTo: viewlet._id
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
import { EmployeeBox, ExpandRightDouble, UserBox } from '@hcengineering/contact-resources'
|
import { EmployeeBox, ExpandRightDouble, UserBox } from '@hcengineering/contact-resources'
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
|
AccountRole,
|
||||||
Class,
|
Class,
|
||||||
Client,
|
Client,
|
||||||
Doc,
|
Doc,
|
||||||
@ -28,10 +29,9 @@
|
|||||||
Ref,
|
Ref,
|
||||||
SortingOrder,
|
SortingOrder,
|
||||||
Space,
|
Space,
|
||||||
|
Status as TaskStatus,
|
||||||
fillDefaults,
|
fillDefaults,
|
||||||
generateId,
|
generateId,
|
||||||
Status as TaskStatus,
|
|
||||||
AccountRole,
|
|
||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
hasAccountRole
|
hasAccountRole
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
@ -43,10 +43,10 @@
|
|||||||
createQuery,
|
createQuery,
|
||||||
getClient
|
getClient
|
||||||
} from '@hcengineering/presentation'
|
} from '@hcengineering/presentation'
|
||||||
import type { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
|
import { recruitId, type Applicant, type Candidate, type Vacancy } from '@hcengineering/recruit'
|
||||||
import task, { TaskType, getStates, makeRank } from '@hcengineering/task'
|
import task, { TaskType, getStates, makeRank } from '@hcengineering/task'
|
||||||
import { TaskKindSelector, selectedTypeStore, typeStore } from '@hcengineering/task-resources'
|
import { TaskKindSelector, selectedTypeStore, typeStore } from '@hcengineering/task-resources'
|
||||||
import { EmptyMarkup } from '@hcengineering/text'
|
import { EmptyMarkup, isEmptyMarkup } from '@hcengineering/text'
|
||||||
import ui, {
|
import ui, {
|
||||||
Button,
|
Button,
|
||||||
ColorPopup,
|
ColorPopup,
|
||||||
@ -132,8 +132,11 @@
|
|||||||
if (candidateInstance === undefined) {
|
if (candidateInstance === undefined) {
|
||||||
throw new Error('contact not found')
|
throw new Error('contact not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ops = client.apply(generateId(), recruitId + '.Create.CreateApplication')
|
||||||
|
|
||||||
if (!client.getHierarchy().hasMixin(candidateInstance, recruit.mixin.Candidate)) {
|
if (!client.getHierarchy().hasMixin(candidateInstance, recruit.mixin.Candidate)) {
|
||||||
await client.createMixin<Contact, Candidate>(
|
await ops.createMixin<Contact, Candidate>(
|
||||||
candidateInstance._id,
|
candidateInstance._id,
|
||||||
candidateInstance._class,
|
candidateInstance._class,
|
||||||
candidateInstance.space,
|
candidateInstance.space,
|
||||||
@ -144,7 +147,7 @@
|
|||||||
|
|
||||||
const number = (incResult as any).object.sequence
|
const number = (incResult as any).object.sequence
|
||||||
|
|
||||||
await client.addCollection(
|
await ops.addCollection(
|
||||||
recruit.class.Applicant,
|
recruit.class.Applicant,
|
||||||
_space,
|
_space,
|
||||||
candidateInstance._id,
|
candidateInstance._id,
|
||||||
@ -166,11 +169,12 @@
|
|||||||
|
|
||||||
await descriptionBox.createAttachments()
|
await descriptionBox.createAttachments()
|
||||||
|
|
||||||
if (_comment.trim().length > 0) {
|
if (_comment.trim().length > 0 && !isEmptyMarkup(_comment)) {
|
||||||
await client.addCollection(chunter.class.ChatMessage, _space, doc._id, recruit.class.Applicant, 'comments', {
|
await ops.addCollection(chunter.class.ChatMessage, _space, doc._id, recruit.class.Applicant, 'comments', {
|
||||||
message: _comment
|
message: _comment
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
await ops.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function invokeValidate (
|
async function invokeValidate (
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: viewlet._id
|
attachedTo: viewlet._id
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
import core, { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { Component } from '@hcengineering/tracker'
|
import { Component } from '@hcengineering/tracker'
|
||||||
import { Loading, Component as ViewComponent } from '@hcengineering/ui'
|
import { Loading, Component as ViewComponent } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||||
@ -34,6 +34,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: viewlet._id
|
attachedTo: viewlet._id
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
import core, { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Issue } from '@hcengineering/tracker'
|
import { Issue } from '@hcengineering/tracker'
|
||||||
import { Button, Chevron, ExpandCollapse, IconAdd, closeTooltip, resizeObserver, showPopup } from '@hcengineering/ui'
|
import { Button, Chevron, ExpandCollapse, IconAdd, closeTooltip, resizeObserver, showPopup } from '@hcengineering/ui'
|
||||||
@ -77,6 +77,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: { $in: configurationRaw.map((it) => it._id) }
|
attachedTo: { $in: configurationRaw.map((it) => it._id) }
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -0,0 +1,225 @@
|
|||||||
|
<!--
|
||||||
|
// 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 core, { SortingOrder, toIdMap, type IdMap, type Ref, type StatusCategory } from '@hcengineering/core'
|
||||||
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
|
import tracker, { type Issue, type Project } from '@hcengineering/tracker'
|
||||||
|
import {
|
||||||
|
createFocusManager,
|
||||||
|
deviceOptionsStore,
|
||||||
|
EditWithIcon,
|
||||||
|
FocusHandler,
|
||||||
|
Icon,
|
||||||
|
IconCheck,
|
||||||
|
IconSearch,
|
||||||
|
Label,
|
||||||
|
ListView,
|
||||||
|
resizeObserver,
|
||||||
|
showPanel,
|
||||||
|
Spinner,
|
||||||
|
type SelectPopupValueType
|
||||||
|
} from '@hcengineering/ui'
|
||||||
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { subIssueListProvider, type IssueRef } from '../../../utils'
|
||||||
|
import RelatedIssuePresenter from './RelatedIssuePresenter.svelte'
|
||||||
|
|
||||||
|
export let refs: IssueRef[]
|
||||||
|
|
||||||
|
export let placeholder: IntlString | undefined = undefined
|
||||||
|
export let placeholderParam: any | undefined = undefined
|
||||||
|
export let searchable: boolean = false
|
||||||
|
export let width: 'medium' | 'large' | 'full' = 'medium'
|
||||||
|
export let showShadow: boolean = true
|
||||||
|
export let embedded: boolean = false
|
||||||
|
export let componentLink: boolean = false
|
||||||
|
export let loading: boolean = false
|
||||||
|
export let currentProject: Project
|
||||||
|
|
||||||
|
let popupElement: HTMLDivElement | undefined = undefined
|
||||||
|
let search: string = ''
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let selection = 0
|
||||||
|
let list: ListView
|
||||||
|
|
||||||
|
let selected: any
|
||||||
|
|
||||||
|
let subIssues: Issue[] = []
|
||||||
|
|
||||||
|
const query = createQuery()
|
||||||
|
query.query(
|
||||||
|
tracker.class.Issue,
|
||||||
|
{ _id: { $in: refs.map((it) => it._id) } },
|
||||||
|
(res) => {
|
||||||
|
subIssues = res
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: { rank: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let categories: IdMap<StatusCategory> = new Map()
|
||||||
|
|
||||||
|
void getClient()
|
||||||
|
.findAll(core.class.StatusCategory, {})
|
||||||
|
.then((res) => {
|
||||||
|
categories = toIdMap(res)
|
||||||
|
})
|
||||||
|
|
||||||
|
$: value = subIssues.map((iss) => {
|
||||||
|
const c = $statusStore.byId.get(iss.status)?.category
|
||||||
|
const category = c !== undefined ? categories.get(c) : undefined
|
||||||
|
return {
|
||||||
|
id: iss._id,
|
||||||
|
isSelected: false,
|
||||||
|
component: RelatedIssuePresenter,
|
||||||
|
props: { project: currentProject, issue: iss },
|
||||||
|
category:
|
||||||
|
category !== undefined
|
||||||
|
? {
|
||||||
|
label: category.label,
|
||||||
|
icon: category.icon
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$: hasSelected = value.some((v) => v.isSelected)
|
||||||
|
|
||||||
|
function openIssue (target: Ref<Issue>): void {
|
||||||
|
subIssueListProvider(subIssues, target)
|
||||||
|
showPanel(tracker.component.EditIssue, target, tracker.class.Issue, 'content')
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendSelect (id: SelectPopupValueType['id']): void {
|
||||||
|
selected = id
|
||||||
|
openIssue(id as Ref<Issue>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onKeydown (key: KeyboardEvent): boolean {
|
||||||
|
if (key.code === 'Tab') {
|
||||||
|
dispatch('close')
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (key.code === 'ArrowUp') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(selection - 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (key.code === 'ArrowDown') {
|
||||||
|
key.stopPropagation()
|
||||||
|
key.preventDefault()
|
||||||
|
list.select(selection + 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (key.code === 'Enter') {
|
||||||
|
key.preventDefault()
|
||||||
|
key.stopPropagation()
|
||||||
|
sendSelect(value[selection].id)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const manager = createFocusManager()
|
||||||
|
|
||||||
|
$: if (popupElement) {
|
||||||
|
popupElement.focus()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FocusHandler {manager} />
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div
|
||||||
|
class="selectPopup"
|
||||||
|
bind:this={popupElement}
|
||||||
|
tabindex="0"
|
||||||
|
class:noShadow={!showShadow}
|
||||||
|
class:full-width={width === 'full'}
|
||||||
|
class:max-width-40={width === 'large'}
|
||||||
|
class:embedded
|
||||||
|
use:resizeObserver={() => {
|
||||||
|
dispatch('changeContent')
|
||||||
|
}}
|
||||||
|
on:keydown={onKeydown}
|
||||||
|
>
|
||||||
|
{#if searchable}
|
||||||
|
<div class="header">
|
||||||
|
<EditWithIcon
|
||||||
|
icon={IconSearch}
|
||||||
|
size={'large'}
|
||||||
|
width={'100%'}
|
||||||
|
autoFocus={!$deviceOptionsStore.isMobile}
|
||||||
|
bind:value={search}
|
||||||
|
{placeholder}
|
||||||
|
{placeholderParam}
|
||||||
|
on:change
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="menu-space" />
|
||||||
|
{/if}
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="box">
|
||||||
|
<ListView bind:this={list} count={value.length} bind:selection on:changeContent={() => dispatch('changeContent')}>
|
||||||
|
<svelte:fragment slot="item" let:item={itemId}>
|
||||||
|
{@const item = value[itemId]}
|
||||||
|
<button
|
||||||
|
class="menu-item withList w-full"
|
||||||
|
on:click={() => {
|
||||||
|
sendSelect(item.id)
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<div class="flex-row-center flex-grow" class:pointer-events-none={!componentLink}>
|
||||||
|
{#if item.component}
|
||||||
|
<div class="flex-grow clear-mins"><svelte:component this={item.component} {...item.props} /></div>
|
||||||
|
{/if}
|
||||||
|
{#if hasSelected}
|
||||||
|
<div class="check">
|
||||||
|
{#if item.isSelected}
|
||||||
|
<Icon icon={IconCheck} size={'small'} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if item.id === selected && loading}
|
||||||
|
<Spinner size={'small'} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="category" let:item={row}>
|
||||||
|
{@const obj = value[row]}
|
||||||
|
{#if obj.category && ((row === 0 && obj.category.label !== undefined) || obj.category.label !== value[row - 1]?.category?.label)}
|
||||||
|
{#if row > 0}<div class="menu-separator" />{/if}
|
||||||
|
<div class="menu-group__header flex-row-center">
|
||||||
|
<span class="overflow-label">
|
||||||
|
<Label label={obj.category.label} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
</ListView>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if !embedded}<div class="menu-space" />{/if}
|
||||||
|
</div>
|
@ -13,18 +13,16 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { Doc, IdMap, Ref, SortingOrder, StatusCategory, WithLookup, toIdMap } from '@hcengineering/core'
|
import { Doc, Ref, WithLookup, type Status } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
|
||||||
import task from '@hcengineering/task'
|
import task from '@hcengineering/task'
|
||||||
import { Issue, Project } from '@hcengineering/tracker'
|
import { Project, type Issue } from '@hcengineering/tracker'
|
||||||
import { Button, ButtonKind, ButtonSize, ProgressCircle, SelectPopup, showPanel } from '@hcengineering/ui'
|
import { Button, ButtonKind, ButtonSize, ProgressCircle } from '@hcengineering/ui'
|
||||||
import { statusStore } from '@hcengineering/view-resources'
|
import { statusStore } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../../plugin'
|
import { listIssueStatusOrder, relatedIssues, type IssueRef } from '../../../utils'
|
||||||
import { listIssueStatusOrder, subIssueListProvider } from '../../../utils'
|
import RelatedIssuePopup from './RelatedIssuePopup.svelte'
|
||||||
import RelatedIssuePresenter from './RelatedIssuePresenter.svelte'
|
|
||||||
|
|
||||||
export let object: WithLookup<Doc & { related: number }> | undefined
|
export let object: WithLookup<Doc> | undefined
|
||||||
export let value: WithLookup<Doc & { related: number }> | undefined
|
export let value: WithLookup<Doc> | undefined
|
||||||
export let currentProject: Project | undefined
|
export let currentProject: Project | undefined
|
||||||
|
|
||||||
export let kind: ButtonKind = 'link-bordered'
|
export let kind: ButtonKind = 'link-bordered'
|
||||||
@ -33,44 +31,13 @@
|
|||||||
export let width: string | undefined = 'min-contet'
|
export let width: string | undefined = 'min-contet'
|
||||||
export let compactMode: boolean = false
|
export let compactMode: boolean = false
|
||||||
|
|
||||||
let _subIssues: Issue[] = []
|
|
||||||
let subIssues: Issue[] = []
|
|
||||||
let countComplete: number = 0
|
|
||||||
|
|
||||||
const query = createQuery()
|
|
||||||
|
|
||||||
$: _object = object ?? value
|
$: _object = object ?? value
|
||||||
|
|
||||||
$: _object != null && update(_object)
|
$: subIssues = sortStatuses(_object?._id !== undefined ? [...($relatedIssues.get(_object?._id) ?? [])] : [])
|
||||||
|
let countComplete: number = 0
|
||||||
|
|
||||||
function update (value: WithLookup<Doc & { related: number }>): void {
|
function sortStatuses (statuses: IssueRef[]): { _id: Ref<Issue>, status: Ref<Status> }[] {
|
||||||
if (value.$lookup?.related !== undefined) {
|
statuses.sort((a, b) => {
|
||||||
query.unsubscribe()
|
|
||||||
_subIssues = value.$lookup.related as Issue[]
|
|
||||||
} else {
|
|
||||||
query.query(
|
|
||||||
tracker.class.Issue,
|
|
||||||
{ 'relations._id': value._id, 'relations._class': value._class },
|
|
||||||
(res) => {
|
|
||||||
_subIssues = res
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sort: { rank: SortingOrder.Ascending }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let categories: IdMap<StatusCategory> = new Map()
|
|
||||||
|
|
||||||
void getClient()
|
|
||||||
.findAll(core.class.StatusCategory, {})
|
|
||||||
.then((res) => {
|
|
||||||
categories = toIdMap(res)
|
|
||||||
})
|
|
||||||
|
|
||||||
$: {
|
|
||||||
_subIssues.sort((a, b) => {
|
|
||||||
const aStatus = $statusStore.byId.get(a.status)
|
const aStatus = $statusStore.byId.get(a.status)
|
||||||
const bStatus = $statusStore.byId.get(b.status)
|
const bStatus = $statusStore.byId.get(b.status)
|
||||||
return (
|
return (
|
||||||
@ -78,39 +45,16 @@
|
|||||||
listIssueStatusOrder.indexOf(bStatus?.category ?? task.statusCategory.UnStarted)
|
listIssueStatusOrder.indexOf(bStatus?.category ?? task.statusCategory.UnStarted)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
subIssues = _subIssues
|
return statuses
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (subIssues != null) {
|
$: if (subIssues.length > 0) {
|
||||||
const doneStatuses = $statusStore.array
|
const doneStatuses = $statusStore.array
|
||||||
.filter((s) => s.category === task.statusCategory.Won || s.category === task.statusCategory.Lost)
|
.filter((s) => s.category === task.statusCategory.Won || s.category === task.statusCategory.Lost)
|
||||||
.map((p) => p._id)
|
.map((p) => p._id)
|
||||||
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
|
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
|
||||||
}
|
}
|
||||||
$: hasSubIssues = (subIssues?.length ?? 0) > 0
|
$: hasSubIssues = subIssues.length > 0
|
||||||
|
|
||||||
function openIssue (target: Ref<Issue>): void {
|
|
||||||
subIssueListProvider(subIssues, target)
|
|
||||||
showPanel(tracker.component.EditIssue, target, tracker.class.Issue, 'content')
|
|
||||||
}
|
|
||||||
|
|
||||||
$: selectValue = subIssues.map((iss) => {
|
|
||||||
const c = $statusStore.byId.get(iss.status)?.category
|
|
||||||
const category = c !== undefined ? categories.get(c) : undefined
|
|
||||||
return {
|
|
||||||
id: iss._id,
|
|
||||||
isSelected: false,
|
|
||||||
component: RelatedIssuePresenter,
|
|
||||||
props: { project: currentProject, issue: iss },
|
|
||||||
category:
|
|
||||||
category !== undefined
|
|
||||||
? {
|
|
||||||
label: category.label,
|
|
||||||
icon: category.icon
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasSubIssues}
|
{#if hasSubIssues}
|
||||||
@ -121,10 +65,10 @@
|
|||||||
{size}
|
{size}
|
||||||
{justify}
|
{justify}
|
||||||
showTooltip={{
|
showTooltip={{
|
||||||
component: SelectPopup,
|
component: RelatedIssuePopup,
|
||||||
props: {
|
props: {
|
||||||
value: selectValue,
|
refs: subIssues,
|
||||||
onSelect: openIssue,
|
currentProject,
|
||||||
showShadow: false,
|
showShadow: false,
|
||||||
width: 'large'
|
width: 'large'
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
import core, { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { Milestone } from '@hcengineering/tracker'
|
import { Milestone } from '@hcengineering/tracker'
|
||||||
import { Component, Loading } from '@hcengineering/ui'
|
import { Component, Loading } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||||
@ -22,6 +22,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: viewlet._id
|
attachedTo: viewlet._id
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
import core, { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { IssueTemplate } from '@hcengineering/tracker'
|
import { IssueTemplate } from '@hcengineering/tracker'
|
||||||
import { Component } from '@hcengineering/ui'
|
import { Component } from '@hcengineering/ui'
|
||||||
@ -19,6 +19,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: viewlet._id
|
attachedTo: viewlet._id
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -28,6 +28,7 @@ import core, {
|
|||||||
type DocumentUpdate,
|
type DocumentUpdate,
|
||||||
type Ref,
|
type Ref,
|
||||||
type Space,
|
type Space,
|
||||||
|
type Status,
|
||||||
type StatusCategory,
|
type StatusCategory,
|
||||||
type TxCollectionCUD,
|
type TxCollectionCUD,
|
||||||
type TxCreateDoc,
|
type TxCreateDoc,
|
||||||
@ -52,7 +53,7 @@ import {
|
|||||||
import { PaletteColorIndexes, areDatesEqual, isWeekend } from '@hcengineering/ui'
|
import { PaletteColorIndexes, areDatesEqual, isWeekend } from '@hcengineering/ui'
|
||||||
import { type KeyFilter, type ViewletDescriptor } from '@hcengineering/view'
|
import { type KeyFilter, type ViewletDescriptor } from '@hcengineering/view'
|
||||||
import { CategoryQuery, ListSelectionProvider, statusStore, type SelectDirection } from '@hcengineering/view-resources'
|
import { CategoryQuery, ListSelectionProvider, statusStore, type SelectDirection } from '@hcengineering/view-resources'
|
||||||
import { derived, get } from 'svelte/store'
|
import { derived, get, writable } from 'svelte/store'
|
||||||
import tracker from './plugin'
|
import tracker from './plugin'
|
||||||
import { defaultMilestoneStatuses, defaultPriorities } from './types'
|
import { defaultMilestoneStatuses, defaultPriorities } from './types'
|
||||||
|
|
||||||
@ -570,3 +571,44 @@ export async function getMilestoneTitle (client: TxOperations, ref: Ref<Mileston
|
|||||||
|
|
||||||
return object?.label ?? ''
|
return object?.label ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IssueRef {
|
||||||
|
status: Ref<Status>
|
||||||
|
_id: Ref<Issue>
|
||||||
|
}
|
||||||
|
export type IssueReverseRevMap = Map<Ref<Doc>, IssueRef[]>
|
||||||
|
export const relatedIssues = writable<IssueReverseRevMap>(new Map())
|
||||||
|
|
||||||
|
function fillStores (): void {
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
if (client !== undefined) {
|
||||||
|
const relatedIssuesQuery = createQuery(true)
|
||||||
|
|
||||||
|
relatedIssuesQuery.query(
|
||||||
|
tracker.class.Issue,
|
||||||
|
{ 'relations._id': { $exists: true } },
|
||||||
|
(res) => {
|
||||||
|
const nMap: IssueReverseRevMap = new Map()
|
||||||
|
for (const r of res) {
|
||||||
|
for (const rr of r.relations ?? []) {
|
||||||
|
nMap.set(rr._id, [...(nMap.get(rr._id) ?? []), { _id: r._id, status: r.status }])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relatedIssues.set(nMap)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
projection: {
|
||||||
|
relations: 1,
|
||||||
|
status: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
fillStores()
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fillStores()
|
||||||
|
@ -154,10 +154,31 @@
|
|||||||
query,
|
query,
|
||||||
(result) => {
|
(result) => {
|
||||||
total = result.total
|
total = result.total
|
||||||
|
if (totalQuery === undefined) {
|
||||||
|
gtotal = total
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ limit: 1, ...options, sort: getSort(_sortKey), lookup, total: true }
|
{ limit: 1, ...options, sort: getSort(_sortKey), lookup, total: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const totalQueryQ = createQuery()
|
||||||
|
$: if (totalQuery !== undefined) {
|
||||||
|
totalQueryQ.query(
|
||||||
|
_class,
|
||||||
|
totalQuery,
|
||||||
|
(result) => {
|
||||||
|
gtotal = result.total === -1 ? 0 : result.total
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup,
|
||||||
|
limit: 1,
|
||||||
|
total: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
gtotal = total
|
||||||
|
}
|
||||||
|
|
||||||
const showContextMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
|
const showContextMenu = async (ev: MouseEvent, object: Doc, row: number): Promise<void> => {
|
||||||
selection = row
|
selection = row
|
||||||
if (!checkedSet.has(object._id)) {
|
if (!checkedSet.has(object._id)) {
|
||||||
@ -258,20 +279,6 @@
|
|||||||
|
|
||||||
let width: number
|
let width: number
|
||||||
|
|
||||||
const totalQueryQ = createQuery()
|
|
||||||
$: totalQueryQ.query(
|
|
||||||
_class,
|
|
||||||
totalQuery ?? query ?? {},
|
|
||||||
(result) => {
|
|
||||||
gtotal = result.total === -1 ? 0 : result.total
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lookup,
|
|
||||||
limit: 1,
|
|
||||||
total: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
let isBuildingModel = true
|
let isBuildingModel = true
|
||||||
let model: AttributeModel[] | undefined
|
let model: AttributeModel[] | undefined
|
||||||
let modelOptions: BuildModelOptions | undefined
|
let modelOptions: BuildModelOptions | undefined
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Class, Doc, DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
import core, { Class, Doc, DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { AnySvelteComponent, Component, Loading } from '@hcengineering/ui'
|
import { AnySvelteComponent, Component, Loading } from '@hcengineering/ui'
|
||||||
@ -44,6 +44,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: { $in: configurationRaw.map((it) => it._id) }
|
attachedTo: { $in: configurationRaw.map((it) => it._id) }
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { activeViewlet, makeViewletKey, setActiveViewletId } from '../utils'
|
import { activeViewlet, makeViewletKey, setActiveViewletId } from '../utils'
|
||||||
import { resolvedLocationStore, Switcher } from '@hcengineering/ui'
|
import { resolvedLocationStore, Switcher } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { DocumentQuery, Ref, WithLookup } from '@hcengineering/core'
|
import core, { DocumentQuery, Ref, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
|
|
||||||
export let viewlet: WithLookup<Viewlet> | undefined
|
export let viewlet: WithLookup<Viewlet> | undefined
|
||||||
@ -69,6 +69,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: viewlet._id
|
attachedTo: viewlet._id
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: { $in: Array.from(viewlets.map((it) => it._id)) }
|
attachedTo: { $in: Array.from(viewlets.map((it) => it._id)) }
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentQuery, WithLookup } from '@hcengineering/core'
|
import core, { DocumentQuery, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { ModernButton, showPopup, closeTooltip } from '@hcengineering/ui'
|
import { ModernButton, showPopup, closeTooltip } from '@hcengineering/ui'
|
||||||
import { ViewOptions, Viewlet, ViewletPreference } from '@hcengineering/view'
|
import { ViewOptions, Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
@ -72,6 +72,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: viewlet._id
|
attachedTo: viewlet._id
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -474,7 +474,10 @@ export function buildConfigLookup<T extends Doc> (
|
|||||||
...((existingLookup as ReverseLookups)._id ?? {}),
|
...((existingLookup as ReverseLookups)._id ?? {}),
|
||||||
...((res as ReverseLookups)._id ?? {})
|
...((res as ReverseLookups)._id ?? {})
|
||||||
}
|
}
|
||||||
res = { ...existingLookup, ...res, _id }
|
res = { ...existingLookup, ...res }
|
||||||
|
if (Object.keys(_id).length > 0) {
|
||||||
|
;(res as any)._id = _id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -42,17 +42,21 @@
|
|||||||
|
|
||||||
$: if (model) {
|
$: if (model) {
|
||||||
const classes = getSpecialSpaceClass(model).flatMap((c) => hierarchy.getDescendants(c))
|
const classes = getSpecialSpaceClass(model).flatMap((c) => hierarchy.getDescendants(c))
|
||||||
query.query(
|
if (classes.length > 0) {
|
||||||
core.class.Space,
|
query.query(
|
||||||
{
|
core.class.Space,
|
||||||
_class: { $in: classes },
|
{
|
||||||
members: getCurrentAccount()._id
|
_class: classes.length === 1 ? classes[0] : { $in: classes },
|
||||||
},
|
members: getCurrentAccount()._id
|
||||||
(result) => {
|
},
|
||||||
spaces = result
|
(result) => {
|
||||||
},
|
spaces = result
|
||||||
{ sort: { name: SortingOrder.Ascending } }
|
},
|
||||||
)
|
{ sort: { name: SortingOrder.Ascending } }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
query.unsubscribe()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let specials: SpecialNavModel[] = []
|
let specials: SpecialNavModel[] = []
|
||||||
|
@ -47,22 +47,28 @@
|
|||||||
const filteredViewsQuery = createQuery()
|
const filteredViewsQuery = createQuery()
|
||||||
let availableFilteredViews: FilteredView[] = []
|
let availableFilteredViews: FilteredView[] = []
|
||||||
let myFilteredViews: FilteredView[] = []
|
let myFilteredViews: FilteredView[] = []
|
||||||
$: filteredViewsQuery.query<FilteredView>(
|
$: if (currentApplication?.alias !== undefined) {
|
||||||
view.class.FilteredView,
|
filteredViewsQuery.query<FilteredView>(
|
||||||
{ attachedTo: currentApplication?.alias },
|
view.class.FilteredView,
|
||||||
(result) => {
|
{ attachedTo: currentApplication?.alias },
|
||||||
myFilteredViews = result.filter((p) => p.users.includes(me))
|
(result) => {
|
||||||
availableFilteredViews = result.filter((p) => p.sharable && !p.users.includes(me))
|
myFilteredViews = result.filter((p) => p.users.includes(me))
|
||||||
|
availableFilteredViews = result.filter((p) => p.sharable && !p.users.includes(me))
|
||||||
|
|
||||||
const location = getLocation()
|
const location = getLocation()
|
||||||
if (location.query?.filterViewId) {
|
if (location.query?.filterViewId) {
|
||||||
const targetView = result.find((view) => view._id === location.query?.filterViewId)
|
const targetView = result.find((view) => view._id === location.query?.filterViewId)
|
||||||
if (targetView) {
|
if (targetView) {
|
||||||
load(targetView)
|
load(targetView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
} else {
|
||||||
|
filteredViewsQuery.unsubscribe()
|
||||||
|
myFilteredViews = []
|
||||||
|
availableFilteredViews = []
|
||||||
|
}
|
||||||
|
|
||||||
async function removeAction (filteredView: FilteredView): Promise<Action[]> {
|
async function removeAction (filteredView: FilteredView): Promise<Action[]> {
|
||||||
return [
|
return [
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Class, Doc, Ref, Space, WithLookup } from '@hcengineering/core'
|
import type { Class, Doc, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
|
import core from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { AnyComponent, Component, Loading } from '@hcengineering/ui'
|
import { AnyComponent, Component, Loading } from '@hcengineering/ui'
|
||||||
@ -42,6 +43,7 @@
|
|||||||
preferenceQuery.query(
|
preferenceQuery.query(
|
||||||
view.class.ViewletPreference,
|
view.class.ViewletPreference,
|
||||||
{
|
{
|
||||||
|
space: core.space.Workspace,
|
||||||
attachedTo: viewlet._id
|
attachedTo: viewlet._id
|
||||||
},
|
},
|
||||||
(res) => {
|
(res) => {
|
||||||
|
@ -57,9 +57,14 @@
|
|||||||
|
|
||||||
const prevSpaceId = spaceId
|
const prevSpaceId = spaceId
|
||||||
|
|
||||||
$: query.query(core.class.Space, { _id: spaceId }, (result) => {
|
$: query.query(
|
||||||
space = result[0]
|
core.class.Space,
|
||||||
})
|
{ _id: spaceId },
|
||||||
|
(result) => {
|
||||||
|
space = result[0]
|
||||||
|
},
|
||||||
|
{ limit: 1 }
|
||||||
|
)
|
||||||
|
|
||||||
function showCreateDialog (ev: Event) {
|
function showCreateDialog (ev: Event) {
|
||||||
showPopup(createItemDialog as AnyComponent, { space: spaceId }, 'top')
|
showPopup(createItemDialog as AnyComponent, { space: spaceId }, 'top')
|
||||||
|
@ -36,9 +36,14 @@
|
|||||||
const clazz = client.getHierarchy().getClass(_class)
|
const clazz = client.getHierarchy().getClass(_class)
|
||||||
|
|
||||||
const query = createQuery()
|
const query = createQuery()
|
||||||
$: query.query(core.class.Space, { _id }, (result) => {
|
$: query.query(
|
||||||
space = result[0]
|
core.class.Space,
|
||||||
})
|
{ _id },
|
||||||
|
(result) => {
|
||||||
|
space = result[0]
|
||||||
|
},
|
||||||
|
{ limit: 1 }
|
||||||
|
)
|
||||||
|
|
||||||
function onNameChange (ev: Event) {
|
function onNameChange (ev: Event) {
|
||||||
const value = (ev.target as HTMLInputElement).value
|
const value = (ev.target as HTMLInputElement).value
|
||||||
@ -46,9 +51,14 @@
|
|||||||
client.updateDoc(_class, space.space, space._id, { name: value })
|
client.updateDoc(_class, space.space, space._id, { name: value })
|
||||||
} else {
|
} else {
|
||||||
// Just refresh value
|
// Just refresh value
|
||||||
query.query(core.class.Space, { _id }, (result) => {
|
query.query(
|
||||||
space = result[0]
|
core.class.Space,
|
||||||
})
|
{ _id },
|
||||||
|
(result) => {
|
||||||
|
space = result[0]
|
||||||
|
},
|
||||||
|
{ limit: 1 }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -9,7 +9,10 @@
|
|||||||
export let level = 0
|
export let level = 0
|
||||||
export let name: string = 'System'
|
export let name: string = 'System'
|
||||||
|
|
||||||
$: haschilds = Object.keys(metrics.measurements).length > 0 || Object.keys(metrics.params).length > 0
|
$: haschilds =
|
||||||
|
Object.keys(metrics.measurements).length > 0 ||
|
||||||
|
Object.keys(metrics.params).length > 0 ||
|
||||||
|
(metrics.topResult?.length ?? 0) > 0
|
||||||
|
|
||||||
function showAvg (name: string, time: number, ops: number): string {
|
function showAvg (name: string, time: number, ops: number): string {
|
||||||
if (name.startsWith('#')) {
|
if (name.startsWith('#')) {
|
||||||
@ -76,13 +79,13 @@
|
|||||||
{#each metrics.topResult ?? [] as r}
|
{#each metrics.topResult ?? [] as r}
|
||||||
<Expandable>
|
<Expandable>
|
||||||
<svelte:fragment slot="title">
|
<svelte:fragment slot="title">
|
||||||
<div class="flex-row-center flex-between flex-grow">
|
<div class="flex-row-center flex-between flex-grow select-text">
|
||||||
Time:{r.value}
|
Time:{r.value}
|
||||||
</div>
|
</div>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<pre>
|
<pre class="select-text">
|
||||||
{JSON.stringify(r, null, 2)}
|
{JSON.stringify(r, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</Expandable>
|
</Expandable>
|
||||||
{/each}
|
{/each}
|
||||||
</Expandable>
|
</Expandable>
|
||||||
@ -127,9 +130,9 @@
|
|||||||
Time:{r.value}
|
Time:{r.value}
|
||||||
</div>
|
</div>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<pre>
|
<pre class="select-text">
|
||||||
{JSON.stringify(r, null, 2)}
|
{JSON.stringify(r, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</Expandable>
|
</Expandable>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"bundle": "mkdir -p bundle && esbuild src/__start.ts --bundle --define:process.env.MODEL_VERSION=$(node ../../common/scripts/show_version.js) --minify --platform=node > bundle/bundle.js",
|
"bundle": "mkdir -p bundle && esbuild src/__start.ts --bundle --define:process.env.MODEL_VERSION=$(node ../../common/scripts/show_version.js) --minify --platform=node > bundle/bundle.js",
|
||||||
"docker:build": "../../common/scripts/docker_build.sh hardcoreeng/account",
|
"docker:build": "../../common/scripts/docker_build.sh hardcoreeng/account",
|
||||||
"docker:tbuild": "docker build -t hardcoreeng/account . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/account",
|
"docker:tbuild": "docker build -t hardcoreeng/account . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/account",
|
||||||
|
"docker:abuild": "docker build -t hardcoreeng/account . --platform=linux/arm64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/account",
|
||||||
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/account staging",
|
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/account staging",
|
||||||
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/account",
|
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/account",
|
||||||
"run-local": "cross-env MONGO_URL=mongodb://localhost:27017 MINIO_ACCESS_KEY=minioadmi MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost SERVER_SECRET='secret' TRANSACTOR_URL=ws://localhost:3333 ts-node src/__start.ts",
|
"run-local": "cross-env MONGO_URL=mongodb://localhost:27017 MINIO_ACCESS_KEY=minioadmi MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost SERVER_SECRET='secret' TRANSACTOR_URL=ws://localhost:3333 ts-node src/__start.ts",
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
"_phase:docker-staging": "rushx docker:staging",
|
"_phase:docker-staging": "rushx docker:staging",
|
||||||
"bundle": "mkdir -p bundle && esbuild src/__start.ts --bundle --platform=node --keep-names > bundle/bundle.js",
|
"bundle": "mkdir -p bundle && esbuild src/__start.ts --bundle --platform=node --keep-names > bundle/bundle.js",
|
||||||
"docker:build": "../../common/scripts/docker_build.sh hardcoreeng/collaborator",
|
"docker:build": "../../common/scripts/docker_build.sh hardcoreeng/collaborator",
|
||||||
|
"docker:tbuild": "docker build -t hardcoreeng/collaborator . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/collaborator",
|
||||||
|
"docker:abuild": "docker build -t hardcoreeng/collaborator . --platform=linux/arm64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/collaborator",
|
||||||
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/collaborator staging",
|
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/collaborator staging",
|
||||||
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/collaborator",
|
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/collaborator",
|
||||||
"run-local": "cross-env MONGO_URL=mongodb://localhost:27017 SECRET=secret MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin ts-node src/__start.ts",
|
"run-local": "cross-env MONGO_URL=mongodb://localhost:27017 SECRET=secret MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin ts-node src/__start.ts",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/front staging",
|
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/front staging",
|
||||||
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/front",
|
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/front",
|
||||||
"docker:tbuild": "docker build -t hardcoreeng/front . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/front",
|
"docker:tbuild": "docker build -t hardcoreeng/front . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/front",
|
||||||
|
"docker:abuild": "docker build -t hardcoreeng/front . --platform=linux/arm64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/front",
|
||||||
"format": "format src",
|
"format": "format src",
|
||||||
"run-local": "cross-env MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost SERVER_SECRET='secret' ACCOUNTS_URL=http://localhost:3000 UPLOAD_URL=/files ELASTIC_URL=http://localhost:9200 MODEL_VERSION=$(node ../../common/scripts/show_version.js) VERSION=$(node ../../common/scripts/show_tag.js) PUBLIC_DIR='.' ts-node ./src/__start.ts",
|
"run-local": "cross-env MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost SERVER_SECRET='secret' ACCOUNTS_URL=http://localhost:3000 UPLOAD_URL=/files ELASTIC_URL=http://localhost:9200 MODEL_VERSION=$(node ../../common/scripts/show_version.js) VERSION=$(node ../../common/scripts/show_tag.js) PUBLIC_DIR='.' ts-node ./src/__start.ts",
|
||||||
"test": "jest --passWithNoTests --silent --forceExit",
|
"test": "jest --passWithNoTests --silent --forceExit",
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
"template": "@hcengineering/node-package",
|
"template": "@hcengineering/node-package",
|
||||||
"license": "EPL-2.0",
|
"license": "EPL-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "rush bundle --to @hcengineering/server && cross-env NODE_ENV=production ELASTIC_INDEX_NAME=local_storage_index MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 ELASTIC_URL=http://localhost:9200 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret node --inspect --enable-source-maps bundle/bundle.js",
|
"start": "rush bundle --to @hcengineering/pod-server && cross-env NODE_ENV=production ELASTIC_INDEX_NAME=local_storage_index MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 ELASTIC_URL=http://localhost:9200 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret node --inspect --enable-source-maps bundle/bundle.js",
|
||||||
"start-u": "rush bundle --to @hcengineering/server && cp ./node_modules/@hcengineering/uws/lib/*.node ./bundle/ && cross-env NODE_ENV=production SERVER_PROVIDER=uweb ELASTIC_INDEX_NAME=local_storage_index MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 ELASTIC_URL=http://localhost:9200 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret node --inspect bundle/bundle.js",
|
"start-u": "rush bundle --to @hcengineering/pod-server && cp ./node_modules/@hcengineering/uws/lib/*.node ./bundle/ && cross-env NODE_ENV=production SERVER_PROVIDER=uweb ELASTIC_INDEX_NAME=local_storage_index MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 ELASTIC_URL=http://localhost:9200 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret node --inspect bundle/bundle.js",
|
||||||
"start-flame": "rush bundle --to @hcengineering/server && cross-env NODE_ENV=production ELASTIC_INDEX_NAME=local_storage_index MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 ELASTIC_URL=http://localhost:9200 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret clinic flame --dest ./out -- node --nolazy -r ts-node/register --enable-source-maps src/__start.ts",
|
"start-flame": "rush bundle --to @hcengineering/pod-server && cross-env NODE_ENV=production ELASTIC_INDEX_NAME=local_storage_index MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 ELASTIC_URL=http://localhost:9200 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret clinic flame --dest ./out -- node --nolazy -r ts-node/register --enable-source-maps src/__start.ts",
|
||||||
"build": "compile",
|
"build": "compile",
|
||||||
"_phase:bundle": "rushx bundle",
|
"_phase:bundle": "rushx bundle",
|
||||||
"_phase:docker-build": "rushx docker:build",
|
"_phase:docker-build": "rushx docker:build",
|
||||||
@ -18,6 +18,7 @@
|
|||||||
"bundle": "mkdir -p bundle && esbuild src/__start.ts --minify --bundle --keep-names --platform=node --external:*.node --external:bufferutil --external:utf-8-validate --define:process.env.MODEL_VERSION=$(node ../../common/scripts/show_version.js) --define:process.env.GIT_REVISION=$(../../common/scripts/git_version.sh) --outfile=bundle/bundle.js --log-level=error --sourcemap=external",
|
"bundle": "mkdir -p bundle && esbuild src/__start.ts --minify --bundle --keep-names --platform=node --external:*.node --external:bufferutil --external:utf-8-validate --define:process.env.MODEL_VERSION=$(node ../../common/scripts/show_version.js) --define:process.env.GIT_REVISION=$(../../common/scripts/git_version.sh) --outfile=bundle/bundle.js --log-level=error --sourcemap=external",
|
||||||
"docker:build": "../../common/scripts/docker_build.sh hardcoreeng/transactor",
|
"docker:build": "../../common/scripts/docker_build.sh hardcoreeng/transactor",
|
||||||
"docker:tbuild": "docker build -t hardcoreeng/transactor . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/transactor",
|
"docker:tbuild": "docker build -t hardcoreeng/transactor . --platform=linux/amd64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/transactor",
|
||||||
|
"docker:abuild": "docker build -t hardcoreeng/transactor . --platform=linux/arm64 && ../../common/scripts/docker_tag_push.sh hardcoreeng/transactor",
|
||||||
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/transactor staging",
|
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/transactor staging",
|
||||||
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/transactor",
|
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/transactor",
|
||||||
"build:watch": "compile",
|
"build:watch": "compile",
|
||||||
|
@ -136,6 +136,7 @@ export async function createReactionNotifications (
|
|||||||
|
|
||||||
res = res.concat(
|
res = res.concat(
|
||||||
await createCollabDocInfo(
|
await createCollabDocInfo(
|
||||||
|
control.ctx,
|
||||||
[user] as Ref<PersonAccount>[],
|
[user] as Ref<PersonAccount>[],
|
||||||
control,
|
control,
|
||||||
tx.tx,
|
tx.tx,
|
||||||
@ -389,7 +390,7 @@ async function ActivityMessagesHandler (tx: TxCUD<Doc>, control: TriggerControl)
|
|||||||
const messages = txes.map((messageTx) => TxProcessor.createDoc2Doc(messageTx.tx as TxCreateDoc<DocUpdateMessage>))
|
const messages = txes.map((messageTx) => TxProcessor.createDoc2Doc(messageTx.tx as TxCreateDoc<DocUpdateMessage>))
|
||||||
|
|
||||||
const notificationTxes = await control.ctx.with(
|
const notificationTxes = await control.ctx.with(
|
||||||
'createNotificationTxes',
|
'createCollaboratorNotifications',
|
||||||
{},
|
{},
|
||||||
async (ctx) => await createCollaboratorNotifications(ctx, tx, control, messages)
|
async (ctx) => await createCollaboratorNotifications(ctx, tx, control, messages)
|
||||||
)
|
)
|
||||||
|
@ -248,7 +248,7 @@ async function checkSpace (
|
|||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
res: Tx[]
|
res: Tx[]
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const space = (await control.findAll<Space>(core.class.Space, { _id: spaceId }))[0]
|
const space = (await control.findAll<Space>(core.class.Space, { _id: spaceId }, { limit: 1 }))[0]
|
||||||
const isMember = space.members.includes(user._id)
|
const isMember = space.members.includes(user._id)
|
||||||
if (space.private) {
|
if (space.private) {
|
||||||
return isMember
|
return isMember
|
||||||
|
@ -166,7 +166,10 @@ async function getCollaboratorsDiff (
|
|||||||
prevValue = hierarchy.as(prevDoc, notification.mixin.Collaborators).collaborators ?? []
|
prevValue = hierarchy.as(prevDoc, notification.mixin.Collaborators).collaborators ?? []
|
||||||
} else if (prevDoc !== undefined) {
|
} else if (prevDoc !== undefined) {
|
||||||
const mixin = hierarchy.classHierarchyMixin(prevDoc._class, notification.mixin.ClassCollaborators)
|
const mixin = hierarchy.classHierarchyMixin(prevDoc._class, notification.mixin.ClassCollaborators)
|
||||||
prevValue = mixin !== undefined ? await getDocCollaborators(prevDoc, mixin, control as TriggerControl) : []
|
prevValue =
|
||||||
|
mixin !== undefined
|
||||||
|
? await getDocCollaborators((control as TriggerControl).ctx, prevDoc, mixin, control as TriggerControl)
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const added = value.filter((item) => !prevValue.includes(item)) as DocAttributeUpdates['added']
|
const added = value.filter((item) => !prevValue.includes(item)) as DocAttributeUpdates['added']
|
||||||
|
@ -201,7 +201,7 @@ async function OnChatMessageCreated (tx: TxCUD<Doc>, control: TriggerControl): P
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const collaborators = await getDocCollaborators(targetDoc, mixin, control)
|
const collaborators = await getDocCollaborators(control.ctx, targetDoc, mixin, control)
|
||||||
if (!collaborators.includes(message.modifiedBy)) {
|
if (!collaborators.includes(message.modifiedBy)) {
|
||||||
collaborators.push(message.modifiedBy)
|
collaborators.push(message.modifiedBy)
|
||||||
}
|
}
|
||||||
@ -542,12 +542,11 @@ async function hideOldChannels (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateChatInfo (control: TriggerControl, status: UserStatus, date: Timestamp): Promise<void> {
|
export async function updateChatInfo (control: TriggerControl, status: UserStatus, date: Timestamp): Promise<void> {
|
||||||
const account = await control.modelDb.findOne(contact.class.PersonAccount, { _id: status.user as Ref<PersonAccount> })
|
const account = await control.modelDb.findOne(contact.class.PersonAccount, { _id: status.user as Ref<PersonAccount> })
|
||||||
if (account === undefined) return
|
if (account === undefined) return
|
||||||
|
|
||||||
const chatUpdates = await control.queryFind(chunter.class.ChatInfo, {})
|
const update = (await control.findAll(chunter.class.ChatInfo, { user: account.person })).shift()
|
||||||
const update = chatUpdates.find(({ user }) => user === account.person)
|
|
||||||
const shouldUpdate = update === undefined || date - update.timestamp > updateChatInfoDelay
|
const shouldUpdate = update === undefined || date - update.timestamp > updateChatInfoDelay
|
||||||
|
|
||||||
if (!shouldUpdate) return
|
if (!shouldUpdate) return
|
||||||
@ -608,23 +607,23 @@ async function updateChatInfo (control: TriggerControl, status: UserStatus, date
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function OnUserStatus (originTx: TxCUD<UserStatus>, control: TriggerControl): Promise<Tx[]> {
|
async function OnUserStatus (originTx: TxCUD<UserStatus>, control: TriggerControl): Promise<Tx[]> {
|
||||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
// const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
||||||
if (tx.objectClass !== core.class.UserStatus) return []
|
// if (tx.objectClass !== core.class.UserStatus) return []
|
||||||
if (tx._class === core.class.TxCreateDoc) {
|
// if (tx._class === core.class.TxCreateDoc) {
|
||||||
const createTx = tx as TxCreateDoc<UserStatus>
|
// const createTx = tx as TxCreateDoc<UserStatus>
|
||||||
const { online } = createTx.attributes
|
// const { online } = createTx.attributes
|
||||||
if (online) {
|
// if (online) {
|
||||||
const status = TxProcessor.createDoc2Doc(createTx)
|
// const status = TxProcessor.createDoc2Doc(createTx)
|
||||||
await updateChatInfo(control, status, originTx.modifiedOn)
|
// await updateChatInfo(control, status, originTx.modifiedOn)
|
||||||
}
|
// }
|
||||||
} else if (tx._class === core.class.TxUpdateDoc) {
|
// } else if (tx._class === core.class.TxUpdateDoc) {
|
||||||
const updateTx = tx as TxUpdateDoc<UserStatus>
|
// const updateTx = tx as TxUpdateDoc<UserStatus>
|
||||||
const { online } = updateTx.operations
|
// const { online } = updateTx.operations
|
||||||
if (online === true) {
|
// if (online === true) {
|
||||||
const status = (await control.findAll(core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
// const status = (await control.findAll(core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||||
await updateChatInfo(control, status, originTx.modifiedOn)
|
// await updateChatInfo(control, status, originTx.modifiedOn)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -633,19 +632,17 @@ async function OnContextUpdate (tx: TxUpdateDoc<DocNotifyContext>, control: Trig
|
|||||||
const hasUpdate = 'lastUpdateTimestamp' in tx.operations && tx.operations.lastUpdateTimestamp !== undefined
|
const hasUpdate = 'lastUpdateTimestamp' in tx.operations && tx.operations.lastUpdateTimestamp !== undefined
|
||||||
if (!hasUpdate) return []
|
if (!hasUpdate) return []
|
||||||
|
|
||||||
const chatUpdates = await control.queryFind(chunter.class.ChatInfo, {})
|
// const update = (await control.findAll(notification.class.DocNotifyContext, { _id: tx.objectId }, { limit: 1 })).shift()
|
||||||
for (const update of chatUpdates) {
|
// if (update !== undefined) {
|
||||||
if (update.hidden.includes(tx.objectId)) {
|
// const as = control.hierarchy.as(update, chunter.mixin.ChannelInfo)
|
||||||
return [
|
// if (as.hidden) {
|
||||||
control.txFactory.createTxMixin(tx.objectId, tx.objectClass, tx.objectSpace, chunter.mixin.ChannelInfo, {
|
// return [
|
||||||
hidden: false
|
// control.txFactory.createTxMixin(tx.objectId, tx.objectClass, tx.objectSpace, chunter.mixin.ChannelInfo, {
|
||||||
}),
|
// hidden: false
|
||||||
control.txFactory.createTxUpdateDoc(update._class, update.space, update._id, {
|
// })
|
||||||
hidden: update.hidden.filter((id) => id !== tx.objectId)
|
// ]
|
||||||
})
|
// }
|
||||||
]
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -284,6 +284,7 @@ async function getKeyCollaborators (
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function getDocCollaborators (
|
export async function getDocCollaborators (
|
||||||
|
ctx: MeasureContext,
|
||||||
doc: Doc,
|
doc: Doc,
|
||||||
mixin: ClassCollaborators,
|
mixin: ClassCollaborators,
|
||||||
control: TriggerControl
|
control: TriggerControl
|
||||||
@ -291,7 +292,11 @@ export async function getDocCollaborators (
|
|||||||
const collaborators = new Set<Ref<Account>>()
|
const collaborators = new Set<Ref<Account>>()
|
||||||
for (const field of mixin.fields) {
|
for (const field of mixin.fields) {
|
||||||
const value = (doc as any)[field]
|
const value = (doc as any)[field]
|
||||||
const newCollaborators = await getKeyCollaborators(doc, value, field, control)
|
const newCollaborators = await ctx.with(
|
||||||
|
'getKeyCollaborators',
|
||||||
|
{},
|
||||||
|
async () => await getKeyCollaborators(doc, value, field, control)
|
||||||
|
)
|
||||||
if (newCollaborators !== undefined) {
|
if (newCollaborators !== undefined) {
|
||||||
for (const newCollaborator of newCollaborators) {
|
for (const newCollaborator of newCollaborators) {
|
||||||
collaborators.add(newCollaborator)
|
collaborators.add(newCollaborator)
|
||||||
@ -544,9 +549,7 @@ export async function createPushNotification (
|
|||||||
const privateKey = getMetadata(serverNotification.metadata.PushPrivateKey)
|
const privateKey = getMetadata(serverNotification.metadata.PushPrivateKey)
|
||||||
const subject = getMetadata(serverNotification.metadata.PushSubject) ?? 'mailto:hey@huly.io'
|
const subject = getMetadata(serverNotification.metadata.PushSubject) ?? 'mailto:hey@huly.io'
|
||||||
if (privateKey === undefined || publicKey === undefined) return
|
if (privateKey === undefined || publicKey === undefined) return
|
||||||
const subscriptions = (await control.queryFind(notification.class.PushSubscription, {})).filter(
|
const subscriptions = await control.findAll(notification.class.PushSubscription, { user: target })
|
||||||
(p) => p.user === target
|
|
||||||
)
|
|
||||||
const data: PushData = {
|
const data: PushData = {
|
||||||
title,
|
title,
|
||||||
body
|
body
|
||||||
@ -784,6 +787,7 @@ async function updateContextsTimestamp (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createCollabDocInfo (
|
export async function createCollabDocInfo (
|
||||||
|
ctx: MeasureContext,
|
||||||
collaborators: Ref<PersonAccount>[],
|
collaborators: Ref<PersonAccount>[],
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
tx: TxCUD<Doc>,
|
tx: TxCUD<Doc>,
|
||||||
@ -799,7 +803,7 @@ export async function createCollabDocInfo (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo: object._id })
|
const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { attachedTo: object._id })
|
||||||
|
|
||||||
await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy)
|
await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy)
|
||||||
|
|
||||||
@ -822,7 +826,11 @@ export async function createCollabDocInfo (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const usersInfo = await getUsersInfo([...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
|
const usersInfo = await ctx.with(
|
||||||
|
'get-user-info',
|
||||||
|
{},
|
||||||
|
async (ctx) => await getUsersInfo(ctx, [...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
|
||||||
|
)
|
||||||
const sender = usersInfo.find(({ _id }) => _id === originTx.modifiedBy) ?? {
|
const sender = usersInfo.find(({ _id }) => _id === originTx.modifiedBy) ?? {
|
||||||
_id: originTx.modifiedBy
|
_id: originTx.modifiedBy
|
||||||
}
|
}
|
||||||
@ -888,7 +896,7 @@ async function getSpaceCollabTxes (
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const space = cache.get(doc.space) ?? (await control.findAll(core.class.Space, { _id: doc.space }))[0]
|
const space = cache.get(doc.space) ?? (await control.findAll(core.class.Space, { _id: doc.space }, { limit: 1 }))[0]
|
||||||
if (space === undefined) return []
|
if (space === undefined) return []
|
||||||
|
|
||||||
cache.set(space._id, space)
|
cache.set(space._id, space)
|
||||||
@ -901,6 +909,7 @@ async function getSpaceCollabTxes (
|
|||||||
const collabs = control.hierarchy.as<Doc, Collaborators>(space, notification.mixin.Collaborators)
|
const collabs = control.hierarchy.as<Doc, Collaborators>(space, notification.mixin.Collaborators)
|
||||||
if (collabs.collaborators !== undefined) {
|
if (collabs.collaborators !== undefined) {
|
||||||
return await createCollabDocInfo(
|
return await createCollabDocInfo(
|
||||||
|
control.ctx,
|
||||||
collabs.collaborators as Ref<PersonAccount>[],
|
collabs.collaborators as Ref<PersonAccount>[],
|
||||||
control,
|
control,
|
||||||
tx,
|
tx,
|
||||||
@ -916,6 +925,7 @@ async function getSpaceCollabTxes (
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createCollaboratorDoc (
|
async function createCollaboratorDoc (
|
||||||
|
ctx: MeasureContext,
|
||||||
tx: TxCreateDoc<Doc>,
|
tx: TxCreateDoc<Doc>,
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
activityMessage: ActivityMessage[],
|
activityMessage: ActivityMessage[],
|
||||||
@ -931,28 +941,45 @@ async function createCollaboratorDoc (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doc = TxProcessor.createDoc2Doc(tx)
|
const doc = TxProcessor.createDoc2Doc(tx)
|
||||||
const collaborators = await getDocCollaborators(doc, mixin, control)
|
const collaborators = await ctx.with(
|
||||||
|
'get-collaborators',
|
||||||
|
{},
|
||||||
|
async (ctx) => await getDocCollaborators(ctx, doc, mixin, control)
|
||||||
|
)
|
||||||
const mixinTx = getMixinTx(tx, control, collaborators)
|
const mixinTx = getMixinTx(tx, control, collaborators)
|
||||||
|
|
||||||
const notificationTxes = await createCollabDocInfo(
|
const notificationTxes = await ctx.with(
|
||||||
collaborators as Ref<PersonAccount>[],
|
'create-collabdocinfo',
|
||||||
control,
|
{},
|
||||||
tx,
|
async () =>
|
||||||
originTx,
|
await createCollabDocInfo(
|
||||||
doc,
|
ctx,
|
||||||
activityMessage,
|
collaborators as Ref<PersonAccount>[],
|
||||||
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
|
control,
|
||||||
cache
|
tx,
|
||||||
|
originTx,
|
||||||
|
doc,
|
||||||
|
activityMessage,
|
||||||
|
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
|
||||||
|
cache
|
||||||
|
)
|
||||||
)
|
)
|
||||||
res.push(mixinTx)
|
res.push(mixinTx)
|
||||||
res.push(...notificationTxes)
|
res.push(...notificationTxes)
|
||||||
|
|
||||||
res.push(...(await getSpaceCollabTxes(control, doc, tx, originTx, activityMessage, cache)))
|
res.push(
|
||||||
|
...(await ctx.with(
|
||||||
|
'get-space-collabtxes',
|
||||||
|
{},
|
||||||
|
async () => await getSpaceCollabTxes(control, doc, tx, originTx, activityMessage, cache)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCollaboratorsMixin (
|
async function updateCollaboratorsMixin (
|
||||||
|
ctx: MeasureContext,
|
||||||
tx: TxMixin<Doc, Collaborators>,
|
tx: TxMixin<Doc, Collaborators>,
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
activityMessages: ActivityMessage[],
|
activityMessages: ActivityMessage[],
|
||||||
@ -969,17 +996,17 @@ async function updateCollaboratorsMixin (
|
|||||||
if (tx.attributes.collaborators !== undefined) {
|
if (tx.attributes.collaborators !== undefined) {
|
||||||
const createTx = hierarchy.isDerived(tx.objectClass, core.class.AttachedDoc)
|
const createTx = hierarchy.isDerived(tx.objectClass, core.class.AttachedDoc)
|
||||||
? (
|
? (
|
||||||
await control.findAll(core.class.TxCollectionCUD, {
|
await control.findAllCtx(ctx, core.class.TxCollectionCUD, {
|
||||||
'tx.objectId': tx.objectId,
|
'tx.objectId': tx.objectId,
|
||||||
'tx._class': core.class.TxCreateDoc
|
'tx._class': core.class.TxCreateDoc
|
||||||
})
|
})
|
||||||
)[0]
|
)[0]
|
||||||
: (
|
: (
|
||||||
await control.findAll(core.class.TxCreateDoc, {
|
await control.findAllCtx(ctx, core.class.TxCreateDoc, {
|
||||||
objectId: tx.objectId
|
objectId: tx.objectId
|
||||||
})
|
})
|
||||||
)[0]
|
)[0]
|
||||||
const mixinTxes = await control.findAll(core.class.TxMixin, {
|
const mixinTxes = await control.findAllCtx(ctx, core.class.TxMixin, {
|
||||||
objectId: tx.objectId
|
objectId: tx.objectId
|
||||||
})
|
})
|
||||||
const prevDoc = TxProcessor.buildDoc2Doc([createTx, ...mixinTxes].filter((t) => t._id !== tx._id)) as Doc
|
const prevDoc = TxProcessor.buildDoc2Doc([createTx, ...mixinTxes].filter((t) => t._id !== tx._id)) as Doc
|
||||||
@ -992,7 +1019,7 @@ async function updateCollaboratorsMixin (
|
|||||||
prevCollabs = new Set(prevDocMixin.collaborators ?? [])
|
prevCollabs = new Set(prevDocMixin.collaborators ?? [])
|
||||||
} else {
|
} else {
|
||||||
const mixin = hierarchy.classHierarchyMixin(prevDoc._class, notification.mixin.ClassCollaborators)
|
const mixin = hierarchy.classHierarchyMixin(prevDoc._class, notification.mixin.ClassCollaborators)
|
||||||
prevCollabs = mixin !== undefined ? new Set(await getDocCollaborators(prevDoc, mixin, control)) : new Set()
|
prevCollabs = mixin !== undefined ? new Set(await getDocCollaborators(ctx, prevDoc, mixin, control)) : new Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = await control.modelDb.findOne(notification.class.BaseNotificationType, {
|
const type = await control.modelDb.findOne(notification.class.BaseNotificationType, {
|
||||||
@ -1017,12 +1044,16 @@ async function updateCollaboratorsMixin (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newCollabs.length > 0) {
|
if (newCollabs.length > 0) {
|
||||||
const docNotifyContexts = await control.findAll(notification.class.DocNotifyContext, {
|
const docNotifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, {
|
||||||
user: { $in: newCollabs },
|
user: { $in: newCollabs },
|
||||||
attachedTo: tx.objectId
|
attachedTo: tx.objectId
|
||||||
})
|
})
|
||||||
|
|
||||||
const infos = await getUsersInfo([...newCollabs, originTx.modifiedBy] as Ref<PersonAccount>[], control)
|
const infos = await ctx.with(
|
||||||
|
'get-user-info',
|
||||||
|
{},
|
||||||
|
async (ctx) => await getUsersInfo(ctx, [...newCollabs, originTx.modifiedBy] as Ref<PersonAccount>[], control)
|
||||||
|
)
|
||||||
const sender = infos.find(({ _id }) => _id === originTx.modifiedBy) ?? { _id: originTx.modifiedBy }
|
const sender = infos.find(({ _id }) => _id === originTx.modifiedBy) ?? { _id: originTx.modifiedBy }
|
||||||
|
|
||||||
for (const collab of newCollabs) {
|
for (const collab of newCollabs) {
|
||||||
@ -1050,13 +1081,14 @@ async function updateCollaboratorsMixin (
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function collectionCollabDoc (
|
async function collectionCollabDoc (
|
||||||
|
ctx: MeasureContext,
|
||||||
tx: TxCollectionCUD<Doc, AttachedDoc>,
|
tx: TxCollectionCUD<Doc, AttachedDoc>,
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
activityMessages: ActivityMessage[],
|
activityMessages: ActivityMessage[],
|
||||||
cache: Map<Ref<Doc>, Doc>
|
cache: Map<Ref<Doc>, Doc>
|
||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||||
let res = await createCollaboratorNotifications(control.ctx, actualTx, control, activityMessages, tx, cache)
|
let res = await createCollaboratorNotifications(ctx, actualTx, control, activityMessages, tx, cache)
|
||||||
|
|
||||||
if (![core.class.TxCreateDoc, core.class.TxRemoveDoc, core.class.TxUpdateDoc].includes(actualTx._class)) {
|
if (![core.class.TxCreateDoc, core.class.TxRemoveDoc, core.class.TxUpdateDoc].includes(actualTx._class)) {
|
||||||
return res
|
return res
|
||||||
@ -1068,7 +1100,12 @@ async function collectionCollabDoc (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = cache.get(tx.objectId) ?? (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
const doc = await ctx.with(
|
||||||
|
'get-doc',
|
||||||
|
{},
|
||||||
|
async (ctx) =>
|
||||||
|
cache.get(tx.objectId) ?? (await control.findAllCtx(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
||||||
|
)
|
||||||
|
|
||||||
if (doc === undefined) {
|
if (doc === undefined) {
|
||||||
return res
|
return res
|
||||||
@ -1076,18 +1113,28 @@ async function collectionCollabDoc (
|
|||||||
|
|
||||||
cache.set(doc._id, doc)
|
cache.set(doc._id, doc)
|
||||||
|
|
||||||
const collaborators = await getCollaborators(doc, control, tx, res)
|
const collaborators = await ctx.with(
|
||||||
|
'get-collaborators',
|
||||||
|
{},
|
||||||
|
async () => await getCollaborators(doc, control, tx, res)
|
||||||
|
)
|
||||||
|
|
||||||
res = res.concat(
|
res = res.concat(
|
||||||
await createCollabDocInfo(
|
await ctx.with(
|
||||||
collaborators as Ref<PersonAccount>[],
|
'create-collab-doc-info',
|
||||||
control,
|
{},
|
||||||
actualTx,
|
async (ctx) =>
|
||||||
tx,
|
await createCollabDocInfo(
|
||||||
doc,
|
ctx,
|
||||||
activityMessages,
|
collaborators as Ref<PersonAccount>[],
|
||||||
{ isOwn: false, isSpace: false, shouldUpdateTimestamp: true },
|
control,
|
||||||
cache
|
actualTx,
|
||||||
|
tx,
|
||||||
|
doc,
|
||||||
|
activityMessages,
|
||||||
|
{ isOwn: false, isSpace: false, shouldUpdateTimestamp: true },
|
||||||
|
cache
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1184,6 +1231,7 @@ async function getNewCollaborators (
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateCollaboratorDoc (
|
async function updateCollaboratorDoc (
|
||||||
|
ctx: MeasureContext,
|
||||||
tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>,
|
tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>,
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
originTx: TxCUD<Doc>,
|
originTx: TxCUD<Doc>,
|
||||||
@ -1194,7 +1242,11 @@ async function updateCollaboratorDoc (
|
|||||||
let res: Tx[] = []
|
let res: Tx[] = []
|
||||||
const mixin = hierarchy.classHierarchyMixin(tx.objectClass, notification.mixin.ClassCollaborators)
|
const mixin = hierarchy.classHierarchyMixin(tx.objectClass, notification.mixin.ClassCollaborators)
|
||||||
if (mixin === undefined) return []
|
if (mixin === undefined) return []
|
||||||
const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
const doc = await ctx.with(
|
||||||
|
'find-doc',
|
||||||
|
{ _class: tx.objectClass },
|
||||||
|
async () => (await control.findAllCtx(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
||||||
|
)
|
||||||
if (doc === undefined) return []
|
if (doc === undefined) return []
|
||||||
const params: NotifyParams = { isOwn: true, isSpace: false, shouldUpdateTimestamp: true }
|
const params: NotifyParams = { isOwn: true, isSpace: false, shouldUpdateTimestamp: true }
|
||||||
if (hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
if (hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
||||||
@ -1202,7 +1254,9 @@ async function updateCollaboratorDoc (
|
|||||||
const collabMixin = hierarchy.as(doc, notification.mixin.Collaborators)
|
const collabMixin = hierarchy.as(doc, notification.mixin.Collaborators)
|
||||||
const collabs = new Set(collabMixin.collaborators)
|
const collabs = new Set(collabMixin.collaborators)
|
||||||
const ops = isMixinTx(tx) ? tx.attributes : tx.operations
|
const ops = isMixinTx(tx) ? tx.attributes : tx.operations
|
||||||
const newCollaborators = (await getNewCollaborators(ops, mixin, doc, control)).filter((p) => !collabs.has(p))
|
const newCollaborators = await ctx.with('get-new-collaborators', {}, async () =>
|
||||||
|
(await getNewCollaborators(ops, mixin, doc, control)).filter((p) => !collabs.has(p))
|
||||||
|
)
|
||||||
|
|
||||||
if (newCollaborators.length > 0) {
|
if (newCollaborators.length > 0) {
|
||||||
res.push(
|
res.push(
|
||||||
@ -1217,22 +1271,33 @@ async function updateCollaboratorDoc (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
res = res.concat(
|
res = res.concat(
|
||||||
await createCollabDocInfo(
|
await ctx.with(
|
||||||
[...collabMixin.collaborators, ...newCollaborators] as Ref<PersonAccount>[],
|
'create-collab-docinfo',
|
||||||
control,
|
{},
|
||||||
tx,
|
async () =>
|
||||||
originTx,
|
await createCollabDocInfo(
|
||||||
doc,
|
ctx,
|
||||||
activityMessages,
|
[...collabMixin.collaborators, ...newCollaborators] as Ref<PersonAccount>[],
|
||||||
params,
|
control,
|
||||||
cache
|
tx,
|
||||||
|
originTx,
|
||||||
|
doc,
|
||||||
|
activityMessages,
|
||||||
|
params,
|
||||||
|
cache
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const collaborators = await getDocCollaborators(doc, mixin, control)
|
const collaborators = await ctx.with(
|
||||||
|
'get-doc-collaborators',
|
||||||
|
{},
|
||||||
|
async () => await getDocCollaborators(ctx, doc, mixin, control)
|
||||||
|
)
|
||||||
res.push(getMixinTx(tx, control, collaborators))
|
res.push(getMixinTx(tx, control, collaborators))
|
||||||
res = res.concat(
|
res = res.concat(
|
||||||
await createCollabDocInfo(
|
await createCollabDocInfo(
|
||||||
|
ctx,
|
||||||
collaborators as Ref<PersonAccount>[],
|
collaborators as Ref<PersonAccount>[],
|
||||||
control,
|
control,
|
||||||
tx,
|
tx,
|
||||||
@ -1245,8 +1310,16 @@ async function updateCollaboratorDoc (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
res = res.concat(await getSpaceCollabTxes(control, doc, tx, originTx, activityMessages, cache))
|
res = res.concat(
|
||||||
res = res.concat(await updateNotifyContextsSpace(control, tx))
|
await ctx.with(
|
||||||
|
'get-space-collabtxes',
|
||||||
|
{},
|
||||||
|
async () => await getSpaceCollabTxes(control, doc, tx, originTx, activityMessages, cache)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
res = res.concat(
|
||||||
|
await ctx.with('update-notify-context-space', {}, async () => await updateNotifyContextsSpace(control, tx))
|
||||||
|
)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -1305,6 +1378,7 @@ export async function OnAttributeUpdate (tx: Tx, control: TriggerControl): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function applyUserTxes (
|
async function applyUserTxes (
|
||||||
|
ctx: MeasureContext,
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
txes: Tx[],
|
txes: Tx[],
|
||||||
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
|
||||||
@ -1342,7 +1416,9 @@ async function applyUserTxes (
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const [user, txs] of map.entries()) {
|
for (const [user, txs] of map.entries()) {
|
||||||
const account = (cache.get(user) as PersonAccount) ?? (await getPersonAccountById(user, control))
|
const account =
|
||||||
|
(cache.get(user) as PersonAccount) ??
|
||||||
|
(await ctx.with('get-person-account', {}, async () => await getPersonAccountById(user, control)))
|
||||||
|
|
||||||
if (account !== undefined) {
|
if (account !== undefined) {
|
||||||
cache.set(account._id, account)
|
cache.set(account._id, account)
|
||||||
@ -1378,21 +1454,47 @@ export async function createCollaboratorNotifications (
|
|||||||
|
|
||||||
switch (tx._class) {
|
switch (tx._class) {
|
||||||
case core.class.TxCreateDoc: {
|
case core.class.TxCreateDoc: {
|
||||||
const res = await createCollaboratorDoc(tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx, cache)
|
const res = await ctx.with(
|
||||||
|
'createCollaboratorDoc',
|
||||||
|
{},
|
||||||
|
async () =>
|
||||||
|
await createCollaboratorDoc(ctx, tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx, cache)
|
||||||
|
)
|
||||||
|
|
||||||
return await applyUserTxes(control, res)
|
return await applyUserTxes(ctx, control, res)
|
||||||
}
|
}
|
||||||
case core.class.TxUpdateDoc:
|
case core.class.TxUpdateDoc:
|
||||||
case core.class.TxMixin: {
|
case core.class.TxMixin: {
|
||||||
let res = await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
let res = await ctx.with(
|
||||||
res = res.concat(
|
'updateCollaboratorDoc',
|
||||||
await updateCollaboratorsMixin(tx as TxMixin<Doc, Collaborators>, control, activityMessages, originTx ?? tx)
|
{},
|
||||||
|
async () =>
|
||||||
|
await updateCollaboratorDoc(ctx, tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
||||||
)
|
)
|
||||||
return await applyUserTxes(control, res)
|
res = res.concat(
|
||||||
|
await ctx.with(
|
||||||
|
'updateCollaboratorMixin',
|
||||||
|
{},
|
||||||
|
async () =>
|
||||||
|
await updateCollaboratorsMixin(
|
||||||
|
ctx,
|
||||||
|
tx as TxMixin<Doc, Collaborators>,
|
||||||
|
control,
|
||||||
|
activityMessages,
|
||||||
|
originTx ?? tx
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return await applyUserTxes(ctx, control, res)
|
||||||
}
|
}
|
||||||
case core.class.TxCollectionCUD: {
|
case core.class.TxCollectionCUD: {
|
||||||
const res = await collectionCollabDoc(tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages, cache)
|
const res = await ctx.with(
|
||||||
return await applyUserTxes(control, res)
|
'collectionCollabDoc',
|
||||||
|
{},
|
||||||
|
async (ctx) =>
|
||||||
|
await collectionCollabDoc(ctx, tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages, cache)
|
||||||
|
)
|
||||||
|
return await applyUserTxes(ctx, control, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1481,7 +1583,7 @@ export async function getCollaborators (
|
|||||||
if (control.hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
if (control.hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
||||||
return control.hierarchy.as(doc, notification.mixin.Collaborators).collaborators
|
return control.hierarchy.as(doc, notification.mixin.Collaborators).collaborators
|
||||||
} else {
|
} else {
|
||||||
const collaborators = await getDocCollaborators(doc, mixin, control)
|
const collaborators = await getDocCollaborators(control.ctx, doc, mixin, control)
|
||||||
|
|
||||||
res.push(getMixinTx(tx, control, collaborators))
|
res.push(getMixinTx(tx, control, collaborators))
|
||||||
return collaborators
|
return collaborators
|
||||||
|
@ -12,14 +12,9 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import notification, {
|
import { DocUpdateMessage } from '@hcengineering/activity'
|
||||||
BaseNotificationType,
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
CommonNotificationType,
|
import contact, { formatName, PersonAccount } from '@hcengineering/contact'
|
||||||
NotificationContent,
|
|
||||||
NotificationProvider,
|
|
||||||
NotificationType
|
|
||||||
} from '@hcengineering/notification'
|
|
||||||
import type { TriggerControl } from '@hcengineering/server-core'
|
|
||||||
import core, {
|
import core, {
|
||||||
Account,
|
Account,
|
||||||
Class,
|
Class,
|
||||||
@ -29,14 +24,25 @@ import core, {
|
|||||||
matchQuery,
|
matchQuery,
|
||||||
MixinUpdate,
|
MixinUpdate,
|
||||||
Ref,
|
Ref,
|
||||||
|
toIdMap,
|
||||||
Tx,
|
Tx,
|
||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
TxCUD,
|
TxCUD,
|
||||||
TxMixin,
|
TxMixin,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxUpdateDoc
|
TxUpdateDoc,
|
||||||
|
type MeasureContext
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
|
import notification, {
|
||||||
|
BaseNotificationType,
|
||||||
|
CommonNotificationType,
|
||||||
|
NotificationContent,
|
||||||
|
NotificationProvider,
|
||||||
|
NotificationType
|
||||||
|
} from '@hcengineering/notification'
|
||||||
|
import { getResource, IntlString, translate } from '@hcengineering/platform'
|
||||||
|
import type { TriggerControl } from '@hcengineering/server-core'
|
||||||
import serverNotification, {
|
import serverNotification, {
|
||||||
getPersonAccountById,
|
getPersonAccountById,
|
||||||
HTMLPresenter,
|
HTMLPresenter,
|
||||||
@ -44,10 +50,6 @@ import serverNotification, {
|
|||||||
TextPresenter,
|
TextPresenter,
|
||||||
UserInfo
|
UserInfo
|
||||||
} from '@hcengineering/server-notification'
|
} from '@hcengineering/server-notification'
|
||||||
import { getResource, IntlString, translate } from '@hcengineering/platform'
|
|
||||||
import contact, { formatName, PersonAccount } from '@hcengineering/contact'
|
|
||||||
import { DocUpdateMessage } from '@hcengineering/activity'
|
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
|
||||||
|
|
||||||
import { NotifyResult } from './types'
|
import { NotifyResult } from './types'
|
||||||
|
|
||||||
@ -134,7 +136,9 @@ export async function isAllowed (
|
|||||||
type: BaseNotificationType,
|
type: BaseNotificationType,
|
||||||
provider: NotificationProvider
|
provider: NotificationProvider
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const providersSettings = await control.queryFind(notification.class.NotificationProviderSetting, {})
|
const providersSettings = await control.queryFind(notification.class.NotificationProviderSetting, {
|
||||||
|
space: core.space.Workspace
|
||||||
|
})
|
||||||
const providerSetting = providersSettings.find(
|
const providerSetting = providersSettings.find(
|
||||||
({ attachedTo, modifiedBy }) => attachedTo === provider._id && modifiedBy === receiver
|
({ attachedTo, modifiedBy }) => attachedTo === provider._id && modifiedBy === receiver
|
||||||
)
|
)
|
||||||
@ -446,13 +450,23 @@ export async function getNotificationContent (
|
|||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsersInfo (ids: Ref<PersonAccount>[], control: TriggerControl): Promise<UserInfo[]> {
|
export async function getUsersInfo (
|
||||||
|
ctx: MeasureContext,
|
||||||
|
ids: Ref<PersonAccount>[],
|
||||||
|
control: TriggerControl
|
||||||
|
): Promise<UserInfo[]> {
|
||||||
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, { _id: { $in: ids } })
|
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, { _id: { $in: ids } })
|
||||||
const persons = await control.queryFind(contact.class.Person, {})
|
const persons = toIdMap(
|
||||||
|
await ctx.with(
|
||||||
|
'query-find',
|
||||||
|
{},
|
||||||
|
async () => await control.findAll(contact.class.Person, { _id: { $in: accounts.map((it) => it.person) } })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return accounts.map((account) => ({
|
return accounts.map((account) => ({
|
||||||
_id: account._id,
|
_id: account._id,
|
||||||
account,
|
account,
|
||||||
person: persons.find(({ _id }) => _id === account.person)
|
person: persons.get(account.person)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
|
|||||||
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, {
|
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, {
|
||||||
attachedTo: doc._id
|
attachedTo: doc._id
|
||||||
})
|
})
|
||||||
const usersInfo = await getUsersInfo([...collaborators, tx.modifiedBy] as Ref<PersonAccount>[], control)
|
const usersInfo = await getUsersInfo(control.ctx, [...collaborators, tx.modifiedBy] as Ref<PersonAccount>[], control)
|
||||||
const senderInfo = usersInfo.find(({ _id }) => _id === tx.modifiedBy) ?? {
|
const senderInfo = usersInfo.find(({ _id }) => _id === tx.modifiedBy) ?? {
|
||||||
_id: tx.modifiedBy
|
_id: tx.modifiedBy
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,10 @@ export async function OnCustomAttributeRemove (tx: Tx, control: TriggerControl):
|
|||||||
const txes = await control.findAll<TxCUD<AnyAttribute>>(core.class.TxCUD, { objectId: ptx.objectId })
|
const txes = await control.findAll<TxCUD<AnyAttribute>>(core.class.TxCUD, { objectId: ptx.objectId })
|
||||||
const attribute = TxProcessor.buildDoc2Doc<AnyAttribute>(txes)
|
const attribute = TxProcessor.buildDoc2Doc<AnyAttribute>(txes)
|
||||||
if (attribute === undefined) return []
|
if (attribute === undefined) return []
|
||||||
const preferences = await control.findAll(view.class.ViewletPreference, { config: attribute.name })
|
const preferences = await control.findAll(view.class.ViewletPreference, {
|
||||||
|
config: attribute.name,
|
||||||
|
space: core.space.Workspace
|
||||||
|
})
|
||||||
const res: Tx[] = []
|
const res: Tx[] = []
|
||||||
for (const preference of preferences) {
|
for (const preference of preferences) {
|
||||||
const tx = control.txFactory.createTxUpdateDoc(preference._class, preference.space, preference._id, {
|
const tx = control.txFactory.createTxUpdateDoc(preference._class, preference.space, preference._id, {
|
||||||
|
@ -128,11 +128,10 @@ export interface DbAdapterOptions {
|
|||||||
abstract class MongoAdapterBase implements DbAdapter {
|
abstract class MongoAdapterBase implements DbAdapter {
|
||||||
_db: DBCollectionHelper
|
_db: DBCollectionHelper
|
||||||
|
|
||||||
findRateLimit = new RateLimiter(parseInt(process.env.FIND_RLIMIT ?? '10'))
|
findRateLimit = new RateLimiter(parseInt(process.env.FIND_RLIMIT ?? '1000'))
|
||||||
rateLimit = new RateLimiter(parseInt(process.env.TX_RLIMIT ?? '1'))
|
rateLimit = new RateLimiter(parseInt(process.env.TX_RLIMIT ?? '5'))
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
readonly globalCtx: MeasureContext,
|
|
||||||
protected readonly db: Db,
|
protected readonly db: Db,
|
||||||
protected readonly hierarchy: Hierarchy,
|
protected readonly hierarchy: Hierarchy,
|
||||||
protected readonly modelDb: ModelDb,
|
protected readonly modelDb: ModelDb,
|
||||||
@ -216,14 +215,14 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
}
|
}
|
||||||
const baseClass = this.hierarchy.getBaseClass(clazz)
|
const baseClass = this.hierarchy.getBaseClass(clazz)
|
||||||
if (baseClass !== core.class.Doc) {
|
if (baseClass !== core.class.Doc) {
|
||||||
const classes = this.hierarchy.getDescendants(baseClass)
|
const classes = this.hierarchy.getDescendants(baseClass).filter((it) => !this.hierarchy.isMixin(it))
|
||||||
|
|
||||||
// Only replace if not specified
|
// Only replace if not specified
|
||||||
if (translated._class === undefined) {
|
if (translated._class === undefined) {
|
||||||
translated._class = { $in: classes }
|
translated._class = { $in: classes }
|
||||||
} else if (typeof translated._class === 'string') {
|
} else if (typeof translated._class === 'string') {
|
||||||
if (!classes.includes(translated._class)) {
|
if (!classes.includes(translated._class)) {
|
||||||
translated._class = { $in: classes.filter((it) => !this.hierarchy.isMixin(it)) }
|
translated._class = classes.length === 1 ? classes[0] : { $in: classes }
|
||||||
}
|
}
|
||||||
} else if (typeof translated._class === 'object' && translated._class !== null) {
|
} else if (typeof translated._class === 'object' && translated._class !== null) {
|
||||||
let descendants: Ref<Class<Doc>>[] = classes
|
let descendants: Ref<Class<Doc>>[] = classes
|
||||||
@ -238,7 +237,8 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
descendants = descendants.filter((c) => !excludedClassesIds.has(c))
|
descendants = descendants.filter((c) => !excludedClassesIds.has(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
translated._class = { $in: descendants.filter((it: any) => !this.hierarchy.isMixin(it as Ref<Class<Doc>>)) }
|
const desc = descendants.filter((it: any) => !this.hierarchy.isMixin(it as Ref<Class<Doc>>))
|
||||||
|
translated._class = desc.length === 1 ? desc[0] : { $in: desc }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseClass !== clazz) {
|
if (baseClass !== clazz) {
|
||||||
@ -279,8 +279,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
from: domain,
|
from: domain,
|
||||||
localField: fullKey,
|
localField: fullKey,
|
||||||
foreignField: '_id',
|
foreignField: '_id',
|
||||||
as: fullKey.split('.').join('') + '_lookup',
|
as: fullKey.split('.').join('') + '_lookup'
|
||||||
pipeline: [{ $project: { '%hash%': 0 } }]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await this.getLookupValue(_class, nested, result, fullKey + '_lookup')
|
await this.getLookupValue(_class, nested, result, fullKey + '_lookup')
|
||||||
@ -294,8 +293,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
from: domain,
|
from: domain,
|
||||||
localField: fullKey,
|
localField: fullKey,
|
||||||
foreignField: '_id',
|
foreignField: '_id',
|
||||||
as: fullKey.split('.').join('') + '_lookup',
|
as: fullKey.split('.').join('') + '_lookup'
|
||||||
pipeline: [{ $project: { '%hash%': 0 } }]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,10 +331,9 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
pipeline: [
|
pipeline: [
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
_class: { $in: desc }
|
_class: desc.length === 1 ? desc[0] : { $in: desc }
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{ $project: { '%hash%': 0 } }
|
|
||||||
],
|
],
|
||||||
as: asVal
|
as: asVal
|
||||||
}
|
}
|
||||||
@ -483,7 +480,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
const pipeline: any[] = []
|
const pipeline: any[] = []
|
||||||
const match = { $match: this.translateQuery(clazz, query) }
|
const match = { $match: this.translateQuery(clazz, query) }
|
||||||
const slowPipeline = isLookupQuery(query) || isLookupSort(options?.sort)
|
const slowPipeline = isLookupQuery(query) || isLookupSort(options?.sort)
|
||||||
const steps = await this.getLookups(clazz, options?.lookup)
|
const steps = await ctx.with('get-lookups', {}, async () => await this.getLookups(clazz, options?.lookup))
|
||||||
if (slowPipeline) {
|
if (slowPipeline) {
|
||||||
for (const step of steps) {
|
for (const step of steps) {
|
||||||
pipeline.push({ $lookup: step })
|
pipeline.push({ $lookup: step })
|
||||||
@ -507,8 +504,6 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
projection[ckey] = options.projection[key]
|
projection[ckey] = options.projection[key]
|
||||||
}
|
}
|
||||||
pipeline.push({ $project: projection })
|
pipeline.push({ $project: projection })
|
||||||
} else {
|
|
||||||
pipeline.push({ $project: { '%hash%': 0 } })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// const domain = this.hierarchy.getDomain(clazz)
|
// const domain = this.hierarchy.getDomain(clazz)
|
||||||
@ -519,15 +514,16 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
let total = options?.total === true ? 0 : -1
|
let total = options?.total === true ? 0 : -1
|
||||||
try {
|
try {
|
||||||
await ctx.with(
|
await ctx.with(
|
||||||
'toArray',
|
'aggregate',
|
||||||
{},
|
{ clazz },
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
result = await toArray(cursor)
|
result = await toArray(cursor)
|
||||||
},
|
},
|
||||||
() => ({
|
() => ({
|
||||||
size: result.length,
|
size: result.length,
|
||||||
domain,
|
domain,
|
||||||
pipeline
|
pipeline,
|
||||||
|
clazz
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -538,6 +534,11 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
await ctx.with('fill-lookup', {}, async (ctx) => {
|
await ctx.with('fill-lookup', {}, async (ctx) => {
|
||||||
await this.fillLookupValue(ctx, clazz, options?.lookup, row)
|
await this.fillLookupValue(ctx, clazz, options?.lookup, row)
|
||||||
})
|
})
|
||||||
|
if (row.$lookup !== undefined) {
|
||||||
|
for (const [, v] of Object.entries(row.$lookup)) {
|
||||||
|
this.stripHash(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
this.clearExtraLookups(row)
|
this.clearExtraLookups(row)
|
||||||
}
|
}
|
||||||
if (options?.total === true) {
|
if (options?.total === true) {
|
||||||
@ -545,10 +546,19 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
const totalCursor = this.collection(domain).aggregate(totalPipeline, {
|
const totalCursor = this.collection(domain).aggregate(totalPipeline, {
|
||||||
checkKeys: false
|
checkKeys: false
|
||||||
})
|
})
|
||||||
const arr = await toArray(totalCursor)
|
const arr = await ctx.with(
|
||||||
|
'aggregate-total',
|
||||||
|
{},
|
||||||
|
async (ctx) => await toArray(totalCursor),
|
||||||
|
() => ({
|
||||||
|
domain,
|
||||||
|
pipeline,
|
||||||
|
clazz
|
||||||
|
})
|
||||||
|
)
|
||||||
total = arr?.[0]?.total ?? 0
|
total = arr?.[0]?.total ?? 0
|
||||||
}
|
}
|
||||||
return toFindResult(this.stripHash(result), total)
|
return toFindResult(this.stripHash(result) as T[], total)
|
||||||
}
|
}
|
||||||
|
|
||||||
private translateKey<T extends Doc>(key: string, clazz: Ref<Class<T>>): string {
|
private translateKey<T extends Doc>(key: string, clazz: Ref<Class<T>>): string {
|
||||||
@ -634,7 +644,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
|
|
||||||
@withContext('groupBy')
|
@withContext('groupBy')
|
||||||
async groupBy<T>(ctx: MeasureContext, domain: Domain, field: string): Promise<Set<T>> {
|
async groupBy<T>(ctx: MeasureContext, domain: Domain, field: string): Promise<Set<T>> {
|
||||||
const result = await this.globalCtx.with(
|
const result = await ctx.with(
|
||||||
'groupBy',
|
'groupBy',
|
||||||
{ domain },
|
{ domain },
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
@ -697,7 +707,6 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@withContext('find-all')
|
|
||||||
async findAll<T extends Doc>(
|
async findAll<T extends Doc>(
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
_class: Ref<Class<T>>,
|
_class: Ref<Class<T>>,
|
||||||
@ -708,26 +717,17 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
return await this.findRateLimit.exec(async () => {
|
return await this.findRateLimit.exec(async () => {
|
||||||
const st = Date.now()
|
const st = Date.now()
|
||||||
const result = await this.collectOps(
|
const result = await this.collectOps(
|
||||||
this.globalCtx,
|
ctx,
|
||||||
this.hierarchy.findDomain(_class),
|
this.hierarchy.findDomain(_class),
|
||||||
'find',
|
'find',
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
|
const domain = options?.domain ?? this.hierarchy.getDomain(_class)
|
||||||
if (
|
if (
|
||||||
options != null &&
|
options != null &&
|
||||||
(options?.lookup != null || this.isEnumSort(_class, options) || this.isRulesSort(options))
|
(options?.lookup != null || this.isEnumSort(_class, options) || this.isRulesSort(options))
|
||||||
) {
|
) {
|
||||||
return await ctx.with(
|
return await this.findWithPipeline(ctx, _class, query, options)
|
||||||
'pipeline',
|
|
||||||
{},
|
|
||||||
async (ctx) => await this.findWithPipeline(ctx, _class, query, options),
|
|
||||||
{
|
|
||||||
_class,
|
|
||||||
query,
|
|
||||||
options
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const domain = options?.domain ?? this.hierarchy.getDomain(_class)
|
|
||||||
const coll = this.collection(domain)
|
const coll = this.collection(domain)
|
||||||
const mongoQuery = this.translateQuery(_class, query)
|
const mongoQuery = this.translateQuery(_class, query)
|
||||||
|
|
||||||
@ -735,7 +735,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
// Skip sort/projection/etc.
|
// Skip sort/projection/etc.
|
||||||
return await ctx.with(
|
return await ctx.with(
|
||||||
'find-one',
|
'find-one',
|
||||||
{},
|
{ domain },
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const findOptions: MongoFindOptions = {}
|
const findOptions: MongoFindOptions = {}
|
||||||
|
|
||||||
@ -744,8 +744,6 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
}
|
}
|
||||||
if (options?.projection !== undefined) {
|
if (options?.projection !== undefined) {
|
||||||
findOptions.projection = this.calcProjection<T>(options, _class)
|
findOptions.projection = this.calcProjection<T>(options, _class)
|
||||||
} else {
|
|
||||||
findOptions.projection = { '%hash%': 0 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = await coll.findOne(mongoQuery, findOptions)
|
const doc = await coll.findOne(mongoQuery, findOptions)
|
||||||
@ -758,7 +756,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
}
|
}
|
||||||
return toFindResult([], total)
|
return toFindResult([], total)
|
||||||
},
|
},
|
||||||
{ mongoQuery }
|
{ domain, mongoQuery }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -769,8 +767,6 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
if (projection != null) {
|
if (projection != null) {
|
||||||
cursor = cursor.project(projection)
|
cursor = cursor.project(projection)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
cursor = cursor.project({ '%hash%': 0 })
|
|
||||||
}
|
}
|
||||||
let total: number = -1
|
let total: number = -1
|
||||||
if (options != null) {
|
if (options != null) {
|
||||||
@ -792,7 +788,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
try {
|
try {
|
||||||
let res: T[] = []
|
let res: T[] = []
|
||||||
await ctx.with(
|
await ctx.with(
|
||||||
'toArray',
|
'find-all',
|
||||||
{},
|
{},
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
res = await toArray(cursor)
|
res = await toArray(cursor)
|
||||||
@ -807,7 +803,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
if (options?.total === true && options?.limit === undefined) {
|
if (options?.total === true && options?.limit === undefined) {
|
||||||
total = res.length
|
total = res.length
|
||||||
}
|
}
|
||||||
return toFindResult(this.stripHash(res), total)
|
return toFindResult(this.stripHash(res) as T[], total)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('error during executing cursor in findAll', _class, cutObjectArray(query), options, e)
|
console.error('error during executing cursor in findAll', _class, cutObjectArray(query), options, e)
|
||||||
throw e
|
throw e
|
||||||
@ -882,13 +878,19 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
return projection
|
return projection
|
||||||
}
|
}
|
||||||
|
|
||||||
stripHash<T extends Doc>(docs: T[]): T[] {
|
stripHash<T extends Doc>(docs: T | T[]): T | T[] {
|
||||||
docs.forEach((it) => {
|
if (Array.isArray(docs)) {
|
||||||
if ('%hash%' in it) {
|
docs.forEach((it) => {
|
||||||
delete it['%hash%']
|
if ('%hash%' in it) {
|
||||||
|
delete it['%hash%']
|
||||||
|
}
|
||||||
|
return it
|
||||||
|
})
|
||||||
|
} else if (typeof docs === 'object' && docs != null) {
|
||||||
|
if ('%hash%' in docs) {
|
||||||
|
delete docs['%hash%']
|
||||||
}
|
}
|
||||||
return it
|
}
|
||||||
})
|
|
||||||
return docs
|
return docs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1000,7 +1002,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
}
|
}
|
||||||
const cursor = this.db.collection<Doc>(domain).find<Doc>({ _id: { $in: docs } }, { limit: docs.length })
|
const cursor = this.db.collection<Doc>(domain).find<Doc>({ _id: { $in: docs } }, { limit: docs.length })
|
||||||
const result = await toArray(cursor)
|
const result = await toArray(cursor)
|
||||||
return this.stripHash(result)
|
return this.stripHash(result) as Doc[]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1108,7 +1110,6 @@ class MongoAdapter extends MongoAdapterBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@withContext('tx')
|
|
||||||
async tx (ctx: MeasureContext, ...txes: Tx[]): Promise<TxResult[]> {
|
async tx (ctx: MeasureContext, ...txes: Tx[]): Promise<TxResult[]> {
|
||||||
const result: TxResult[] = []
|
const result: TxResult[] = []
|
||||||
|
|
||||||
@ -1147,7 +1148,7 @@ class MongoAdapter extends MongoAdapterBase {
|
|||||||
}
|
}
|
||||||
domains.push(
|
domains.push(
|
||||||
this.collectOps(
|
this.collectOps(
|
||||||
this.globalCtx,
|
ctx,
|
||||||
domain,
|
domain,
|
||||||
'tx',
|
'tx',
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
@ -1454,13 +1455,12 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
|||||||
await this._db.init(DOMAIN_TX)
|
await this._db.init(DOMAIN_TX)
|
||||||
}
|
}
|
||||||
|
|
||||||
@withContext('tx')
|
|
||||||
override async tx (ctx: MeasureContext, ...tx: Tx[]): Promise<TxResult[]> {
|
override async tx (ctx: MeasureContext, ...tx: Tx[]): Promise<TxResult[]> {
|
||||||
if (tx.length === 0) {
|
if (tx.length === 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
await this.collectOps(
|
await this.collectOps(
|
||||||
this.globalCtx,
|
ctx,
|
||||||
DOMAIN_TX,
|
DOMAIN_TX,
|
||||||
'tx',
|
'tx',
|
||||||
async () => {
|
async () => {
|
||||||
@ -1490,9 +1490,6 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
|||||||
sort: {
|
sort: {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
modifiedOn: 1
|
modifiedOn: 1
|
||||||
},
|
|
||||||
projection: {
|
|
||||||
'%hash%': 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1650,7 +1647,7 @@ export async function createMongoAdapter (
|
|||||||
const client = getMongoClient(url)
|
const client = getMongoClient(url)
|
||||||
const db = getWorkspaceDB(await client.getClient(), workspaceId)
|
const db = getWorkspaceDB(await client.getClient(), workspaceId)
|
||||||
|
|
||||||
return new MongoAdapter(ctx.newChild('mongoDb', {}), db, hierarchy, modelDb, client, options)
|
return new MongoAdapter(db, hierarchy, modelDb, client, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1666,5 +1663,5 @@ export async function createMongoTxAdapter (
|
|||||||
const client = getMongoClient(url)
|
const client = getMongoClient(url)
|
||||||
const db = getWorkspaceDB(await client.getClient(), workspaceId)
|
const db = getWorkspaceDB(await client.getClient(), workspaceId)
|
||||||
|
|
||||||
return new MongoTxAdapter(ctx.newChild('mongoDbTx', {}), db, hierarchy, modelDb, client)
|
return new MongoTxAdapter(db, hierarchy, modelDb, client)
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,8 @@ export function getMongoClient (uri: string, options?: MongoClientOptions): Mong
|
|||||||
MongoClient.connect(uri, {
|
MongoClient.connect(uri, {
|
||||||
appName: 'transactor',
|
appName: 'transactor',
|
||||||
...options,
|
...options,
|
||||||
...extraOptions
|
...extraOptions,
|
||||||
|
enableUtf8Validation: false
|
||||||
}),
|
}),
|
||||||
() => {
|
() => {
|
||||||
connections.delete(key)
|
connections.delete(key)
|
||||||
|
@ -2,6 +2,8 @@ import { type Locator, type Page, expect } from '@playwright/test'
|
|||||||
import { NewToDo, Slot } from './types'
|
import { NewToDo, Slot } from './types'
|
||||||
import { CalendarPage } from '../calendar-page'
|
import { CalendarPage } from '../calendar-page'
|
||||||
|
|
||||||
|
const retryOptions = { intervals: [1000, 1500, 2500], timeout: 60000 }
|
||||||
|
|
||||||
export class PlanningPage extends CalendarPage {
|
export class PlanningPage extends CalendarPage {
|
||||||
readonly page: Page
|
readonly page: Page
|
||||||
|
|
||||||
@ -81,14 +83,17 @@ export class PlanningPage extends CalendarPage {
|
|||||||
|
|
||||||
async dragdropTomorrow (title: string, time: string): Promise<void> {
|
async dragdropTomorrow (title: string, time: string): Promise<void> {
|
||||||
await this.toDosContainer().getByRole('button', { name: title }).hover()
|
await this.toDosContainer().getByRole('button', { name: title }).hover()
|
||||||
await this.page.mouse.down()
|
|
||||||
const boundingBox = await this.selectTomorrow(time).boundingBox()
|
await expect(async () => {
|
||||||
expect(boundingBox).toBeTruthy()
|
await this.page.mouse.down()
|
||||||
if (boundingBox != null) {
|
const boundingBox = await this.selectTomorrow(time).boundingBox()
|
||||||
await this.page.mouse.move(boundingBox.x + 10, boundingBox.y + 10)
|
expect(boundingBox).toBeTruthy()
|
||||||
await this.page.mouse.move(boundingBox.x + 10, boundingBox.y + 20)
|
if (boundingBox != null) {
|
||||||
await this.page.mouse.up()
|
await this.page.mouse.move(boundingBox.x + 10, boundingBox.y + 10)
|
||||||
}
|
await this.page.mouse.move(boundingBox.x + 10, boundingBox.y + 20)
|
||||||
|
await this.page.mouse.up()
|
||||||
|
}
|
||||||
|
}).toPass(retryOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkInSchedule (title: string): Promise<void> {
|
async checkInSchedule (title: string): Promise<void> {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { test } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { generateId, PlatformSetting, PlatformURI } from '../utils'
|
import { generateId, PlatformSetting, PlatformURI } from '../utils'
|
||||||
import { PlanningPage } from '../model/planning/planning-page'
|
import { PlanningPage } from '../model/planning/planning-page'
|
||||||
import { NewToDo } from '../model/planning/types'
|
import { NewToDo } from '../model/planning/types'
|
||||||
@ -8,6 +8,8 @@ test.use({
|
|||||||
storageState: PlatformSetting
|
storageState: PlatformSetting
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const retryOptions = { intervals: [1000, 1500, 2500], timeout: 60000 }
|
||||||
|
|
||||||
test.describe('Planning ToDo tests', () => {
|
test.describe('Planning ToDo tests', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await (await page.goto(`${PlatformURI}/workbench/sanity-ws/time`))?.finished()
|
await (await page.goto(`${PlatformURI}/workbench/sanity-ws/time`))?.finished()
|
||||||
@ -42,9 +44,13 @@ test.describe('Planning ToDo tests', () => {
|
|||||||
const planningPage = new PlanningPage(page)
|
const planningPage = new PlanningPage(page)
|
||||||
const planningNavigationMenuPage = new PlanningNavigationMenuPage(page)
|
const planningNavigationMenuPage = new PlanningNavigationMenuPage(page)
|
||||||
await planningNavigationMenuPage.clickOnButtonUnplanned()
|
await planningNavigationMenuPage.clickOnButtonUnplanned()
|
||||||
await planningNavigationMenuPage.compareCountersUnplannedToDos()
|
await expect(async () => {
|
||||||
|
await planningNavigationMenuPage.compareCountersUnplannedToDos()
|
||||||
|
}).toPass(retryOptions)
|
||||||
await planningPage.createNewToDo(newToDo)
|
await planningPage.createNewToDo(newToDo)
|
||||||
await planningNavigationMenuPage.compareCountersUnplannedToDos()
|
await expect(async () => {
|
||||||
|
await planningNavigationMenuPage.compareCountersUnplannedToDos()
|
||||||
|
}).toPass(retryOptions)
|
||||||
await planningNavigationMenuPage.clickOnButtonToDoAll()
|
await planningNavigationMenuPage.clickOnButtonToDoAll()
|
||||||
|
|
||||||
await planningPage.checkToDoExist(newToDo.title)
|
await planningPage.checkToDoExist(newToDo.title)
|
||||||
|
Loading…
Reference in New Issue
Block a user