From 7d0313837db2b0bf3cd2cdfbf925eb5f609764be Mon Sep 17 00:00:00 2001 From: "Heiko W. Rupp" Date: Wed, 5 Jun 2024 15:22:16 +0200 Subject: [PATCH] Tracking of dashboard version and users's capabilities --- docs/dev-setup.md | 2 +- frontend/src/utilities/segmentIOUtils.tsx | 84 ++++++++++++++++++-- frontend/src/utilities/useSegmentTracking.ts | 59 +++++++++++++- 3 files changed, 136 insertions(+), 9 deletions(-) diff --git a/docs/dev-setup.md b/docs/dev-setup.md index eea4d051f8..c540962724 100644 --- a/docs/dev-setup.md +++ b/docs/dev-setup.md @@ -58,7 +58,7 @@ Run the tests. For in-depth testing guidance review the [testing guidelines](./testing.md) -## Deploying the ODH Dashbard +## Deploying the ODH Dashboard ### Official Image Builds diff --git a/frontend/src/utilities/segmentIOUtils.tsx b/frontend/src/utilities/segmentIOUtils.tsx index 3f4484c7ec..aa6bb429f1 100644 --- a/frontend/src/utilities/segmentIOUtils.tsx +++ b/frontend/src/utilities/segmentIOUtils.tsx @@ -15,7 +15,54 @@ export const fireTrackingEventRaw = (eventType: string, properties?: any): void }`, ); } else if (window.analytics) { - window.analytics.track(eventType, { ...properties, clusterID }); + window.analytics.track( + eventType, + { + ...properties, + clusterID, + }, + { + app: { + version: INTERNAL_DASHBOARD_VERSION, + }, + }, + ); + } +}; + +export type IdentifyProps = { + anonymousID: string; + canCreate: boolean; + canDelete: boolean; + canUpdate: boolean; + isAdmin: boolean; +}; + +export const fireIdentifyEvent = (properties?: { + anonymousID: string; + canUpdate: boolean; + canDelete: boolean; + isAdmin: boolean; + canCreate: boolean; +}): void => { + const clusterID = window.clusterID ?? ''; + if (DEV_MODE) { + /* eslint-disable-next-line no-console */ + console.log( + `Telemetry identify triggered: ${properties ? ` - ${JSON.stringify(properties)}` : ''}`, + ); + } else if (window.analytics) { + const canCreate = properties?.canCreate || false; + const canDelete = properties?.canDelete || false; + const canUpdate: boolean = properties?.canUpdate || false; + const isAdmin = properties?.isAdmin || false; + window.analytics.identify(properties?.anonymousID, { + clusterID, + canCreate, + canDelete, + canUpdate, + isAdmin, + }); } }; @@ -39,20 +86,45 @@ export const fireTrackingEvent = ( window.analytics.identify(properties?.anonymousID, { clusterID }); break; case 'page': - window.analytics.page(undefined, { clusterID }); + window.analytics.page( + undefined, + { + clusterID, + }, + { + app: { + version: INTERNAL_DASHBOARD_VERSION, + }, + }, + ); break; default: - window.analytics.track(eventType, { ...properties, clusterID }); + window.analytics.track( + eventType, + { + ...properties, + clusterID, + }, + { + app: { + version: INTERNAL_DASHBOARD_VERSION, + }, + }, + ); } } }; export const initSegment = async (props: { segmentKey: string; - username: string; + isAdmin: boolean; + canCreate: boolean; + canDelete: boolean; + canUpdate: boolean; enabled: boolean; + username: string; }): Promise => { - const { segmentKey, username, enabled } = props; + const { segmentKey, username, enabled, canCreate, canDelete, canUpdate, isAdmin } = props; const analytics = (window.analytics = window.analytics || []); if (analytics.initialize) { return; @@ -126,7 +198,7 @@ export const initSegment = async (props: { ); const anonymousIDArray = Array.from(new Uint8Array(anonymousIDBuffer)); const anonymousID = anonymousIDArray.map((b) => b.toString(16).padStart(2, '0')).join(''); - fireTrackingEvent('identify', { anonymousID }); + fireIdentifyEvent({ anonymousID, canCreate, canDelete, canUpdate, isAdmin }); fireTrackingEvent('page'); } }; diff --git a/frontend/src/utilities/useSegmentTracking.ts b/frontend/src/utilities/useSegmentTracking.ts index da4e7cae9c..9d7a822a2f 100644 --- a/frontend/src/utilities/useSegmentTracking.ts +++ b/frontend/src/utilities/useSegmentTracking.ts @@ -2,6 +2,9 @@ import React from 'react'; import { useAppContext } from '~/app/AppContext'; import { initSegment } from '~/utilities/segmentIOUtils'; import { useAppSelector } from '~/redux/hooks'; +import { AccessReviewResourceAttributes } from '~/k8sTypes'; +import { useAccessReview } from '~/api'; +import { useUser } from '~/redux/selectors'; import { useWatchSegmentKey } from './useWatchSegmentKey'; export const useSegmentTracking = (): void => { @@ -10,14 +13,66 @@ export const useSegmentTracking = (): void => { const username = useAppSelector((state) => state.user); const clusterID = useAppSelector((state) => state.clusterID); + // TODO this should go to a helper, as it is called from multiple places + const createReviewResource: AccessReviewResourceAttributes = { + group: 'project.openshift.io', + resource: 'projectrequests', + verb: 'create', + }; + const [allowCreate, createLoaded] = useAccessReview(createReviewResource); + + // TODO this should go to a helper, as it is called from multiple places + const deleteReviewResource: AccessReviewResourceAttributes = { + group: 'project.openshift.io', + resource: 'projectrequests', + verb: 'delete', + }; + const [allowDelete, deleteLoaded] = useAccessReview(deleteReviewResource); + + const updateReviewResource: AccessReviewResourceAttributes = { + group: 'datasciencecluster.opendatahub.io/v1', + resource: 'DataScienceCluster', + verb: 'update', + }; + const [allowUpdate, updateLoaded] = useAccessReview(updateReviewResource); + + const { isAdmin } = useUser(); + React.useEffect(() => { - if (segmentKey && loaded && !loadError && username && clusterID) { + if ( + segmentKey && + loaded && + !loadError && + createLoaded && + deleteLoaded && + updateLoaded && + username && + clusterID + ) { window.clusterID = clusterID; initSegment({ segmentKey, username, enabled: !dashboardConfig.spec.dashboardConfig.disableTracking, + canCreate: allowCreate, + canDelete: allowDelete, + canUpdate: allowUpdate, + isAdmin, }); } - }, [clusterID, loadError, loaded, segmentKey, username, dashboardConfig]); + }, [ + clusterID, + loadError, + loaded, + segmentKey, + username, + dashboardConfig, + allowCreate, + allowDelete, + isAdmin, + createLoaded, + deleteLoaded, + updateLoaded, + allowUpdate, + ]); };