feat: integrate umami (#1809)

* feat: integrate umami

* fix: linter issue

* fix: run eslint

* fix window umami null

* fix property type error

* fix: check configuration before requesting analytics script

* fix: test cases

---------

Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
Hieu 2024-02-01 23:00:16 +09:00 committed by GitHub
parent 11e2a763cb
commit 36cd5988d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 101 additions and 89 deletions

View File

@ -98,8 +98,8 @@ jobs:
make build-and-publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANALYTICS_ID: ${{ secrets.JAN_APP_POSTHOG_PROJECT_API_KEY }}
ANALYTICS_HOST: ${{ secrets.JAN_APP_POSTHOG_URL }}
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
- name: Upload Artifact .deb file
if: inputs.public_provider != 'github'

View File

@ -137,8 +137,8 @@ jobs:
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APP_PATH: "."
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
ANALYTICS_ID: ${{ secrets.JAN_APP_POSTHOG_PROJECT_API_KEY }}
ANALYTICS_HOST: ${{ secrets.JAN_APP_POSTHOG_URL }}
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
- name: Upload Artifact
if: inputs.public_provider != 'github'

View File

@ -127,8 +127,8 @@ jobs:
make build-and-publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANALYTICS_ID: ${{ secrets.JAN_APP_POSTHOG_PROJECT_API_KEY }}
ANALYTICS_HOST: ${{ secrets.JAN_APP_POSTHOG_URL }}
ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}

View File

@ -1,5 +1,5 @@
GTM_ID=xxxx
POSTHOG_PROJECT_API_KEY=xxxx
POSTHOG_APP_URL=xxxx
UMAMI_PROJECT_API_KEY=xxxx
UMAMI_APP_URL=xxxx
ALGOLIA_API_KEY=xxxx
ALGOLIA_APP_ID=xxxx

View File

@ -38,7 +38,6 @@ test.afterAll(async () => {
})
test('explores hub', async () => {
// Set the timeout for this test to 60 seconds
test.setTimeout(TIMEOUT)
await page.getByTestId('Hub').first().click({
timeout: TIMEOUT,

View File

@ -38,6 +38,7 @@ test.afterAll(async () => {
})
test('renders left navigation panel', async () => {
test.setTimeout(TIMEOUT)
const systemMonitorBtn = await page
.getByTestId('System Monitor')
.first()
@ -50,8 +51,11 @@ test('renders left navigation panel', async () => {
.isEnabled({ timeout: TIMEOUT })
expect([systemMonitorBtn, settingsBtn].filter((e) => !e).length).toBe(0)
// Chat section should be there
const apiServer = await page.getByTestId('Local API Server').first()
expect(apiServer).toBeVisible({
await page.getByTestId('Local API Server').first().click({
timeout: TIMEOUT,
})
const localServer = await page.getByTestId('local-server-testid').first()
await expect(localServer).toBeVisible({
timeout: TIMEOUT,
})
})

View File

@ -38,7 +38,8 @@ test.afterAll(async () => {
})
test('shows settings', async () => {
test.setTimeout(TIMEOUT)
await page.getByTestId('Settings').first().click({ timeout: TIMEOUT })
const settingDescription = page.getByTestId('testid-setting-description')
expect(settingDescription).toBeVisible({ timeout: TIMEOUT })
await expect(settingDescription).toBeVisible({ timeout: TIMEOUT })
})

View File

@ -6,8 +6,6 @@ import { Toaster } from 'react-hot-toast'
import { TooltipProvider } from '@janhq/uikit'
import { PostHogProvider } from 'posthog-js/react'
import GPUDriverPrompt from '@/containers/GPUDriverPromptModal'
import EventListenerWrapper from '@/containers/Providers/EventListener'
import JotaiWrapper from '@/containers/Providers/Jotai'
@ -21,7 +19,7 @@ import {
setupBaseExtensions,
} from '@/services/extensionService'
import { instance } from '@/utils/posthog'
import Umami from '@/utils/umami'
import KeyListener from './KeyListener'
@ -70,25 +68,22 @@ const Providers = (props: PropsWithChildren) => {
}, [setupCore])
return (
<PostHogProvider client={instance}>
<JotaiWrapper>
<ThemeWrapper>
{setupCore && activated && (
<KeyListener>
<FeatureToggleWrapper>
<EventListenerWrapper>
<TooltipProvider delayDuration={0}>
{children}
</TooltipProvider>
{!isMac && <GPUDriverPrompt />}
</EventListenerWrapper>
<Toaster />
</FeatureToggleWrapper>
</KeyListener>
)}
</ThemeWrapper>
</JotaiWrapper>
</PostHogProvider>
<JotaiWrapper>
<ThemeWrapper>
<Umami />
{setupCore && activated && (
<KeyListener>
<FeatureToggleWrapper>
<EventListenerWrapper>
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
{!isMac && <GPUDriverPrompt />}
</EventListenerWrapper>
<Toaster />
</FeatureToggleWrapper>
</KeyListener>
)}
</ThemeWrapper>
</JotaiWrapper>
)
}

View File

@ -25,10 +25,8 @@ const nextConfig = {
...config.plugins,
new webpack.DefinePlugin({
VERSION: JSON.stringify(packageJson.version),
ANALYTICS_ID:
JSON.stringify(process.env.ANALYTICS_ID) ?? JSON.stringify('xxx'),
ANALYTICS_HOST:
JSON.stringify(process.env.ANALYTICS_HOST) ?? JSON.stringify('xxx'),
ANALYTICS_ID: JSON.stringify(process.env.ANALYTICS_ID),
ANALYTICS_HOST: JSON.stringify(process.env.ANALYTICS_HOST),
API_BASE_URL: JSON.stringify('http://localhost:1337'),
isMac: process.platform === 'darwin',
isWindows: process.platform === 'win32',

View File

@ -103,7 +103,7 @@ const LocalServerScreen = () => {
}, [handleChangePort, port])
return (
<div className="flex h-full w-full">
<div className="flex h-full w-full" data-testid="local-server-testid">
{/* Left SideBar */}
<div className="flex h-full w-60 flex-shrink-0 flex-col overflow-y-auto border-r border-border">
<div className="p-4">

View File

@ -1,50 +0,0 @@
import posthog, { Properties } from 'posthog-js'
// Initialize PostHog
posthog.init(ANALYTICS_ID, {
api_host: ANALYTICS_HOST,
autocapture: false,
capture_pageview: false,
capture_pageleave: false,
rageclick: false,
})
// Export the PostHog instance
export const instance = posthog
// Enum for Analytics Events
export enum AnalyticsEvent {
Ping = 'Ping',
}
// Function to determine the operating system
function getOperatingSystem(): string {
if (isMac) return 'MacOS'
if (isWindows) return 'Windows'
if (isLinux) return 'Linux'
return 'Unknown'
}
function captureAppVersionAndOS() {
const properties: Properties = {
$appVersion: VERSION,
$userOperatingSystem: getOperatingSystem(),
// Set the following Posthog default properties to empty strings
$initial_browser: '',
$browser: '',
$initial_browser_version: '',
$browser_version: '',
$initial_current_url: '',
$current_url: '',
$initial_device_type: '',
$device_type: '',
$initial_pathname: '',
$pathname: '',
$initial_referrer: '',
$referrer: '',
$initial_referring_domain: '',
$referring_domain: '',
}
posthog.capture(AnalyticsEvent.Ping, properties)
}
captureAppVersionAndOS()

65
web/utils/umami.tsx Normal file
View File

@ -0,0 +1,65 @@
import { useEffect } from 'react'
import Script from 'next/script'
// Define the type for the umami data object
interface UmamiData {
version: string
}
declare global {
interface Window {
umami:
| {
track: (event: string, data?: UmamiData) => void
}
| undefined
}
}
const Umami = () => {
const appVersion = VERSION
const analyticsHost = ANALYTICS_HOST
const analyticsId = ANALYTICS_ID
useEffect(() => {
if (!appVersion || !analyticsHost || !analyticsId) return
const ping = () => {
// Check if umami is defined before ping
if (window.umami !== null && typeof window.umami !== 'undefined') {
window.umami.track(appVersion, {
version: appVersion,
})
}
}
// Wait for umami to be defined before ping
if (window.umami !== null && typeof window.umami !== 'undefined') {
ping()
} else {
// Listen for umami script load event
document.addEventListener('umami:loaded', ping)
}
// Cleanup function to remove event listener if the component unmounts
return () => {
document.removeEventListener('umami:loaded', ping)
}
}, [appVersion, analyticsHost, analyticsId])
return (
<>
{appVersion && analyticsHost && analyticsId && (
<Script
src={analyticsHost}
data-website-id={analyticsId}
data-cache="true"
defer
onLoad={() => document.dispatchEvent(new Event('umami:loaded'))}
/>
)}
</>
)
}
export default Umami