mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-28 19:25:36 +00:00
TSK-1062: Work on Employee and EmployeeAccount migration (#2986)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
3ef19ed8d0
commit
62b18e1ce8
@ -173,6 +173,7 @@ export class TEmployee extends TPerson implements Employee {
|
|||||||
export class TEmployeeAccount extends TAccount implements EmployeeAccount {
|
export class TEmployeeAccount extends TAccount implements EmployeeAccount {
|
||||||
employee!: Ref<Employee>
|
employee!: Ref<Employee>
|
||||||
name!: string
|
name!: string
|
||||||
|
mergedTo!: Ref<EmployeeAccount>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(contact.class.Organizations, core.class.Space)
|
@Model(contact.class.Organizations, core.class.Space)
|
||||||
|
@ -47,8 +47,11 @@ export function createModel (builder: Builder): void {
|
|||||||
trigger: serverContact.trigger.OnChannelUpdate
|
trigger: serverContact.trigger.OnChannelUpdate
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.AsyncTrigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverContact.trigger.OnEmployeeUpdate,
|
trigger: serverContact.trigger.OnEmployeeUpdate,
|
||||||
classes: [contact.class.Employee]
|
txMatch: {
|
||||||
|
objectClass: contact.class.Employee,
|
||||||
|
_class: core.class.TxUpdateDoc
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -14,30 +14,21 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Model, Builder } from '@hcengineering/model'
|
import { Builder, Model } from '@hcengineering/model'
|
||||||
import type { Resource } from '@hcengineering/platform'
|
|
||||||
import { TClass, TDoc } from '@hcengineering/model-core'
|
import { TClass, TDoc } from '@hcengineering/model-core'
|
||||||
|
import type { Resource } from '@hcengineering/platform'
|
||||||
|
|
||||||
import type {
|
|
||||||
AsyncTrigger,
|
|
||||||
ObjectDDParticipant,
|
|
||||||
Trigger,
|
|
||||||
TriggerFunc,
|
|
||||||
AsyncTriggerState,
|
|
||||||
AsyncTriggerFunc
|
|
||||||
} from '@hcengineering/server-core'
|
|
||||||
import core, {
|
import core, {
|
||||||
Class,
|
Class,
|
||||||
|
DOMAIN_MODEL,
|
||||||
Doc,
|
Doc,
|
||||||
DocumentQuery,
|
DocumentQuery,
|
||||||
DOMAIN_DOC_INDEX_STATE,
|
|
||||||
DOMAIN_MODEL,
|
|
||||||
FindOptions,
|
FindOptions,
|
||||||
FindResult,
|
FindResult,
|
||||||
Hierarchy,
|
Hierarchy,
|
||||||
Ref,
|
Ref
|
||||||
TxCUD
|
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
|
import type { ObjectDDParticipant, Trigger, TriggerFunc } from '@hcengineering/server-core'
|
||||||
import serverCore from '@hcengineering/server-core'
|
import serverCore from '@hcengineering/server-core'
|
||||||
|
|
||||||
@Model(serverCore.class.Trigger, core.class.Doc, DOMAIN_MODEL)
|
@Model(serverCore.class.Trigger, core.class.Doc, DOMAIN_MODEL)
|
||||||
@ -45,18 +36,6 @@ export class TTrigger extends TDoc implements Trigger {
|
|||||||
trigger!: Resource<TriggerFunc>
|
trigger!: Resource<TriggerFunc>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(serverCore.class.AsyncTrigger, core.class.Doc, DOMAIN_MODEL)
|
|
||||||
export class TAsyncTrigger extends TDoc implements AsyncTrigger {
|
|
||||||
trigger!: Resource<AsyncTriggerFunc>
|
|
||||||
classes!: Ref<Class<Doc>>[]
|
|
||||||
}
|
|
||||||
|
|
||||||
@Model(serverCore.class.AsyncTriggerState, core.class.Doc, DOMAIN_DOC_INDEX_STATE)
|
|
||||||
export class TAsyncTriggerState extends TDoc implements AsyncTriggerState {
|
|
||||||
tx!: TxCUD<Doc>
|
|
||||||
message!: string
|
|
||||||
}
|
|
||||||
|
|
||||||
@Model(serverCore.mixin.ObjectDDParticipant, core.class.Class)
|
@Model(serverCore.mixin.ObjectDDParticipant, core.class.Class)
|
||||||
export class TObjectDDParticipant extends TClass implements ObjectDDParticipant {
|
export class TObjectDDParticipant extends TClass implements ObjectDDParticipant {
|
||||||
collectDocs!: Resource<
|
collectDocs!: Resource<
|
||||||
@ -73,5 +52,5 @@ export class TObjectDDParticipant extends TClass implements ObjectDDParticipant
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TTrigger, TObjectDDParticipant, TAsyncTriggerState, TAsyncTrigger)
|
builder.createModel(TTrigger, TObjectDDParticipant)
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,11 @@ export class TOpenAIConfiguration extends TConfiguration implements OpenAIConfig
|
|||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TOpenAIConfiguration)
|
builder.createModel(TOpenAIConfiguration)
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.AsyncTrigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: openai.trigger.AsyncOnGPTRequest,
|
trigger: openai.trigger.AsyncOnGPTRequest,
|
||||||
classes: [chunter.class.Comment, recruit.class.ApplicantMatch]
|
txMatch: {
|
||||||
|
objectClass: { $in: [chunter.class.Comment, recruit.class.ApplicantMatch] },
|
||||||
|
_class: core.class.TxCreateDoc
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,6 @@ export interface ServerStorage extends LowLevelStorage {
|
|||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
) => Promise<FindResult<T>>
|
) => Promise<FindResult<T>>
|
||||||
tx: (ctx: MeasureContext, tx: Tx) => Promise<[TxResult, Tx[]]>
|
tx: (ctx: MeasureContext, tx: Tx) => Promise<[TxResult, Tx[]]>
|
||||||
apply: (ctx: MeasureContext, tx: Tx[], broadcast: boolean, updateTx: boolean) => Promise<Tx[]>
|
apply: (ctx: MeasureContext, tx: Tx[], broadcast: boolean) => Promise<Tx[]>
|
||||||
close: () => Promise<void>
|
close: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
@ -17,26 +17,28 @@
|
|||||||
import { AttachmentDocList } from '@hcengineering/attachment-resources'
|
import { AttachmentDocList } from '@hcengineering/attachment-resources'
|
||||||
import type { Comment } from '@hcengineering/chunter'
|
import type { Comment } from '@hcengineering/chunter'
|
||||||
import chunter from '@hcengineering/chunter'
|
import chunter from '@hcengineering/chunter'
|
||||||
import contact, { Employee, EmployeeAccount, getName } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount, getName } from '@hcengineering/contact'
|
||||||
import { Avatar, employeeByIdStore } from '@hcengineering/contact-resources'
|
import { Avatar, employeeAccountByIdStore, employeeByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { Ref } from '@hcengineering/core'
|
import { IdMap, Ref } from '@hcengineering/core'
|
||||||
import { getClient, MessageViewer } from '@hcengineering/presentation'
|
import { MessageViewer } from '@hcengineering/presentation'
|
||||||
import { Icon, ShowMore, TimeSince } from '@hcengineering/ui'
|
import { Icon, ShowMore, TimeSince } from '@hcengineering/ui'
|
||||||
|
|
||||||
export let value: Comment
|
export let value: Comment
|
||||||
export let inline: boolean = false
|
export let inline: boolean = false
|
||||||
export let disableClick = false
|
export let disableClick = false
|
||||||
|
|
||||||
const client = getClient()
|
|
||||||
|
|
||||||
const cutId = (str: string): string => {
|
const cutId = (str: string): string => {
|
||||||
return str.slice(0, 4) + '...' + str.slice(-4)
|
return str.slice(0, 4) + '...' + str.slice(-4)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getEmployee (value: Comment): Promise<Employee | undefined> {
|
async function getEmployee (
|
||||||
const acc = await client.findOne(contact.class.EmployeeAccount, { _id: value.modifiedBy as Ref<EmployeeAccount> })
|
value: Comment,
|
||||||
|
employees: IdMap<Employee>,
|
||||||
|
accounts: IdMap<EmployeeAccount>
|
||||||
|
): Promise<Employee | undefined> {
|
||||||
|
const acc = accounts.get(value.modifiedBy as Ref<EmployeeAccount>)
|
||||||
if (acc !== undefined) {
|
if (acc !== undefined) {
|
||||||
const emp = $employeeByIdStore.get(acc.employee)
|
const emp = employees.get(acc.employee)
|
||||||
return emp
|
return emp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +54,7 @@
|
|||||||
<span class="dark-color">#{cutId(value._id.toString())}</span>
|
<span class="dark-color">#{cutId(value._id.toString())}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex-row-top">
|
<div class="flex-row-top">
|
||||||
{#await getEmployee(value) then employee}
|
{#await getEmployee(value, $employeeByIdStore, $employeeAccountByIdStore) then employee}
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<Avatar size={'medium'} avatar={employee?.avatar} />
|
<Avatar size={'medium'} avatar={employee?.avatar} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import chunter, { ChunterMessage } from '@hcengineering/chunter'
|
import chunter, { ChunterMessage } from '@hcengineering/chunter'
|
||||||
import contact, { Employee, EmployeeAccount, getName } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount, getName } from '@hcengineering/contact'
|
||||||
import { employeeByIdStore } from '@hcengineering/contact-resources'
|
import { Avatar, employeeAccountByIdStore, employeeByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { IdMap, Ref, Space, toIdMap } from '@hcengineering/core'
|
import { IdMap, Ref, Space } from '@hcengineering/core'
|
||||||
import { createQuery, MessageViewer } from '@hcengineering/presentation'
|
import { MessageViewer, createQuery } from '@hcengineering/presentation'
|
||||||
import { Avatar } from '@hcengineering/contact-resources'
|
|
||||||
import { IconClose } from '@hcengineering/ui'
|
import { IconClose } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { UnpinMessage } from '../index'
|
import { UnpinMessage } from '../index'
|
||||||
@ -31,19 +30,14 @@
|
|||||||
pinnedMessages = res
|
pinnedMessages = res
|
||||||
})
|
})
|
||||||
|
|
||||||
const employeeAccoutsQuery = createQuery()
|
|
||||||
let employeeAcounts: IdMap<EmployeeAccount> = new Map()
|
|
||||||
|
|
||||||
employeeAccoutsQuery.query(contact.class.EmployeeAccount, {}, (res) => (employeeAcounts = toIdMap(res)))
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
function getEmployee (
|
function getEmployee (
|
||||||
message: ChunterMessage,
|
message: ChunterMessage,
|
||||||
employeeAcounts: IdMap<EmployeeAccount>,
|
employeeAccounts: IdMap<EmployeeAccount>,
|
||||||
employees: IdMap<Employee>
|
employees: IdMap<Employee>
|
||||||
): Employee | undefined {
|
): Employee | undefined {
|
||||||
const acc = employeeAcounts.get(message.createBy as Ref<EmployeeAccount>)
|
const acc = employeeAccounts.get(message.createBy as Ref<EmployeeAccount>)
|
||||||
if (acc) {
|
if (acc) {
|
||||||
return employees.get(acc.employee)
|
return employees.get(acc.employee)
|
||||||
}
|
}
|
||||||
@ -52,7 +46,7 @@
|
|||||||
|
|
||||||
<div class="antiPopup vScroll popup">
|
<div class="antiPopup vScroll popup">
|
||||||
{#each pinnedMessages as message}
|
{#each pinnedMessages as message}
|
||||||
{@const employee = getEmployee(message, employeeAcounts, $employeeByIdStore)}
|
{@const employee = getEmployee(message, $employeeAccountByIdStore, $employeeByIdStore)}
|
||||||
<div class="message">
|
<div class="message">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
|
@ -1,16 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact, { Employee, EmployeeAccount, getName } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount, getName } from '@hcengineering/contact'
|
||||||
import { employeeByIdStore } from '@hcengineering/contact-resources'
|
import { employeeAccountByIdStore, employeeByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { Account, IdMap, Ref, toIdMap } from '@hcengineering/core'
|
import { Account, IdMap, Ref } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
|
||||||
|
|
||||||
export let reactionAccounts: Ref<Account>[]
|
export let reactionAccounts: Ref<Account>[]
|
||||||
let accounts: IdMap<EmployeeAccount> = new Map()
|
|
||||||
|
|
||||||
const query = createQuery()
|
|
||||||
$: query.query(contact.class.EmployeeAccount, {}, (res) => {
|
|
||||||
accounts = toIdMap(res)
|
|
||||||
})
|
|
||||||
|
|
||||||
function getAccName (acc: Ref<Account>, accounts: IdMap<EmployeeAccount>, employees: IdMap<Employee>): string {
|
function getAccName (acc: Ref<Account>, accounts: IdMap<EmployeeAccount>, employees: IdMap<Employee>): string {
|
||||||
const account = accounts.get(acc as Ref<EmployeeAccount>)
|
const account = accounts.get(acc as Ref<EmployeeAccount>)
|
||||||
@ -24,6 +17,6 @@
|
|||||||
|
|
||||||
{#each reactionAccounts as acc}
|
{#each reactionAccounts as acc}
|
||||||
<div>
|
<div>
|
||||||
{getAccName(acc, accounts, $employeeByIdStore)}
|
{getAccName(acc, $employeeAccountByIdStore, $employeeByIdStore)}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -2,30 +2,26 @@
|
|||||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||||
import { AttachmentPreview } from '@hcengineering/attachment-resources'
|
import { AttachmentPreview } from '@hcengineering/attachment-resources'
|
||||||
import { ChunterMessage } from '@hcengineering/chunter'
|
import { ChunterMessage } from '@hcengineering/chunter'
|
||||||
import contact, { EmployeeAccount, getName as getContactName } from '@hcengineering/contact'
|
import { EmployeeAccount, getName as getContactName } from '@hcengineering/contact'
|
||||||
import { employeeByIdStore } from '@hcengineering/contact-resources'
|
import { employeeAccountByIdStore, employeeByIdStore } from '@hcengineering/contact-resources'
|
||||||
import core, { IdMap, Ref, toIdMap, WithLookup } from '@hcengineering/core'
|
import core, { IdMap, Ref, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Label, Scroller } from '@hcengineering/ui'
|
import { Label, Scroller } from '@hcengineering/ui'
|
||||||
import chunter from '../plugin'
|
import chunter from '../plugin'
|
||||||
import { getTime, openMessageFromSpecial } from '../utils'
|
import { getTime, openMessageFromSpecial } from '../utils'
|
||||||
import Bookmark from './icons/Bookmark.svelte'
|
|
||||||
import Message from './Message.svelte'
|
import Message from './Message.svelte'
|
||||||
|
import Bookmark from './icons/Bookmark.svelte'
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
let savedMessagesIds: Ref<ChunterMessage>[] = []
|
let savedMessagesIds: Ref<ChunterMessage>[] = []
|
||||||
let savedMessages: WithLookup<ChunterMessage>[] = []
|
let savedMessages: WithLookup<ChunterMessage>[] = []
|
||||||
let savedAttachmentsIds: Ref<Attachment>[] = []
|
let savedAttachmentsIds: Ref<Attachment>[] = []
|
||||||
let savedAttachments: WithLookup<Attachment>[] = []
|
let savedAttachments: WithLookup<Attachment>[] = []
|
||||||
let accounts: IdMap<EmployeeAccount> = new Map()
|
|
||||||
|
|
||||||
const messagesQuery = createQuery()
|
const messagesQuery = createQuery()
|
||||||
const attachmentsQuery = createQuery()
|
const attachmentsQuery = createQuery()
|
||||||
const savedMessagesQuery = createQuery()
|
const savedMessagesQuery = createQuery()
|
||||||
const savedAttachmentsQuery = createQuery()
|
const savedAttachmentsQuery = createQuery()
|
||||||
const accQ = createQuery()
|
|
||||||
|
|
||||||
accQ.query(contact.class.EmployeeAccount, {}, (res) => (accounts = toIdMap(res)))
|
|
||||||
|
|
||||||
savedMessagesQuery.query(chunter.class.SavedMessages, {}, (res) => {
|
savedMessagesQuery.query(chunter.class.SavedMessages, {}, (res) => {
|
||||||
savedMessagesIds = res.map((r) => r.attachedTo)
|
savedMessagesIds = res.map((r) => r.attachedTo)
|
||||||
@ -78,8 +74,8 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getName (a: Attachment): string | undefined {
|
function getName (a: Attachment, employeeAccountByIdStore: IdMap<EmployeeAccount>): string | undefined {
|
||||||
const acc = accounts.get(a.modifiedBy as Ref<EmployeeAccount>)
|
const acc = employeeAccountByIdStore.get(a.modifiedBy as Ref<EmployeeAccount>)
|
||||||
if (acc !== undefined) {
|
if (acc !== undefined) {
|
||||||
const emp = $employeeByIdStore.get(acc?.employee)
|
const emp = $employeeByIdStore.get(acc?.employee)
|
||||||
if (emp !== undefined) {
|
if (emp !== undefined) {
|
||||||
@ -112,7 +108,10 @@
|
|||||||
<div class="attachmentContainer" on:click={() => openAttachment(att)}>
|
<div class="attachmentContainer" on:click={() => openAttachment(att)}>
|
||||||
<AttachmentPreview value={att} isSaved={true} />
|
<AttachmentPreview value={att} isSaved={true} />
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<Label label={chunter.string.SharedBy} params={{ name: getName(att), time: getTime(att.modifiedOn) }} />
|
<Label
|
||||||
|
label={chunter.string.SharedBy}
|
||||||
|
params={{ name: getName(att, $employeeAccountByIdStore), time: getTime(att.modifiedOn) }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||||
|
import { employeeAccountByIdStore } from '../utils'
|
||||||
import UserBoxList from './UserBoxList.svelte'
|
import UserBoxList from './UserBoxList.svelte'
|
||||||
|
|
||||||
export let label: IntlString
|
export let label: IntlString
|
||||||
@ -40,14 +41,6 @@
|
|||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountQuery = createQuery()
|
|
||||||
|
|
||||||
let accounts: Account[] = []
|
|
||||||
|
|
||||||
$: accountQuery.query(core.class.Account, { _id: { $in: value } }, (res) => {
|
|
||||||
accounts = res
|
|
||||||
})
|
|
||||||
|
|
||||||
const excludedQuery = createQuery()
|
const excludedQuery = createQuery()
|
||||||
|
|
||||||
let excluded: Account[] = []
|
let excluded: Account[] = []
|
||||||
@ -61,7 +54,9 @@
|
|||||||
excluded = []
|
excluded = []
|
||||||
}
|
}
|
||||||
|
|
||||||
$: employess = accounts.map((it) => (it as EmployeeAccount).employee)
|
$: employees = Array.from(
|
||||||
|
(value ?? []).map((it) => $employeeAccountByIdStore.get(it as Ref<EmployeeAccount>)?.employee)
|
||||||
|
).filter((it) => it !== undefined) as Ref<Employee>[]
|
||||||
|
|
||||||
$: docQuery =
|
$: docQuery =
|
||||||
excluded.length > 0
|
excluded.length > 0
|
||||||
@ -75,7 +70,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<UserBoxList
|
<UserBoxList
|
||||||
items={employess}
|
items={employees}
|
||||||
{label}
|
{label}
|
||||||
{readonly}
|
{readonly}
|
||||||
{docQuery}
|
{docQuery}
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
import { Employee } from '@hcengineering/contact'
|
||||||
import { Account, DocumentQuery, Ref } from '@hcengineering/core'
|
import { Account, DocumentQuery, Ref } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
|
||||||
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import contact from '../plugin'
|
import contact from '../plugin'
|
||||||
|
import { employeeAccountByIdStore } from '../utils'
|
||||||
import UserBox from './UserBox.svelte'
|
import UserBox from './UserBox.svelte'
|
||||||
|
|
||||||
export let label: IntlString = contact.string.Employee
|
export let label: IntlString = contact.string.Employee
|
||||||
@ -29,16 +29,10 @@
|
|||||||
export let size: ButtonSize = 'small'
|
export let size: ButtonSize = 'small'
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
|
|
||||||
const query = createQuery()
|
$: accounts = Array.from($employeeAccountByIdStore.values())
|
||||||
|
|
||||||
let accounts: EmployeeAccount[] = []
|
|
||||||
|
|
||||||
query.query<EmployeeAccount>(contact.class.EmployeeAccount, docQuery as DocumentQuery<EmployeeAccount>, (res) => {
|
|
||||||
accounts = res
|
|
||||||
map = new Map(res.map((p) => [p.employee, p._id]))
|
|
||||||
})
|
|
||||||
|
|
||||||
let map: Map<Ref<Employee>, Ref<Account>> = new Map()
|
let map: Map<Ref<Employee>, Ref<Account>> = new Map()
|
||||||
|
$: map = new Map(accounts.map((p) => [p.employee, p._id]))
|
||||||
|
|
||||||
$: employees = accounts.map((p) => p.employee)
|
$: employees = accounts.map((p) => p.employee)
|
||||||
$: selectedEmp = value && accounts.find((p) => p._id === value)?.employee
|
$: selectedEmp = value && accounts.find((p) => p._id === value)?.employee
|
||||||
|
@ -16,18 +16,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { EmployeeAccount } from '@hcengineering/contact'
|
import { EmployeeAccount } from '@hcengineering/contact'
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { employeeAccountByIdStore } from '../utils'
|
||||||
import contact from '../plugin'
|
|
||||||
import EmployeeAccountPresenter from './EmployeeAccountPresenter.svelte'
|
import EmployeeAccountPresenter from './EmployeeAccountPresenter.svelte'
|
||||||
|
|
||||||
export let value: Ref<EmployeeAccount>
|
export let value: Ref<EmployeeAccount>
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
|
||||||
let account: EmployeeAccount | undefined
|
$: account = $employeeAccountByIdStore.get(value)
|
||||||
|
|
||||||
const query = createQuery()
|
|
||||||
|
|
||||||
$: query.query(contact.class.EmployeeAccount, { _id: value }, (r) => ([account] = r))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if account}
|
{#if account}
|
||||||
|
@ -39,7 +39,12 @@
|
|||||||
const query: DocumentQuery<EmployeeAccount> =
|
const query: DocumentQuery<EmployeeAccount> =
|
||||||
isSearch > 0 ? { name: { $like: '%' + search + '%' } } : { _id: { $in: accounts as Ref<EmployeeAccount>[] } }
|
isSearch > 0 ? { name: { $like: '%' + search + '%' } } : { _id: { $in: accounts as Ref<EmployeeAccount>[] } }
|
||||||
const employess = await client.findAll(contact.class.EmployeeAccount, query)
|
const employess = await client.findAll(contact.class.EmployeeAccount, query)
|
||||||
members = new Set(employess.filter((p) => accounts.includes(p._id)).map((p) => p.employee))
|
members = new Set(
|
||||||
|
employess
|
||||||
|
.filter((it) => it.mergedTo == null)
|
||||||
|
.filter((p) => accounts.includes(p._id))
|
||||||
|
.map((p) => p.employee)
|
||||||
|
)
|
||||||
return await client.findAll(
|
return await client.findAll(
|
||||||
contact.class.Employee,
|
contact.class.Employee,
|
||||||
{
|
{
|
||||||
|
@ -223,6 +223,9 @@ async function generateLocation (loc: Location, id: Ref<Contact>): Promise<Resol
|
|||||||
|
|
||||||
export const employeeByIdStore = writable<IdMap<Employee>>(new Map())
|
export const employeeByIdStore = writable<IdMap<Employee>>(new Map())
|
||||||
export const employeesStore = writable<Employee[]>([])
|
export const employeesStore = writable<Employee[]>([])
|
||||||
|
|
||||||
|
export const employeeAccountByIdStore = writable<IdMap<EmployeeAccount>>(new Map())
|
||||||
|
|
||||||
export const channelProviders = writable<ChannelProvider[]>([])
|
export const channelProviders = writable<ChannelProvider[]>([])
|
||||||
|
|
||||||
function fillStores (): void {
|
function fillStores (): void {
|
||||||
@ -234,6 +237,22 @@ function fillStores (): void {
|
|||||||
employeeByIdStore.set(toIdMap(res))
|
employeeByIdStore.set(toIdMap(res))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const accountQ = createQuery(true)
|
||||||
|
accountQ.query(contact.class.EmployeeAccount, {}, (res) => {
|
||||||
|
const mergedEmployees = res.filter((it) => it.mergedTo !== undefined)
|
||||||
|
const activeEmployees = res.filter((it) => it.mergedTo === undefined)
|
||||||
|
const ids = toIdMap(activeEmployees)
|
||||||
|
for (const e of mergedEmployees) {
|
||||||
|
if (e.mergedTo !== undefined) {
|
||||||
|
const mergeTo = ids.get(e.mergedTo)
|
||||||
|
if (mergeTo !== undefined) {
|
||||||
|
ids.set(e._id, mergeTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
employeeAccountByIdStore.set(ids)
|
||||||
|
})
|
||||||
|
|
||||||
const providerQuery = createQuery(true)
|
const providerQuery = createQuery(true)
|
||||||
providerQuery.query(contact.class.ChannelProvider, {}, (res) => channelProviders.set(res))
|
providerQuery.query(contact.class.ChannelProvider, {}, (res) => channelProviders.set(res))
|
||||||
} else {
|
} else {
|
||||||
|
@ -151,6 +151,7 @@ export interface Employee extends Person {
|
|||||||
export interface EmployeeAccount extends Account {
|
export interface EmployeeAccount extends Account {
|
||||||
employee: Ref<Employee>
|
employee: Ref<Employee>
|
||||||
name: string
|
name: string
|
||||||
|
mergedTo?: Ref<EmployeeAccount>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact, { Channel, Contact, EmployeeAccount } from '@hcengineering/contact'
|
import { Channel, Contact } from '@hcengineering/contact'
|
||||||
import { employeeByIdStore } from '@hcengineering/contact-resources'
|
import { employeeAccountByIdStore, employeeByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { IdMap, Ref, SortingOrder, toIdMap } from '@hcengineering/core'
|
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||||
import { Message, SharedMessage } from '@hcengineering/gmail'
|
import { Message, SharedMessage } from '@hcengineering/gmail'
|
||||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
@ -32,14 +32,11 @@
|
|||||||
export let enabled: boolean
|
export let enabled: boolean
|
||||||
|
|
||||||
let messages: Message[] = []
|
let messages: Message[] = []
|
||||||
let accounts: IdMap<EmployeeAccount> = new Map()
|
|
||||||
let selected: Set<Ref<SharedMessage>> = new Set<Ref<SharedMessage>>()
|
let selected: Set<Ref<SharedMessage>> = new Set<Ref<SharedMessage>>()
|
||||||
let selectable = false
|
let selectable = false
|
||||||
|
|
||||||
const messagesQuery = createQuery()
|
const messagesQuery = createQuery()
|
||||||
const accountsQuery = createQuery()
|
|
||||||
|
|
||||||
accountsQuery.query(contact.class.EmployeeAccount, {}, (res) => (accounts = toIdMap(res)))
|
|
||||||
|
|
||||||
const notificationClient = NotificationClientImpl.getClient()
|
const notificationClient = NotificationClientImpl.getClient()
|
||||||
|
|
||||||
@ -68,7 +65,7 @@
|
|||||||
object._class,
|
object._class,
|
||||||
'gmailSharedMessages',
|
'gmailSharedMessages',
|
||||||
{
|
{
|
||||||
messages: convertMessages(object, channel, selectedMessages, accounts, $employeeByIdStore)
|
messages: convertMessages(object, channel, selectedMessages, $employeeAccountByIdStore, $employeeByIdStore)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
|
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
|
||||||
@ -126,7 +123,7 @@
|
|||||||
<div class="popupPanel-body__main-content py-4 clear-mins flex-no-shrink">
|
<div class="popupPanel-body__main-content py-4 clear-mins flex-no-shrink">
|
||||||
{#if messages && messages.length > 0}
|
{#if messages && messages.length > 0}
|
||||||
<Messages
|
<Messages
|
||||||
messages={convertMessages(object, channel, messages, accounts, $employeeByIdStore)}
|
messages={convertMessages(object, channel, messages, $employeeAccountByIdStore, $employeeByIdStore)}
|
||||||
{selectable}
|
{selectable}
|
||||||
bind:selected
|
bind:selected
|
||||||
on:select
|
on:select
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TxViewlet } from '@hcengineering/activity'
|
import { TxViewlet } from '@hcengineering/activity'
|
||||||
import { ActivityKey } from '@hcengineering/activity-resources'
|
import { ActivityKey } from '@hcengineering/activity-resources'
|
||||||
import contact, { EmployeeAccount, getName } from '@hcengineering/contact'
|
import { EmployeeAccount, getName } from '@hcengineering/contact'
|
||||||
import { Avatar, employeeByIdStore } from '@hcengineering/contact-resources'
|
import { Avatar, employeeAccountByIdStore, employeeByIdStore } from '@hcengineering/contact-resources'
|
||||||
import core, { Doc, Ref, TxCUD, TxProcessor } from '@hcengineering/core'
|
import core, { Doc, Ref, TxCUD, TxProcessor } from '@hcengineering/core'
|
||||||
import notification, { DocUpdates } from '@hcengineering/notification'
|
import notification, { DocUpdates } from '@hcengineering/notification'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
@ -54,9 +54,8 @@
|
|||||||
getResource(presenterRes).then((res) => (presenter = res))
|
getResource(presenterRes).then((res) => (presenter = res))
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = createQuery()
|
$: account = $employeeAccountByIdStore.get(tx?.modifiedBy as Ref<EmployeeAccount>)
|
||||||
$: tx &&
|
|
||||||
query.query(contact.class.EmployeeAccount, { _id: tx.modifiedBy as Ref<EmployeeAccount> }, (r) => ([account] = r))
|
|
||||||
$: employee = account && $employeeByIdStore.get(account.employee)
|
$: employee = account && $employeeByIdStore.get(account.employee)
|
||||||
|
|
||||||
const docQuery = createQuery()
|
const docQuery = createQuery()
|
||||||
|
@ -16,15 +16,16 @@
|
|||||||
import { DisplayTx, TxViewlet } from '@hcengineering/activity'
|
import { DisplayTx, TxViewlet } from '@hcengineering/activity'
|
||||||
import {
|
import {
|
||||||
ActivityKey,
|
ActivityKey,
|
||||||
|
TxDisplayViewlet,
|
||||||
getValue,
|
getValue,
|
||||||
newDisplayTx,
|
newDisplayTx,
|
||||||
TxDisplayViewlet,
|
|
||||||
updateViewlet
|
updateViewlet
|
||||||
} from '@hcengineering/activity-resources'
|
} from '@hcengineering/activity-resources'
|
||||||
import activity from '@hcengineering/activity-resources/src/plugin'
|
import activity from '@hcengineering/activity-resources/src/plugin'
|
||||||
import contact, { EmployeeAccount } from '@hcengineering/contact'
|
import { EmployeeAccount } from '@hcengineering/contact'
|
||||||
|
import { employeeAccountByIdStore } from '@hcengineering/contact-resources'
|
||||||
import core, { AnyAttribute, Doc, Ref, TxCUD } from '@hcengineering/core'
|
import core, { AnyAttribute, Doc, Ref, TxCUD } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Component, Label, ShowMore } from '@hcengineering/ui'
|
import { Component, Label, ShowMore } from '@hcengineering/ui'
|
||||||
import type { AttributeModel } from '@hcengineering/view'
|
import type { AttributeModel } from '@hcengineering/view'
|
||||||
import { ObjectPresenter } from '@hcengineering/view-resources'
|
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||||
@ -52,8 +53,6 @@
|
|||||||
model = []
|
model = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = createQuery()
|
|
||||||
|
|
||||||
function getProps (props: any): any {
|
function getProps (props: any): any {
|
||||||
return { ...props, attr: ptx?.collectionAttribute }
|
return { ...props, attr: ptx?.collectionAttribute }
|
||||||
}
|
}
|
||||||
@ -67,14 +66,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$: query.query(
|
$: employee = $employeeAccountByIdStore.get(tx.modifiedBy as Ref<EmployeeAccount>)
|
||||||
contact.class.EmployeeAccount,
|
|
||||||
{ _id: tx.modifiedBy as Ref<EmployeeAccount> },
|
|
||||||
(account) => {
|
|
||||||
;[employee] = account
|
|
||||||
},
|
|
||||||
{ limit: 1 }
|
|
||||||
)
|
|
||||||
|
|
||||||
function isMessageType (attr?: AnyAttribute): boolean {
|
function isMessageType (attr?: AnyAttribute): boolean {
|
||||||
return attr?.type._class === core.class.TypeMarkup
|
return attr?.type._class === core.class.TypeMarkup
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
import { DisplayTx, TxViewlet } from '@hcengineering/activity'
|
import { DisplayTx, TxViewlet } from '@hcengineering/activity'
|
||||||
import { ActivityKey, getValue, newDisplayTx, updateViewlet } from '@hcengineering/activity-resources'
|
import { ActivityKey, getValue, newDisplayTx, updateViewlet } from '@hcengineering/activity-resources'
|
||||||
import activity from '@hcengineering/activity-resources/src/plugin'
|
import activity from '@hcengineering/activity-resources/src/plugin'
|
||||||
import contact, { EmployeeAccount } from '@hcengineering/contact'
|
import { EmployeeAccount } from '@hcengineering/contact'
|
||||||
|
import { employeeAccountByIdStore } from '@hcengineering/contact-resources'
|
||||||
import core, { AnyAttribute, Doc, Ref, Tx, TxCUD } from '@hcengineering/core'
|
import core, { AnyAttribute, Doc, Ref, Tx, TxCUD } from '@hcengineering/core'
|
||||||
import { Asset } from '@hcengineering/platform'
|
import { Asset } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Label } from '@hcengineering/ui'
|
import { Label } from '@hcengineering/ui'
|
||||||
import type { AttributeModel } from '@hcengineering/view'
|
import type { AttributeModel } from '@hcengineering/view'
|
||||||
import { ObjectPresenter } from '@hcengineering/view-resources'
|
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||||
@ -37,7 +38,7 @@
|
|||||||
let modelIcon: Asset | undefined = undefined
|
let modelIcon: Asset | undefined = undefined
|
||||||
|
|
||||||
$: if (tx._id !== ptx?.tx._id) {
|
$: if (tx._id !== ptx?.tx._id) {
|
||||||
ptx = newDisplayTx(tx as TxCUD<Doc>, client.getHierarchy())
|
ptx = newDisplayTx(tx as TxCUD<Doc>, client.getHierarchy(), false)
|
||||||
if (tx.modifiedBy !== employee?._id) {
|
if (tx.modifiedBy !== employee?._id) {
|
||||||
employee = undefined
|
employee = undefined
|
||||||
}
|
}
|
||||||
@ -45,8 +46,6 @@
|
|||||||
model = []
|
model = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = createQuery()
|
|
||||||
|
|
||||||
$: ptx &&
|
$: ptx &&
|
||||||
updateViewlet(client, viewlets, ptx).then((result) => {
|
updateViewlet(client, viewlets, ptx).then((result) => {
|
||||||
if (result.id === tx._id) {
|
if (result.id === tx._id) {
|
||||||
@ -56,14 +55,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$: query.query(
|
$: employee = $employeeAccountByIdStore.get(tx.modifiedBy as Ref<EmployeeAccount>)
|
||||||
contact.class.EmployeeAccount,
|
|
||||||
{ _id: tx.modifiedBy as Ref<EmployeeAccount> },
|
|
||||||
(account) => {
|
|
||||||
;[employee] = account
|
|
||||||
},
|
|
||||||
{ limit: 1 }
|
|
||||||
)
|
|
||||||
|
|
||||||
function isMessageType (attr?: AnyAttribute): boolean {
|
function isMessageType (attr?: AnyAttribute): boolean {
|
||||||
return attr?.type._class === core.class.TypeMarkup
|
return attr?.type._class === core.class.TypeMarkup
|
||||||
|
@ -23,7 +23,8 @@
|
|||||||
EmployeeAccount,
|
EmployeeAccount,
|
||||||
getName as getContactName
|
getName as getContactName
|
||||||
} from '@hcengineering/contact'
|
} from '@hcengineering/contact'
|
||||||
import { Class, generateId, getCurrentAccount, IdMap, Ref, SortingOrder } from '@hcengineering/core'
|
import { Avatar, employeeAccountByIdStore, employeeByIdStore } from '@hcengineering/contact-resources'
|
||||||
|
import { Class, IdMap, Ref, SortingOrder, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||||
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
|
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
@ -32,22 +33,21 @@
|
|||||||
import templates, { TemplateDataProvider } from '@hcengineering/templates'
|
import templates, { TemplateDataProvider } from '@hcengineering/templates'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
eventToHTMLElement,
|
|
||||||
Icon,
|
Icon,
|
||||||
IconShare,
|
IconShare,
|
||||||
Label,
|
Label,
|
||||||
Panel,
|
Panel,
|
||||||
Scroller,
|
Scroller,
|
||||||
|
eventToHTMLElement,
|
||||||
showPopup,
|
showPopup,
|
||||||
tooltip
|
tooltip
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { Avatar, employeeByIdStore } from '@hcengineering/contact-resources'
|
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||||
import telegram from '../plugin'
|
import telegram from '../plugin'
|
||||||
import Connect from './Connect.svelte'
|
import Connect from './Connect.svelte'
|
||||||
import TelegramIcon from './icons/Telegram.svelte'
|
|
||||||
import Messages from './Messages.svelte'
|
import Messages from './Messages.svelte'
|
||||||
import Reconnect from './Reconnect.svelte'
|
import Reconnect from './Reconnect.svelte'
|
||||||
|
import TelegramIcon from './icons/Telegram.svelte'
|
||||||
|
|
||||||
export let _id: Ref<Contact>
|
export let _id: Ref<Contact>
|
||||||
export let _class: Ref<Class<Contact>>
|
export let _class: Ref<Class<Contact>>
|
||||||
@ -95,13 +95,11 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
let messages: TelegramMessage[] = []
|
let messages: TelegramMessage[] = []
|
||||||
let accounts: EmployeeAccount[] = []
|
|
||||||
let integration: Integration | undefined
|
let integration: Integration | undefined
|
||||||
let selected: Set<Ref<SharedTelegramMessage>> = new Set<Ref<SharedTelegramMessage>>()
|
let selected: Set<Ref<SharedTelegramMessage>> = new Set<Ref<SharedTelegramMessage>>()
|
||||||
let selectable = false
|
let selectable = false
|
||||||
|
|
||||||
const messagesQuery = createQuery()
|
const messagesQuery = createQuery()
|
||||||
const accauntsQuery = createQuery()
|
|
||||||
const settingsQuery = createQuery()
|
const settingsQuery = createQuery()
|
||||||
const accountId = getCurrentAccount()._id
|
const accountId = getCurrentAccount()._id
|
||||||
|
|
||||||
@ -127,10 +125,6 @@
|
|||||||
|
|
||||||
$: channel && updateMessagesQuery(channel._id)
|
$: channel && updateMessagesQuery(channel._id)
|
||||||
|
|
||||||
accauntsQuery.query(contact.class.EmployeeAccount, {}, (result) => {
|
|
||||||
accounts = result
|
|
||||||
})
|
|
||||||
|
|
||||||
settingsQuery.query(
|
settingsQuery.query(
|
||||||
setting.class.Integration,
|
setting.class.Integration,
|
||||||
{ type: telegram.integrationType.Telegram, createdBy: accountId },
|
{ type: telegram.integrationType.Telegram, createdBy: accountId },
|
||||||
@ -160,8 +154,8 @@
|
|||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function getName (message: TelegramMessage, accounts: EmployeeAccount[]): string {
|
function getName (message: TelegramMessage, accounts: IdMap<EmployeeAccount>): string {
|
||||||
return message.incoming ? object.name : accounts.find((p) => p._id === message.modifiedBy)?.name ?? ''
|
return message.incoming ? object.name : accounts.get(message.modifiedBy as Ref<EmployeeAccount>)?.name ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
async function share (): Promise<void> {
|
async function share (): Promise<void> {
|
||||||
@ -173,7 +167,7 @@
|
|||||||
object._class,
|
object._class,
|
||||||
'sharedTelegramMessages',
|
'sharedTelegramMessages',
|
||||||
{
|
{
|
||||||
messages: convertMessages(selectedMessages, accounts)
|
messages: convertMessages(selectedMessages, $employeeAccountByIdStore)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (channel !== undefined) {
|
if (channel !== undefined) {
|
||||||
@ -188,7 +182,7 @@
|
|||||||
selected = selected
|
selected = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertMessages (messages: TelegramMessage[], accounts: EmployeeAccount[]): SharedTelegramMessage[] {
|
function convertMessages (messages: TelegramMessage[], accounts: IdMap<EmployeeAccount>): SharedTelegramMessage[] {
|
||||||
return messages.map((m) => {
|
return messages.map((m) => {
|
||||||
return {
|
return {
|
||||||
...m,
|
...m,
|
||||||
@ -219,16 +213,16 @@
|
|||||||
|
|
||||||
function getParticipants (
|
function getParticipants (
|
||||||
messages: TelegramMessage[],
|
messages: TelegramMessage[],
|
||||||
accounts: EmployeeAccount[],
|
accounts: IdMap<EmployeeAccount>,
|
||||||
object: Contact | undefined,
|
object: Contact | undefined,
|
||||||
employees: IdMap<Employee>
|
employees: IdMap<Employee>
|
||||||
): Contact[] {
|
): Contact[] {
|
||||||
if (object === undefined || accounts.length === 0) return []
|
if (object === undefined || accounts.size === 0) return []
|
||||||
const res: IdMap<Contact> = new Map()
|
const res: IdMap<Contact> = new Map()
|
||||||
res.set(object._id, object)
|
res.set(object._id, object)
|
||||||
const accs = new Set(messages.map((p) => p.modifiedBy))
|
const accs = new Set(messages.map((p) => p.modifiedBy))
|
||||||
for (const acc of accs) {
|
for (const acc of accs) {
|
||||||
const account = accounts.find((p) => p._id === acc)
|
const account = accounts.get(acc as Ref<EmployeeAccount>)
|
||||||
if (account !== undefined) {
|
if (account !== undefined) {
|
||||||
const emp = employees.get(account.employee)
|
const emp = employees.get(account.employee)
|
||||||
if (emp !== undefined) {
|
if (emp !== undefined) {
|
||||||
@ -239,7 +233,7 @@
|
|||||||
return Array.from(res.values())
|
return Array.from(res.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
$: participants = getParticipants(messages, accounts, object, $employeeByIdStore)
|
$: participants = getParticipants(messages, $employeeAccountByIdStore, object, $employeeByIdStore)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if object !== undefined}
|
{#if object !== undefined}
|
||||||
@ -298,8 +292,8 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
<Scroller bottomStart autoscroll>
|
<Scroller bottomStart autoscroll>
|
||||||
{#if messages && accounts}
|
{#if messages}
|
||||||
<Messages messages={convertMessages(messages, accounts)} {selectable} bind:selected />
|
<Messages messages={convertMessages(messages, $employeeAccountByIdStore)} {selectable} bind:selected />
|
||||||
{/if}
|
{/if}
|
||||||
</Scroller>
|
</Scroller>
|
||||||
|
|
||||||
|
@ -13,15 +13,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact, { EmployeeAccount } from '@hcengineering/contact'
|
import { EmployeeAccount } from '@hcengineering/contact'
|
||||||
import { employeeByIdStore, EmployeeBox } from '@hcengineering/contact-resources'
|
import { EmployeeBox, employeeAccountByIdStore, employeeByIdStore } from '@hcengineering/contact-resources'
|
||||||
import core, { ClassifierKind, Doc, Mixin, Ref } from '@hcengineering/core'
|
import core, { ClassifierKind, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||||
import { AttributeBarEditor, createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
import { AttributeBarEditor, KeyedAttribute, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
|
|
||||||
import tags from '@hcengineering/tags'
|
import tags from '@hcengineering/tags'
|
||||||
import type { Issue } from '@hcengineering/tracker'
|
import type { Issue } from '@hcengineering/tracker'
|
||||||
import { Component, Label } from '@hcengineering/ui'
|
import { Component, Label } from '@hcengineering/ui'
|
||||||
import { getFiltredKeys, isCollectionAttr, ObjectBox } from '@hcengineering/view-resources'
|
import { ObjectBox, getFiltredKeys, isCollectionAttr } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import ComponentEditor from '../../components/ComponentEditor.svelte'
|
import ComponentEditor from '../../components/ComponentEditor.svelte'
|
||||||
import SprintEditor from '../../sprints/SprintEditor.svelte'
|
import SprintEditor from '../../sprints/SprintEditor.svelte'
|
||||||
@ -87,19 +87,10 @@
|
|||||||
'blockedBy'
|
'blockedBy'
|
||||||
])
|
])
|
||||||
|
|
||||||
const employeeAccountQuery = createQuery()
|
|
||||||
|
|
||||||
let account: EmployeeAccount | undefined
|
let account: EmployeeAccount | undefined
|
||||||
$: employee = account && $employeeByIdStore.get(account.employee)
|
$: employee = account && $employeeByIdStore.get(account.employee)
|
||||||
|
|
||||||
$: employeeAccountQuery.query(
|
$: account = $employeeAccountByIdStore.get(issue.createdBy as Ref<EmployeeAccount>)
|
||||||
contact.class.EmployeeAccount,
|
|
||||||
{ _id: issue.createdBy as Ref<EmployeeAccount> },
|
|
||||||
(res) => {
|
|
||||||
;[account] = res
|
|
||||||
},
|
|
||||||
{ limit: 1 }
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -66,7 +66,7 @@ export async function connect (title: string): Promise<Client | undefined> {
|
|||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const newVersion = await _client?.findOne<Version>(core.class.Version, {})
|
const newVersion = await _client?.findOne<Version>(core.class.Version, {})
|
||||||
console.log('Reconnect Model version', version)
|
console.log('Reconnect Model version', newVersion)
|
||||||
|
|
||||||
const currentVersionStr = versionToString(version as Version)
|
const currentVersionStr = versionToString(version as Version)
|
||||||
const reconnectVersionStr = versionToString(newVersion as Version)
|
const reconnectVersionStr = versionToString(newVersion as Version)
|
||||||
|
@ -41,7 +41,7 @@ import core, {
|
|||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { Collaborators } from '@hcengineering/notification'
|
import notification, { Collaborators } from '@hcengineering/notification'
|
||||||
import { getMetadata } from '@hcengineering/platform'
|
import { getMetadata } from '@hcengineering/platform'
|
||||||
import serverCore, { AsyncTriggerControl, TriggerControl } from '@hcengineering/server-core'
|
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,13 +110,13 @@ export async function OnContactDelete (
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateAllRefs (
|
async function updateAllRefs (
|
||||||
control: AsyncTriggerControl,
|
control: TriggerControl,
|
||||||
sourceAccount: EmployeeAccount,
|
sourceAccount: EmployeeAccount,
|
||||||
targetAccount: EmployeeAccount,
|
targetAccount: EmployeeAccount,
|
||||||
modifiedOn: Timestamp,
|
modifiedOn: Timestamp,
|
||||||
modifiedBy: Ref<Account>
|
modifiedBy: Ref<Account>
|
||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
const res: Tx[] = []
|
console.log('merge employee:', sourceAccount.name, 'to', targetAccount.name)
|
||||||
// Move all possible references to Account and Employee and replace to target one.
|
// Move all possible references to Account and Employee and replace to target one.
|
||||||
const reftos = (await control.modelDb.findAll(core.class.Attribute, { 'type._class': core.class.RefTo })).filter(
|
const reftos = (await control.modelDb.findAll(core.class.Attribute, { 'type._class': core.class.RefTo })).filter(
|
||||||
(it) => {
|
(it) => {
|
||||||
@ -133,6 +133,9 @@ async function updateAllRefs (
|
|||||||
if (to.to === contact.class.Employee) {
|
if (to.to === contact.class.Employee) {
|
||||||
const descendants = control.hierarchy.getDescendants(attr.attributeOf)
|
const descendants = control.hierarchy.getDescendants(attr.attributeOf)
|
||||||
for (const d of descendants) {
|
for (const d of descendants) {
|
||||||
|
if (control.hierarchy.isDerived(d, core.class.Tx)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (control.hierarchy.findDomain(d) !== undefined) {
|
if (control.hierarchy.findDomain(d) !== undefined) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const values = await control.findAll(d, { [attr.name]: sourceAccount.employee }, { limit: 100 })
|
const values = await control.findAll(d, { [attr.name]: sourceAccount.employee }, { limit: 100 })
|
||||||
@ -144,7 +147,10 @@ async function updateAllRefs (
|
|||||||
for (const v of values) {
|
for (const v of values) {
|
||||||
await updateAttribute(builder, v, d, { key: attr.name, attr }, targetAccount.employee, targetAccount._id)
|
await updateAttribute(builder, v, d, { key: attr.name, attr }, targetAccount.employee, targetAccount._id)
|
||||||
}
|
}
|
||||||
await control.apply(builder.txes, true, true)
|
if (builder.txes.length > 0) {
|
||||||
|
console.log('merge employee:', sourceAccount.name, 'to', targetAccount.name, d, builder.txes.length)
|
||||||
|
await control.apply(builder.txes, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,6 +162,9 @@ async function updateAllRefs (
|
|||||||
) {
|
) {
|
||||||
const descendants = control.hierarchy.getDescendants(attr.attributeOf)
|
const descendants = control.hierarchy.getDescendants(attr.attributeOf)
|
||||||
for (const d of descendants) {
|
for (const d of descendants) {
|
||||||
|
if (control.hierarchy.isDerived(d, core.class.Tx)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (control.hierarchy.findDomain(d) !== undefined) {
|
if (control.hierarchy.findDomain(d) !== undefined) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const values = await control.findAll(d, { [attr.name]: sourceAccount._id }, { limit: 100 })
|
const values = await control.findAll(d, { [attr.name]: sourceAccount._id }, { limit: 100 })
|
||||||
@ -166,7 +175,10 @@ async function updateAllRefs (
|
|||||||
for (const v of values) {
|
for (const v of values) {
|
||||||
await updateAttribute(builder, v, d, { key: attr.name, attr }, targetAccount._id, targetAccount._id)
|
await updateAttribute(builder, v, d, { key: attr.name, attr }, targetAccount._id, targetAccount._id)
|
||||||
}
|
}
|
||||||
await control.apply(builder.txes, true, true)
|
if (builder.txes.length > 0) {
|
||||||
|
console.log('merge employee:', sourceAccount.name, 'to', targetAccount.name, d, builder.txes.length)
|
||||||
|
await control.apply(builder.txes, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,6 +193,9 @@ async function updateAllRefs (
|
|||||||
if (to.to === contact.class.Employee) {
|
if (to.to === contact.class.Employee) {
|
||||||
const descendants = control.hierarchy.getDescendants(attr.attributeOf)
|
const descendants = control.hierarchy.getDescendants(attr.attributeOf)
|
||||||
for (const d of descendants) {
|
for (const d of descendants) {
|
||||||
|
if (control.hierarchy.isDerived(d, core.class.Tx)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (control.hierarchy.findDomain(d) !== undefined) {
|
if (control.hierarchy.findDomain(d) !== undefined) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const values = await control.findAll(
|
const values = await control.findAll(
|
||||||
@ -195,7 +210,10 @@ async function updateAllRefs (
|
|||||||
for (const v of values) {
|
for (const v of values) {
|
||||||
await updateAttribute(builder, v, d, { key: attr.name, attr }, targetAccount.employee, targetAccount._id)
|
await updateAttribute(builder, v, d, { key: attr.name, attr }, targetAccount.employee, targetAccount._id)
|
||||||
}
|
}
|
||||||
await control.apply(builder.txes, true, true)
|
if (builder.txes.length > 0) {
|
||||||
|
console.log('merge employee:', sourceAccount.name, 'to', targetAccount.name, d, builder.txes.length)
|
||||||
|
await control.apply(builder.txes, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,6 +225,9 @@ async function updateAllRefs (
|
|||||||
) {
|
) {
|
||||||
const descendants = control.hierarchy.getDescendants(attr.attributeOf)
|
const descendants = control.hierarchy.getDescendants(attr.attributeOf)
|
||||||
for (const d of descendants) {
|
for (const d of descendants) {
|
||||||
|
if (control.hierarchy.isDerived(d, core.class.Tx)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (control.hierarchy.findDomain(d) !== undefined) {
|
if (control.hierarchy.findDomain(d) !== undefined) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const values = await control.findAll(d, { [attr.name]: sourceAccount._id }, { limit: 100 })
|
const values = await control.findAll(d, { [attr.name]: sourceAccount._id }, { limit: 100 })
|
||||||
@ -217,27 +238,30 @@ async function updateAllRefs (
|
|||||||
for (const v of values) {
|
for (const v of values) {
|
||||||
await updateAttribute(builder, v, d, { key: attr.name, attr }, targetAccount._id, targetAccount._id)
|
await updateAttribute(builder, v, d, { key: attr.name, attr }, targetAccount._id, targetAccount._id)
|
||||||
}
|
}
|
||||||
await control.apply(builder.txes, true, true)
|
if (builder.txes.length > 0) {
|
||||||
|
console.log('merge employee:', sourceAccount.name, 'to', targetAccount.name, d, builder.txes.length)
|
||||||
|
await control.apply(builder.txes, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const employee = (await control.findAll(contact.class.Employee, { _id: sourceAccount.employee })).shift()
|
const employee = (await control.findAll(contact.class.Employee, { _id: sourceAccount.employee })).shift()
|
||||||
|
|
||||||
const builder = new TxBuilder(control.hierarchy, control.modelDb, modifiedBy)
|
const builder = new TxBuilder(control.hierarchy, control.modelDb, modifiedBy)
|
||||||
await builder.remove(sourceAccount)
|
|
||||||
if (employee !== undefined) {
|
if (employee !== undefined) {
|
||||||
await builder.remove(employee)
|
await builder.remove(employee)
|
||||||
}
|
}
|
||||||
await control.apply(builder.txes, true, true)
|
await builder.update(sourceAccount, { mergedTo: targetAccount._id })
|
||||||
|
await control.apply(builder.txes, true)
|
||||||
|
|
||||||
return res
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mergeEmployee (control: AsyncTriggerControl, uTx: TxUpdateDoc<Employee>): Promise<Tx[]> {
|
async function mergeEmployee (control: TriggerControl, uTx: TxUpdateDoc<Employee>): Promise<Tx[]> {
|
||||||
if (uTx.operations.mergedTo === undefined) return []
|
if (uTx.operations.mergedTo === undefined) return []
|
||||||
const target = uTx.operations.mergedTo
|
const target = uTx.operations.mergedTo
|
||||||
const res: Tx[] = []
|
|
||||||
|
|
||||||
const attributes = control.hierarchy.getAllAttributes(contact.class.Employee)
|
const attributes = control.hierarchy.getAllAttributes(contact.class.Employee)
|
||||||
|
|
||||||
@ -245,7 +269,12 @@ async function mergeEmployee (control: AsyncTriggerControl, uTx: TxUpdateDoc<Emp
|
|||||||
if (control.hierarchy.isDerived(attribute[1].type._class, core.class.Collection)) {
|
if (control.hierarchy.isDerived(attribute[1].type._class, core.class.Collection)) {
|
||||||
if (attribute[1]._id === contact.class.Contact + '_channels') continue
|
if (attribute[1]._id === contact.class.Contact + '_channels') continue
|
||||||
const collection = attribute[1].type as Collection<AttachedDoc>
|
const collection = attribute[1].type as Collection<AttachedDoc>
|
||||||
const allAttached = await control.findAll(collection.of, { attachedTo: uTx.objectId })
|
const res: Tx[] = []
|
||||||
|
while (true) {
|
||||||
|
const allAttached = await control.findAll(collection.of, { attachedTo: uTx.objectId }, { limit: 100 })
|
||||||
|
if (allAttached.length === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
for (const attached of allAttached) {
|
for (const attached of allAttached) {
|
||||||
const tx = control.txFactory.createTxUpdateDoc(attached._class, attached.space, attached._id, {
|
const tx = control.txFactory.createTxUpdateDoc(attached._class, attached.space, attached._id, {
|
||||||
attachedTo: target
|
attachedTo: target
|
||||||
@ -259,6 +288,8 @@ async function mergeEmployee (control: AsyncTriggerControl, uTx: TxUpdateDoc<Emp
|
|||||||
)
|
)
|
||||||
res.push(parent)
|
res.push(parent)
|
||||||
}
|
}
|
||||||
|
await control.apply(res, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,32 +298,18 @@ async function mergeEmployee (control: AsyncTriggerControl, uTx: TxUpdateDoc<Emp
|
|||||||
)[0]
|
)[0]
|
||||||
const newEmployeeAccount = (await control.modelDb.findAll(contact.class.EmployeeAccount, { employee: target }))[0]
|
const newEmployeeAccount = (await control.modelDb.findAll(contact.class.EmployeeAccount, { employee: target }))[0]
|
||||||
|
|
||||||
if (oldEmployeeAccount === undefined || newEmployeeAccount === undefined) return res
|
if (oldEmployeeAccount === undefined || newEmployeeAccount === undefined) {
|
||||||
const accountTxes = await updateAllRefs(
|
return []
|
||||||
control,
|
}
|
||||||
oldEmployeeAccount,
|
return await updateAllRefs(control, oldEmployeeAccount, newEmployeeAccount, uTx.modifiedOn, uTx.modifiedBy)
|
||||||
newEmployeeAccount,
|
|
||||||
uTx.modifiedOn,
|
|
||||||
uTx.modifiedBy
|
|
||||||
)
|
|
||||||
res.push(...accountTxes)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function OnEmployeeUpdate (tx: Tx, control: AsyncTriggerControl): Promise<Tx[]> {
|
export async function OnEmployeeUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||||
if (tx._class !== core.class.TxUpdateDoc) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const uTx = tx as TxUpdateDoc<Employee>
|
const uTx = tx as TxUpdateDoc<Employee>
|
||||||
|
|
||||||
if (!control.hierarchy.isDerived(uTx.objectClass, contact.class.Employee)) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: Tx[] = []
|
const result: Tx[] = []
|
||||||
|
|
||||||
const txes = await mergeEmployee(control, uTx)
|
const txes = await mergeEmployee(control, uTx)
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Resource, Plugin } from '@hcengineering/platform'
|
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import type { AsyncTriggerFunc, TriggerFunc } from '@hcengineering/server-core'
|
import type { TriggerFunc } from '@hcengineering/server-core'
|
||||||
import { Presenter } from '@hcengineering/server-notification'
|
import { Presenter } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,7 +31,7 @@ export default plugin(serverContactId, {
|
|||||||
trigger: {
|
trigger: {
|
||||||
OnContactDelete: '' as Resource<TriggerFunc>,
|
OnContactDelete: '' as Resource<TriggerFunc>,
|
||||||
OnChannelUpdate: '' as Resource<TriggerFunc>,
|
OnChannelUpdate: '' as Resource<TriggerFunc>,
|
||||||
OnEmployeeUpdate: '' as Resource<AsyncTriggerFunc>
|
OnEmployeeUpdate: '' as Resource<TriggerFunc>
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
PersonHTMLPresenter: '' as Resource<Presenter>,
|
PersonHTMLPresenter: '' as Resource<Presenter>,
|
||||||
|
@ -17,7 +17,7 @@ import type { Plugin, Resource } from '@hcengineering/platform'
|
|||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
|
|
||||||
import type { Account, Class, Ref } from '@hcengineering/core'
|
import type { Account, Class, Ref } from '@hcengineering/core'
|
||||||
import { AsyncTriggerFunc } from '@hcengineering/server-core'
|
import { TriggerFunc } from '@hcengineering/server-core'
|
||||||
import type { OpenAIConfiguration } from './types'
|
import type { OpenAIConfiguration } from './types'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
@ -31,7 +31,7 @@ export const openAIId = 'openai' as Plugin
|
|||||||
*/
|
*/
|
||||||
const openaiPlugin = plugin(openAIId, {
|
const openaiPlugin = plugin(openAIId, {
|
||||||
trigger: {
|
trigger: {
|
||||||
AsyncOnGPTRequest: '' as Resource<AsyncTriggerFunc>
|
AsyncOnGPTRequest: '' as Resource<TriggerFunc>
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
OpenAIConfiguration: '' as Ref<Class<OpenAIConfiguration>>
|
OpenAIConfiguration: '' as Ref<Class<OpenAIConfiguration>>
|
||||||
|
@ -27,7 +27,7 @@ import core, {
|
|||||||
TxProcessor
|
TxProcessor
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import recruit, { ApplicantMatch } from '@hcengineering/recruit'
|
import recruit, { ApplicantMatch } from '@hcengineering/recruit'
|
||||||
import type { AsyncTriggerControl } from '@hcengineering/server-core'
|
import type { TriggerControl } from '@hcengineering/server-core'
|
||||||
import got from 'got'
|
import got from 'got'
|
||||||
import { convert } from 'html-to-text'
|
import { convert } from 'html-to-text'
|
||||||
import { chunks } from './encoder/encoder'
|
import { chunks } from './encoder/encoder'
|
||||||
@ -111,7 +111,7 @@ async function performCompletion (
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function AsyncOnGPTRequest (tx: Tx, tc: AsyncTriggerControl): Promise<Tx[]> {
|
export async function AsyncOnGPTRequest (tx: Tx, tc: TriggerControl): Promise<Tx[]> {
|
||||||
const actualTx = TxProcessor.extractTx(tx)
|
const actualTx = TxProcessor.extractTx(tx)
|
||||||
|
|
||||||
if (tc.hierarchy.isDerived(actualTx._class, core.class.TxCUD) && actualTx.modifiedBy !== openai.account.GPT) {
|
if (tc.hierarchy.isDerived(actualTx._class, core.class.TxCUD) && actualTx.modifiedBy !== openai.account.GPT) {
|
||||||
@ -127,7 +127,7 @@ export async function AsyncOnGPTRequest (tx: Tx, tc: AsyncTriggerControl): Promi
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleComment (tx: Tx, tc: AsyncTriggerControl): Promise<Tx[]> {
|
async function handleComment (tx: Tx, tc: TriggerControl): Promise<Tx[]> {
|
||||||
const actualTx = TxProcessor.extractTx(tx)
|
const actualTx = TxProcessor.extractTx(tx)
|
||||||
const cud: TxCUD<Doc> = actualTx as TxCUD<Doc>
|
const cud: TxCUD<Doc> = actualTx as TxCUD<Doc>
|
||||||
|
|
||||||
@ -269,7 +269,7 @@ async function summarizeVacancy (config: OpenAIConfiguration, chunks: string[],
|
|||||||
return getText(await performCompletion(candidateSummaryRequest, options, config, maxLen)) ?? chunks[0]
|
return getText(await performCompletion(candidateSummaryRequest, options, config, maxLen)) ?? chunks[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleApplicantMatch (tx: Tx, tc: AsyncTriggerControl): Promise<Tx[]> {
|
async function handleApplicantMatch (tx: Tx, tc: TriggerControl): Promise<Tx[]> {
|
||||||
const [config] = await tc.findAll(openai.class.OpenAIConfiguration, {})
|
const [config] = await tc.findAll(openai.class.OpenAIConfiguration, {})
|
||||||
|
|
||||||
if (!(config?.enabled ?? false)) {
|
if (!(config?.enabled ?? false)) {
|
||||||
|
@ -500,18 +500,27 @@ export async function restore (
|
|||||||
let idx: number | undefined
|
let idx: number | undefined
|
||||||
let loaded = 0
|
let loaded = 0
|
||||||
let last = 0
|
let last = 0
|
||||||
|
let el = 0
|
||||||
|
let chunks = 0
|
||||||
while (true) {
|
while (true) {
|
||||||
|
const st = Date.now()
|
||||||
const it = await connection.loadChunk(c, idx)
|
const it = await connection.loadChunk(c, idx)
|
||||||
|
chunks++
|
||||||
|
|
||||||
idx = it.idx
|
idx = it.idx
|
||||||
|
el += Date.now() - st
|
||||||
|
|
||||||
for (const [_id, hash] of Object.entries(it.docs)) {
|
for (const [_id, hash] of Object.entries(it.docs)) {
|
||||||
serverChangeset.set(_id as Ref<Doc>, hash)
|
serverChangeset.set(_id as Ref<Doc>, hash)
|
||||||
loaded++
|
loaded++
|
||||||
}
|
}
|
||||||
|
|
||||||
const mr = Math.round(loaded / 10000)
|
const mr = Math.round(loaded / 10000)
|
||||||
if (mr !== last) {
|
if (mr !== last) {
|
||||||
last = mr
|
last = mr
|
||||||
console.log(' loaded', loaded)
|
console.log(' loaded from server', loaded, el, chunks)
|
||||||
|
el = 0
|
||||||
|
chunks = 0
|
||||||
}
|
}
|
||||||
if (it.finished) {
|
if (it.finished) {
|
||||||
break
|
break
|
||||||
@ -649,7 +658,10 @@ export async function restore (
|
|||||||
await sendChunk(undefined, 0)
|
await sendChunk(undefined, 0)
|
||||||
if (docsToRemove.length > 0 && merge !== true) {
|
if (docsToRemove.length > 0 && merge !== true) {
|
||||||
console.log('cleanup', docsToRemove.length)
|
console.log('cleanup', docsToRemove.length)
|
||||||
await connection.clean(c, docsToRemove)
|
while (docsToRemove.length > 0) {
|
||||||
|
const part = docsToRemove.splice(0, 10000)
|
||||||
|
await connection.clean(c, part)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -18,7 +18,7 @@ import type { Metadata, Plugin } from '@hcengineering/platform'
|
|||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
|
|
||||||
import type { Class, Ref, Space } from '@hcengineering/core'
|
import type { Class, Ref, Space } from '@hcengineering/core'
|
||||||
import type { AsyncTrigger, AsyncTriggerState, ObjectDDParticipant, Trigger } from './types'
|
import type { ObjectDDParticipant, Trigger } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -30,9 +30,7 @@ export const serverCoreId = 'server-core' as Plugin
|
|||||||
*/
|
*/
|
||||||
const serverCore = plugin(serverCoreId, {
|
const serverCore = plugin(serverCoreId, {
|
||||||
class: {
|
class: {
|
||||||
Trigger: '' as Ref<Class<Trigger>>,
|
Trigger: '' as Ref<Class<Trigger>>
|
||||||
AsyncTrigger: '' as Ref<Class<AsyncTrigger>>,
|
|
||||||
AsyncTriggerState: '' as Ref<Class<AsyncTriggerState>>
|
|
||||||
},
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
ObjectDDParticipant: '' as Ref<ObjectDDParticipant>
|
ObjectDDParticipant: '' as Ref<ObjectDDParticipant>
|
||||||
|
@ -1,133 +0,0 @@
|
|||||||
import core, {
|
|
||||||
Class,
|
|
||||||
Doc,
|
|
||||||
Hierarchy,
|
|
||||||
MeasureContext,
|
|
||||||
ModelDb,
|
|
||||||
Ref,
|
|
||||||
ServerStorage,
|
|
||||||
Tx,
|
|
||||||
TxCUD,
|
|
||||||
TxFactory,
|
|
||||||
TxProcessor
|
|
||||||
} from '@hcengineering/core'
|
|
||||||
import { getResource } from '@hcengineering/platform'
|
|
||||||
import plugin from '../plugin'
|
|
||||||
import { AsyncTrigger, AsyncTriggerControl, AsyncTriggerFunc } from '../types'
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export class AsyncTriggerProcessor {
|
|
||||||
canceling: boolean = false
|
|
||||||
|
|
||||||
processing: Promise<void> | undefined
|
|
||||||
|
|
||||||
triggers: AsyncTrigger[] = []
|
|
||||||
|
|
||||||
classes: Ref<Class<Doc>>[] = []
|
|
||||||
|
|
||||||
factory = new TxFactory(core.account.System, true)
|
|
||||||
|
|
||||||
functions: AsyncTriggerFunc[] = []
|
|
||||||
|
|
||||||
trigger = (): void => {}
|
|
||||||
|
|
||||||
control: AsyncTriggerControl
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
readonly model: ModelDb,
|
|
||||||
readonly hierarchy: Hierarchy,
|
|
||||||
readonly storage: ServerStorage,
|
|
||||||
readonly metrics: MeasureContext
|
|
||||||
) {
|
|
||||||
this.control = {
|
|
||||||
hierarchy: this.hierarchy,
|
|
||||||
modelDb: this.model,
|
|
||||||
txFactory: this.factory,
|
|
||||||
findAll: async (_class, query, options) => {
|
|
||||||
return await this.storage.findAll(this.metrics, _class, query, options)
|
|
||||||
},
|
|
||||||
apply: async (tx: Tx[], broadcast: boolean, updateTx: boolean): Promise<void> => {
|
|
||||||
await this.storage.apply(this.metrics, tx, broadcast, updateTx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async cancel (): Promise<void> {
|
|
||||||
this.canceling = true
|
|
||||||
await this.processing
|
|
||||||
}
|
|
||||||
|
|
||||||
async start (): Promise<void> {
|
|
||||||
await this.updateTriggers()
|
|
||||||
this.processing = this.doProcessing()
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateTriggers (): Promise<void> {
|
|
||||||
try {
|
|
||||||
this.triggers = await this.model.findAll(plugin.class.AsyncTrigger, {})
|
|
||||||
this.classes = this.triggers.reduce<Ref<Class<Doc>>[]>((arr, it) => arr.concat(it.classes), [])
|
|
||||||
this.functions = await Promise.all(this.triggers.map(async (trigger) => await getResource(trigger.trigger)))
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async tx (tx: Tx[]): Promise<void> {
|
|
||||||
const result: Tx[] = []
|
|
||||||
for (const _tx of tx) {
|
|
||||||
const actualTx = TxProcessor.extractTx(_tx)
|
|
||||||
if (
|
|
||||||
this.hierarchy.isDerived(actualTx._class, core.class.TxCUD) &&
|
|
||||||
this.hierarchy.isDerived(_tx._class, core.class.TxCUD)
|
|
||||||
) {
|
|
||||||
const cud = actualTx as TxCUD<Doc>
|
|
||||||
if (this.classes.some((it) => this.hierarchy.isDerived(cud.objectClass, it))) {
|
|
||||||
// We need processing
|
|
||||||
result.push(
|
|
||||||
this.factory.createTxCreateDoc(plugin.class.AsyncTriggerState, plugin.space.TriggerState, {
|
|
||||||
tx: _tx as TxCUD<Doc>,
|
|
||||||
message: 'Processing...'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result.length > 0) {
|
|
||||||
await this.storage.apply(this.metrics, result, false, false)
|
|
||||||
this.processing = this.doProcessing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doProcessing (): Promise<void> {
|
|
||||||
while (!this.canceling) {
|
|
||||||
const docs = await this.storage.findAll(this.metrics, plugin.class.AsyncTriggerState, {}, { limit: 10 })
|
|
||||||
if (docs.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const doc of docs) {
|
|
||||||
const result: Tx[] = []
|
|
||||||
if (this.canceling) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (const f of this.functions) {
|
|
||||||
result.push(...(await f(doc.tx, this.control)))
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
await this.storage.apply(
|
|
||||||
this.metrics,
|
|
||||||
[this.factory.createTxRemoveDoc(doc._class, doc.space, doc._id)],
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
await this.storage.apply(this.metrics, result, true, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -58,7 +58,6 @@ import { FullTextIndex } from './fulltext'
|
|||||||
import { FullTextIndexPipeline } from './indexer'
|
import { FullTextIndexPipeline } from './indexer'
|
||||||
import { FullTextPipelineStage } from './indexer/types'
|
import { FullTextPipelineStage } from './indexer/types'
|
||||||
import serverCore from './plugin'
|
import serverCore from './plugin'
|
||||||
import { AsyncTriggerProcessor } from './processor'
|
|
||||||
import { Triggers } from './triggers'
|
import { Triggers } from './triggers'
|
||||||
import type {
|
import type {
|
||||||
ContentAdapterFactory,
|
ContentAdapterFactory,
|
||||||
@ -68,7 +67,7 @@ import type {
|
|||||||
ObjectDDParticipant,
|
ObjectDDParticipant,
|
||||||
TriggerControl
|
TriggerControl
|
||||||
} from './types'
|
} from './types'
|
||||||
import { createCacheFindAll } from './utils'
|
import { createFindAll } from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -104,7 +103,6 @@ export interface DbConfiguration {
|
|||||||
class TServerStorage implements ServerStorage {
|
class TServerStorage implements ServerStorage {
|
||||||
private readonly fulltext: FullTextIndex
|
private readonly fulltext: FullTextIndex
|
||||||
hierarchy: Hierarchy
|
hierarchy: Hierarchy
|
||||||
triggerProcessor: AsyncTriggerProcessor
|
|
||||||
|
|
||||||
scopes = new Map<string, Promise<any>>()
|
scopes = new Map<string, Promise<any>>()
|
||||||
|
|
||||||
@ -124,15 +122,11 @@ class TServerStorage implements ServerStorage {
|
|||||||
) {
|
) {
|
||||||
this.hierarchy = hierarchy
|
this.hierarchy = hierarchy
|
||||||
this.fulltext = indexFactory(this)
|
this.fulltext = indexFactory(this)
|
||||||
this.triggerProcessor = new AsyncTriggerProcessor(modelDb, hierarchy, this, metrics.newChild('triggers', {}))
|
|
||||||
void this.triggerProcessor.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async close (): Promise<void> {
|
async close (): Promise<void> {
|
||||||
console.timeLog(this.workspace.name, 'closing')
|
console.timeLog(this.workspace.name, 'closing')
|
||||||
await this.fulltext.close()
|
await this.fulltext.close()
|
||||||
console.timeLog(this.workspace.name, 'closing triggers')
|
|
||||||
await this.triggerProcessor.cancel()
|
|
||||||
console.timeLog(this.workspace.name, 'closing adapters')
|
console.timeLog(this.workspace.name, 'closing adapters')
|
||||||
for (const o of this.adapters.values()) {
|
for (const o of this.adapters.values()) {
|
||||||
await o.close()
|
await o.close()
|
||||||
@ -525,13 +519,15 @@ class TServerStorage implements ServerStorage {
|
|||||||
},
|
},
|
||||||
findAll: fAll(ctx),
|
findAll: fAll(ctx),
|
||||||
modelDb: this.modelDb,
|
modelDb: this.modelDb,
|
||||||
hierarchy: this.hierarchy
|
hierarchy: this.hierarchy,
|
||||||
|
apply: async (tx, broadcast) => {
|
||||||
|
await this.apply(ctx, tx, broadcast)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const triggers = await ctx.with('process-triggers', {}, async (ctx) => {
|
const triggers = await ctx.with('process-triggers', {}, async (ctx) => {
|
||||||
const result: Tx[] = []
|
const result: Tx[] = []
|
||||||
for (const tx of txes) {
|
for (const tx of txes) {
|
||||||
result.push(...(await this.triggers.apply(tx.modifiedBy, tx, triggerControl)))
|
result.push(...(await this.triggers.apply(tx.modifiedBy, tx, triggerControl)))
|
||||||
await ctx.with('async-triggers', {}, (ctx) => this.triggerProcessor.tx([tx]))
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
@ -597,26 +593,14 @@ class TServerStorage implements ServerStorage {
|
|||||||
return { passed, onEnd }
|
return { passed, onEnd }
|
||||||
}
|
}
|
||||||
|
|
||||||
async apply (ctx: MeasureContext, tx: Tx[], broadcast: boolean, updateTx: boolean): Promise<Tx[]> {
|
async apply (ctx: MeasureContext, tx: Tx[], broadcast: boolean): Promise<Tx[]> {
|
||||||
const triggerFx = new Effects()
|
const triggerFx = new Effects()
|
||||||
const cacheFind = createCacheFindAll(this)
|
const _findAll = createFindAll(this)
|
||||||
|
|
||||||
const txToStore = tx.filter(
|
const txToStore = tx.filter(
|
||||||
(it) => it.space !== core.space.DerivedTx && !this.hierarchy.isDerived(it._class, core.class.TxApplyIf)
|
(it) => it.space !== core.space.DerivedTx && !this.hierarchy.isDerived(it._class, core.class.TxApplyIf)
|
||||||
)
|
)
|
||||||
if (updateTx) {
|
|
||||||
const ops = new Map(
|
|
||||||
tx
|
|
||||||
.filter((it) => it._class === core.class.TxUpdateDoc)
|
|
||||||
.map((it) => [(it as TxUpdateDoc<Tx>).objectId, (it as TxUpdateDoc<Tx>).operations])
|
|
||||||
)
|
|
||||||
|
|
||||||
if (ops.size > 0) {
|
|
||||||
await ctx.with('domain-tx-update', {}, async () => await this.getAdapter(DOMAIN_TX).update(DOMAIN_TX, ops))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await ctx.with('domain-tx', {}, async () => await this.getAdapter(DOMAIN_TX).tx(...txToStore))
|
await ctx.with('domain-tx', {}, async () => await this.getAdapter(DOMAIN_TX).tx(...txToStore))
|
||||||
}
|
|
||||||
|
|
||||||
const removedMap = new Map<Ref<Doc>, Doc>()
|
const removedMap = new Map<Ref<Doc>, Doc>()
|
||||||
await ctx.with('apply', {}, (ctx) => this.routeTx(ctx, removedMap, ...tx))
|
await ctx.with('apply', {}, (ctx) => this.routeTx(ctx, removedMap, ...tx))
|
||||||
@ -626,7 +610,7 @@ class TServerStorage implements ServerStorage {
|
|||||||
this.options?.broadcast?.(tx)
|
this.options?.broadcast?.(tx)
|
||||||
}
|
}
|
||||||
// invoke triggers and store derived objects
|
// invoke triggers and store derived objects
|
||||||
const derived = await this.processDerived(ctx, tx, triggerFx, cacheFind, removedMap)
|
const derived = await this.processDerived(ctx, tx, triggerFx, _findAll, removedMap)
|
||||||
|
|
||||||
// index object
|
// index object
|
||||||
for (const _tx of tx) {
|
for (const _tx of tx) {
|
||||||
@ -641,13 +625,16 @@ class TServerStorage implements ServerStorage {
|
|||||||
for (const fx of triggerFx.effects) {
|
for (const fx of triggerFx.effects) {
|
||||||
await fx()
|
await fx()
|
||||||
}
|
}
|
||||||
|
if (broadcast && derived.length > 0) {
|
||||||
|
this.options?.broadcast?.(derived)
|
||||||
|
}
|
||||||
return [...tx, ...derived]
|
return [...tx, ...derived]
|
||||||
}
|
}
|
||||||
|
|
||||||
async tx (ctx: MeasureContext, tx: Tx): Promise<[TxResult, Tx[]]> {
|
async tx (ctx: MeasureContext, tx: Tx): Promise<[TxResult, Tx[]]> {
|
||||||
// store tx
|
// store tx
|
||||||
const _class = txClass(tx)
|
const _class = txClass(tx)
|
||||||
const cacheFind = createCacheFindAll(this)
|
const _findAll = createFindAll(this)
|
||||||
const objClass = txObjectClass(tx)
|
const objClass = txObjectClass(tx)
|
||||||
const removedDocs = new Map<Ref<Doc>, Doc>()
|
const removedDocs = new Map<Ref<Doc>, Doc>()
|
||||||
return await ctx.with('tx', { _class, objClass }, async (ctx) => {
|
return await ctx.with('tx', { _class, objClass }, async (ctx) => {
|
||||||
@ -673,7 +660,7 @@ class TServerStorage implements ServerStorage {
|
|||||||
const applyIf = tx as TxApplyIf
|
const applyIf = tx as TxApplyIf
|
||||||
// Wait for scope promise if found
|
// Wait for scope promise if found
|
||||||
let passed: boolean
|
let passed: boolean
|
||||||
;({ passed, onEnd } = await this.verifyApplyIf(ctx, applyIf, cacheFind))
|
;({ passed, onEnd } = await this.verifyApplyIf(ctx, applyIf, _findAll))
|
||||||
result = passed
|
result = passed
|
||||||
if (passed) {
|
if (passed) {
|
||||||
// Store apply if transaction's if required
|
// Store apply if transaction's if required
|
||||||
@ -681,13 +668,13 @@ class TServerStorage implements ServerStorage {
|
|||||||
const atx = await this.getAdapter(DOMAIN_TX)
|
const atx = await this.getAdapter(DOMAIN_TX)
|
||||||
await atx.tx(...applyIf.txes)
|
await atx.tx(...applyIf.txes)
|
||||||
})
|
})
|
||||||
derived = await this.processDerivedTxes(applyIf.txes, ctx, triggerFx, cacheFind, removedDocs)
|
derived = await this.processDerivedTxes(applyIf.txes, ctx, triggerFx, _findAll, removedDocs)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// store object
|
// store object
|
||||||
result = await ctx.with('route-tx', { _class, objClass }, (ctx) => this.routeTx(ctx, removedDocs, tx))
|
result = await ctx.with('route-tx', { _class, objClass }, (ctx) => this.routeTx(ctx, removedDocs, tx))
|
||||||
// invoke triggers and store derived objects
|
// invoke triggers and store derived objects
|
||||||
derived = await this.processDerived(ctx, [tx], triggerFx, cacheFind, removedDocs)
|
derived = await this.processDerived(ctx, [tx], triggerFx, _findAll, removedDocs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// index object
|
// index object
|
||||||
|
@ -14,8 +14,18 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Tx, Doc, TxCreateDoc, Ref, Account, TxCollectionCUD, AttachedDoc } from '@hcengineering/core'
|
import core, {
|
||||||
import core, { TxFactory } from '@hcengineering/core'
|
Tx,
|
||||||
|
Doc,
|
||||||
|
TxCreateDoc,
|
||||||
|
Ref,
|
||||||
|
Account,
|
||||||
|
TxCollectionCUD,
|
||||||
|
AttachedDoc,
|
||||||
|
DocumentQuery,
|
||||||
|
matchQuery,
|
||||||
|
TxFactory
|
||||||
|
} from '@hcengineering/core'
|
||||||
|
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import type { Trigger, TriggerFunc, TriggerControl } from './types'
|
import type { Trigger, TriggerFunc, TriggerControl } from './types'
|
||||||
@ -26,7 +36,7 @@ import serverCore from './plugin'
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export class Triggers {
|
export class Triggers {
|
||||||
private readonly triggers: TriggerFunc[] = []
|
private readonly triggers: [DocumentQuery<Tx> | undefined, TriggerFunc][] = []
|
||||||
|
|
||||||
async tx (tx: Tx): Promise<void> {
|
async tx (tx: Tx): Promise<void> {
|
||||||
if (tx._class === core.class.TxCollectionCUD) {
|
if (tx._class === core.class.TxCollectionCUD) {
|
||||||
@ -36,14 +46,18 @@ export class Triggers {
|
|||||||
const createTx = tx as TxCreateDoc<Doc>
|
const createTx = tx as TxCreateDoc<Doc>
|
||||||
if (createTx.objectClass === serverCore.class.Trigger) {
|
if (createTx.objectClass === serverCore.class.Trigger) {
|
||||||
const trigger = (createTx as TxCreateDoc<Trigger>).attributes.trigger
|
const trigger = (createTx as TxCreateDoc<Trigger>).attributes.trigger
|
||||||
|
const match = (createTx as TxCreateDoc<Trigger>).attributes.txMatch
|
||||||
const func = await getResource(trigger)
|
const func = await getResource(trigger)
|
||||||
this.triggers.push(func)
|
this.triggers.push([match, func])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async apply (account: Ref<Account>, tx: Tx, ctrl: Omit<TriggerControl, 'txFactory'>): Promise<Tx[]> {
|
async apply (account: Ref<Account>, tx: Tx, ctrl: Omit<TriggerControl, 'txFactory'>): Promise<Tx[]> {
|
||||||
const derived = this.triggers.map((trigger) => trigger(tx, { ...ctrl, txFactory: new TxFactory(account, true) }))
|
const control = { ...ctrl, txFactory: new TxFactory(account, true) }
|
||||||
|
const derived = this.triggers
|
||||||
|
.filter(([query]) => query === undefined || matchQuery([tx], query, core.class.Tx, control.hierarchy).length > 0)
|
||||||
|
.map(([, trigger]) => trigger(tx, control))
|
||||||
const result = await Promise.all(derived)
|
const result = await Promise.all(derived)
|
||||||
return result.flatMap((x) => x)
|
return result.flatMap((x) => x)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ import {
|
|||||||
Storage,
|
Storage,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
Tx,
|
Tx,
|
||||||
TxCUD,
|
|
||||||
TxFactory,
|
TxFactory,
|
||||||
TxResult,
|
TxResult,
|
||||||
WorkspaceId
|
WorkspaceId
|
||||||
@ -104,6 +103,9 @@ export interface TriggerControl {
|
|||||||
// Later can be replaced with generic one with bucket encapsulated inside.
|
// Later can be replaced with generic one with bucket encapsulated inside.
|
||||||
storageFx: (f: (adapter: MinioService, workspaceId: WorkspaceId) => Promise<void>) => void
|
storageFx: (f: (adapter: MinioService, workspaceId: WorkspaceId) => Promise<void>) => void
|
||||||
fx: (f: () => Promise<void>) => void
|
fx: (f: () => Promise<void>) => void
|
||||||
|
|
||||||
|
// Bulk operations in case trigger require some
|
||||||
|
apply: (tx: Tx[], broadcast: boolean) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,42 +113,14 @@ export interface TriggerControl {
|
|||||||
*/
|
*/
|
||||||
export type TriggerFunc = (tx: Tx, ctrl: TriggerControl) => Promise<Tx[]>
|
export type TriggerFunc = (tx: Tx, ctrl: TriggerControl) => Promise<Tx[]>
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface AsyncTriggerControl {
|
|
||||||
txFactory: TxFactory
|
|
||||||
findAll: Storage['findAll']
|
|
||||||
apply: (tx: Tx[], broadcast: boolean, updateTx: boolean) => Promise<void>
|
|
||||||
hierarchy: Hierarchy
|
|
||||||
modelDb: ModelDb
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export type AsyncTriggerFunc = (tx: Tx, ctrl: AsyncTriggerControl) => Promise<Tx[]>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface Trigger extends Doc {
|
export interface Trigger extends Doc {
|
||||||
trigger: Resource<TriggerFunc>
|
trigger: Resource<TriggerFunc>
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// We should match transaction
|
||||||
* @public
|
txMatch?: DocumentQuery<Tx>
|
||||||
*/
|
|
||||||
export interface AsyncTrigger extends Doc {
|
|
||||||
trigger: Resource<AsyncTriggerFunc>
|
|
||||||
classes: Ref<Class<Doc>>[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export interface AsyncTriggerState extends Doc {
|
|
||||||
tx: TxCUD<Doc>
|
|
||||||
message: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,23 +12,13 @@ import {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function createCacheFindAll (storage: ServerStorage): ServerStorage['findAll'] {
|
export function createFindAll (storage: ServerStorage): ServerStorage['findAll'] {
|
||||||
// We will cache all queries for same objects for all derived data checks.
|
|
||||||
const queryCache = new Map<string, FindResult<Doc>>()
|
|
||||||
|
|
||||||
return async <T extends Doc>(
|
return async <T extends Doc>(
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
clazz: Ref<Class<T>>,
|
clazz: Ref<Class<T>>,
|
||||||
query: DocumentQuery<T>,
|
query: DocumentQuery<T>,
|
||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
): Promise<FindResult<T>> => {
|
): Promise<FindResult<T>> => {
|
||||||
const key = JSON.stringify(clazz) + JSON.stringify(query) + JSON.stringify(options)
|
return await storage.findAll(ctx, clazz, query, options)
|
||||||
let cacheResult = queryCache.get(key)
|
|
||||||
if (cacheResult !== undefined) {
|
|
||||||
return cacheResult as FindResult<T>
|
|
||||||
}
|
|
||||||
cacheResult = await storage.findAll(ctx, clazz, query, options)
|
|
||||||
queryCache.set(key, cacheResult)
|
|
||||||
return storage.hierarchy.clone(cacheResult) as FindResult<T>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,6 @@ import core, {
|
|||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import type { IntlString, Plugin } from '@hcengineering/platform'
|
import type { IntlString, Plugin } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import server from '@hcengineering/server-core'
|
|
||||||
|
|
||||||
export const txFactory = new TxFactory(core.account.System)
|
export const txFactory = new TxFactory(core.account.System)
|
||||||
|
|
||||||
@ -126,22 +125,7 @@ export function genMinModel (): TxCUD<Doc>[] {
|
|||||||
domain: DOMAIN_DOC_INDEX_STATE
|
domain: DOMAIN_DOC_INDEX_STATE
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
txes.push(
|
|
||||||
createClass(server.class.AsyncTrigger, {
|
|
||||||
label: 'AsyncTrigger' as IntlString,
|
|
||||||
extends: core.class.Doc,
|
|
||||||
kind: ClassifierKind.CLASS,
|
|
||||||
domain: DOMAIN_MODEL
|
|
||||||
})
|
|
||||||
)
|
|
||||||
txes.push(
|
|
||||||
createClass(server.class.AsyncTriggerState, {
|
|
||||||
label: 'AsyncTriggerState' as IntlString,
|
|
||||||
extends: core.class.Doc,
|
|
||||||
kind: ClassifierKind.CLASS,
|
|
||||||
domain: DOMAIN_DOC_INDEX_STATE
|
|
||||||
})
|
|
||||||
)
|
|
||||||
txes.push(
|
txes.push(
|
||||||
createClass(core.class.Account, {
|
createClass(core.class.Account, {
|
||||||
label: 'Account' as IntlString,
|
label: 'Account' as IntlString,
|
||||||
|
@ -542,7 +542,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
|||||||
|
|
||||||
find (domain: Domain): StorageIterator {
|
find (domain: Domain): StorageIterator {
|
||||||
const coll = this.db.collection<Doc>(domain)
|
const coll = this.db.collection<Doc>(domain)
|
||||||
const iterator = coll.find({}, {})
|
const iterator = coll.find({}, {}).batchSize(100)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
next: async () => {
|
next: async () => {
|
||||||
|
@ -65,7 +65,7 @@ export class BackupClientSession extends ClientSession implements BackupSession
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
size = size + doc.id.length + doc.hash.length + doc.size
|
size = size + doc.size
|
||||||
docs[doc.id] = doc.hash
|
docs[doc.id] = doc.hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,7 +355,7 @@ async function handleRequest<S extends Session> (
|
|||||||
}
|
}
|
||||||
}, 30000)
|
}, 30000)
|
||||||
|
|
||||||
const result = await f.apply(service, params)
|
let result = await f.apply(service, params)
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
clearTimeout(hangTimeout)
|
clearTimeout(hangTimeout)
|
||||||
const resp: Response<any> = { id: request.id, result }
|
const resp: Response<any> = { id: request.id, result }
|
||||||
@ -373,7 +373,11 @@ async function handleRequest<S extends Session> (
|
|||||||
diff
|
diff
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ws.send(serialize(resp))
|
const toSend = serialize(resp)
|
||||||
|
// Clear for gc to make work
|
||||||
|
resp.result = undefined
|
||||||
|
result = undefined
|
||||||
|
ws.send(toSend)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (LOGGING_ENABLED) console.error(err)
|
if (LOGGING_ENABLED) console.error(err)
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
@ -427,7 +431,7 @@ export function start (
|
|||||||
})
|
})
|
||||||
const session = await sessions.addSession(ctx, ws, token, pipelineFactory, productId, sessionId)
|
const session = await sessions.addSession(ctx, ws, token, pipelineFactory, productId, sessionId)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
ws.on('message', async (msg: RawData) => {
|
ws.on('message', (msg: RawData) => {
|
||||||
let msgStr = ''
|
let msgStr = ''
|
||||||
if (typeof msg === 'string') {
|
if (typeof msg === 'string') {
|
||||||
msgStr = msg
|
msgStr = msg
|
||||||
@ -436,7 +440,7 @@ export function start (
|
|||||||
} else if (Array.isArray(msg)) {
|
} else if (Array.isArray(msg)) {
|
||||||
msgStr = Buffer.concat(msg).toString()
|
msgStr = Buffer.concat(msg).toString()
|
||||||
}
|
}
|
||||||
await handleRequest(ctx, session, ws, msgStr, token.workspace.name)
|
void handleRequest(ctx, session, ws, msgStr, token.workspace.name)
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
ws.on('close', (code: number, reason: Buffer) => {
|
ws.on('close', (code: number, reason: Buffer) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user