move analytics identifiers to backend

This commit is contained in:
Ashley Harrison
2023-02-28 15:58:50 +00:00
parent ab8de1a0e3
commit 720b363d70
12 changed files with 68 additions and 66 deletions
@@ -96,6 +96,11 @@ export type OAuth =
*/
export type OAuthSettings = Partial<Record<OAuth, { name: string; icon?: IconName }>>;
export interface AnalyticsSettings {
identifier: string;
hashedIdentifier?: string;
}
/** Current user info included in bootData
*
* @internal
@@ -119,6 +124,7 @@ export interface CurrentUserDTO {
locale: string;
language: string;
permissions?: Record<string, boolean>;
analytics: AnalyticsSettings;
/** @deprecated Use theme instead */
lightTheme: boolean;
+1
View File
@@ -38,6 +38,7 @@ export * from './geometry';
export { isUnsignedPluginSignature } from './pluginSignature';
export type {
CurrentUserDTO,
AnalyticsSettings,
BootData,
OAuth,
OAuthSettings,
+6
View File
@@ -48,6 +48,12 @@ type CurrentUser struct {
HelpFlags1 user.HelpFlags1 `json:"helpFlags1"`
HasEditPermissionInFolders bool `json:"hasEditPermissionInFolders"`
Permissions UserPermissionsMap `json:"permissions,omitempty"`
Analytics AnalyticsSettings `json:"analytics"`
}
type AnalyticsSettings struct {
Identifier string `json:"identifier"`
HashedIdentifier string `json:"hashedIdentifier,omitempty"`
}
type UserPermissionsMap map[string]bool
+4
View File
@@ -112,6 +112,10 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
Language: language,
HelpFlags1: c.HelpFlags1,
HasEditPermissionInFolders: hasEditPerm,
Analytics: dtos.AnalyticsSettings{
Identifier: c.SignedInUser.Analytics.Identifier,
HashedIdentifier: c.SignedInUser.Analytics.HashedIdentifier,
},
},
Settings: settings,
Theme: prefs.Theme,
+6
View File
@@ -193,6 +193,11 @@ type GetSignedInUserQuery struct {
OrgID int64 `xorm:"org_id"`
}
type AnalyticsSettings struct {
Identifier string
HashedIdentifier string
}
type SignedInUser struct {
UserID int64 `xorm:"user_id"`
OrgID int64 `xorm:"org_id"`
@@ -212,6 +217,7 @@ type SignedInUser struct {
HelpFlags1 HelpFlags1
LastSeenAt time.Time
Teams []int64
Analytics AnalyticsSettings
// Permissions grouped by orgID and actions
Permissions map[int64]map[string][]string `json:"-"`
}
+2
View File
@@ -432,6 +432,8 @@ func (ss *sqlStore) GetSignedInUser(ctx context.Context, query *user.GetSignedIn
if signedInUser.ExternalAuthModule != "oauth_grafana_com" {
signedInUser.ExternalAuthID = ""
}
signedInUser.Analytics = buildUserAnalyticsSettings(signedInUser)
return nil
})
return &signedInUser, err
+17
View File
@@ -594,3 +594,20 @@ func (s *Service) supportBundleCollector() supportbundles.Collector {
Fn: collectorFn,
}
}
func hashUserIdentifier(identifier string) string {
return identifier
}
func buildUserAnalyticsSettings(signedInUser user.SignedInUser) user.AnalyticsSettings {
var settings user.AnalyticsSettings
if signedInUser.ExternalAuthID != "" {
settings.Identifier = signedInUser.ExternalAuthID
} else {
settings.Identifier = signedInUser.Email
}
settings.HashedIdentifier = hashUserIdentifier(settings.Identifier)
return settings
}
+5 -1
View File
@@ -1,6 +1,6 @@
import { extend } from 'lodash';
import { OrgRole, rangeUtil, WithAccessControlMetadata } from '@grafana/data';
import { AnalyticsSettings, OrgRole, rangeUtil, WithAccessControlMetadata } from '@grafana/data';
import { featureEnabled, getBackendSrv } from '@grafana/runtime';
import { AccessControlAction, UserPermission } from 'app/types';
import { CurrentUserInternal } from 'app/types/config';
@@ -28,6 +28,7 @@ export class User implements Omit<CurrentUserInternal, 'lightTheme'> {
helpFlags1: number;
hasEditPermissionInFolders: boolean;
permissions?: UserPermission;
analytics: AnalyticsSettings;
fiscalYearStartMonth: number;
constructor() {
@@ -51,6 +52,9 @@ export class User implements Omit<CurrentUserInternal, 'lightTheme'> {
this.language = '';
this.weekStart = '';
this.gravatarUrl = '';
this.analytics = {
identifier: '',
};
if (config.bootData.user) {
extend(this, config.bootData.user);
@@ -1,7 +1,7 @@
import { CurrentUserDTO } from '@grafana/data';
import { EchoBackend, EchoEventType, PageviewEchoEvent } from '@grafana/runtime';
import { getUserIdentifier, loadScript } from '../../utils';
import { loadScript } from '../../utils';
declare global {
interface Window {
@@ -34,7 +34,7 @@ export class GA4EchoBackend implements EchoBackend<PageviewEchoEvent, GA4EchoBac
};
if (options.user) {
configOptions.user_id = getUserIdentifier(options.user);
configOptions.user_id = options.user.analytics.identifier;
}
this.googleAnalytics4SendManualPageViews = options.googleAnalytics4SendManualPageViews;
@@ -1,4 +1,4 @@
import type { identify, load, page, track } from 'rudder-sdk-js'; // SDK is loaded dynamically from config, so we only import types from the SDK package
import type { apiOptions, identify, load, page, track } from 'rudder-sdk-js'; // SDK is loaded dynamically from config, so we only import types from the SDK package
import { CurrentUserDTO } from '@grafana/data';
import {
@@ -10,7 +10,7 @@ import {
PageviewEchoEvent,
} from '@grafana/runtime';
import { getUserIdentifier, loadScript } from '../../utils';
import { loadScript } from '../../utils';
interface Rudderstack {
identify: typeof identify;
@@ -70,13 +70,24 @@ export class RudderstackBackend implements EchoBackend<PageviewEchoEvent, Rudder
window.rudderanalytics?.load?.(options.writeKey, options.dataPlaneUrl, { configUrl: options.configUrl });
if (options.user) {
const identifier = getUserIdentifier(options.user);
const { identifier, hashedIdentifier } = options.user.analytics;
const apiOptions: apiOptions = {};
window.rudderanalytics?.identify?.(identifier, {
email: options.user.email,
orgId: options.user.orgId,
language: options.user.language,
});
if (hashedIdentifier) {
apiOptions.Intercom = {
user_hash: hashedIdentifier,
};
}
window.rudderanalytics?.identify?.(
identifier,
{
email: options.user.email,
orgId: options.user.orgId,
language: options.user.language,
},
apiOptions
);
}
}
@@ -1,41 +0,0 @@
import { CurrentUserDTO, OrgRole } from '@grafana/data';
import { getUserIdentifier } from './utils';
const baseUser: CurrentUserDTO = {
isSignedIn: true,
id: 3,
login: 'myUsername',
email: 'email@example.com',
name: 'My Name',
theme: 'dark',
lightTheme: false, // deprecated
orgCount: 1,
orgId: 1,
orgName: 'Main Org.',
orgRole: OrgRole.Admin,
isGrafanaAdmin: false,
gravatarUrl: '/avatar/abc-123',
timezone: 'browser',
weekStart: 'browser',
locale: 'en-AU',
language: 'en-US',
externalUserId: '',
};
const gcomUser: CurrentUserDTO = {
...baseUser,
externalUserId: 'abc-123',
};
describe('echo getUserIdentifier', () => {
it('should return the external user ID (gcom ID) if available', () => {
const id = getUserIdentifier(gcomUser);
expect(id).toBe('abc-123');
});
it('should fall back to the email address', () => {
const id = getUserIdentifier(baseUser);
expect(id).toBe('email@example.com');
});
});
-14
View File
@@ -1,19 +1,5 @@
import { CurrentUserDTO } from '@grafana/data';
import { attachDebugger, createLogger } from '@grafana/ui';
/**
* Returns an opaque identifier for a user, for reporting purposes.
* Because this is for use when reporting across multiple Grafana installations
* It cannot simply be user.id because that's not unique across two installations.
*/
export function getUserIdentifier(user: CurrentUserDTO) {
if (user.externalUserId.length) {
return user.externalUserId;
}
return user.email;
}
export function loadScript(url: string, async = false) {
return new Promise((resolve) => {
const script = document.createElement('script');