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 make build-and-publish
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANALYTICS_ID: ${{ secrets.JAN_APP_POSTHOG_PROJECT_API_KEY }} ANALYTICS_ID: ${{ secrets.JAN_APP_UMAMI_PROJECT_API_KEY }}
ANALYTICS_HOST: ${{ secrets.JAN_APP_POSTHOG_URL }} ANALYTICS_HOST: ${{ secrets.JAN_APP_UMAMI_URL }}
- name: Upload Artifact .deb file - name: Upload Artifact .deb file
if: inputs.public_provider != 'github' if: inputs.public_provider != 'github'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -38,7 +38,8 @@ test.afterAll(async () => {
}) })
test('shows settings', async () => { test('shows settings', async () => {
test.setTimeout(TIMEOUT)
await page.getByTestId('Settings').first().click({ timeout: TIMEOUT }) await page.getByTestId('Settings').first().click({ timeout: TIMEOUT })
const settingDescription = page.getByTestId('testid-setting-description') 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 { TooltipProvider } from '@janhq/uikit'
import { PostHogProvider } from 'posthog-js/react'
import GPUDriverPrompt from '@/containers/GPUDriverPromptModal' import GPUDriverPrompt from '@/containers/GPUDriverPromptModal'
import EventListenerWrapper from '@/containers/Providers/EventListener' import EventListenerWrapper from '@/containers/Providers/EventListener'
import JotaiWrapper from '@/containers/Providers/Jotai' import JotaiWrapper from '@/containers/Providers/Jotai'
@ -21,7 +19,7 @@ import {
setupBaseExtensions, setupBaseExtensions,
} from '@/services/extensionService' } from '@/services/extensionService'
import { instance } from '@/utils/posthog' import Umami from '@/utils/umami'
import KeyListener from './KeyListener' import KeyListener from './KeyListener'
@ -70,25 +68,22 @@ const Providers = (props: PropsWithChildren) => {
}, [setupCore]) }, [setupCore])
return ( return (
<PostHogProvider client={instance}> <JotaiWrapper>
<JotaiWrapper> <ThemeWrapper>
<ThemeWrapper> <Umami />
{setupCore && activated && ( {setupCore && activated && (
<KeyListener> <KeyListener>
<FeatureToggleWrapper> <FeatureToggleWrapper>
<EventListenerWrapper> <EventListenerWrapper>
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>{children}</TooltipProvider>
{children} {!isMac && <GPUDriverPrompt />}
</TooltipProvider> </EventListenerWrapper>
{!isMac && <GPUDriverPrompt />} <Toaster />
</EventListenerWrapper> </FeatureToggleWrapper>
<Toaster /> </KeyListener>
</FeatureToggleWrapper> )}
</KeyListener> </ThemeWrapper>
)} </JotaiWrapper>
</ThemeWrapper>
</JotaiWrapper>
</PostHogProvider>
) )
} }

View File

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

View File

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