mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[FullStory] Demote the deployment information to setVars
instead of setUserVars
(#132837)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
848c831233
commit
0b190b9f16
5 changed files with 85 additions and 26 deletions
|
@ -32,6 +32,10 @@ export interface EventContext {
|
|||
* The Cloud ID.
|
||||
*/
|
||||
cloudId?: string;
|
||||
/**
|
||||
* `true` if the user is logged in via the Elastic Cloud authentication provider.
|
||||
*/
|
||||
isElasticCloudUser?: boolean;
|
||||
/**
|
||||
* The product's version.
|
||||
*/
|
||||
|
|
|
@ -52,9 +52,27 @@ describe('FullStoryShipper', () => {
|
|||
});
|
||||
|
||||
describe('FS.setUserVars', () => {
|
||||
test('calls `setUserVars` when version is provided', () => {
|
||||
fullstoryShipper.extendContext({ version: '1.2.3' });
|
||||
test('calls `setUserVars` when isElasticCloudUser: true is provided', () => {
|
||||
fullstoryShipper.extendContext({ isElasticCloudUser: true });
|
||||
expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
isElasticCloudUser_bool: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('calls `setUserVars` when isElasticCloudUser: false is provided', () => {
|
||||
fullstoryShipper.extendContext({ isElasticCloudUser: false });
|
||||
expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
isElasticCloudUser_bool: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('FS.setVars', () => {
|
||||
test('calls `setVars` when version is provided', () => {
|
||||
fullstoryShipper.extendContext({ version: '1.2.3' });
|
||||
expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
|
||||
version_str: '1.2.3',
|
||||
version_major_int: 1,
|
||||
version_minor_int: 2,
|
||||
|
@ -62,14 +80,20 @@ describe('FullStoryShipper', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('calls `setUserVars` when cloudId is provided', () => {
|
||||
test('calls `setVars` when cloudId is provided', () => {
|
||||
fullstoryShipper.extendContext({ cloudId: 'test-es-org-id' });
|
||||
expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({ org_id_str: 'test-es-org-id' });
|
||||
expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
cloudId_str: 'test-es-org-id',
|
||||
org_id_str: 'test-es-org-id',
|
||||
});
|
||||
});
|
||||
|
||||
test('merges both: version and cloudId if both are provided', () => {
|
||||
fullstoryShipper.extendContext({ version: '1.2.3', cloudId: 'test-es-org-id' });
|
||||
expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
|
||||
expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
cloudId_str: 'test-es-org-id',
|
||||
org_id_str: 'test-es-org-id',
|
||||
version_str: '1.2.3',
|
||||
version_major_int: 1,
|
||||
|
@ -77,9 +101,7 @@ describe('FullStoryShipper', () => {
|
|||
version_patch_int: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('FS.setVars', () => {
|
||||
test('adds the rest of the context to `setVars`', () => {
|
||||
const context = {
|
||||
userId: 'test-user-id',
|
||||
|
@ -88,7 +110,16 @@ describe('FullStoryShipper', () => {
|
|||
foo: 'bar',
|
||||
};
|
||||
fullstoryShipper.extendContext(context);
|
||||
expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', { foo_str: 'bar' });
|
||||
expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
|
||||
version_str: '1.2.3',
|
||||
version_major_int: 1,
|
||||
version_minor_int: 2,
|
||||
version_patch_int: 3,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
cloudId_str: 'test-es-org-id',
|
||||
org_id_str: 'test-es-org-id',
|
||||
foo_str: 'bar',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,9 +14,9 @@ import type {
|
|||
} from '@kbn/analytics-client';
|
||||
import type { FullStoryApi } from './types';
|
||||
import type { FullStorySnippetConfig } from './load_snippet';
|
||||
import { getParsedVersion } from './get_parsed_version';
|
||||
import { formatPayload } from './format_payload';
|
||||
import { loadSnippet } from './load_snippet';
|
||||
import { getParsedVersion } from './get_parsed_version';
|
||||
|
||||
/**
|
||||
* FullStory shipper configuration.
|
||||
|
@ -62,7 +62,7 @@ export class FullStoryShipper implements IShipper {
|
|||
this.initContext.logger.debug(`Received context ${JSON.stringify(newContext)}`);
|
||||
|
||||
// FullStory requires different APIs for different type of contexts.
|
||||
const { userId, version, cloudId, ...nonUserContext } = newContext;
|
||||
const { userId, isElasticCloudUser, ...nonUserContext } = newContext;
|
||||
|
||||
// Call it only when the userId changes
|
||||
if (userId && userId !== this.lastUserId) {
|
||||
|
@ -73,14 +73,15 @@ export class FullStoryShipper implements IShipper {
|
|||
}
|
||||
|
||||
// User-level context
|
||||
if (version || cloudId) {
|
||||
if (typeof isElasticCloudUser === 'boolean') {
|
||||
this.initContext.logger.debug(
|
||||
`Calling FS.setUserVars with version ${version} and cloudId ${cloudId}`
|
||||
`Calling FS.setUserVars with isElasticCloudUser ${isElasticCloudUser}`
|
||||
);
|
||||
this.fullStoryApi.setUserVars(
|
||||
formatPayload({
|
||||
isElasticCloudUser,
|
||||
})
|
||||
);
|
||||
this.fullStoryApi.setUserVars({
|
||||
...(version ? getParsedVersion(version) : {}),
|
||||
...(cloudId ? { org_id_str: cloudId } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
// Event-level context. At the moment, only the scope `page` is supported by FullStory for webapps.
|
||||
|
@ -88,11 +89,15 @@ export class FullStoryShipper implements IShipper {
|
|||
// Keeping these fields for backwards compatibility.
|
||||
if (nonUserContext.applicationId) nonUserContext.app_id = nonUserContext.applicationId;
|
||||
if (nonUserContext.entityId) nonUserContext.ent_id = nonUserContext.entityId;
|
||||
if (nonUserContext.cloudId) nonUserContext.org_id = nonUserContext.cloudId;
|
||||
|
||||
this.initContext.logger.debug(
|
||||
`Calling FS.setVars with context ${JSON.stringify(nonUserContext)}`
|
||||
);
|
||||
this.fullStoryApi.setVars('page', formatPayload(nonUserContext));
|
||||
this.fullStoryApi.setVars('page', {
|
||||
...formatPayload(nonUserContext),
|
||||
...(nonUserContext.version ? getParsedVersion(nonUserContext.version) : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -136,6 +136,7 @@ describe('Cloud Plugin', () => {
|
|||
|
||||
await expect(firstValueFrom(context$)).resolves.toEqual({
|
||||
userId: '5ef112cfdae3dea57097bc276e275b2816e73ef2a398dc0ffaf5b6b4e3af2041',
|
||||
isElasticCloudUser: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -150,7 +151,7 @@ describe('Cloud Plugin', () => {
|
|||
([{ name }]) => name === 'cloud_user_id'
|
||||
)!;
|
||||
|
||||
const hashId1 = await firstValueFrom(context1$);
|
||||
const { userId: hashId1 } = (await firstValueFrom(context1$)) as { userId: string };
|
||||
expect(hashId1).not.toEqual(expectedHashedPlainUsername);
|
||||
|
||||
const { coreSetup: coreSetup2 } = await setupPlugin({
|
||||
|
@ -163,7 +164,7 @@ describe('Cloud Plugin', () => {
|
|||
([{ name }]) => name === 'cloud_user_id'
|
||||
)!;
|
||||
|
||||
const hashId2 = await firstValueFrom(context2$);
|
||||
const { userId: hashId2 } = (await firstValueFrom(context2$)) as { userId: string };
|
||||
expect(hashId2).not.toEqual(expectedHashedPlainUsername);
|
||||
|
||||
expect(hashId1).not.toEqual(hashId2);
|
||||
|
@ -186,6 +187,7 @@ describe('Cloud Plugin', () => {
|
|||
|
||||
await expect(firstValueFrom(context$)).resolves.toEqual({
|
||||
userId: expectedHashedPlainUsername,
|
||||
isElasticCloudUser: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -203,6 +205,7 @@ describe('Cloud Plugin', () => {
|
|||
|
||||
await expect(firstValueFrom(context$)).resolves.toEqual({
|
||||
userId: expectedHashedPlainUsername,
|
||||
isElasticCloudUser: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -217,7 +220,10 @@ describe('Cloud Plugin', () => {
|
|||
([{ name }]) => name === 'cloud_user_id'
|
||||
)!;
|
||||
|
||||
await expect(firstValueFrom(context$)).resolves.toEqual({ userId: undefined });
|
||||
await expect(firstValueFrom(context$)).resolves.toEqual({
|
||||
userId: undefined,
|
||||
isElasticCloudUser: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -267,22 +267,35 @@ export class CloudPlugin implements Plugin<CloudSetup> {
|
|||
user.authentication_realm?.type === 'saml' &&
|
||||
user.authentication_realm?.name === 'cloud-saml-kibana'
|
||||
) {
|
||||
// If authenticated via Cloud SAML, use the SAML username as the user ID
|
||||
return user.username;
|
||||
// If the user is managed by ESS, use the plain username as the user ID:
|
||||
// The username is expected to be unique for these users,
|
||||
// and it matches how users are identified in the Cloud UI, so it allows us to correlate them.
|
||||
return { userId: user.username, isElasticCloudUser: true };
|
||||
}
|
||||
|
||||
return cloudId ? `${cloudId}:${user.username}` : user.username;
|
||||
return {
|
||||
// For the rest of the authentication providers, we want to add the cloud deployment ID to make it unique.
|
||||
// Especially in the case of Elasticsearch-backed authentication, where users are commonly repeated
|
||||
// across multiple deployments (i.e.: `elastic` superuser).
|
||||
userId: cloudId ? `${cloudId}:${user.username}` : user.username,
|
||||
isElasticCloudUser: false,
|
||||
};
|
||||
}),
|
||||
// Join the cloud org id and the user to create a truly unique user id.
|
||||
// The hashing here is to keep it at clear as possible in our source code that we do not send literal user IDs
|
||||
map((userId) => ({ userId: sha256(userId) })),
|
||||
catchError(() => of({ userId: undefined }))
|
||||
map(({ userId, isElasticCloudUser }) => ({ userId: sha256(userId), isElasticCloudUser })),
|
||||
catchError(() => of({ userId: undefined, isElasticCloudUser: false }))
|
||||
),
|
||||
schema: {
|
||||
userId: {
|
||||
type: 'keyword',
|
||||
_meta: { description: 'The user id scoped as seen by Cloud (hashed)' },
|
||||
},
|
||||
isElasticCloudUser: {
|
||||
type: 'boolean',
|
||||
_meta: {
|
||||
description: '`true` if the user is managed by ESS.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue