mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 04:25:03 +02:00
Use our analytics service properly (#9985)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -8,9 +8,6 @@ export interface Config {
|
||||
ACCOUNTS_URL: string
|
||||
AI_URL?: string
|
||||
ANALYTICS_COLLECTOR_URL?: string
|
||||
POSTHOG_API_KEY?: string
|
||||
POSTHOG_HOST?: string
|
||||
SENTRY_DSN?: string
|
||||
BRANDING_URL?: string
|
||||
CALENDAR_URL: string
|
||||
COLLABORATOR?: string
|
||||
|
||||
+17
-16
@@ -426,22 +426,23 @@ services:
|
||||
# - STATS_URL=http://huly.local:4900
|
||||
# # - LOVE_ENDPOINT=http://huly.local:8096
|
||||
# # - OPENAI_API_KEY=token
|
||||
# analytics:
|
||||
# image: hardcoreeng/analytics-collector
|
||||
# extra_hosts:
|
||||
# - 'huly.local:host-gateway'
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - 4017:4017
|
||||
# environment:
|
||||
# - SECRET=secret
|
||||
# - PORT=4017
|
||||
# - SERVICE_ID=analytics-collector-service
|
||||
# - ACCOUNTS_URL=http://huly.local:3000
|
||||
# - STATS_URL=http://huly.local:4900
|
||||
# - POSTHOG_HOST=${POSTHOG_HOST}
|
||||
# - POSTHOG_API_KEY=${POSTHOG_API_KEY}
|
||||
# - MAX_PAYLOAD_SIZE=10mb
|
||||
analytics:
|
||||
image: hardcoreeng/analytics-collector
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 4017:4017
|
||||
environment:
|
||||
- SECRET=secret
|
||||
- PORT=4017
|
||||
- SERVICE_ID=analytics-collector-service
|
||||
- ACCOUNTS_URL=http://huly.local:3000
|
||||
- STATS_URL=http://huly.local:4900
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces
|
||||
# - POSTHOG_HOST=${POSTHOG_HOST}
|
||||
# - POSTHOG_API_KEY=${POSTHOG_API_KEY}
|
||||
# - MAX_PAYLOAD_SIZE=10mb
|
||||
export:
|
||||
image: hardcoreeng/export
|
||||
extra_hosts:
|
||||
|
||||
@@ -282,8 +282,6 @@
|
||||
"@hcengineering/ai-assistant": "^0.6.0",
|
||||
"@hcengineering/ai-assistant-assets": "^0.6.0",
|
||||
"@hcengineering/ai-assistant-resources": "^0.6.0",
|
||||
"@sentry/svelte": "^9.22.0",
|
||||
"posthog-js": "^1.246.0",
|
||||
"readable-stream": "^4.7.0",
|
||||
"svelte": "^4.2.20"
|
||||
}
|
||||
|
||||
@@ -29,5 +29,6 @@
|
||||
"BACKUP_URL": "http://huly.local:4039/api/backup",
|
||||
"PULSE_URL": "ws://huly.local:8099/ws",
|
||||
"HULYLAKE_URL": "http://huly.local:8096",
|
||||
"EXCLUDED_APPLICATIONS_FOR_ANONYMOUS": "[\"chunter\", \"notification\"]"
|
||||
"EXCLUDED_APPLICATIONS_FOR_ANONYMOUS": "[\"chunter\", \"notification\"]",
|
||||
"ANALYTICS_COLLECTOR_URL": "http://huly.local:4017"
|
||||
}
|
||||
|
||||
@@ -168,13 +168,10 @@ export interface Config {
|
||||
GITHUB_APP?: string
|
||||
GITHUB_CLIENTID?: string
|
||||
GITHUB_URL: string
|
||||
SENTRY_DSN?: string
|
||||
LOVE_ENDPOINT?: string
|
||||
LIVEKIT_WS?: string
|
||||
SIGN_URL?: string
|
||||
PRINT_URL?: string
|
||||
POSTHOG_API_KEY?: string
|
||||
POSTHOG_HOST?: string
|
||||
ANALYTICS_COLLECTOR_URL?: string
|
||||
BRANDING_URL?: string
|
||||
TELEGRAM_BOT_URL?: string
|
||||
|
||||
@@ -45,8 +45,6 @@
|
||||
"@hcengineering/analytics-collector": "^0.6.0",
|
||||
"@hcengineering/presentation": "^0.6.3",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"posthog-js": "^1.246.0",
|
||||
"@sentry/svelte": "^9.22.0",
|
||||
"ua-parser-js": "^2.0.4",
|
||||
"@hcengineering/ui": "^0.6.15"
|
||||
},
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
//
|
||||
|
||||
import { type AnalyticProvider } from '@hcengineering/analytics'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { AnalyticEventType } from '@hcengineering/analytics-collector'
|
||||
import { collectEventMetadata, triggerUrlChange } from './utils'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { type QueuedEvent } from './types'
|
||||
import { collectEventMetadata, triggerUrlChange } from './utils'
|
||||
|
||||
export class AnalyticsCollectorProvider implements AnalyticProvider {
|
||||
private readonly collectIntervalMs = 5000
|
||||
@@ -39,6 +39,7 @@ export class AnalyticsCollectorProvider implements AnalyticProvider {
|
||||
if (this.url !== undefined && this.url !== '' && this.url !== null) {
|
||||
this.initializeAnonymousId()
|
||||
this.startCollectionTimer()
|
||||
this.registerExceptionHandlers()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -48,6 +49,18 @@ export class AnalyticsCollectorProvider implements AnalyticProvider {
|
||||
this.anonymousId = this.generateAnonymousId()
|
||||
}
|
||||
|
||||
private registerExceptionHandlers (): void {
|
||||
// Capture unhandled errors
|
||||
window.addEventListener('error', (event) => {
|
||||
this.handleError(event.error ?? new Error(event.message))
|
||||
})
|
||||
|
||||
// Capture unhandled promise rejections
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
this.handleError(event.reason instanceof Error ? event.reason : new Error(String(event.reason)))
|
||||
})
|
||||
}
|
||||
|
||||
private generateAnonymousId (): string {
|
||||
return 'anon_' + Date.now() + '_' + Math.random().toString(36).substring(2, 15)
|
||||
}
|
||||
@@ -151,7 +164,13 @@ export class AnalyticsCollectorProvider implements AnalyticProvider {
|
||||
$is_identified: this.isAuthenticated
|
||||
}
|
||||
|
||||
const eventMetadata: Record<string, any> = collectEventMetadata(baseProperties)
|
||||
let eventMetadata: Record<string, any> = {}
|
||||
|
||||
try {
|
||||
eventMetadata = collectEventMetadata(baseProperties)
|
||||
} catch (err: any) {
|
||||
// Ignore metadata collection errors
|
||||
}
|
||||
if (eventType === AnalyticEventType.CustomEvent && (eventName !== '' || eventName != null)) {
|
||||
eventMetadata.event = eventName
|
||||
}
|
||||
@@ -271,16 +290,20 @@ export class AnalyticsCollectorProvider implements AnalyticProvider {
|
||||
|
||||
handleError (error: Error): void {
|
||||
const currentId = this.isAuthenticated && (this.email != null || this.email !== '') ? this.email : this.anonymousId
|
||||
this.addEvent(
|
||||
AnalyticEventType.Error,
|
||||
{
|
||||
|
||||
const event: QueuedEvent = {
|
||||
event: AnalyticEventType.Error,
|
||||
properties: {
|
||||
error_message: error.message ?? 'Unknown error',
|
||||
error_type: error.name ?? 'Error',
|
||||
error_stack: error.stack ?? ''
|
||||
},
|
||||
'$exception',
|
||||
currentId
|
||||
)
|
||||
timestamp: Date.now(),
|
||||
distinct_id: currentId ?? ''
|
||||
}
|
||||
this.events.push(event)
|
||||
// We need to trigger sending immediately for errors
|
||||
void this.sendEvents()
|
||||
}
|
||||
|
||||
navigate (path: string): void {
|
||||
|
||||
@@ -15,22 +15,14 @@
|
||||
|
||||
import { type AnalyticProvider, Analytics } from '@hcengineering/analytics'
|
||||
import { AnalyticsCollectorProvider } from './analyticsCollector'
|
||||
import { PosthogAnalyticProvider } from './posthog'
|
||||
import { SentryAnalyticProvider } from './sentry'
|
||||
import { type AnalyticsConfig } from './types'
|
||||
|
||||
export * from './analyticsCollector'
|
||||
export * from './posthog'
|
||||
export * from './sentry'
|
||||
export * from './utils'
|
||||
export * from './types'
|
||||
|
||||
export function configureAnalyticsProviders (config: AnalyticsConfig): void {
|
||||
const providers: AnalyticProvider[] = [
|
||||
new AnalyticsCollectorProvider(),
|
||||
new SentryAnalyticProvider(),
|
||||
new PosthogAnalyticProvider()
|
||||
]
|
||||
const providers: AnalyticProvider[] = [new AnalyticsCollectorProvider()]
|
||||
|
||||
for (const provider of providers) {
|
||||
Analytics.init(provider, config)
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
//
|
||||
|
||||
export * from './analyticsCollector'
|
||||
export * from './posthog'
|
||||
export * from './sentry'
|
||||
export * from './utils'
|
||||
export * from './types'
|
||||
export * from './configure'
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
//
|
||||
// Copyright © 2025 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.
|
||||
//
|
||||
|
||||
import { type AnalyticProvider } from '@hcengineering/analytics'
|
||||
import posthog from 'posthog-js'
|
||||
|
||||
export class PosthogAnalyticProvider implements AnalyticProvider {
|
||||
init (config: Record<string, any>): boolean {
|
||||
if (config.POSTHOG_API_KEY !== undefined && config.POSTHOG_API_KEY !== '' && config.POSTHOG_HOST !== null) {
|
||||
posthog.init(config.POSTHOG_API_KEY, {
|
||||
api_host: config.POSTHOG_HOST,
|
||||
autocapture: false,
|
||||
capture_pageview: false,
|
||||
capture_pageleave: false
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
setUser (email: string): void {
|
||||
if (!posthog._isIdentified()) {
|
||||
posthog.identify(email, { email })
|
||||
}
|
||||
}
|
||||
|
||||
setAlias (distinctId: string, alias: string): void {
|
||||
posthog.alias(alias, distinctId)
|
||||
}
|
||||
|
||||
setTag (key: string, value: string | number): void {
|
||||
posthog.setPersonProperties({ [key]: value })
|
||||
}
|
||||
|
||||
setWorkspace (ws: string, guest: boolean): void {
|
||||
const prop: string = guest ? 'visited-workspace' : 'workspace'
|
||||
this.setTag(prop, ws)
|
||||
if (!guest) posthog.group(prop, ws, { name: `${ws}` })
|
||||
}
|
||||
|
||||
logout (): void {
|
||||
posthog.reset(true)
|
||||
}
|
||||
|
||||
handleEvent (event: string, params: Record<string, any>): void {
|
||||
posthog.capture(event, params)
|
||||
}
|
||||
|
||||
handleError (error: Error): void {
|
||||
posthog.capture(error.message)
|
||||
}
|
||||
|
||||
navigate (path: string): void {
|
||||
posthog.capture('$pageview')
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
//
|
||||
// Copyright © 2025 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.
|
||||
//
|
||||
|
||||
import { type AnalyticProvider } from '@hcengineering/analytics'
|
||||
import * as Sentry from '@sentry/svelte'
|
||||
|
||||
export class SentryAnalyticProvider implements AnalyticProvider {
|
||||
navigate (path: string): void {}
|
||||
init (config: Record<string, any>): boolean {
|
||||
if (config.SENTRY_DSN !== undefined && config.SENTRY_DSN !== '') {
|
||||
Sentry.init({
|
||||
dsn: config.SENTRY_DSN,
|
||||
integrations: [
|
||||
Sentry.browserTracingIntegration(),
|
||||
Sentry.replayIntegration({
|
||||
maskAllText: false,
|
||||
blockAllMedia: false
|
||||
})
|
||||
],
|
||||
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
// of transactions for performance monitoring.
|
||||
// We recommend adjusting this value in production
|
||||
tracesSampleRate: 1.0,
|
||||
|
||||
tracePropagationTargets: [/^https:\/\/huly\.app/, /^https:\/\/app\.huly\.io/, /^https:\/\/account\.huly\.io/],
|
||||
|
||||
replaysSessionSampleRate: 0.0,
|
||||
replaysOnErrorSampleRate: 1.0
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
setUser (email: string): void {
|
||||
Sentry.setUser({ email })
|
||||
}
|
||||
|
||||
setAlias (distinctId: string, alias: string): void {}
|
||||
|
||||
logout (): void {
|
||||
Sentry.setUser(null)
|
||||
}
|
||||
|
||||
setTag (key: string, value: string | number): void {
|
||||
Sentry.setTag(key, value)
|
||||
}
|
||||
|
||||
setWorkspace (ws: string, guest: boolean): void {
|
||||
const prop: string = guest ? 'visited-workspace' : 'workspace'
|
||||
this.setTag(prop, ws)
|
||||
}
|
||||
|
||||
handleEvent (event: string): void {
|
||||
// currently we don't need it, but maybe in future
|
||||
// Sentry.captureMessage(event, 'log')
|
||||
}
|
||||
|
||||
handleError (error: Error): void {
|
||||
Sentry.captureException(error)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import { type AnalyticEvent } from '@hcengineering/analytics-collector'
|
||||
|
||||
export interface AnalyticsConfig {
|
||||
ANALYTICS_COLLECTOR_URL?: string
|
||||
SENTRY_DSN?: string
|
||||
POSTHOG_API?: string
|
||||
POSTHOG_HOST?: string
|
||||
[key: string]: any
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
import presentation from '..'
|
||||
import { ObjectCreate } from '../types'
|
||||
import { getClient } from '../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let objects: Doc[] = []
|
||||
@@ -184,7 +185,7 @@
|
||||
presenter = result
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to find presenter for class ' + _class, err)
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@
|
||||
} catch (error: any) {
|
||||
commandProcessor.set([])
|
||||
Analytics.handleError(error)
|
||||
console.error('Failed to parse drawing content', error)
|
||||
}
|
||||
} else {
|
||||
commandProcessor.set([])
|
||||
@@ -151,7 +150,6 @@
|
||||
}
|
||||
createDrawing(data).catch((error) => {
|
||||
Analytics.handleError(error)
|
||||
console.error('Failed to save drawing', error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
import DrawingBoardToolbarColorIcon from './DrawingBoardToolbarColorIcon.svelte'
|
||||
import DrawingBoardColorSelectorIcon from './DrawingBoardColorSelectorIcon.svelte'
|
||||
import { ColorsList, DrawingBoardColoringSetup } from '../drawingColors'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
interface DrawingBoardToolbarEvents {
|
||||
undo: undefined
|
||||
@@ -130,9 +131,7 @@
|
||||
break
|
||||
}
|
||||
default: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _exhaustive: never = id
|
||||
console.error('Unknown command id', id)
|
||||
Analytics.handleError(new Error(`Unknown command id '${id as any}'`))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
.catch((error) => {
|
||||
drawingLoading = false
|
||||
Analytics.handleError(error)
|
||||
console.error('Failed to load drawings for file', file, error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import uiPlugin, { navigate, parseLocation } from '@hcengineering/ui'
|
||||
|
||||
import presentation from '../../plugin'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let mark: MarkupMark
|
||||
|
||||
@@ -40,8 +41,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to handle link', mark, err)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/card": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
|
||||
@@ -22,15 +22,16 @@
|
||||
} from '@hcengineering/activity'
|
||||
import { Class, Doc, getCurrentAccount, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Grid, Section, Spinner, location, Lazy } from '@hcengineering/ui'
|
||||
import { Grid, Lazy, location, Section, Spinner } from '@hcengineering/ui'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
import { editingMessageStore, messageInFocus } from '../activity'
|
||||
import { combineActivityMessages, sortActivityMessages } from '../activityMessagesUtils'
|
||||
import { canGroupMessages, getActivityNewestFirst, getMessageFromLoc, getSpace } from '../utils'
|
||||
import ActivityMessagePresenter from './activity-message/ActivityMessagePresenter.svelte'
|
||||
import ActivityExtensionComponent from './ActivityExtension.svelte'
|
||||
import ActivityFilter from './ActivityFilter.svelte'
|
||||
import { combineActivityMessages, sortActivityMessages } from '../activityMessagesUtils'
|
||||
import { canGroupMessages, getMessageFromLoc, getSpace, getActivityNewestFirst } from '../utils'
|
||||
import ActivityMessagePresenter from './activity-message/ActivityMessagePresenter.svelte'
|
||||
import { editingMessageStore, messageInFocus } from '../activity'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let object: WithReferences<Doc>
|
||||
export let showCommenInput: boolean = true
|
||||
@@ -185,8 +186,8 @@
|
||||
}
|
||||
clazz = hierarchy.getClass(clazz).extends
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch (e: any) {
|
||||
Analytics.handleError(e)
|
||||
return []
|
||||
}
|
||||
return []
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import aiAssistant from '../plugin'
|
||||
import { buildSocialIdString, getCurrentAccount, SocialIdType, type PersonId } from '@hcengineering/core'
|
||||
import HulyAssistant from './icons/HulyAssistant.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let integration: Integration | undefined = undefined
|
||||
|
||||
@@ -51,9 +52,10 @@
|
||||
|
||||
isLoading = false
|
||||
dispatch('close')
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
isLoading = false
|
||||
console.error('Failed to find/create huly assistant social id/integration:', err)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import { getImageDimensions } from '../utils'
|
||||
import LinkPreviewIcon from './LinkPreviewIcon.svelte'
|
||||
import LinkPreviewImage from './LinkPreviewImage.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let attachment: WithLookup<Attachment>
|
||||
export let isOwn = false
|
||||
@@ -56,7 +57,7 @@
|
||||
})
|
||||
.catch((err) => {
|
||||
viewModel = undefined
|
||||
console.error(err)
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
try {
|
||||
attr = getClient().getHierarchy().getAttribute(value.ofClass, value.attributeName)
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/account-client": "^0.6.0",
|
||||
"@hcengineering/login": "^0.6.12",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { getCurrentAccount, pickPrimarySocialId, SocialId, SocialIdType } from '@hcengineering/core'
|
||||
import { getAccountClient } from '../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
const workspaceUuid = getCurrentWorkspaceUuid()
|
||||
|
||||
@@ -89,7 +90,7 @@
|
||||
selectedSocialId = socialId
|
||||
accessEnabled = integrationWs != null && integrationGlobal != null
|
||||
} catch (err: any) {
|
||||
console.error('Failed to load CalDAV intergrations', err)
|
||||
Analytics.handleError(err)
|
||||
error = `${err.message ?? 'Unable to load CalDAV intergrations'}`
|
||||
} finally {
|
||||
loading = false
|
||||
@@ -151,7 +152,7 @@
|
||||
dispatch('close', true)
|
||||
} catch (err: any) {
|
||||
error = `${err.message ?? 'Unable to save CalDAV intergration'}`
|
||||
console.error('Failed to save CalDAV integration', err)
|
||||
Analytics.handleError(err)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
import calendar from '../plugin'
|
||||
import CalendarSelector from './CalendarSelector.svelte'
|
||||
import TimeZoneSelector from './TimeZoneSelector.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let schedule: Schedule | undefined
|
||||
|
||||
@@ -99,8 +100,8 @@
|
||||
.then((text) => {
|
||||
durationVariants.push({ msec, text })
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
.catch((err: any) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -112,8 +113,8 @@
|
||||
.then((text) => {
|
||||
intervalVariants.push({ msec, text })
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
.catch((err: any) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -170,8 +171,8 @@
|
||||
.then((res) => {
|
||||
formattedMeetingDuration = res
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
.catch((err: any) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -180,8 +181,8 @@
|
||||
.then((res) => {
|
||||
formattedMeetingInterval = res
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
.catch((err: any) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
import chat from '@hcengineering/chat'
|
||||
|
||||
import EditorActions from './EditorActions.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const communicationClient = getCommunicationClient()
|
||||
@@ -90,7 +91,7 @@
|
||||
const createdCard = await createCard(type, space, data, cardDescription, _id)
|
||||
if (isThread) {
|
||||
if (createdCard == null) {
|
||||
console.error('Failed to create thread card')
|
||||
Analytics.handleError(new Error('Failed to create thread card'))
|
||||
return
|
||||
}
|
||||
const blobs: (BlobParams & { mimeType: string })[] = descriptionBox.getAttachments().map((attachment) => ({
|
||||
|
||||
@@ -13,26 +13,27 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { MasterTag } from '@hcengineering/card'
|
||||
import core, { Doc, Ref } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
AnyComponent,
|
||||
AnySvelteComponent,
|
||||
BreadcrumbItem,
|
||||
Breadcrumbs,
|
||||
Header,
|
||||
Location,
|
||||
resizeObserver,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
getCurrentLocation,
|
||||
Header,
|
||||
Location,
|
||||
navigate,
|
||||
resizeObserver,
|
||||
resolvedLocationStore
|
||||
} from '@hcengineering/ui'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import card from '../../plugin'
|
||||
import MasterTagEditor from './MasterTagEditor.svelte'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
let masterTag: MasterTag | undefined
|
||||
let visibleSecondNav: boolean = true
|
||||
@@ -62,10 +63,10 @@
|
||||
selectedSubObjectId = objId
|
||||
void getResource(editorId)
|
||||
.then((res) => (subEditor = res))
|
||||
.catch((err) => {
|
||||
.catch((err: any) => {
|
||||
subEditor = undefined
|
||||
subEditorParamas = []
|
||||
console.error(err)
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
} else {
|
||||
subEditorParamas = []
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
try {
|
||||
const _classLabel = hierarchy.getClass(_class)
|
||||
return _classLabel.label
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
return core.string.Class
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import chat from '../../plugin'
|
||||
import InboxViewSettings from './InboxViewSettings.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
const communicationClient = getCommunicationClient()
|
||||
|
||||
@@ -30,9 +31,9 @@
|
||||
const contexts = await communicationClient.findNotificationContexts({ order: SortingOrder.Ascending })
|
||||
await Promise.all(contexts.map((context) => communicationClient.removeNotificationContext(context.id)))
|
||||
clearing = false
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
clearing = false
|
||||
console.error(e)
|
||||
Analytics.handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
import { getChannelName, getObjectIcon } from '../utils'
|
||||
import chunter from '../plugin'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let object: Doc
|
||||
export let readonly = false
|
||||
@@ -48,8 +49,8 @@
|
||||
}
|
||||
clazz = hierarchy.getClass(clazz).extends
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch (e: any) {
|
||||
Analytics.handleError(e)
|
||||
return []
|
||||
}
|
||||
return []
|
||||
|
||||
@@ -138,7 +138,6 @@
|
||||
const objectId = await getObjectId(object, client.getHierarchy())
|
||||
Analytics.handleEvent(ChunterEvents.MessageCreated, { ok: false, objectId, objectClass: object._class })
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +150,6 @@
|
||||
const objectId = await getObjectId(object, client.getHierarchy())
|
||||
Analytics.handleEvent(ChunterEvents.MessageEdited, { ok: false, objectId, objectClass: object._class })
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/ai-bot-resources": "^0.6.0",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
|
||||
@@ -14,51 +14,52 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Markup, RateLimiter, Ref, generateId } from '@hcengineering/core'
|
||||
import { tick, createEventDispatcher } from 'svelte'
|
||||
import {
|
||||
uploadFile,
|
||||
deleteFile,
|
||||
getCommunicationClient,
|
||||
getClient,
|
||||
getFileMetadata,
|
||||
isLinkPreviewEnabled
|
||||
} from '@hcengineering/presentation'
|
||||
import {
|
||||
Message,
|
||||
MessageID,
|
||||
LinkPreviewParams,
|
||||
BlobParams,
|
||||
AttachmentID,
|
||||
linkPreviewType,
|
||||
BlobAttachment,
|
||||
AppletParams
|
||||
} from '@hcengineering/communication-types'
|
||||
import { AttachmentPresenter, LinkPreviewCard } from '@hcengineering/attachment-resources'
|
||||
import { areEqualMarkups, isEmptyMarkup } from '@hcengineering/text'
|
||||
import { clearTyping, setTyping } from '@hcengineering/presence-resources'
|
||||
import { FileUploadCallbackParams, getUploadHandlers, UploadHandlerDefinition } from '@hcengineering/uploader'
|
||||
import { Component, showPopup, ThrottledCaller } from '@hcengineering/ui'
|
||||
import { getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { isAppletAttachment, isBlobAttachment, isLinkPreviewAttachment } from '@hcengineering/communication-shared'
|
||||
import {
|
||||
AppletParams,
|
||||
AttachmentID,
|
||||
BlobAttachment,
|
||||
BlobParams,
|
||||
LinkPreviewParams,
|
||||
linkPreviewType,
|
||||
Message,
|
||||
MessageID
|
||||
} from '@hcengineering/communication-types'
|
||||
import { getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { generateId, Markup, RateLimiter, Ref } from '@hcengineering/core'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { clearTyping, setTyping } from '@hcengineering/presence-resources'
|
||||
import {
|
||||
deleteFile,
|
||||
getClient,
|
||||
getCommunicationClient,
|
||||
getFileMetadata,
|
||||
isLinkPreviewEnabled,
|
||||
uploadFile
|
||||
} from '@hcengineering/presentation'
|
||||
import { areEqualMarkups, isEmptyMarkup } from '@hcengineering/text'
|
||||
import { Component, showPopup, ThrottledCaller } from '@hcengineering/ui'
|
||||
import { FileUploadCallbackParams, getUploadHandlers, UploadHandlerDefinition } from '@hcengineering/uploader'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
|
||||
import TextInput from '../TextInput.svelte'
|
||||
import IconAttach from '../icons/Attach.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { getDraft, getEmptyDraft, messageToDraft, removeDraft, saveDraft } from '../../draft'
|
||||
import communication from '../../plugin'
|
||||
import { messageEditingStore } from '../../stores'
|
||||
import { type TextInputAction, AppletDraft, MessageDraft } from '../../types'
|
||||
import {
|
||||
defaultMessageInputActions,
|
||||
toMarkdown,
|
||||
toMarkup,
|
||||
loadLinkPreviewParams,
|
||||
isCardAllowedForCommunications,
|
||||
showForbidden
|
||||
loadLinkPreviewParams,
|
||||
showForbidden,
|
||||
toMarkdown,
|
||||
toMarkup
|
||||
} from '../../utils'
|
||||
import communication from '../../plugin'
|
||||
import { type TextInputAction, MessageDraft, AppletDraft } from '../../types'
|
||||
import TextInput from '../TextInput.svelte'
|
||||
import TypingPresenter from '../TypingPresenter.svelte'
|
||||
import { getDraft, messageToDraft, saveDraft, getEmptyDraft, removeDraft } from '../../draft'
|
||||
import { messageEditingStore } from '../../stores'
|
||||
import IconAttach from '../icons/Attach.svelte'
|
||||
|
||||
export let card: Card
|
||||
export let message: Message | undefined = undefined
|
||||
@@ -156,8 +157,8 @@
|
||||
const r = await getResource(ap.createFn)
|
||||
await r(card, messageId, appletDraft.params)
|
||||
toAttach.push(appletDraft)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { Contact, Employee, getCurrentEmployee, getName, Person } from '@hcengineering/contact'
|
||||
import { AccountUuid, notEmpty, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
@@ -48,7 +49,7 @@
|
||||
const empRef = $employeeRefByAccountUuidStore.get(uuid)
|
||||
|
||||
if (empRef === undefined) {
|
||||
console.error('Employee not found by account id', uuid)
|
||||
Analytics.handleError(new Error(`Employee not found by account id ${uuid}`))
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import contact from '../plugin'
|
||||
import { employeeByPersonIdStore, primarySocialIdByEmployeeRefStore } from '../utils'
|
||||
import UserBox from './UserBox.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let label: IntlString = contact.string.Employee
|
||||
export let value: PersonId | null | undefined
|
||||
@@ -73,6 +74,7 @@
|
||||
}
|
||||
if (socialString === undefined) {
|
||||
console.error('Social id not found for person', e.detail)
|
||||
Analytics.handleError(new Error(`Social id not found for person ${e.detail}`))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import contact from '../plugin'
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
import { employeeByIdStore, employeeRefByAccountUuidStore } from '../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let value: Space
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -19,6 +20,7 @@
|
||||
|
||||
if (employeeRef === undefined) {
|
||||
console.error(`Employee with account ${acc} not found`)
|
||||
Analytics.handleError(new Error(`Employee with account ${acc} not found`))
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import contact from '../plugin'
|
||||
import { employeeByPersonIdStore, primarySocialIdByEmployeeRefStore } from '../utils'
|
||||
import UserBoxList from './UserBoxList.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let label: IntlString
|
||||
export let value: PersonId[]
|
||||
@@ -47,6 +48,7 @@
|
||||
|
||||
if (ref == null) {
|
||||
console.error('Employee not found for social id', p)
|
||||
Analytics.handleError(new Error(`Employee not found for social id ${p}`))
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -71,6 +73,7 @@
|
||||
|
||||
if (primaryId === undefined) {
|
||||
console.error('Primary social id not found for person', person)
|
||||
Analytics.handleError(new Error(`Primary social id not found for person ${person}`))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
import { getIntegrationClient } from '../api'
|
||||
import gmail from '../plugin'
|
||||
import { isNewGmailIntegration } from '../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let integration: AccountIntegration
|
||||
let integrationSettings: Integration | undefined
|
||||
@@ -51,7 +52,9 @@
|
||||
|
||||
async function change (shared: AccountUuid[]) {
|
||||
if (integrationSettings == null) {
|
||||
console.error('Integrations settings are not found', integration.socialId, integration.kind)
|
||||
Analytics.handleError(
|
||||
new Error(`Integrations settings are not found for ${integration.socialId} ${integration.kind}`)
|
||||
)
|
||||
return
|
||||
}
|
||||
integrationSettings.shared = shared
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import gmail from '../plugin'
|
||||
import core, { getCurrentAccount, Ref, Space } from '@hcengineering/core'
|
||||
import GmailColor from './icons/GmailColor.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let integration: Integration
|
||||
|
||||
@@ -44,9 +45,10 @@
|
||||
personSpace = personSpaceObj?._id
|
||||
selectedSpace = integrationSpace ?? personSpace
|
||||
isLoading = false
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
isLoading = false
|
||||
console.error('Failed to find person space:', err)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
dispatch('close')
|
||||
} catch (e: any) {
|
||||
Analytics.handleError(e)
|
||||
console.error(e)
|
||||
error = true
|
||||
} finally {
|
||||
connecting = false
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import gmail from '../plugin'
|
||||
import { getState } from '../api'
|
||||
import platform, { IntlString, OK, ERROR, Status, Severity } from '@hcengineering/platform'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let integration: Integration
|
||||
|
||||
@@ -40,7 +41,7 @@
|
||||
isLoading = false
|
||||
subscribe()
|
||||
status = state?.status === 'inactive' ? new Status(Severity.WARNING, platform.status.OK, {}) : OK
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
status = ERROR
|
||||
error = err instanceof Error ? err.message : 'Failed to load gmail state'
|
||||
if (error.includes('Failed to fetch')) {
|
||||
@@ -48,6 +49,7 @@
|
||||
}
|
||||
isLoading = false
|
||||
console.error('Error loading gmail state:', err)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -75,6 +77,7 @@
|
||||
state = await getState(integration.socialId)
|
||||
} catch (err: any) {
|
||||
console.error('Error refresh gmail state:', err.message)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +253,6 @@
|
||||
return await titleProvider(client, _id)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
type PersonId
|
||||
} from '@hcengineering/core'
|
||||
import HulyMail from './icons/HulyMail.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let integration: Integration | undefined = undefined
|
||||
|
||||
@@ -57,9 +58,9 @@
|
||||
personSpace = personSpaceObj?._id
|
||||
selectedSpace = integrationSpace ?? personSpace
|
||||
isLoading = false
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
isLoading = false
|
||||
console.error('Failed to find person space:', err)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
} else {
|
||||
if (result.email == null) {
|
||||
console.error('No email in auto join info')
|
||||
Analytics.handleError(new Error('No email in auto join info'))
|
||||
navigate({ path: [loginId] })
|
||||
return
|
||||
}
|
||||
@@ -82,6 +83,9 @@
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Failed to check auto join', err)
|
||||
Analytics.handleError(
|
||||
new Error('Failed to check auto join: ' + (err instanceof Error ? err.message : String(err)))
|
||||
)
|
||||
navigate({ path: [loginId] })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import { Dropdown, Icon } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { rooms } from '../stores'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let value: Ref<Room> | undefined
|
||||
export let disabled: boolean = false
|
||||
@@ -64,8 +65,8 @@
|
||||
items = [...items]
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
.catch((err: any) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
} else if (room.person !== null) {
|
||||
const person = $personByRefStore.get(room.person)
|
||||
@@ -79,8 +80,8 @@
|
||||
items = [...items]
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
.catch((err: any) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -93,8 +94,8 @@
|
||||
items = [...items]
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
.catch((err: any) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
return await parseLinkId(linkProviders, id, _class as Ref<Class<Doc>>)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { type Blob, type BlobMetadata, type Ref } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation, { getFileUrl } from '@hcengineering/presentation'
|
||||
import { EmbeddedPDF, Spinner, themeStore } from '@hcengineering/ui'
|
||||
import { convertToHTML } from '@hcengineering/print'
|
||||
import { EmbeddedPDF, Spinner, themeStore } from '@hcengineering/ui'
|
||||
|
||||
export let value: Ref<Blob>
|
||||
export let name: string
|
||||
@@ -29,8 +30,8 @@
|
||||
convertedFile = res
|
||||
isLoading = false
|
||||
},
|
||||
(err) => {
|
||||
console.error(err)
|
||||
(err: any) => {
|
||||
Analytics.handleError(err)
|
||||
isLoading = false
|
||||
}
|
||||
)
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
},
|
||||
(err) => {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
isLoading = false
|
||||
}
|
||||
)
|
||||
@@ -69,7 +68,6 @@
|
||||
},
|
||||
(err) => {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
isLoading = false
|
||||
}
|
||||
)
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/contact-resources": "^0.6.0",
|
||||
"@hcengineering/presentation": "^0.6.3",
|
||||
"@hcengineering/setting-resources": "^0.6.0",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { ExecutionError } from '@hcengineering/process'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
@@ -24,8 +25,8 @@
|
||||
for (const key in val.intlProps) {
|
||||
try {
|
||||
val.props[key] = await translate(val.intlProps[key], {})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import { getContext } from '../../utils'
|
||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let readonly: boolean
|
||||
export let process: Process
|
||||
@@ -57,11 +58,11 @@
|
||||
.then((p) => {
|
||||
editor = p
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
.catch((e: any) => {
|
||||
Analytics.handleError(e)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch (e: any) {
|
||||
Analytics.handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let func: ProcessFunction
|
||||
export let process: Process
|
||||
@@ -49,11 +50,11 @@
|
||||
.then((p) => {
|
||||
editor = p
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
.catch((e: any) => {
|
||||
Analytics.handleError(e)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch (e: any) {
|
||||
Analytics.handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let func: ProcessFunction
|
||||
export let process: Process
|
||||
@@ -49,11 +50,11 @@
|
||||
.then((p) => {
|
||||
editor = p
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
.catch((e: any) => {
|
||||
Analytics.handleError(e)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch (e: any) {
|
||||
Analytics.handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
toIdMap,
|
||||
TxProcessor,
|
||||
WithLookup,
|
||||
type Blob
|
||||
type Blob,
|
||||
type PersonId
|
||||
} from '@hcengineering/core'
|
||||
import { getMetadata, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import presentation, {
|
||||
@@ -456,7 +457,6 @@
|
||||
object.skills = [...object.skills, ...newSkills]
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,8 +464,8 @@
|
||||
if (object.resumeUuid) {
|
||||
try {
|
||||
await deleteFile(object.resumeUuid)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import { IntlString, translateCB } from '@hcengineering/platform'
|
||||
import contact, { getCurrentEmployee, SocialIdentityRef } from '@hcengineering/contact'
|
||||
import { buildSocialIdString, SocialIdType } from '@hcengineering/core'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let mailboxOptions: MailboxOptions
|
||||
|
||||
@@ -83,6 +84,7 @@
|
||||
loading = false
|
||||
formatError(err)
|
||||
console.error('Failed to create mailbox', err)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
import { getAccountClient } from '../utils'
|
||||
import contact, { getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { buildSocialIdString, SocialIdType } from '@hcengineering/core'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let mailbox: MailboxInfo
|
||||
export let mailboxIdx: number
|
||||
@@ -49,8 +50,9 @@
|
||||
loadingRequested()
|
||||
try {
|
||||
await deleteMailbox()
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error('Failed to delete mailbox', err)
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
reloadRequested()
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
import { onMount } from 'svelte'
|
||||
import { MailboxInfo, MailboxOptions } from '@hcengineering/account-client'
|
||||
import MailboxItem from './MailboxItem.svelte'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
let boxesLoading = true
|
||||
let optionsLoading = true
|
||||
@@ -61,6 +62,7 @@
|
||||
.catch((err: any) => {
|
||||
optionsLoading = false
|
||||
console.error('Failed to load mailbox options', err)
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import setting from '../plugin'
|
||||
import { getAccountClient } from '../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
const query = createQuery()
|
||||
const currentAccount = getCurrentAccount()
|
||||
@@ -61,8 +62,8 @@
|
||||
try {
|
||||
await accountClient.updateWorkspaceRole(personUuid, value)
|
||||
workspaceMembers[personUuid] = value
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch (e: any) {
|
||||
Analytics.handleError(e)
|
||||
}
|
||||
}
|
||||
let search = ''
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
label = setting.string.Saved
|
||||
} catch (e: any) {
|
||||
Analytics.handleError(e)
|
||||
console.error(e)
|
||||
label = presentation.string.Save
|
||||
saved = false
|
||||
error = true
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
await disconnect(integration)
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error disconnecting integration:', err)
|
||||
Analytics.handleError(err)
|
||||
const errorMessage: string = err.message ?? 'Unknown error'
|
||||
const integrationError = errorMessage.includes('Failed to fetch')
|
||||
? setting.string.ServiceIsUnavailable
|
||||
@@ -102,7 +102,7 @@
|
||||
await disconnectAll(integration)
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error disconnecting integration:', err)
|
||||
Analytics.handleError(err)
|
||||
const errorMessage: string = err.message ?? 'Unknown error'
|
||||
const integrationError = errorMessage.includes('Failed to fetch')
|
||||
? setting.string.ServiceIsUnavailable
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import IntegrationCard from './IntegrationCard.svelte'
|
||||
import IntegrationErrorNotification from './IntegrationErrorNotification.svelte'
|
||||
import { getAccountClient } from '../../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
const typeQuery = createQuery()
|
||||
|
||||
@@ -76,7 +77,7 @@
|
||||
|
||||
if (error != null) {
|
||||
const decodedError = decodeURIComponent(error)
|
||||
console.error('Integration error:', decodedError)
|
||||
Analytics.handleError(new Error(`Integration error: ${decodedError}`))
|
||||
await showErrorNotification(decodedError)
|
||||
// Clean up integrationError parameter from the URL
|
||||
urlParams.delete('integrationError')
|
||||
@@ -89,8 +90,8 @@
|
||||
}
|
||||
subscribe()
|
||||
startRefreshTimer()
|
||||
} catch (err) {
|
||||
console.error('Error loading integrations:', err)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
await showLoadErrorNotification()
|
||||
connections = []
|
||||
integrations = []
|
||||
@@ -149,8 +150,8 @@
|
||||
connections = await accountClient.listIntegrations({ workspaceUuid: null })
|
||||
integrations = await accountClient.listIntegrations({ workspaceUuid: workspace })
|
||||
lastEventTime = Date.now() // Update last event time after successful refresh
|
||||
} catch (err) {
|
||||
console.error('Error refreshing integrations:', err)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
})()
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import AddSocialId from './AddSocialId.svelte'
|
||||
import setting from '../../plugin'
|
||||
import { getAccountClient } from '../../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let provider: SocialIdentityProvider
|
||||
export let onAdded: (socialId: SocialId) => void | Promise<void>
|
||||
@@ -62,7 +63,7 @@
|
||||
retryOn = otpInfo.retryOn
|
||||
canResend = false
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
Analytics.handleError(err)
|
||||
if (err instanceof PlatformError) {
|
||||
status = err.status
|
||||
} else {
|
||||
@@ -97,7 +98,9 @@
|
||||
|
||||
const newSocialId = socialIds.find((it) => it._id === newSocialIdLoginInfo.socialId)
|
||||
if (newSocialId == null) {
|
||||
console.error(`New social id ${newSocialIdLoginInfo.socialId} not found in the updated social ids list`)
|
||||
Analytics.handleError(
|
||||
new Error(`New social id ${newSocialIdLoginInfo.socialId} not found in the updated social ids list`)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,7 +145,7 @@
|
||||
}
|
||||
dispatch('close')
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
Analytics.handleError(err)
|
||||
if (err instanceof PlatformError) {
|
||||
status = err.status
|
||||
} else {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
import setting from '../../plugin'
|
||||
import { getAccountClient } from '../../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let socialId: SocialId
|
||||
export let socialIdProvider: SocialIdentityProvider
|
||||
@@ -79,7 +80,7 @@
|
||||
currAcc = updatedAccount
|
||||
dispatch('released')
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
Analytics.handleError(err)
|
||||
void setPlatformStatus(unknownError(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"svelte-eslint-parser": "^0.33.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/panel": "^0.6.23",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import SurveyPopup from './SurveyPopup.svelte'
|
||||
import survey from '../plugin'
|
||||
import { makePollData } from '../utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let objectId: Ref<Doc>
|
||||
export let space: Ref<Space>
|
||||
@@ -49,7 +50,7 @@
|
||||
|
||||
const poll = await client.findOne(survey.class.Poll, { _id: pollId })
|
||||
if (poll === undefined) {
|
||||
console.error(`Could not find just created poll ${pollId}.`)
|
||||
Analytics.handleError(new Error(`Could not find just created poll ${pollId}.`))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export let value: Association
|
||||
|
||||
@@ -26,8 +27,8 @@
|
||||
try {
|
||||
const _classLabel = hierarchy.getClass(_class)
|
||||
return _classLabel.label
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
return core.string.Class
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,25 +19,26 @@
|
||||
import { getMetadata, getResource } from '@hcengineering/platform'
|
||||
import presentation, { decodeTokenPayload, isAdminUser } from '@hcengineering/presentation'
|
||||
import {
|
||||
Icon,
|
||||
IconCheck,
|
||||
Label,
|
||||
Loading,
|
||||
Location,
|
||||
SearchEdit,
|
||||
closePopup,
|
||||
fetchMetadataLocalStorage,
|
||||
getCurrentLocation,
|
||||
Icon,
|
||||
IconCheck,
|
||||
isSameSegments,
|
||||
Label,
|
||||
Loading,
|
||||
Location,
|
||||
locationStorageKeyId,
|
||||
locationToUrl,
|
||||
navigate,
|
||||
resolvedLocationStore,
|
||||
SearchEdit,
|
||||
ticker
|
||||
} from '@hcengineering/ui'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { workspacesStore } from '../utils'
|
||||
// import Drag from './icons/Drag.svelte'
|
||||
|
||||
@@ -124,8 +125,8 @@
|
||||
.then(async (json) => {
|
||||
data = await json.json()
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
.catch((err: any) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
@@ -344,7 +344,6 @@
|
||||
return await titleProvider(client, _id)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,15 +32,12 @@ startFront(metricsContext, {
|
||||
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID ?? '',
|
||||
INTERCOM_API_URL: process.env.INTERCOM_API_URL ?? '',
|
||||
GITHUB_URL: process.env.GITHUB_URL ?? '',
|
||||
SENTRY_DSN: process.env.SENTRY_DSN ?? '',
|
||||
LIVEKIT_WS: process.env.LIVEKIT_WS ?? '',
|
||||
LOVE_ENDPOINT: process.env.LOVE_ENDPOINT ?? '',
|
||||
SIGN_URL: process.env.SIGN_URL ?? '',
|
||||
PRINT_URL: process.env.PRINT_URL ?? '',
|
||||
PRESENCE_URL: process.env.PRESENCE_URL ?? '',
|
||||
PUSH_PUBLIC_KEY: process.env.PUSH_PUBLIC_KEY ?? '',
|
||||
POSTHOG_API_KEY: process.env.POSTHOG_API_KEY,
|
||||
POSTHOG_HOST: process.env.POSTHOG_HOST,
|
||||
DESKTOP_UPDATES_URL: process.env.DESKTOP_UPDATES_URL,
|
||||
DESKTOP_UPDATES_CHANNEL: process.env.DESKTOP_UPDATES_CHANNEL,
|
||||
DESKTOP_UPDATES_CHANNELS: process.env.DESKTOP_UPDATES_CHANNELS,
|
||||
|
||||
@@ -18,9 +18,12 @@ export interface Config {
|
||||
Secret: string
|
||||
ServiceID: string
|
||||
AccountsUrl: string
|
||||
PostHogHost: string
|
||||
PostHogAPI: string
|
||||
SentryDSN?: string
|
||||
|
||||
// Optional PostHog configuration
|
||||
// If posthog is not configured, will use OTLP for send events as measurements and errors as errors
|
||||
PostHogHost?: string
|
||||
PostHogAPI?: string
|
||||
|
||||
MaxPayloadSize?: string
|
||||
}
|
||||
|
||||
@@ -34,11 +37,12 @@ const config: Config = (() => {
|
||||
AccountsUrl: process.env.ACCOUNTS_URL,
|
||||
PostHogHost: process.env.POSTHOG_HOST,
|
||||
PostHogAPI: process.env.POSTHOG_API_KEY,
|
||||
SentryDSN: process.env.SENTRY_DSN ?? '',
|
||||
MaxPayloadSize: process.env.MAX_PAYLOAD_SIZE ?? '10mb'
|
||||
}
|
||||
|
||||
const missingEnv = (Object.keys(params) as Array<keyof Config>).filter((key) => params[key] === undefined)
|
||||
const requiredParams = ['Secret', 'AccountsUrl'] as Array<keyof Config>
|
||||
|
||||
const missingEnv = requiredParams.filter((key) => params[key] === undefined)
|
||||
|
||||
if (missingEnv.length > 0) {
|
||||
throw Error(`Missing config for attributes: ${missingEnv.join(', ')}`)
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type MeasureContext } from '@hcengineering/core'
|
||||
import * as fs from 'fs'
|
||||
import * as maxmind from 'maxmind'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import { type MeasureContext } from '@hcengineering/core'
|
||||
|
||||
// MaxMind reader instances
|
||||
let cityReader: maxmind.Reader<maxmind.CityResponse> | null = null
|
||||
|
||||
@@ -17,4 +17,6 @@ import { config } from 'dotenv'
|
||||
import { main } from './main'
|
||||
|
||||
config()
|
||||
void main()
|
||||
void main().catch((err) => {
|
||||
console.error('Error occurred while starting the application:', err)
|
||||
})
|
||||
|
||||
@@ -56,7 +56,7 @@ export const main = async (): Promise<void> => {
|
||||
accountsUrl: config.AccountsUrl
|
||||
})
|
||||
|
||||
const app = createServer()
|
||||
const app = createServer(ctx)
|
||||
const server = listen(app, config.Port)
|
||||
|
||||
const shutdown = (): void => {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// Copyright © 2024 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.
|
||||
//
|
||||
|
||||
import { Client } from '@hcengineering/core'
|
||||
import { createClient, getTransactorEndpoint } from '@hcengineering/server-client'
|
||||
|
||||
export async function connectPlatform (token: string): Promise<Client> {
|
||||
const endpoint = await getTransactorEndpoint(token)
|
||||
return await createClient(endpoint, token)
|
||||
}
|
||||
@@ -13,15 +13,16 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { AnalyticEvent, AnalyticEventType } from '@hcengineering/analytics-collector'
|
||||
import type { MeasureContext } from '@hcengineering/core'
|
||||
import { extractToken } from '@hcengineering/server-client'
|
||||
import { Token } from '@hcengineering/server-token'
|
||||
import cors from 'cors'
|
||||
import express, { type Express, type NextFunction, type Request, type Response } from 'express'
|
||||
import { type Server } from 'http'
|
||||
import { AnalyticEvent, AnalyticEventType } from '@hcengineering/analytics-collector'
|
||||
import { extractToken } from '@hcengineering/server-client'
|
||||
import config from './config'
|
||||
import { ApiError } from './error'
|
||||
import { getGeoLocationFromIp, getClientIp, geoFieldMapping, getAllPossibleIps } from './geoip'
|
||||
import { geoFieldMapping, getAllPossibleIps, getClientIp, getGeoLocationFromIp } from './geoip'
|
||||
|
||||
type AsyncRequestHandler = (req: Request, res: Response, token: Token, next: NextFunction) => Promise<void>
|
||||
|
||||
@@ -236,7 +237,7 @@ async function preparePostHogEvent (event: AnalyticEvent, req: Request): Promise
|
||||
return regularEventForPostHog
|
||||
}
|
||||
|
||||
export function createServer (): Express {
|
||||
export function createServer (ctx: MeasureContext): Express {
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
app.use(express.json({ limit: config.MaxPayloadSize }))
|
||||
@@ -256,48 +257,23 @@ export function createServer (): Express {
|
||||
const payloadSize = JSON.stringify(req.body).length
|
||||
|
||||
console.log(`Received batch: ${events.length} events, ${payloadSize} bytes`)
|
||||
|
||||
const posthogEvents = await Promise.all(
|
||||
events.map(async (event) => {
|
||||
return await preparePostHogEvent(event, req)
|
||||
})
|
||||
)
|
||||
|
||||
const payload = {
|
||||
api_key: config.PostHogAPI,
|
||||
batch: posthogEvents.reverse()
|
||||
}
|
||||
|
||||
const posthogPayloadSize = JSON.stringify(payload).length
|
||||
console.log(`Sending to PostHog: ${posthogEvents.length} events, ${posthogPayloadSize} bytes`)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${config.PostHogHost}/batch/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'Analytics-Collector/1.0'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
console.error(`PostHog API error: ${response.status} ${response.statusText}`, errorText)
|
||||
} else {
|
||||
console.log(`Successfully sent ${posthogEvents.length} events to PostHog`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to send events to PostHog:', error)
|
||||
}
|
||||
|
||||
res.status(200)
|
||||
res.json({
|
||||
received: events.length,
|
||||
processed: posthogEvents.length,
|
||||
payloadSize,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
res.json({})
|
||||
|
||||
if (config.PostHogHost !== undefined) {
|
||||
void sendEventsToPosthog(events, req).catch((err) => {
|
||||
console.error('Error sending events to PostHog:', err)
|
||||
})
|
||||
}
|
||||
|
||||
for (const evt of events) {
|
||||
if (evt.event === AnalyticEventType.Error) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { error_message, error_type, error_stack } = evt.properties ?? {}
|
||||
ctx.error(error_message ?? '', { error_type, error_stack })
|
||||
}
|
||||
// TODO: Handle other events as gauges per minute
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
@@ -314,6 +290,41 @@ export function createServer (): Express {
|
||||
return app
|
||||
}
|
||||
|
||||
async function sendEventsToPosthog (events: AnalyticEvent[], req: Request): Promise<void> {
|
||||
const posthogEvents: Record<string, any>[] = []
|
||||
for (const evt of events) {
|
||||
posthogEvents.push(await preparePostHogEvent(evt, req))
|
||||
}
|
||||
|
||||
const payload = {
|
||||
api_key: config.PostHogAPI,
|
||||
batch: posthogEvents.reverse()
|
||||
}
|
||||
|
||||
const posthogPayloadSize = JSON.stringify(payload).length
|
||||
console.log(`Sending to PostHog: ${posthogEvents.length} events, ${posthogPayloadSize} bytes`)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${config.PostHogHost}/batch/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'Analytics-Collector/1.0'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
console.error(`PostHog API error: ${response.status} ${response.statusText}`, errorText)
|
||||
} else {
|
||||
console.log(`Successfully sent ${posthogEvents.length} events to PostHog`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to send events to PostHog:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export function listen (e: Express, port: number, host?: string): Server {
|
||||
const cb = (): void => {
|
||||
console.log(`Analytics collector service has been started at ${host ?? '*'}:${port}`)
|
||||
|
||||
@@ -18,7 +18,6 @@ dotenvConfig()
|
||||
|
||||
interface Config {
|
||||
secret: string
|
||||
sentryDSN: string
|
||||
accountsUrl: string
|
||||
smtpUrl: string
|
||||
queueRegion: string
|
||||
@@ -26,7 +25,6 @@ interface Config {
|
||||
|
||||
const config: Config = {
|
||||
secret: process.env.SECRET ?? 'secret',
|
||||
sentryDSN: process.env.SENTRY_DSN ?? '',
|
||||
accountsUrl: process.env.ACCOUNTS_URL ?? 'http://localhost:3000',
|
||||
smtpUrl: process.env.SMTP_URL ?? 'http://localhost:8097',
|
||||
queueRegion: process.env.QUEUE_REGION ?? 'localhost'
|
||||
|
||||
@@ -22,7 +22,6 @@ interface Config {
|
||||
|
||||
BotName: string
|
||||
|
||||
SentryDSN: string
|
||||
BrandingPath: string
|
||||
|
||||
WorkspaceInactivityInterval: number // Interval in days to stop workspace synchronization if not visited
|
||||
@@ -49,7 +48,6 @@ const envMap: { [key in keyof Config]: string } = {
|
||||
|
||||
CollaboratorURL: 'COLLABORATOR_URL',
|
||||
|
||||
SentryDSN: 'SENTRY_DSN',
|
||||
BrandingPath: 'BRANDING_PATH',
|
||||
|
||||
WorkspaceInactivityInterval: 'WORKSPACE_INACTIVITY_INTERVAL',
|
||||
@@ -94,7 +92,6 @@ const config: Config = (() => {
|
||||
|
||||
CollaboratorURL: process.env[envMap.CollaboratorURL],
|
||||
|
||||
SentryDSN: process.env[envMap.SentryDSN],
|
||||
BrandingPath: process.env[envMap.BrandingPath] ?? '',
|
||||
WorkspaceInactivityInterval: parseInt(process.env[envMap.WorkspaceInactivityInterval] ?? '3'), // In days
|
||||
RateLimit: parseInt(process.env[envMap.RateLimit] ?? '25')
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
|
||||
## Overview
|
||||
|
||||
The Mail Service is responsible for sending emails using SMTP or SES transfer.
|
||||
The Mail Service is responsible for sending emails using SMTP or SES transfer.
|
||||
It supports sending emails with multiple recipients, along with optional CC, BCC, and HTML content.
|
||||
|
||||
### Configuration
|
||||
|
||||
Environment variables should be set to configure the Mail Service:
|
||||
|
||||
- `PORT`: The port on which the mail service listens for incoming HTTP requests.
|
||||
- `API_KEY`: An API key that clients must pass. The parameter is optional, should be provided when external access to the service is allowed.
|
||||
- `SENTRY_DSN`: (optional) The Data Source Name for Sentry error tracking. This is optional and should be set if you wish to enable error tracking with Sentry.
|
||||
- `SOURCE`: The sender source (fallback for when emails emit from the system).
|
||||
- `REPLY_TO`: (optional) Email to use for replies (useful for uni-directional STMP setups where emails are only emitted, but not received).
|
||||
|
||||
Settings for SMTP or SES email service should be specified, simultaneous use of both protocols is not supported
|
||||
|
||||
SMTP settings:
|
||||
|
||||
- `SMTP_HOST`: Hostname of the SMTP server used for sending emails.
|
||||
- `SMTP_PORT`: Port number of the SMTP server.
|
||||
- `SMTP_USERNAME`: Username for authenticating with the SMTP server. Refer to your SMTP server documentation for the appropriate format.
|
||||
@@ -29,6 +30,7 @@ SMTP settings:
|
||||
- `SMTP_ALLOW_SELF_SIGNED` (Optional): Allow self-signed certificates for TLS connections. Set to 'true' to enable (not recommended for production use). Default: false
|
||||
|
||||
SES settings:
|
||||
|
||||
- `SES_ACCESS_KEY`: AWS SES access key for authentication.
|
||||
- `SES_SECRET_KEY`: AWS SES secret key for authentication.
|
||||
- `SES_REGION`: AWS SES region where your SES service is hosted.
|
||||
@@ -36,14 +38,17 @@ SES settings:
|
||||
### Running the Service
|
||||
|
||||
Add .env file to the root of the project with the following content to add integration with fake SMTP server:
|
||||
|
||||
```
|
||||
PORT=8097
|
||||
SMTP_HOST="mail.smtpbucket.com"
|
||||
SMTP_PORT=8025
|
||||
SMTP_PORT=8025
|
||||
```
|
||||
|
||||
To use the real SMTP server it is required to register an account in some email service provider and specify settings and credentials for it.
|
||||
|
||||
Start the service locally using:
|
||||
|
||||
```bash
|
||||
rushx run-local
|
||||
```
|
||||
@@ -76,6 +81,7 @@ Send an email message.
|
||||
- `raw`: An optional special value that overrides the entire contents of the current MIME node, including MIME headers. Useful if you want to prepare node contents yourself.
|
||||
|
||||
Request body example:
|
||||
|
||||
```
|
||||
{
|
||||
"subject": "Test SMTP",
|
||||
@@ -87,7 +93,7 @@ Request body example:
|
||||
"filename": "test.txt",
|
||||
"content": "Hello world",
|
||||
"contentType": "text/plain"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface Config {
|
||||
QueueConfig: string
|
||||
QueueRegion: string
|
||||
Secret: string
|
||||
SentryDSN: string
|
||||
ServiceId: string
|
||||
}
|
||||
|
||||
@@ -46,7 +45,6 @@ const config: Config = (() => {
|
||||
App: process.env.APP ?? 'Huly',
|
||||
OtpTimeToLiveSec: parseNumber(process.env.OTP_TIME_TO_LIVE_SEC) ?? 5 * 60,
|
||||
OtpRetryDelaySec: parseNumber(process.env.OTP_RETRY_DELAY_SEC) ?? 60,
|
||||
SentryDSN: process.env.SENTRY_DSN ?? '',
|
||||
AccountsURL: process.env.ACCOUNTS_URL,
|
||||
DbUrl: process.env.DB_URL,
|
||||
QueueRegion: process.env.QUEUE_REGION,
|
||||
|
||||
Reference in New Issue
Block a user