UBERF-5490 (#4650)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2024-02-15 21:19:33 +06:00 committed by GitHub
parent 96b81eabef
commit e8084b3110
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 685 additions and 309 deletions

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
"preformat-svelte": "prettier -w src/**/*.svelte",
"lint": "",
"lint:fix": "yarn preformat-svelte && eslint --fix src",
"format": "",
"format": "format src",
"deploy": "cp -p public/* dist && aws s3 sync dist s3://anticrm-platform --delete --acl public-read"
},
"devDependencies": {

View File

@ -16,34 +16,33 @@
import activity from '@hcengineering/activity'
import {
type AvatarProvider,
AvatarType,
contactId,
type AvatarProvider,
type Channel,
type ChannelProvider,
type Contact,
type ContactsTab,
type Employee,
type PersonAccount,
type GetAvatarUrl,
type Member,
type Organization,
type Organizations,
type Person,
type PersonAccount,
type Persons,
type Status,
contactId
type Status
} from '@hcengineering/contact'
import {
type Class,
DOMAIN_MODEL,
DateRangeMode,
type Domain,
IndexKind,
type Class,
type Domain,
type Ref,
type Timestamp
} from '@hcengineering/core'
import {
type Builder,
Collection,
Hidden,
Index,
@ -51,26 +50,28 @@ import {
Model,
Prop,
ReadOnly,
TypeAttachment,
TypeBoolean,
TypeDate,
TypeRef,
TypeString,
TypeTimestamp,
TypeAttachment,
UX
UX,
type Builder
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
import core, { TAccount, TAttachedDoc, TDoc, TSpace } from '@hcengineering/model-core'
import presentation from '@hcengineering/model-presentation'
import view, { type ViewAction, type Viewlet, createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import presentation from '@hcengineering/model-presentation'
import view, { createAction, type Viewlet } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
import setting from '@hcengineering/setting'
import templates from '@hcengineering/templates'
import { type AnyComponent } from '@hcengineering/ui/src/types'
import { type Action } from '@hcengineering/view'
import contact from './plugin'
export { contactId } from '@hcengineering/contact'
@ -90,7 +91,7 @@ export class TAvatarProvider extends TDoc implements AvatarProvider {
export class TChannelProvider extends TDoc implements ChannelProvider {
label!: IntlString
icon?: Asset
action?: ViewAction
action?: Ref<Action>
placeholder!: IntlString
}
@ -574,7 +575,7 @@ export function createModel (builder: Builder): void {
label: contact.string.LinkedIn,
icon: contact.icon.LinkedIn,
placeholder: contact.string.LinkedInPlaceholder,
action: contact.actionImpl.OpenChannel
action: contact.action.OpenChannel
},
contact.channelProvider.LinkedIn
)
@ -586,7 +587,7 @@ export function createModel (builder: Builder): void {
label: contact.string.Twitter,
icon: contact.icon.Twitter,
placeholder: contact.string.AtPlaceHolder,
action: contact.actionImpl.OpenChannel
action: contact.action.OpenChannel
},
contact.channelProvider.Twitter
)
@ -598,7 +599,7 @@ export function createModel (builder: Builder): void {
label: contact.string.GitHub,
icon: contact.icon.GitHub,
placeholder: contact.string.AtPlaceHolder,
action: contact.actionImpl.OpenChannel
action: contact.action.OpenChannel
},
contact.channelProvider.GitHub
)
@ -610,7 +611,7 @@ export function createModel (builder: Builder): void {
label: contact.string.Facebook,
icon: contact.icon.Facebook,
placeholder: contact.string.FacebookPlaceholder,
action: contact.actionImpl.OpenChannel
action: contact.action.OpenChannel
},
contact.channelProvider.Facebook
)
@ -622,7 +623,7 @@ export function createModel (builder: Builder): void {
label: contact.string.Homepage,
icon: contact.icon.Homepage,
placeholder: contact.string.HomepagePlaceholder,
action: contact.actionImpl.OpenChannel
action: contact.action.OpenChannel
},
contact.channelProvider.Homepage
)
@ -656,7 +657,7 @@ export function createModel (builder: Builder): void {
label: contact.string.Profile,
icon: contact.icon.Profile,
placeholder: contact.string.ProfilePlaceholder,
action: contact.actionImpl.OpenChannel
action: contact.action.OpenChannel
},
contact.channelProvider.Profile
)
@ -839,6 +840,21 @@ export function createModel (builder: Builder): void {
contact.action.KickEmployee
)
createAction(
builder,
{
action: contact.actionImpl.OpenChannel,
category: contact.category.Channel,
label: contact.string.Channel,
input: 'none',
context: {
mode: ['none']
},
target: contact.class.Channel
},
contact.action.OpenChannel
)
createAction(
builder,
{

View File

@ -109,7 +109,8 @@ export default mergeIds(contactId, contact, {
OrganizationCategory: '' as Ref<ObjectSearchCategory>
},
category: {
Contact: '' as Ref<ActionCategory>
Contact: '' as Ref<ActionCategory>,
Channel: '' as Ref<ActionCategory>
},
ids: {
OrganizationNotificationGroup: '' as Ref<NotificationGroup>,
@ -121,7 +122,8 @@ export default mergeIds(contactId, contact, {
action: {
KickEmployee: '' as Ref<Action>,
DeleteEmployee: '' as Ref<Action>,
MergePersons: '' as Ref<Action<Doc, any>>
MergePersons: '' as Ref<Action<Doc, any>>,
OpenChannel: '' as Ref<Action>
},
actionImpl: {
KickEmployee: '' as ViewAction,

View File

@ -589,6 +589,15 @@ export function createModel (builder: Builder): void {
view.pipeline.PresentationMiddleware
)
builder.createDoc(
presentation.class.PresentationMiddlewareFactory,
core.space.Model,
{
createPresentationMiddleware: view.function.AnalyticsMiddleware
},
view.pipeline.AnalyticsMiddleware
)
createAction(
builder,
{

View File

@ -128,6 +128,7 @@ export default mergeIds(viewId, view, {
ShowEmptyGroups: '' as ViewCategoryAction
},
pipeline: {
PresentationMiddleware: '' as Ref<PresentationMiddlewareFactory>
PresentationMiddleware: '' as Ref<PresentationMiddlewareFactory>,
AnalyticsMiddleware: '' as Ref<PresentationMiddlewareFactory>
}
})

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}

View File

@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}

View File

@ -0,0 +1,37 @@
{
"name": "@hcengineering/analytics",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"test": "jest --passWithNoTests --silent",
"format": "format src",
"_phase:build": "compile",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"simplytyped": "^3.3.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"prettier-plugin-svelte": "^3.1.0"
},
"repository": "https://github.com/hcengineering/anticrm",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}

View File

@ -0,0 +1,53 @@
//
// Copyright © 2024 Hardcore Engineering Inc
//
export const providers: AnalyticProvider[] = []
export interface AnalyticProvider {
init: (config: Record<string, any>) => boolean
setUser: (email: string) => void
setTag: (key: string, value: string) => void
setWorkspace: (ws: string) => void
handleEvent: (event: string) => void
handleError: (error: Error) => void
}
export const Analytics = {
init (provider: AnalyticProvider, config: Record<string, any>): void {
const res = provider.init(config)
if (res) {
providers.push(provider)
}
},
setUser (email: string): void {
providers.forEach((provider) => {
provider.setUser(email)
})
},
setTag (key: string, value: string): void {
providers.forEach((provider) => {
provider.setTag(key, value)
})
},
setWorkspace (ws: string): void {
providers.forEach((provider) => {
provider.setWorkspace(ws)
})
},
handleEvent (event: string): void {
providers.forEach((provider) => {
provider.handleEvent(event)
})
},
handleError (error: Error): void {
providers.forEach((provider) => {
provider.handleError(error)
})
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}

View File

@ -1,4 +1,5 @@
import {
toFindResult,
type Class,
type Client,
type Doc,
@ -6,17 +7,16 @@ import {
type FindOptions,
type FindResult,
type Hierarchy,
type MeasureClient,
type MeasureDoneOperation,
type ModelDb,
type Ref,
type SearchOptions,
type SearchQuery,
type SearchResult,
type Tx,
type TxResult,
type WithLookup,
toFindResult,
type SearchQuery,
type SearchOptions,
type SearchResult,
type MeasureClient,
type MeasureDoneOperation
type WithLookup
} from '@hcengineering/core'
import { type Resource } from '@hcengineering/platform'

View File

@ -36,7 +36,8 @@
},
"dependencies": {
"svelte": "^4.2.5",
"@hcengineering/platform": "^0.6.9"
"@hcengineering/platform": "^0.6.9",
"@hcengineering/analytics": "^0.6.0"
},
"repository": "https://github.com/hcenginneing/anticrm",
"publishConfig": {

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import platform, { loadPluginStrings, setMetadata } from '@hcengineering/platform'
import { onMount, setContext } from 'svelte'
import {
@ -52,6 +53,7 @@
if (set) {
localStorage.setItem('lang', language)
}
Analytics.setTag('language', language)
setMetadata(platform.metadata.locale, currentLanguage)
await loadPluginStrings(currentLanguage, set)
setOptions(getCurrentFontSize(), getCurrentTheme(), language)

View File

@ -13,6 +13,7 @@
// limitations under the License.
//
import { Analytics } from '@hcengineering/analytics'
import '@hcengineering/platform-rig/profiles/ui/svelte'
import { writable } from 'svelte/store'
@ -53,7 +54,11 @@ export const getCurrentFontSize = (): string =>
/**
* @public
*/
export const getCurrentLanguage = (): string => localStorage.getItem('lang') ?? getDefaultProps('lang', 'en')
export const getCurrentLanguage = (): string => {
const lang = localStorage.getItem('lang') ?? getDefaultProps('lang', 'en')
Analytics.setTag('language', lang)
return lang
}
export class ThemeOptions {
constructor (

View File

@ -42,7 +42,7 @@
on:click={(e) => {
const unarchiveAction = actions.find((a) => a._id === board.action.SendToBoard)
if (unarchiveAction) {
invokeAction(card, e, unarchiveAction.action, unarchiveAction.actionProps)
invokeAction(card, e, unarchiveAction)
}
}}
/>
@ -51,7 +51,7 @@
on:click={async (e) => {
const deleteAction = actions.find((a) => a._id === board.action.Delete)
if (deleteAction) {
invokeAction(card, e, deleteAction.action, deleteAction.actionProps)
invokeAction(card, e, deleteAction)
}
}}
/>

View File

@ -80,7 +80,7 @@
if (!object) {
return
}
invokeAction(object, e, result[0].action, result[0].actionProps)
invokeAction(object, e, result[0])
}
}
})

View File

@ -37,6 +37,7 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/activity": "^0.6.0",
"@hcengineering/activity-resources": "^0.6.1",
"@hcengineering/attachment": "^0.6.9",

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import { createEventDispatcher } from 'svelte'
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
import { Class, Doc, generateId, getCurrentAccount, Ref } from '@hcengineering/core'
@ -104,7 +105,8 @@
// Remove draft from Local Storage
currentMessage = getDefault()
_id = currentMessage._id
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
console.error(err)
}
dispatch('submit', false)

View File

@ -36,6 +36,7 @@
"prettier-plugin-svelte": "^3.1.0"
},
"dependencies": {
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/core": "^0.6.28",
"@hcengineering/client": "^0.6.14",

View File

@ -14,6 +14,7 @@
// limitations under the License.
//
import { Analytics } from '@hcengineering/analytics'
import client, { ClientSocket, ClientSocketReadyState } from '@hcengineering/client'
import core, {
Account,
@ -146,6 +147,7 @@ class Connection implements ClientConnection {
this.pending = undefined
console.log('failed to connect', err)
if (err?.code === UNAUTHORIZED.code) {
Analytics.handleError(err)
this.onUnauthorized?.()
throw err
}

View File

@ -27,9 +27,9 @@
</script>
<script lang="ts">
import contact, { AvatarProvider, AvatarType, getFirstName, getLastName } from '@hcengineering/contact'
import contact, { AvatarProvider, AvatarType, getFirstName, getLastName, getName } from '@hcengineering/contact'
import { Client, Ref } from '@hcengineering/core'
import { Asset, getResource } from '@hcengineering/platform'
import { Asset, getMetadata, getResource } from '@hcengineering/platform'
import { getBlobURL, getClient } from '@hcengineering/presentation'
import {
AnySvelteComponent,
@ -54,10 +54,17 @@
let avatarProvider: AvatarProvider | undefined
let color: ColorDefinition | undefined = undefined
$: fname = getFirstName(name ?? '')
$: lname = getLastName(name ?? '')
$: displayName =
name != null ? (lname.length > 1 ? lname.trim()[0] : lname) + (fname.length > 1 ? fname.trim()[0] : fname) : ''
$: displayName = getDisplayName(name)
function getDisplayName (name: string | null | undefined): string {
if (name == null) {
return ''
}
const lastFirst = getMetadata(contact.metadata.LastNameFirst) === true
const fname = getFirstName(name ?? '').trim()[0]
const lname = getLastName(name ?? '').trim()[0]
return lastFirst ? lname + ' ' + fname : fname + ' ' + lname
}
async function update (size: IconSize, avatar?: string | null, direct?: Blob, name?: string | null) {
if (direct !== undefined) {

View File

@ -19,7 +19,7 @@
import { AttachedData, Doc, Ref, toIdMap } from '@hcengineering/core'
import notification, { DocNotifyContext, InboxNotification } from '@hcengineering/notification'
import { Asset, IntlString, getResource } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import presentation, { getClient } from '@hcengineering/presentation'
import {
Action,
AnyComponent,
@ -32,7 +32,7 @@
getFocusManager,
showPopup
} from '@hcengineering/ui'
import { ViewAction } from '@hcengineering/view'
import view, { Action as ViewAction } from '@hcengineering/view'
import { invokeAction } from '@hcengineering/view-resources'
import { createEventDispatcher, tick } from 'svelte'
import { readable, Readable, Writable, writable } from 'svelte/store'
@ -66,7 +66,7 @@
icon: Asset
value: string
presenter?: AnyComponent
action?: ViewAction
action?: Ref<ViewAction>
placeholder: IntlString
channel: AttachedData<Channel> | Channel
provider: Ref<ChannelProvider>
@ -211,6 +211,8 @@
dispatch('remove', removed.channel)
}
const client = getClient()
const editChannel = (el: HTMLElement, n: number, item: Item): void => {
if (opened !== n) {
opened = n
@ -230,7 +232,8 @@
if (result === 'open') {
if (item.action) {
const doc = item.channel as Channel
invokeAction(doc, result, item.action)
const action = client.getModel().findAllSync(view.class.Action, { _id: item.action })[0]
invokeAction(doc, result, action)
} else {
dispatch('open', item)
}
@ -270,7 +273,8 @@
closeTooltip()
if (item.action) {
const doc = item.channel as Channel
invokeAction(doc, result, item.action)
const action = client.getModel().findAllSync(view.class.Action, { _id: item.action })[0]
invokeAction(doc, result, action)
} else {
dispatch('open', item)
}

View File

@ -19,7 +19,7 @@ import type { Asset, Metadata, Plugin, Resource } from '@hcengineering/platform'
import { IntlString, plugin } from '@hcengineering/platform'
import { TemplateField, TemplateFieldCategory } from '@hcengineering/templates'
import type { AnyComponent, IconSize, ResolvedLocation } from '@hcengineering/ui'
import { FilterMode, ViewAction, Viewlet } from '@hcengineering/view'
import { Action, FilterMode, Viewlet } from '@hcengineering/view'
/**
* @public
@ -42,7 +42,7 @@ export interface ChannelProvider extends Doc, UXObject {
presenter?: AnyComponent
// Action to be performed if there is no presenter defined.
action?: ViewAction
action?: Ref<Action>
// Integration type
integrationType?: Ref<Doc>

View File

@ -54,6 +54,7 @@
"@hcengineering/login": "^0.6.8",
"@hcengineering/core": "^0.6.28",
"@hcengineering/panel": "^0.6.15",
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/templates": "^0.6.7"
}
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import attachmentP, { Attachment } from '@hcengineering/attachment'
import { AttachmentPresenter } from '@hcengineering/attachment-resources'
import contact, { Channel, Contact, getName } from '@hcengineering/contact'
@ -129,6 +130,7 @@
}
)
} catch (err: any) {
Analytics.handleError(err)
setPlatformStatus(unknownError(err))
}
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import attachmentP, { Attachment } from '@hcengineering/attachment'
import { AttachmentPresenter } from '@hcengineering/attachment-resources'
import contact, { Channel, Contact, getName as getContactName } from '@hcengineering/contact'
@ -155,6 +156,7 @@
}
)
} catch (err: any) {
Analytics.handleError(err)
setPlatformStatus(unknownError(err))
}
}

View File

@ -44,6 +44,7 @@
"@hcengineering/core": "^0.6.28",
"@hcengineering/presentation": "^0.6.2",
"@hcengineering/setting": "^0.6.11",
"@hcengineering/theme": "^0.6.3"
"@hcengineering/theme": "^0.6.3",
"@hcengineering/analytics": "^0.6.0"
}
}

View File

@ -13,24 +13,26 @@
// limitations under the License.
//
import { Analytics } from '@hcengineering/analytics'
import login, { type LoginInfo, type Workspace, type WorkspaceLoginInfo } from '@hcengineering/login'
import {
OK,
PlatformError,
type Status,
getMetadata,
setMetadata,
unknownError,
unknownStatus
unknownStatus,
type Status,
translate
} from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import {
type Location,
fetchMetadataLocalStorage,
getCurrentLocation,
locationStorageKeyId,
navigate,
setMetadataLocalStorage,
locationStorageKeyId
type Location
} from '@hcengineering/ui'
import { workbenchId } from '@hcengineering/workbench'
@ -69,9 +71,16 @@ export async function doLogin (email: string, password: string): Promise<[Status
})
const result = await response.json()
console.log('login result', result)
if (result.error == null) {
Analytics.handleEvent('login')
Analytics.setUser(email)
} else {
await handleStatusError('Login error', result.error)
}
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
console.log('login error', err)
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
@ -110,8 +119,15 @@ export async function signUp (
body: JSON.stringify(request)
})
const result = await response.json()
if (result.error == null) {
Analytics.handleEvent('signup')
Analytics.setUser(email)
} else {
await handleStatusError('Sign up error', result.error)
}
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
@ -158,8 +174,15 @@ export async function createWorkspace (
body: JSON.stringify(request)
})
const result = await response.json()
if (result.error == null) {
Analytics.handleEvent('create workspace')
Analytics.setTag('workspace', workspaceName)
} else {
await handleStatusError('Create workspace error', result.error)
}
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
@ -262,7 +285,9 @@ export async function getAccount (doNavigate: boolean = true, token?: string): P
throw new PlatformError(result.error)
}
return result.result
} catch (err) {}
} catch (err: any) {
Analytics.handleError(err)
}
}
export async function selectWorkspace (workspace: string): Promise<[Status, WorkspaceLoginInfo | undefined]> {
@ -305,8 +330,15 @@ export async function selectWorkspace (workspace: string): Promise<[Status, Work
body: JSON.stringify(request)
})
const result = await response.json()
if (result.error == null) {
Analytics.handleEvent('Select workspace')
Analytics.setTag('workspace', workspace)
} else {
await handleStatusError('Select workspace error', result.error)
}
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
@ -387,7 +419,8 @@ export async function checkJoined (inviteId: string): Promise<[Status, Workspace
})
const result = await response.json()
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
@ -460,8 +493,15 @@ export async function join (
body: JSON.stringify(request)
})
const result = await response.json()
if (result.error == null) {
Analytics.handleEvent('Join')
Analytics.setUser(email)
} else {
await handleStatusError('Join error', result.error)
}
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
@ -501,8 +541,15 @@ export async function signUpJoin (
body: JSON.stringify(request)
})
const result = await response.json()
if (result.error == null) {
Analytics.handleEvent('Signup Join')
Analytics.setUser(email)
} else {
await handleStatusError('Sign up join error', result.error)
}
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
@ -538,7 +585,9 @@ export async function changePassword (oldPassword: string, password: string): Pr
})
const resp = await response.json()
if (resp.error !== undefined) {
throw new PlatformError(resp.error)
const err = new PlatformError(resp.error)
Analytics.handleError(err)
throw err
}
}
@ -632,8 +681,12 @@ export async function requestPassword (email: string): Promise<Status> {
body: JSON.stringify(request)
})
const result = await response.json()
if (result.error != null) {
await handleStatusError('Request password error', result.error)
}
return result.error ?? OK
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return unknownError(err)
}
}
@ -666,8 +719,14 @@ export async function confirm (email: string): Promise<[Status, LoginInfo | unde
body: JSON.stringify(request)
})
const result = await response.json()
if (result.error != null) {
await handleStatusError('Confirm email error', result.error)
} else {
Analytics.handleEvent('Confirm email')
}
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
@ -694,8 +753,19 @@ export async function restorePassword (token: string, password: string): Promise
body: JSON.stringify(request)
})
const result = await response.json()
if (result.error != null) {
await handleStatusError('Restore password error', result.error)
} else {
Analytics.handleEvent('Restore password')
}
return [result.error ?? OK, result.result]
} catch (err) {
} catch (err: any) {
Analytics.handleError(err)
return [unknownError(err), undefined]
}
}
async function handleStatusError (message: string, err: Status): Promise<void> {
const label = await translate(err.code, err.params, 'en')
Analytics.handleError(new Error(`${message}: ${label}`))
}

View File

@ -35,6 +35,7 @@
"svelte-eslint-parser": "^0.33.1"
},
"dependencies": {
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/activity": "^0.6.0",
"@hcengineering/attachment": "^0.6.9",
"@hcengineering/attachment-resources": "^0.6.0",

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import attachment from '@hcengineering/attachment'
import { deleteFile } from '@hcengineering/attachment-resources/src/utils'
import contact, { Channel, ChannelProvider, combineName, findContacts, Person } from '@hcengineering/contact'
@ -437,6 +438,7 @@
}
object.skills = [...object.skills, ...newSkills]
} catch (err: any) {
Analytics.handleError(err)
console.error(err)
}
}
@ -464,6 +466,7 @@
await recognize(file)
} catch (err: any) {
Analytics.handleError(err)
setPlatformStatus(unknownError(err))
} finally {
loading = false

View File

@ -38,6 +38,7 @@
},
"dependencies": {
"@hcengineering/platform": "^0.6.9",
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/core": "^0.6.28",
"svelte": "^4.2.5",
"@hcengineering/setting": "^0.6.11",

View File

@ -13,14 +13,14 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import setting from '@hcengineering/setting'
import presentation from '@hcengineering/presentation'
import { Button, EditBox, Icon, Label, Header, Breadcrumb } from '@hcengineering/ui'
import login from '@hcengineering/login'
import Error from './icons/Error.svelte'
import plugin from '../plugin'
import { getResource } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import setting from '@hcengineering/setting'
import { Breadcrumb, Button, EditBox, Header, Icon, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../plugin'
import Error from './icons/Error.svelte'
export let visibleNav: boolean = true

View File

@ -39,6 +39,7 @@
"dependencies": {
"@hcengineering/activity": "^0.6.0",
"@hcengineering/activity-resources": "^0.6.1",
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/attachment": "^0.6.9",
"@hcengineering/attachment-resources": "^0.6.0",
"@hcengineering/calendar": "^0.6.17",

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import { Attachment } from '@hcengineering/attachment'
import { AttachmentPresenter, AttachmentStyledBox } from '@hcengineering/attachment-resources'
import chunter from '@hcengineering/chunter'
@ -514,6 +515,7 @@
} catch (err: any) {
console.error(err)
await doneOp() // Complete in case of error
Analytics.handleError(err)
}
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
//
import { Analytics } from '@hcengineering/analytics'
import core, {
ClassifierKind,
DOMAIN_CONFIGURATION,
@ -318,6 +319,7 @@ async function deleteProject (project: Project | undefined): Promise<void> {
}
} catch (err: any) {
console.error(err)
Analytics.handleError(err)
}
}
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
//
import { Analytics } from '@hcengineering/analytics'
import { type Contact } from '@hcengineering/contact'
import core, {
SortingOrder,
@ -402,13 +403,14 @@ export async function moveIssuesToAnotherMilestone (
await Promise.all(awaitedUpdates)
return true
} catch (error) {
} catch (error: any) {
console.error(
`Error happened while moving issues between milestones from ${oldMilestone.label} to ${
newMilestone?.label ?? 'No Milestone'
}: `,
error
)
Analytics.handleError(error)
return false
}
}

View File

@ -51,6 +51,7 @@
"@hcengineering/presentation": "^0.6.2",
"@hcengineering/setting": "^0.6.11",
"@hcengineering/text-editor": "^0.6.0",
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/query": "^0.6.8",
"fast-equals": "^2.0.3"
}

View File

@ -14,15 +14,16 @@
// limitations under the License.
//
import { Analytics } from '@hcengineering/analytics'
import core, {
AccountRole,
getCurrentAccount,
matchQuery,
type Class,
type Client,
type Doc,
type Ref,
type WithLookup,
getCurrentAccount,
matchQuery
type WithLookup
} from '@hcengineering/core'
import { getResource } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
@ -30,7 +31,6 @@ import {
type Action,
type ActionGroup,
type ActionIgnore,
type ViewAction,
type ViewActionInput,
type ViewContextType
} from '@hcengineering/view'
@ -115,11 +115,15 @@ export async function filterAvailableActions (
export async function invokeAction (
object: Doc | Doc[],
evt: Event,
action: ViewAction,
action: Action,
props?: Record<string, any>
): Promise<void> {
const impl = await getResource(action)
await impl(Array.isArray(object) && object.length === 1 ? object[0] : object, evt, props)
const impl = await getResource(action.action)
Analytics.handleEvent(action._id)
await impl(Array.isArray(object) && object.length === 1 ? object[0] : object, evt, {
...action.actionProps,
...props
})
}
export async function getContextActions (

View File

@ -63,7 +63,7 @@
on:click={async (event) => {
if (action !== null) {
isBeingInvoked = true
await invokeAction(object, event, action.action, action.actionProps)
await invokeAction(object, event, action)
isBeingInvoked = false
}
}}

View File

@ -13,9 +13,10 @@
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import core, { Doc, Hierarchy, Ref, TxRemoveDoc } from '@hcengineering/core'
import { getResource } from '@hcengineering/platform'
import { addTxListener, getClient, contextStore } from '@hcengineering/presentation'
import { addTxListener, contextStore, getClient } from '@hcengineering/presentation'
import { AnyComponent, Component } from '@hcengineering/ui'
import { Action, ViewContextType } from '@hcengineering/view'
import { fly } from 'svelte/transition'
@ -183,6 +184,7 @@
sequences = []
lastKey = undefined
delayedAction = undefined
Analytics.handleEvent(a._id)
await action(selectionDocs, evt, a.actionProps)
return
}
@ -201,10 +203,12 @@
lastKey = undefined
sequences = []
delayedAction = undefined
Analytics.handleEvent(a._id)
await action(selectionDocs, evt, a.actionProps)
return
} else {
delayedAction = async () => {
Analytics.handleEvent(a._id)
await action(selectionDocs, evt, a.actionProps)
}
found = true

View File

@ -57,7 +57,7 @@
inline: a.inline,
group: a.context.group ?? 'other',
action: async (_: any, evt: Event) => {
invokeAction(object, evt, a.action, a.actionProps)
invokeAction(object, evt, a)
},
component: a.actionPopup,
props: { ...a.actionProps, value: object }

View File

@ -113,7 +113,7 @@ import {
import { IndexedDocumentPreview } from '@hcengineering/presentation'
import { showEmptyGroups } from './viewOptions'
import { AggregationMiddleware } from './middleware'
import { AggregationMiddleware, AnalyticsMiddleware } from './middleware'
export { getActions, invokeAction, getContextActions } from './actions'
export { default as ActionButton } from './components/ActionButton.svelte'
export { default as ActionHandler } from './components/ActionHandler.svelte'
@ -284,6 +284,8 @@ export default async (): Promise<Resources> => ({
FilterDateNotSpecified: dateNotSpecified,
FilterDateCustom: dateCustom,
// eslint-disable-next-line @typescript-eslint/unbound-method
CreateDocMiddleware: AggregationMiddleware.create
CreateDocMiddleware: AggregationMiddleware.create,
// eslint-disable-next-line @typescript-eslint/unbound-method
AnalyticsMiddleware: AnalyticsMiddleware.create
}
})

View File

@ -1,22 +1,26 @@
import { Analytics } from '@hcengineering/analytics'
import core, {
type Doc,
type Ref,
Hierarchy,
type TxApplyIf,
type TxCUD,
TxProcessor,
generateId,
type AnyAttribute,
type Attribute,
type Class,
type Client,
type Doc,
type DocumentQuery,
type FindOptions,
type Client,
type Tx,
type TxResult,
type FindResult,
type Attribute,
Hierarchy,
type Ref,
type RefTo,
generateId
type Tx,
type TxResult
} from '@hcengineering/core'
import { getResource, translate } from '@hcengineering/platform'
import { BasePresentationMiddleware, type PresentationMiddleware } from '@hcengineering/presentation'
import view, { type AggregationManager } from '@hcengineering/view'
import { getResource } from '@hcengineering/platform'
/**
* @public
@ -233,8 +237,57 @@ export class AggregationMiddleware extends BasePresentationMiddleware implements
}
}
} catch (err: any) {
Analytics.handleError(err)
console.error(err)
}
}
}
}
/**
* @public
*/
export class AnalyticsMiddleware extends BasePresentationMiddleware implements PresentationMiddleware {
private constructor (client: Client, next?: PresentationMiddleware) {
super(client, next)
}
async notifyTx (tx: Tx): Promise<void> {
await this.provideNotifyTx(tx)
}
async close (): Promise<void> {
await this.provideClose()
}
static create (client: Client, next?: PresentationMiddleware): AnalyticsMiddleware {
return new AnalyticsMiddleware(client, next)
}
async tx (tx: Tx): Promise<TxResult> {
void this.handleTx(tx)
return await this.provideTx(tx)
}
private async handleTx (tx: Tx): Promise<void> {
const etx = TxProcessor.extractTx(tx)
if (etx._class === core.class.TxApplyIf) {
const applyIf = etx as TxApplyIf
applyIf.txes.forEach((it) => {
void this.handleTx(it)
})
}
if (this.client.getHierarchy().isDerived(etx._class, core.class.TxCUD)) {
const cud = etx as TxCUD<Doc>
const _class = this.client.getHierarchy().getClass(cud.objectClass)
const label = await translate(_class.label, {}, 'en')
if (cud._class === core.class.TxCreateDoc) {
Analytics.handleEvent(`Create ${label}`)
} else if (cud._class === core.class.TxUpdateDoc || cud._class === core.class.TxMixin) {
Analytics.handleEvent(`Update ${label}`)
} else if (cud._class === core.class.TxRemoveDoc) {
Analytics.handleEvent(`Delete ${label}`)
}
}
}
}

View File

@ -100,6 +100,7 @@ export default mergeIds(viewId, view, {
ToViewCommands: '' as IntlString
},
function: {
CreateDocMiddleware: '' as Resource<PresentationMiddlewareCreator>
CreateDocMiddleware: '' as Resource<PresentationMiddlewareCreator>,
AnalyticsMiddleware: '' as Resource<PresentationMiddlewareCreator>
}
})

View File

@ -14,6 +14,7 @@
// limitations under the License.
//
import { Analytics } from '@hcengineering/analytics'
import core, {
AccountRole,
type FindOptions,
@ -364,6 +365,7 @@ export async function buildModel (options: BuildModelOptions): Promise<Attribute
return undefined
}
const stringKey = key.label ?? key.key
Analytics.handleError(err)
console.error('Failed to find presenter for', key, err)
const errorPresenter: AttributeModel = {
key: '',

View File

@ -54,6 +54,7 @@
"@hcengineering/support": "^0.6.1",
"@hcengineering/support-resources": "^0.6.0",
"@hcengineering/view-resources": "^0.6.0",
"fast-copy": "~3.0.1"
"fast-copy": "~3.0.1",
"@hcengineering/analytics": "^0.6.0"
}
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import contact, { Employee, PersonAccount } from '@hcengineering/contact'
import core, { AccountRole, Class, Doc, Ref, Space, getCurrentAccount } from '@hcengineering/core'
import login from '@hcengineering/login'
@ -220,6 +221,7 @@
try {
return await titleProvider(client, _id as Ref<Doc>)
} catch (err: any) {
Analytics.handleError(err)
console.error(err)
}
}

View File

@ -1,3 +1,4 @@
import { Analytics } from '@hcengineering/analytics'
import client from '@hcengineering/client'
import core, {
ClientConnectEvent,
@ -138,6 +139,8 @@ export async function connect (title: string): Promise<Client | undefined> {
const me = await _client?.getAccount()
if (me !== undefined) {
Analytics.setUser(me.email)
Analytics.setTag('workspace', ws)
console.log('login: employee account', me)
setCurrentAccount(me)
} else {

View File

@ -461,6 +461,11 @@
"projectFolder": "packages/core",
"shouldPublish": true
},
{
"packageName": "@hcengineering/analytics",
"projectFolder": "packages/analytics",
"shouldPublish": false
},
{
"packageName": "@hcengineering/server-ws",
"projectFolder": "server/ws",