mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-30 04:05:39 +00:00
UBER-30 Notify user when added to collaborators (#3200)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
72f1570b7f
commit
4949142b19
@ -32,6 +32,7 @@
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-preference": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.6",
|
||||
"@hcengineering/workbench": "^0.6.6",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
|
@ -52,6 +52,7 @@ import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import notification from './plugin'
|
||||
import activity from '@hcengineering/activity'
|
||||
|
||||
export { notificationId } from '@hcengineering/notification'
|
||||
export { notificationOperation } from './migration'
|
||||
@ -301,6 +302,48 @@ export function createModel (builder: Builder): void {
|
||||
mode: ['workbench', 'browser', 'editor', 'panel', 'popup']
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationGroup,
|
||||
core.space.Model,
|
||||
{
|
||||
label: notification.string.Notifications,
|
||||
icon: notification.icon.Notifications
|
||||
},
|
||||
notification.ids.NotificationGroup
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
generated: false,
|
||||
label: notification.string.Collaborators,
|
||||
group: notification.ids.NotificationGroup,
|
||||
txClasses: [],
|
||||
objectClass: notification.mixin.Collaborators,
|
||||
providers: {
|
||||
[notification.providers.PlatformNotification]: true
|
||||
}
|
||||
},
|
||||
notification.ids.CollaboratoAddNotification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: notification.mixin.Collaborators,
|
||||
icon: notification.icon.Notifications,
|
||||
txClass: core.class.TxMixin,
|
||||
component: notification.activity.TxCollaboratorsChange,
|
||||
display: 'inline',
|
||||
editable: false,
|
||||
hideOnRemove: true
|
||||
},
|
||||
notification.ids.TxCollaboratorsChange
|
||||
)
|
||||
}
|
||||
|
||||
export function generateClassNotificationTypes (
|
||||
|
@ -20,6 +20,7 @@ import { IntlString, Resource, mergeIds } from '@hcengineering/platform'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Action, ActionCategory, ViewAction } from '@hcengineering/view'
|
||||
import { Application } from '@hcengineering/workbench'
|
||||
import { TxViewlet } from '@hcengineering/activity'
|
||||
|
||||
export default mergeIds(notificationId, notification, {
|
||||
string: {
|
||||
@ -33,6 +34,12 @@ export default mergeIds(notificationId, notification, {
|
||||
app: {
|
||||
Notification: '' as Ref<Application>
|
||||
},
|
||||
activity: {
|
||||
TxCollaboratorsChange: '' as AnyComponent
|
||||
},
|
||||
ids: {
|
||||
TxCollaboratorsChange: '' as Ref<TxViewlet>
|
||||
},
|
||||
component: {
|
||||
NotificationSettings: '' as AnyComponent
|
||||
},
|
||||
|
@ -351,13 +351,13 @@
|
||||
</div>
|
||||
{:else if hasMessageType && model.length > 0 && (tx.updateTx || tx.mixinTx)}
|
||||
{#await getValue(client, model[0], tx) then value}
|
||||
{@const compareValue = getPrevValue(client, model[0], tx)}
|
||||
{@const prevValue = getPrevValue(client, model[0], tx)}
|
||||
<div class="activity-content content" class:indent={isAttached} class:contentHidden>
|
||||
<ShowMore ignore={edit || compareValue !== undefined}>
|
||||
<ShowMore ignore={edit || prevValue !== undefined}>
|
||||
{#if value.isObjectSet}
|
||||
<ObjectPresenter value={value.set} inline />
|
||||
{:else if showDiff}
|
||||
<svelte:component this={model[0].presenter} value={value.set} inline {compareValue} showOnlyDiff />
|
||||
<svelte:component this={model[0].presenter} value={value.set} inline {prevValue} showOnlyDiff />
|
||||
{/if}
|
||||
</ShowMore>
|
||||
</div>
|
||||
|
@ -77,7 +77,7 @@ async function createPseudoViewlet (
|
||||
}
|
||||
|
||||
export function getDTxProps (dtx: DisplayTx): any {
|
||||
return { tx: dtx.tx, value: dtx.doc, isOwnTx: dtx.isOwnTx }
|
||||
return { tx: dtx.tx, value: dtx.doc, isOwnTx: dtx.isOwnTx, prevValue: dtx.prevDoc }
|
||||
}
|
||||
|
||||
function getViewlet (viewlets: Map<ActivityKey, TxViewlet>, dtx: DisplayTx): TxDisplayViewlet | undefined {
|
||||
|
@ -17,6 +17,8 @@
|
||||
"Inbox": "Inbox",
|
||||
"Collaborators": "Collaborators",
|
||||
"Change": "Change",
|
||||
"AddedRemoved": "Added/removed"
|
||||
"AddedRemoved": "Added/removed",
|
||||
"YouAddedCollaborators": "You was added to collaborators",
|
||||
"ChangeCollaborators": "changed collaborators"
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
"Inbox": "Входящие",
|
||||
"Collaborators": "Участники",
|
||||
"Change": "Изменено",
|
||||
"AddedRemoved": "Добавлено/удалено"
|
||||
"AddedRemoved": "Добавлено/удалено",
|
||||
"YouAddedCollaborators": "Вы были добавлены как участник",
|
||||
"ChangeCollaborators": "изменил(а) участники"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { EmployeeAccount } from '@hcengineering/contact'
|
||||
import { EmployeeAccountRefPresenter } from '@hcengineering/contact-resources'
|
||||
import { Doc, Ref, TxMixin } from '@hcengineering/core'
|
||||
import { Collaborators } from '@hcengineering/notification'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { IconAdd, IconDelete, Label } from '@hcengineering/ui'
|
||||
import notification from '../../plugin'
|
||||
|
||||
export let tx: TxMixin<Doc, Collaborators>
|
||||
export let value: Collaborators
|
||||
export let prevValue: Collaborators | undefined = undefined
|
||||
|
||||
interface Diff {
|
||||
added: Ref<EmployeeAccount>[]
|
||||
removed: Ref<EmployeeAccount>[]
|
||||
}
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
function buildDiff (value: Collaborators, prev: Collaborators | undefined): Diff | undefined {
|
||||
if (prev === undefined) return
|
||||
const added: Ref<EmployeeAccount>[] = []
|
||||
const removed: Ref<EmployeeAccount>[] = []
|
||||
const mixin = hierarchy.as(value, notification.mixin.Collaborators)
|
||||
const prevMixin = hierarchy.as(prev, notification.mixin.Collaborators)
|
||||
const prevSet = new Set(prevMixin?.collaborators ?? [])
|
||||
const newSet = new Set(mixin.collaborators)
|
||||
|
||||
for (const newCollab of mixin.collaborators) {
|
||||
if (!prevSet.has(newCollab)) added.push(newCollab as Ref<EmployeeAccount>)
|
||||
}
|
||||
|
||||
for (const oldCollab of prevMixin?.collaborators ?? []) {
|
||||
if (!newSet.has(oldCollab)) removed.push(oldCollab as Ref<EmployeeAccount>)
|
||||
}
|
||||
|
||||
return {
|
||||
added,
|
||||
removed
|
||||
}
|
||||
}
|
||||
|
||||
$: diff = buildDiff(value, prevValue)
|
||||
</script>
|
||||
|
||||
{#if diff}
|
||||
<div class="flex-presenter">
|
||||
<Label label={notification.string.ChangeCollaborators} />
|
||||
{#if diff.added.length > 0}
|
||||
<IconAdd size={'x-small'} fill={'var(--theme-trans-color)'} />
|
||||
{#each diff.added as add}
|
||||
<EmployeeAccountRefPresenter value={add} disabled />
|
||||
{/each}
|
||||
{/if}
|
||||
{#if diff.removed.length > 0}
|
||||
<IconDelete size={'x-small'} fill={'var(--theme-trans-color)'} />
|
||||
{#each diff.removed as removed}
|
||||
<EmployeeAccountRefPresenter value={removed} disabled />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<Label label={notification.string.YouAddedCollaborators} />
|
||||
{/if}
|
@ -18,6 +18,7 @@ import { Resources } from '@hcengineering/platform'
|
||||
import Inbox from './components/Inbox.svelte'
|
||||
import NotificationSettings from './components/NotificationSettings.svelte'
|
||||
import NotificationPresenter from './components/NotificationPresenter.svelte'
|
||||
import TxCollaboratorsChange from './components/activity/TxCollaboratorsChange.svelte'
|
||||
import { NotificationClientImpl, hasntNotifications, hide, markAsUnread, unsubscribe } from './utils'
|
||||
|
||||
export * from './utils'
|
||||
@ -30,6 +31,9 @@ export default async (): Promise<Resources> => ({
|
||||
NotificationPresenter,
|
||||
NotificationSettings
|
||||
},
|
||||
activity: {
|
||||
TxCollaboratorsChange
|
||||
},
|
||||
function: {
|
||||
GetNotificationClient: NotificationClientImpl.getClient,
|
||||
HasntNotifications: hasntNotifications
|
||||
|
@ -28,6 +28,8 @@ export default mergeIds(notificationId, notification, {
|
||||
MarkAsRead: '' as IntlString,
|
||||
MarkAllAsRead: '' as IntlString,
|
||||
Change: '' as IntlString,
|
||||
AddedRemoved: '' as IntlString
|
||||
AddedRemoved: '' as IntlString,
|
||||
YouAddedCollaborators: '' as IntlString,
|
||||
ChangeCollaborators: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -202,7 +202,9 @@ const notification = plugin(notificationId, {
|
||||
NotificationGroup: '' as Ref<Class<NotificationGroup>>
|
||||
},
|
||||
ids: {
|
||||
NotificationSettings: '' as Ref<Doc>
|
||||
NotificationSettings: '' as Ref<Doc>,
|
||||
NotificationGroup: '' as Ref<NotificationGroup>,
|
||||
CollaboratoAddNotification: '' as Ref<NotificationType>
|
||||
},
|
||||
providers: {
|
||||
PlatformNotification: '' as Ref<NotificationProvider>,
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { ShowMore } from '@hcengineering/ui'
|
||||
|
||||
export let value: string | undefined
|
||||
export let compareValue: string | undefined = undefined
|
||||
export let prevValue: string | undefined = undefined
|
||||
export let showOnlyDiff: boolean = false
|
||||
|
||||
function removeSimilarLines (str1: string | undefined, str2: string | undefined) {
|
||||
@ -35,14 +35,14 @@
|
||||
}
|
||||
}
|
||||
value = result1
|
||||
compareValue = result2
|
||||
prevValue = result2
|
||||
}
|
||||
|
||||
$: showOnlyDiff && removeSimilarLines(value, compareValue)
|
||||
$: showOnlyDiff && removeSimilarLines(value, prevValue)
|
||||
</script>
|
||||
|
||||
<ShowMore>
|
||||
{#key [value, compareValue]}
|
||||
<CollaborationDiffViewer content={value ?? ''} comparedVersion={compareValue ?? ''} noButton readonly />
|
||||
{#key [value, prevValue]}
|
||||
<CollaborationDiffViewer content={value ?? ''} comparedVersion={prevValue ?? ''} noButton readonly />
|
||||
{/key}
|
||||
</ShowMore>
|
||||
|
@ -421,6 +421,45 @@ async function isShouldNotify (
|
||||
}
|
||||
}
|
||||
|
||||
function pushNotification (
|
||||
control: TriggerControl,
|
||||
res: Tx[],
|
||||
target: Ref<Account>,
|
||||
object: Doc,
|
||||
originTx: TxCUD<Doc>,
|
||||
docUpdates: DocUpdates[]
|
||||
): void {
|
||||
const current = docUpdates.find((p) => p.user === target)
|
||||
if (current === undefined) {
|
||||
res.push(
|
||||
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, object.space, {
|
||||
user: target,
|
||||
attachedTo: object._id,
|
||||
attachedToClass: object._class,
|
||||
hidden: false,
|
||||
lastTx: originTx._id,
|
||||
lastTxTime: originTx.modifiedOn,
|
||||
txes: [[originTx._id, originTx.modifiedOn]]
|
||||
})
|
||||
)
|
||||
} else {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||
$push: {
|
||||
txes: [originTx._id, originTx.modifiedOn]
|
||||
}
|
||||
})
|
||||
)
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||
lastTx: originTx._id,
|
||||
lastTxTime: originTx.modifiedOn,
|
||||
hidden: false
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function getNotificationTxes (
|
||||
control: TriggerControl,
|
||||
object: Doc,
|
||||
@ -432,35 +471,7 @@ async function getNotificationTxes (
|
||||
const res: Tx[] = []
|
||||
const allowed = await isShouldNotify(control, originTx, object, target, isSpace)
|
||||
if (allowed.allowed) {
|
||||
const current = docUpdates.find((p) => p.user === target)
|
||||
if (current === undefined) {
|
||||
res.push(
|
||||
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, object.space, {
|
||||
user: target,
|
||||
attachedTo: object._id,
|
||||
attachedToClass: object._class,
|
||||
hidden: false,
|
||||
lastTx: originTx._id,
|
||||
lastTxTime: originTx.modifiedOn,
|
||||
txes: [[originTx._id, originTx.modifiedOn]]
|
||||
})
|
||||
)
|
||||
} else {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||
$push: {
|
||||
txes: [originTx._id, originTx.modifiedOn]
|
||||
}
|
||||
})
|
||||
)
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
|
||||
lastTx: originTx._id,
|
||||
lastTxTime: originTx.modifiedOn,
|
||||
hidden: false
|
||||
})
|
||||
)
|
||||
}
|
||||
pushNotification(control, res, target, object, originTx, docUpdates)
|
||||
}
|
||||
for (const type of allowed.emails) {
|
||||
const emailTx = await createEmailNotificationTxes(
|
||||
@ -572,9 +583,12 @@ export async function collaboratorDocHandler (
|
||||
if (tx.space === core.space.DerivedTx) return []
|
||||
return await createCollaboratorDoc(tx as TxCreateDoc<Doc>, control, originTx ?? tx)
|
||||
case core.class.TxUpdateDoc:
|
||||
case core.class.TxMixin:
|
||||
case core.class.TxMixin: {
|
||||
if (tx.space === core.space.DerivedTx) return []
|
||||
return await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx)
|
||||
let res = await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx)
|
||||
res = res.concat(await updateCollaboratorsMixin(tx as TxMixin<Doc, Collaborators>, control, originTx ?? tx))
|
||||
return res
|
||||
}
|
||||
case core.class.TxRemoveDoc:
|
||||
return await removeCollaboratorDoc(tx as TxRemoveDoc<Doc>, control)
|
||||
case core.class.TxCollectionCUD:
|
||||
@ -584,6 +598,60 @@ export async function collaboratorDocHandler (
|
||||
return []
|
||||
}
|
||||
|
||||
async function updateCollaboratorsMixin (
|
||||
tx: TxMixin<Doc, Collaborators>,
|
||||
control: TriggerControl,
|
||||
originTx: TxCUD<Doc>
|
||||
): Promise<Tx[]> {
|
||||
if (tx._class !== core.class.TxMixin) return []
|
||||
if (!control.hierarchy.isDerived(tx.mixin, notification.mixin.Collaborators)) return []
|
||||
const res: Tx[] = []
|
||||
if (tx.attributes.collaborators !== undefined) {
|
||||
const createTx = control.hierarchy.isDerived(tx.objectClass, core.class.AttachedDoc)
|
||||
? (
|
||||
await control.findAll(core.class.TxCollectionCUD, {
|
||||
'tx.objectId': tx.objectId,
|
||||
'tx._class': core.class.TxCreateDoc
|
||||
})
|
||||
)[0]
|
||||
: (
|
||||
await control.findAll(core.class.TxCreateDoc, {
|
||||
objectId: tx.objectId
|
||||
})
|
||||
)[0]
|
||||
const mixinTxes = await control.findAll(core.class.TxMixin, {
|
||||
objectId: tx.objectId
|
||||
})
|
||||
const prevDoc = TxProcessor.buildDoc2Doc([createTx, ...mixinTxes]) as Collaborators
|
||||
const set = new Set(prevDoc?.collaborators ?? [])
|
||||
const newCollabs: Ref<Account>[] = []
|
||||
for (const collab of tx.attributes.collaborators) {
|
||||
if (!set.has(collab)) {
|
||||
if (
|
||||
await isAllowed(
|
||||
control,
|
||||
collab as Ref<EmployeeAccount>,
|
||||
notification.ids.CollaboratoAddNotification,
|
||||
notification.providers.PlatformNotification
|
||||
)
|
||||
) {
|
||||
newCollabs.push(collab)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newCollabs.length > 0) {
|
||||
const docUpdates = await control.findAll(notification.class.DocUpdates, {
|
||||
user: { $in: newCollabs },
|
||||
attachedTo: tx.objectId
|
||||
})
|
||||
for (const collab of newCollabs) {
|
||||
pushNotification(control, res, collab, prevDoc, originTx, docUpdates)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
async function collectionCollabDoc (tx: TxCollectionCUD<Doc, AttachedDoc>, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
let res = await collaboratorDocHandler(actualTx as TxCUD<Doc>, control, tx)
|
||||
|
Loading…
Reference in New Issue
Block a user