Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(geolocation): allow to control the geolocation feature #960

Merged
merged 11 commits into from
Feb 14, 2025
5 changes: 4 additions & 1 deletion demo/src/client/faro/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { createRoutesFromChildren, matchRoutes, Routes, useLocation, useNavigati

import {
initializeFaro as coreInit,
genShortID,
getWebInstrumentations,
ReactIntegration,
ReactRouterVersion,
Expand Down Expand Up @@ -42,6 +41,10 @@ export function initializeFaro(): Faro {
version: env.package.version,
environment: env.mode.name,
},

locationTracking: {
enabled: true,
},
});

faro.api.pushLog(['Faro was initialized']);
Expand Down
14 changes: 11 additions & 3 deletions packages/core/src/api/meta/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,25 @@ export function initializeMetaAPI(
};

const setSession: MetaAPI['setSession'] = (session, options) => {
const overrides = options?.overrides;
const newOverrides = options?.overrides;
const overrides = newOverrides
? {
overrides: {
...metaSession?.session?.overrides,
...newOverrides,
},
}
: {};

if (metaSession) {
metas.remove(metaSession);
}

metaSession = {
session: {
// if session is empty, session manager force creates a new session
// if session is undefined, session manager force creates a new session
...(isEmpty(session) ? undefined : session),
...(overrides ? { overrides } : {}),
...overrides,
},
};

Expand Down
34 changes: 34 additions & 0 deletions packages/core/src/api/meta/initilialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,40 @@ describe('Meta API', () => {
api.setSession(undefined, { overrides });
expect(api.getSession()).toEqual({ overrides });
});

it('merges the new overrides with the existing session meta overrides', () => {
const initialSession = { id: 'my-session' };

const mc = mockConfig({
sessionTracking: {
session: initialSession,
},
geoLocationTracking: {
enabled: false,
},
});

// mockConfig is the result of calling makeCoreConfig in faro-web-sdk package.
// It it reads the geoLocationTracking properties it adds them to the sessionTracking.session.overrides object.
mc.sessionTracking!.session!.overrides = { geoLocationTrackingEnabled: false };

const { api } = initializeFaro(mc);

expect(api.getSession()?.id).toEqual(initialSession.id);
expect(api.getSession()?.overrides).toBeDefined();
expect(api.getSession()?.overrides).toStrictEqual({ geoLocationTrackingEnabled: false });

const overrides = { serviceName: 'service-1' };
const newSession = { id: 'my-new-session' };
api.setSession(newSession, { overrides });
expect(api.getSession()?.id).toEqual(newSession.id);
expect(api.getSession()?.overrides).toStrictEqual({ ...overrides, geoLocationTrackingEnabled: false });

const newOverrides = { serviceName: 'service-2' };
api.setSession(newSession, { overrides: newOverrides });
expect(api.getSession()?.id).toEqual(newSession.id);
expect(api.getSession()?.overrides).toStrictEqual({ ...newOverrides, geoLocationTrackingEnabled: false });
});
});

describe('setPage / getPage', () => {
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/api/meta/types.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import type { MetaOverrides, MetaPage, MetaSession, MetaUser, MetaView } from '../../metas';

type OverridesAvailableThroughApi = Pick<MetaOverrides, 'serviceName'>;

export interface MetaAPI {
setUser: (user?: MetaUser | undefined) => void;
resetUser: () => void;
setSession: (
session?: MetaSession | undefined,
options?: {
overrides: MetaOverrides;
overrides: OverridesAvailableThroughApi;
}
) => void;
resetSession: () => void;
getSession: () => MetaSession | undefined;
setView: (
view?: MetaView | undefined,
options?: {
overrides: MetaOverrides;
overrides: OverridesAvailableThroughApi;
}
) => void;
getView: () => MetaView | undefined;
/**
* If a string is provided, it will be used as the page id.
* @returns
*/
setPage: (page?: MetaPage | string | undefined) => void;
getPage: () => MetaPage | undefined;
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ export interface Config<P = APIEvent> {
errorSerializer?: LogArgsSerializer;
};

/**
* Configuration for the page tracking
*/
pageTracking?: {
/**
* The page meta for initial page settings
Expand All @@ -204,6 +207,20 @@ export interface Config<P = APIEvent> {
*/
generatePageId?: (location: Location) => string;
};

/**
* Configuration for the location tracking (Grafana cloud only)
*/
geoLocationTracking?: {
/**
* Enable or disable geolocation tracking.
* Geolocation tracking must be enabled in the Grafana Cloud settings first.
* It cannot be enabled solely on the client side.
* This option allows control over tracking on the client side to comply with user
* privacy requirements.
*/
enabled?: boolean;
};
}

export type Patterns = Array<string | RegExp>;
14 changes: 13 additions & 1 deletion packages/core/src/metas/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,20 @@ export interface Meta {
}

/**
* Overrides are instructions that allow the receiver to override certain properties.
* MetaOverrides are instructions that allow the receiver to override certain properties (Grafana Cloud only).
*/
export type MetaOverrides = {
/**
* New service name (Grafana Cloud only)
*/
serviceName?: string;

/**
* Enable or disable geolocation tracking (Grafana Cloud only).
* Geolocation tracking must be enabled in the Grafana Cloud settings first.
* It cannot be enabled solely on the client side.
* This option allows control over tracking on the client side to comply with user
* privacy requirements.
*/
geoLocationTrackingEnabled?: boolean;
};
32 changes: 32 additions & 0 deletions packages/web-sdk/src/config/makeCoreConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,36 @@ describe('config', () => {
expect(config.ignoreUrls[0].test(url)).toBe(true);
}
);

it('updates the overrides object in the session config with the geoLocationTracking.enabled value', () => {
const browserConfig = {
url: 'http://example.com/my-collector',
app: {},
sessionTracking: {
session: { id: 'my-session' },
},
};

let config = makeCoreConfig({ ...browserConfig, geoLocationTracking: { enabled: true } });
expect(config?.sessionTracking?.session?.overrides).toHaveProperty('geoLocationTrackingEnabled');
expect(config?.sessionTracking?.session?.overrides?.geoLocationTrackingEnabled).toBe(true);

config = makeCoreConfig({ ...browserConfig, geoLocationTracking: { enabled: false } });
expect(config?.sessionTracking?.session?.overrides).toHaveProperty('geoLocationTrackingEnabled');
expect(config?.sessionTracking?.session?.overrides?.geoLocationTrackingEnabled).toBe(false);

// Also test that the session object is not created or mutated if geoLocationTracking is not enabled
config = makeCoreConfig(browserConfig);
expect(config?.sessionTracking?.session).toBeDefined();

const sessionMeta = { id: 'test', attributes: { foo: 'bar' } };
config = makeCoreConfig({
...browserConfig,
sessionTracking: { session: sessionMeta },
});

expect(config?.sessionTracking?.session).toBeDefined();
expect(config?.sessionTracking?.session?.overrides).toBeUndefined();
expect(config?.sessionTracking?.session).toStrictEqual(sessionMeta);
});
});
27 changes: 26 additions & 1 deletion packages/web-sdk/src/config/makeCoreConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import {
defaultInternalLoggerLevel,
defaultLogArgsSerializer,
defaultUnpatchedConsole,
isEmpty,
isObject,
} from '@grafana/faro-core';
import type { Config, MetaItem, Transport } from '@grafana/faro-core';
import type { Config, MetaItem, MetaSession, Transport } from '@grafana/faro-core';

import { defaultEventDomain } from '../consts';
import { parseStacktrace } from '../instrumentations';
Expand Down Expand Up @@ -53,6 +54,7 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config {
trackWebVitalsAttribution,
user,
view,
geoLocationTracking,

// properties with default values
dedupe = true,
Expand Down Expand Up @@ -94,6 +96,7 @@ export function makeCoreConfig(browserConfig: BrowserConfig): Config {
sessionTracking: {
...defaultSessionTrackingConfig,
...sessionTracking,
...crateSessionMeta({ geoLocationTracking, sessionTracking }),
},
user,
view,
Expand All @@ -119,3 +122,25 @@ function createDefaultMetas(browserConfig: BrowserConfig): MetaItem[] {

return initialMetas;
}

function crateSessionMeta({
geoLocationTracking,
sessionTracking,
}: Pick<BrowserConfig, 'geoLocationTracking' | 'sessionTracking'>): { session: MetaSession } | {} {
const overrides: MetaSession['overrides'] = {};

if (geoLocationTracking?.enabled != null) {
overrides.geoLocationTrackingEnabled = geoLocationTracking.enabled;
}

if (isEmpty(overrides)) {
return {};
}

return {
session: {
...(sessionTracking?.session ?? {}),
overrides,
},
};
}
Loading
Loading