Skip to content

Commit 3b18183

Browse files
committed
feat(desktop): add umami
feat(destkop):add umami
1 parent b33e2ec commit 3b18183

File tree

17 files changed

+198
-60
lines changed

17 files changed

+198
-60
lines changed

Diff for: frontend/desktop/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"@sealos/driver": "workspace:^",
3535
"@sealos/ui": "workspace:^",
3636
"@tanstack/react-query": "^4.35.3",
37+
"@umami/node": "^0.4.0",
3738
"axios": "^1.5.1",
3839
"clsx": "^1.2.1",
3940
"cors": "^2.8.5",
@@ -91,6 +92,7 @@
9192
"@types/nprogress": "^0.2.1",
9293
"@types/react": "18.2.37",
9394
"@types/react-dom": "18.0.11",
95+
"@types/umami-browser": "^2.3.2",
9496
"@types/uuid": "^9.0.4",
9597
"dotenv-cli": "^7.3.0",
9698
"jest": "^29.7.0",

Diff for: frontend/desktop/src/api/auth.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,16 @@ export const _enterpriseRealNameAuthCancelRequest = (request: AxiosInstance) =>
208208

209209
export const _getAmount = (request: AxiosInstance) => () =>
210210
request<never, ApiResp<{ balance: number; deductionBalance: number }>>('/api/account/getAmount');
211-
211+
export const _verifyToken = (request: AxiosInstance) => () =>
212+
request<never, ApiResp<null>>('/api/auth/verify');
212213
export const passwordExistRequest = _passwordExistRequest(request);
213214
export const passwordLoginRequest = _passwordLoginRequest(request, (token) => {
214215
useSessionStore.setState({ token });
215216
});
217+
216218
export const passwordModifyRequest = _passwordModifyRequest(request);
217219
export const UserInfo = _UserInfo(request);
220+
export const verifyToken = _verifyToken(request);
218221
export const regionList = _regionList(request);
219222

220223
export const getSmsBindCodeRequest = _getSmsBindCodeRequest(request);

Diff for: frontend/desktop/src/components/team/WorkspaceToggle.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { nsListRequest, switchRequest } from '@/api/namespace';
22
import NsListItem from '@/components/team/NsListItem';
33
import TeamCenter from '@/components/team/TeamCenter';
4-
import useAppStore from '@/stores/app';
54
import useSessionStore from '@/stores/session';
65
import { NSType } from '@/types/team';
76
import { AccessTokenPayload } from '@/types/token';
@@ -16,13 +15,12 @@ import { CubeIcon, DesktopExchangeIcon } from '../icons';
1615

1716
export default function WorkspaceToggle() {
1817
const disclosure = useDisclosure();
19-
const { setWorkSpaceId, session } = useSessionStore();
18+
const { session } = useSessionStore();
2019
const { t } = useTranslation();
2120
const user = session?.user;
2221
const ns_uid = user?.ns_uid || '';
2322
const router = useRouter();
2423
const queryClient = useQueryClient();
25-
const { init } = useAppStore();
2624
const mutation = useMutation({
2725
mutationFn: switchRequest,
2826
async onSuccess(data) {

Diff for: frontend/desktop/src/constants/account.ts

+5
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ export const LicenseFrontendKey = 'cloud.sealos.io/license-frontend';
44
export const templateDeployKey = 'cloud.sealos.io/deploy-on-sealos';
55

66
export const userSystemNamespace = 'user-system' as const;
7+
8+
export enum trackEventName {
9+
'dailyLoginFirst' = 'dailyLoginFirst',
10+
'signUp' = 'signUp'
11+
}

Diff for: frontend/desktop/src/pages/_app.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,5 @@ const App = ({ Component, pageProps }: AppProps) => {
4848
</QueryClientProvider>
4949
);
5050
};
51+
5152
export default appWithTranslation(App);

Diff for: frontend/desktop/src/pages/_document.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Head, Html, Main, NextScript } from 'next/document';
2-
import { ColorModeScript } from '@chakra-ui/react';
31
import { theme } from '@/styles/chakraTheme';
2+
import { ColorModeScript } from '@chakra-ui/react';
3+
import { Head, Html, Main, NextScript } from 'next/document';
44

55
export default function Document() {
66
return (

Diff for: frontend/desktop/src/pages/api/platform/getAppConfig.ts

+22-14
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1+
import { getAuthClientConfig } from '@/pages/api/platform/getAuthConfig';
2+
import { getCloudConfig } from '@/pages/api/platform/getCloudConfig';
3+
import { getLayoutConfig } from '@/pages/api/platform/getLayoutConfig';
4+
import {
5+
commitTransactionjob,
6+
finishTransactionJob,
7+
runTransactionjob
8+
} from '@/services/backend/cronjob';
19
import { jsonRes } from '@/services/backend/response';
2-
import type { NextApiRequest, NextApiResponse } from 'next';
310
import {
411
AppClientConfigType,
512
AuthClientConfigType,
613
CloudConfigType,
714
CommonClientConfigType,
815
DefaultAppClientConfig,
9-
LayoutConfigType
16+
LayoutConfigType,
17+
TrackingConfigType
1018
} from '@/types/system';
11-
import { getCloudConfig } from '@/pages/api/platform/getCloudConfig';
12-
import { getAuthClientConfig } from '@/pages/api/platform/getAuthConfig';
13-
import { getLayoutConfig } from '@/pages/api/platform/getLayoutConfig';
14-
import { getCommonClientConfig } from './getCommonConfig';
1519
import { Cron } from 'croner';
16-
import {
17-
commitTransactionjob,
18-
finishTransactionJob,
19-
runTransactionjob
20-
} from '@/services/backend/cronjob';
20+
import type { NextApiRequest, NextApiResponse } from 'next';
21+
import { getCommonClientConfig } from './getCommonConfig';
2122

2223
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
2324
const config = await getAppConfig();
@@ -31,15 +32,17 @@ function genResConfig(
3132
cloudConf: CloudConfigType,
3233
authConf: AuthClientConfigType,
3334
commonConf: CommonClientConfigType,
34-
layoutConf: LayoutConfigType
35+
layoutConf: LayoutConfigType,
36+
tracking: Required<TrackingConfigType>
3537
): AppClientConfigType {
3638
return {
3739
cloud: cloudConf,
3840
common: commonConf,
3941
desktop: {
4042
auth: authConf,
4143
layout: layoutConf
42-
}
44+
},
45+
tracking: tracking
4346
};
4447
}
4548

@@ -49,7 +52,12 @@ export async function getAppConfig(): Promise<AppClientConfigType> {
4952
const authConf = await getAuthClientConfig();
5053
const commonConf = await getCommonClientConfig();
5154
const layoutConf = await getLayoutConfig();
52-
const conf = genResConfig(cloudConf, authConf, commonConf, layoutConf);
55+
const _tracking = global.AppConfig.tracking;
56+
const tracking: Required<TrackingConfigType> = {
57+
websiteId: _tracking.websiteId || '',
58+
hostUrl: _tracking.hostUrl || ''
59+
};
60+
const conf = genResConfig(cloudConf, authConf, commonConf, layoutConf, tracking);
5361
if (!global.commitCroner) {
5462
// console.log('init commit croner');
5563
global.commitCroner = new Cron('* * * * * *', commitTransactionjob, {

Diff for: frontend/desktop/src/pages/api/platform/getCommonConfig.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import {
66
DefaultCommonClientConfig
77
} from '@/types/system';
88
import { readFileSync } from 'fs';
9-
import type { NextApiRequest, NextApiResponse } from 'next';
109
import yaml from 'js-yaml';
10+
import type { NextApiRequest, NextApiResponse } from 'next';
1111

1212
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
1313
const config = await getCommonClientConfig();
@@ -18,6 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
1818
}
1919
function genResCommonClientConfig(common: CommonConfigType): CommonClientConfigType {
2020
return {
21+
trackingEnabled: !!common.trackingEnabled,
2122
enterpriseRealNameAuthEnabled: !!common.enterpriseRealNameAuthEnabled,
2223
realNameAuthEnabled: !!common.realNameAuthEnabled,
2324
realNameReward: common.realNameReward || 0,

Diff for: frontend/desktop/src/pages/index.tsx

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { nsListRequest, switchRequest } from '@/api/namespace';
22
import DesktopContent from '@/components/desktop_content';
3+
import { trackEventName } from '@/constants/account';
34
import useAppStore from '@/stores/app';
45
import useCallbackStore from '@/stores/callback';
56
import { useConfigStore } from '@/stores/config';
@@ -14,6 +15,7 @@ import { switchKubeconfigNamespace } from '@/utils/switchKubeconfigNamespace';
1415
import { compareFirstLanguages } from '@/utils/tools';
1516
import { Box, useColorMode } from '@chakra-ui/react';
1617
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
18+
import dayjs from 'dayjs';
1719
import { jwtDecode } from 'jwt-decode';
1820
import { isString } from 'lodash';
1921
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
@@ -32,13 +34,13 @@ export const MoreAppsContext = createContext<IMoreAppsContext | null>(null);
3234

3335
export default function Home({ sealos_cloud_domain }: { sealos_cloud_domain: string }) {
3436
const router = useRouter();
35-
const { isUserLogin } = useSessionStore();
37+
const { firstUse, setFirstUse, isUserLogin } = useSessionStore();
3638
const { colorMode, toggleColorMode } = useColorMode();
3739
const init = useAppStore((state) => state.init);
3840
const setAutoLaunch = useAppStore((state) => state.setAutoLaunch);
3941
const { autolaunchWorkspaceUid } = useAppStore();
4042
const { session } = useSessionStore();
41-
const { layoutConfig } = useConfigStore();
43+
const { layoutConfig, commonConfig, trackingConfig } = useConfigStore();
4244
const { workspaceInviteCode, setWorkspaceInviteCode } = useCallbackStore();
4345
const { setCanShowGuide } = useDesktopConfigStore();
4446

@@ -78,6 +80,9 @@ export default function Home({ sealos_cloud_domain }: { sealos_cloud_domain: str
7880
const is_login = isUserLogin();
7981
const whitelistApps = ['system-template', 'system-fastdeploy'];
8082
if (!is_login) {
83+
// clear firstusetime
84+
setFirstUse(null);
85+
8186
const { appkey, appQuery } = parseOpenappQuery((query?.openapp as string) || '');
8287
// Invited new user
8388
if (query?.uid && typeof query?.uid === 'string') {
@@ -95,6 +100,7 @@ export default function Home({ sealos_cloud_domain }: { sealos_cloud_domain: str
95100
if (isString(query?.workspaceUid)) workspaceUid = query.workspaceUid;
96101
if (appkey && typeof appQuery === 'string')
97102
setAutoLaunch(appkey, { raw: appQuery }, workspaceUid);
103+
98104
router.replace(destination);
99105
} else {
100106
let workspaceUid: string | undefined;
@@ -205,7 +211,31 @@ export default function Home({ sealos_cloud_domain }: { sealos_cloud_domain: str
205211
return;
206212
}
207213
}, [workspaceInviteCode]);
208-
214+
useEffect(() => {
215+
(async (state) => {
216+
try {
217+
if (
218+
commonConfig?.trackingEnabled &&
219+
(!firstUse || !dayjs(firstUse).isSame(dayjs(), 'day'))
220+
) {
221+
const umami = window.umami;
222+
if (!!umami) {
223+
const result = await umami.track(trackEventName.dailyLoginFirst, {
224+
userId: session?.user.userId!,
225+
userUid: session?.user.userUid!
226+
});
227+
if (result.ok && result.status === 200) {
228+
setFirstUse(new Date());
229+
} else {
230+
console.error('Failed to update first use date');
231+
}
232+
}
233+
}
234+
} catch (e) {
235+
console.log(e);
236+
}
237+
})();
238+
}, [commonConfig, firstUse]);
209239
return (
210240
<Box position={'relative'} overflow={'hidden'} w="100vw" h="100vh">
211241
<Head>

Diff for: frontend/desktop/src/services/backend/globalAuth.ts

+40-24
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
User,
1313
UserStatus
1414
} from 'prisma/global/generated/client';
15-
import { enableSignUp } from '../enable';
15+
import { enableSignUp, enableTracking } from '../enable';
16+
import { trackSignUp } from './tracking';
1617

1718
type TransactionClient = Omit<
1819
PrismaClient,
@@ -254,7 +255,6 @@ export async function signUpByPassword({
254255

255256
return { user };
256257
});
257-
258258
return result;
259259
} catch (error) {
260260
console.error('globalAuth: Error during sign up:', error);
@@ -329,13 +329,21 @@ export const getGlobalToken = async ({
329329
password,
330330
semData
331331
});
332-
result && (user = result.user);
333-
if (inviterId && result) {
334-
inviteHandler({
335-
inviterId: inviterId,
336-
inviteeId: result?.user.name,
337-
signResult: result
338-
});
332+
if (!!result) {
333+
user = result.user;
334+
if (inviterId && result) {
335+
inviteHandler({
336+
inviterId: inviterId,
337+
inviteeId: result?.user.name,
338+
signResult: result
339+
});
340+
}
341+
if (enableTracking()) {
342+
await trackSignUp({
343+
userId: result.user.id,
344+
userUid: result.user.uid
345+
});
346+
}
339347
}
340348
} else {
341349
const result = await signInByPassword({
@@ -356,22 +364,30 @@ export const getGlobalToken = async ({
356364
avatar_url,
357365
semData
358366
});
359-
result && (user = result.user);
360-
if (inviterId && result) {
361-
inviteHandler({
362-
inviterId: inviterId,
363-
inviteeId: result?.user.name,
364-
signResult: result
365-
});
366-
}
367-
if (bdVid && result) {
368-
uploadConvertData({ newType: [3], bdVid })
369-
.then((res) => {
370-
console.log(res);
371-
})
372-
.catch((err) => {
373-
console.log(err);
367+
if (result) {
368+
user = result.user;
369+
if (inviterId) {
370+
inviteHandler({
371+
inviterId: inviterId,
372+
inviteeId: result?.user.name,
373+
signResult: result
374374
});
375+
}
376+
if (bdVid) {
377+
await uploadConvertData({ newType: [3], bdVid })
378+
.then((res) => {
379+
console.log(res);
380+
})
381+
.catch((err) => {
382+
console.log(err);
383+
});
384+
}
385+
if (enableTracking()) {
386+
await trackSignUp({
387+
userId: result.user.id,
388+
userUid: result.user.uid
389+
});
390+
}
375391
}
376392
} else {
377393
const result = await signIn({
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { trackEventName } from '@/constants/account';
2+
import { Umami } from '@umami/node';
3+
const getUmami = () => {
4+
return new Umami({
5+
websiteId: global.AppConfig.tracking.websiteId,
6+
hostUrl: global.AppConfig.tracking.hostUrl
7+
});
8+
};
9+
export type TLoginPayload = {
10+
userUid: string;
11+
userId: string;
12+
};
13+
export const trackSignUp = (data: TLoginPayload) => {
14+
console.log('signUpstart');
15+
const umami = getUmami();
16+
console.log('umami', umami);
17+
return umami
18+
.track(trackEventName.signUp, data)
19+
.then((res) => {
20+
console.log('[tracking][signUp][success]');
21+
})
22+
.catch((e) => {
23+
console.error('[tracking][signUp]:', e);
24+
return Promise.resolve(null);
25+
});
26+
};
27+
export const trackDailyLoginFirst = (data: TLoginPayload) => {
28+
const umami = getUmami();
29+
console;
30+
return umami.track(trackEventName.dailyLoginFirst, data).catch((e) => {
31+
console.error('[tracking][dailyLoginFirst]:', e);
32+
return Promise.resolve(null);
33+
});
34+
};

Diff for: frontend/desktop/src/services/enable.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const getBillingUrl = () => global.AppConfig.desktop.auth.billingUrl || '
1818
export const getWorkorderUrl = () => global.AppConfig.desktop.auth.workorderUrl || '';
1919
export const getCvmUrl = () => global.AppConfig.desktop.auth.cloudVitrualMachineUrl || '';
2020
export const getTeamLimit = () => global.AppConfig.desktop.teamManagement?.maxTeamCount || 50;
21+
export const enableTracking = () => !!global.AppConfig.common.trackingEnabled;
2122
export const getTeamInviteLimit = () =>
2223
global.AppConfig.desktop.teamManagement?.maxTeamMemberCount || 50;
2324

0 commit comments

Comments
 (0)