platform/services/analytics-collector/pod-analytics-collector/src/format.ts
Andrey Sobolev ddecae80dd
Move services to public (#6156)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
2024-07-28 14:55:43 +07:00

401 lines
12 KiB
TypeScript

//
// 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 analyticsCollector, { AnalyticEvent, AnalyticEventType } from '@hcengineering/analytics-collector'
import chunter, { chunterId } from '@hcengineering/chunter'
import contact, { contactId } from '@hcengineering/contact'
import document, { documentId } from '@hcengineering/document'
import drive, { driveId } from '@hcengineering/drive'
import love, { loveId } from '@hcengineering/love'
import notification, { notificationId } from '@hcengineering/notification'
import recruit, { recruitId } from '@hcengineering/recruit'
import time, { timeId } from '@hcengineering/time'
import tracker, { trackerId } from '@hcengineering/tracker'
import { Class, Doc, Hierarchy, Markup, Ref } from '@hcengineering/core'
import { MarkupNode, MarkupNodeType, MarkupMark, MarkupMarkType } from '@hcengineering/text'
import { translate } from '@hcengineering/platform'
export async function eventToMarkup (event: AnalyticEvent, hierarchy: Hierarchy): Promise<Markup | undefined> {
switch (event.event) {
case AnalyticEventType.CustomEvent:
return formatCustomEvent(event)
case AnalyticEventType.Error:
return await formatErrorEvent(event)
case AnalyticEventType.Navigation:
return await formatNavigationEvent(event, hierarchy)
case AnalyticEventType.SetTag:
return await formatSetTagEvent(event)
case AnalyticEventType.SetUser:
return undefined
}
}
function toMarkup (items: MarkupNode[]): Markup {
return JSON.stringify({
type: 'doc',
content: [
{
type: 'paragraph',
content: items
}
]
})
}
function toText (text: string, display: 'normal' | 'bold' | 'code' = 'normal'): MarkupNode {
const marks: MarkupMark[] = []
if (display === 'bold') {
marks.push({ type: MarkupMarkType.bold, attrs: {} })
}
if (display === 'code') {
marks.push({ type: MarkupMarkType.code, attrs: {} })
}
return { type: MarkupNodeType.text, text, marks }
}
function formatCustomEvent (event: AnalyticEvent): string | undefined {
const text = event.params.event as string | undefined
if (text === undefined || text === '') return
return toMarkup([toText(text)])
}
async function formatErrorEvent (event: AnalyticEvent): Promise<string | undefined> {
const error = event.params.error
if (error === undefined || (typeof error === 'object' && Object.keys(error).length === 0)) {
return
}
const text = await translate(analyticsCollector.string.Error, {})
return toMarkup([toText(text), toText(error.message ?? JSON.stringify(error), 'code')])
}
async function formatSetTagEvent (event: AnalyticEvent): Promise<string | undefined> {
const key = event.params.key
if (key === undefined || key === '' || key === 'workspace') return
const value = event.params.value
const setLabel = await translate(analyticsCollector.string.Set, {})
const toLabel = await translate(analyticsCollector.string.To, {})
return toMarkup([toText(setLabel), toText(key, 'bold'), toText(toLabel), toText(value, 'bold')])
}
async function formatNavigationEvent (event: AnalyticEvent, hierarchy: Hierarchy): Promise<string | undefined> {
const path = event.params.path as string | undefined
if (path === undefined || path === '') return
const location = parseLocation(new URL('http://localhost:8080' + path))
if (location.path.length < 3) {
return undefined
}
return toMarkup(await formatPath(location, hierarchy))
}
async function formatPath (location: Location, hierarchy: Hierarchy): Promise<MarkupNode[]> {
const { path } = location
const app = path[2]
if (app === chunterId) {
const app = await translate(chunter.string.Chat, {})
return await formatLocWithThreads(location, hierarchy, app)
}
if (app === notificationId) {
const app = await translate(notification.string.Inbox, {})
return await formatLocWithThreads(location, hierarchy, app)
}
if (app === timeId) {
return await formatTimeLoc(location, hierarchy)
}
if (app === loveId) {
const app = await translate(love.string.Office, {})
return await navigateToApp(app)
}
if (app === contactId) {
return await formatContactsLoc(location)
}
if (app === recruitId) {
return await formatRecruitLoc(location, hierarchy)
}
if (app === trackerId) {
return await formatTrackerLoc(location, hierarchy)
}
if (app === documentId) {
return await formatDocumentLoc(location, hierarchy)
}
if (app === 'team') {
const app = await translate(time.string.Team, {})
return await navigateToApp(app)
}
if (app === driveId) {
return await formatDriveLoc(location)
}
return await formatDefaultLoc(location)
}
async function navigateToApp (app: string): Promise<MarkupNode[]> {
const workbench = await translate(analyticsCollector.string.Workbench, {})
return [toText(workbench, 'bold'), toText(app)]
}
async function navigateToSpecial (app: string, special: string, id?: string): Promise<MarkupNode[]> {
const openSpecial = await translate(analyticsCollector.string.OpenSpecial, { special })
return [
toText(app, 'bold'),
toText(': ', 'bold'),
toText(openSpecial),
...(id !== undefined ? [toText(' '), toText(id, 'code')] : [])
]
}
async function formatLocWithThreads (location: Location, hierarchy: Hierarchy, app: string): Promise<MarkupNode[]> {
const { path } = location
if (path.length < 4) {
return await navigateToApp(app)
}
const [_id, _class] = decodeURIComponent(path[3]).split('|')
if (_id === undefined || _class === undefined) {
return await navigateToSpecial(app, path[3])
}
const label = await translate(hierarchy.getClass(_class as Ref<Class<Doc>>).label, {})
const thread = await translate(chunter.string.Thread, {})
const openSpecial = await translate(analyticsCollector.string.OpenSpecial, { special: thread })
return [
...(await navigateToSpecial(app, label, _id)),
...(path[4] !== undefined ? [toText(', '), toText(openSpecial), toText(path[4], 'code')] : [])
]
}
async function formatTimeLoc (location: Location, hierarchy: Hierarchy): Promise<MarkupNode[]> {
const { fragment } = location
const app = await translate(time.string.Planner, {})
if (fragment === undefined || fragment === '') {
return await navigateToApp(app)
}
const [, _id, _class] = decodeURIComponent(fragment).split('|')
if (_id === undefined || _class === undefined) {
return await navigateToApp(app)
}
const label = await translate(hierarchy.getClass(_class as Ref<Class<Doc>>).label, {})
return await navigateToSpecial(app, label, _id)
}
async function formatRecruitLoc (location: Location, hierarchy: Hierarchy): Promise<MarkupNode[]> {
const { path } = location
const app = await translate(recruit.string.RecruitApplication, {})
if (path.length < 4) {
return await navigateToApp(app)
}
const applicationClass = hierarchy.getClass(recruit.class.Applicant)
const vacancyClass = hierarchy.getClass(recruit.class.Vacancy)
const reviewClass = hierarchy.getClass(recruit.class.Review)
const talentClass = hierarchy.getClass(recruit.mixin.Candidate)
const vacancyListClass = hierarchy.getClass(recruit.mixin.VacancyList)
const [prefix] = path[3].split('-')
let label: string | undefined
if (prefix === applicationClass.shortLabel) {
label = await translate(applicationClass.label, {})
}
if (prefix === vacancyClass.shortLabel) {
label = await translate(vacancyClass.label, {})
}
if (prefix === reviewClass.shortLabel) {
label = await translate(reviewClass.label, {})
}
if (prefix === talentClass.shortLabel) {
label = await translate(talentClass.label, {})
}
if (prefix === vacancyListClass.shortLabel) {
label = await translate(vacancyListClass.label, {})
}
if (label !== undefined) {
return await navigateToSpecial(app, label, path[3])
}
return await navigateToSpecial(app, path[3])
}
async function formatTrackerLoc (location: Location, hierarchy: Hierarchy): Promise<MarkupNode[]> {
const { path, fragment } = location
const app = await translate(tracker.string.TrackerApplication, {})
if (path.length < 4) {
return await navigateToApp(app)
}
if (path.length === 4) {
const issue = await translate(tracker.string.Issue, {})
return await navigateToSpecial(app, issue, path[3])
}
const [, _id, _class] = fragment !== undefined ? decodeURIComponent(fragment).split('|') : []
if (_id === undefined || _class === undefined) {
const toSpecialLabel = await navigateToSpecial(app, path[4])
const inProject = await translate(analyticsCollector.string.InProject, {})
return [...toSpecialLabel, toText(inProject), toText(path[3], 'code')]
}
const clazz = hierarchy.getClass(_class as Ref<Class<Doc>>)
const label = await translate(clazz.label, {})
const openSpecial = await translate(analyticsCollector.string.OpenSpecial, { special: label })
const inProject = await translate(analyticsCollector.string.InProject, {})
return [
toText(app, 'bold'),
toText(': ', 'bold'),
toText(openSpecial),
toText(_id, 'code'),
toText(inProject),
toText(path[3], 'code')
]
}
async function formatDriveLoc (location: Location): Promise<MarkupNode[]> {
const { path } = location
const app = await translate(drive.string.Drive, {})
if (path.length < 4) {
return await navigateToApp(app)
}
if (path.length === 4) {
return await navigateToSpecial(app, path[3])
}
const label = await translate(drive.string.Folder, {})
return await navigateToSpecial(app, label, path[4])
}
async function formatContactsLoc (location: Location): Promise<MarkupNode[]> {
const { path } = location
const app = await translate(contact.string.Contacts, {})
if (path.length < 4) {
return await navigateToApp(app)
}
const spaces = ['employees', 'contacts', 'persons', 'companies']
if (spaces.includes(path[3])) {
return await navigateToSpecial(app, path[3])
}
return [
toText(app, 'bold'),
toText(': ', 'bold'),
toText(await translate(analyticsCollector.string.Open, {})),
toText(path[3], 'code')
]
}
async function formatDocumentLoc (location: Location, hierarchy: Hierarchy): Promise<MarkupNode[]> {
const { path } = location
const app = await translate(document.string.Documents, {})
if (path.length < 4) {
return await navigateToApp(app)
}
const clazz = hierarchy.getClass(document.class.Document)
const label = await translate(clazz.label, {})
return await navigateToSpecial(app, label, path[3])
}
async function formatDefaultLoc (location: Location, app?: string): Promise<MarkupNode[]> {
const { path } = location
if (path.length < 4) {
return await navigateToApp(app ?? path[2])
}
return await navigateToSpecial(app ?? path[2], path[3])
}
export interface Location {
path: string[]
fragment?: string
}
export function parseLocation (location: URL): Location {
return {
path: parsePath(location.pathname),
fragment: parseHash(location.hash)
}
}
function parsePath (path: string): string[] {
const split = path.split('/').map((ps) => decodeURIComponent(ps))
if (split.length >= 1) {
if (split[0] === '') {
split.splice(0, 1)
}
}
if (split.length >= 1) {
if (split[split.length - 1] === '') {
split.splice(split.length - 1, 1)
}
}
return split
}
function parseHash (hash: string): string {
if (hash.startsWith('#')) {
return decodeURIComponent(hash.substring(1))
}
return decodeURIComponent(hash)
}