diff --git a/redisinsight/api/src/dto/server.dto.ts b/redisinsight/api/src/dto/server.dto.ts index cf7d71fb01..cd297f3b1f 100644 --- a/redisinsight/api/src/dto/server.dto.ts +++ b/redisinsight/api/src/dto/server.dto.ts @@ -42,4 +42,10 @@ export class GetServerInfoResponse { example: ['PLAIN', 'KEYTAR'], }) encryptionStrategies: string[]; + + @ApiProperty({ + description: 'Server session id.', + type: Number, + }) + sessionId: number; } diff --git a/redisinsight/api/src/modules/core/providers/server-on-premise/server-on-premise.service.spec.ts b/redisinsight/api/src/modules/core/providers/server-on-premise/server-on-premise.service.spec.ts index e77b7bee33..26ff9a3c40 100644 --- a/redisinsight/api/src/modules/core/providers/server-on-premise/server-on-premise.service.spec.ts +++ b/redisinsight/api/src/modules/core/providers/server-on-premise/server-on-premise.service.spec.ts @@ -39,6 +39,7 @@ describe('ServerOnPremiseService', () => { let serverRepository: MockType>; let eventEmitter: EventEmitter2; let encryptionService: MockType; + const sessionId = new Date().getTime(); beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -88,12 +89,12 @@ describe('ServerOnPremiseService', () => { serverRepository.findOne.mockResolvedValue(null); serverRepository.create.mockReturnValue(mockServerEntity); - await service.onApplicationBootstrap(); + await service.onApplicationBootstrap(sessionId); expect(eventEmitter.emit).toHaveBeenNthCalledWith( 1, AppAnalyticsEvents.Initialize, - mockServerEntity.id, + { anonymousId: mockServerEntity.id, sessionId }, ); expect(eventEmitter.emit).toHaveBeenNthCalledWith( 2, @@ -107,12 +108,12 @@ describe('ServerOnPremiseService', () => { it('should emit APPLICATION_STARTED on second application launch', async () => { serverRepository.findOne.mockResolvedValue(mockServerEntity); - await service.onApplicationBootstrap(); + await service.onApplicationBootstrap(sessionId); expect(eventEmitter.emit).toHaveBeenNthCalledWith( 1, AppAnalyticsEvents.Initialize, - mockServerEntity.id, + { anonymousId: mockServerEntity.id, sessionId }, ); expect(eventEmitter.emit).toHaveBeenNthCalledWith( 2, diff --git a/redisinsight/api/src/modules/core/providers/server-on-premise/server-on-premise.service.ts b/redisinsight/api/src/modules/core/providers/server-on-premise/server-on-premise.service.ts index 5a03b20ac2..8d98f541ba 100644 --- a/redisinsight/api/src/modules/core/providers/server-on-premise/server-on-premise.service.ts +++ b/redisinsight/api/src/modules/core/providers/server-on-premise/server-on-premise.service.ts @@ -27,13 +27,16 @@ implements OnApplicationBootstrap, IServerProvider { private encryptionService: EncryptionService; + private sessionId: number; + constructor(repository, eventEmitter, encryptionService) { this.repository = repository; this.eventEmitter = eventEmitter; this.encryptionService = encryptionService; } - async onApplicationBootstrap() { + async onApplicationBootstrap(sessionId: number = new Date().getTime()) { + this.sessionId = sessionId; await this.upsertServerInfo(); } @@ -45,7 +48,7 @@ implements OnApplicationBootstrap, IServerProvider { // Create default server info on first application launch serverInfo = this.repository.create({}); await this.repository.save(serverInfo); - this.eventEmitter.emit(AppAnalyticsEvents.Initialize, serverInfo.id); + this.eventEmitter.emit(AppAnalyticsEvents.Initialize, { anonymousId: serverInfo.id, sessionId: this.sessionId }); this.eventEmitter.emit(AppAnalyticsEvents.Track, { event: TelemetryEvents.ApplicationFirstStart, eventData: { @@ -57,7 +60,7 @@ implements OnApplicationBootstrap, IServerProvider { }); } else { this.logger.log('Application started.'); - this.eventEmitter.emit(AppAnalyticsEvents.Initialize, serverInfo.id); + this.eventEmitter.emit(AppAnalyticsEvents.Initialize, { anonymousId: serverInfo.id, sessionId: this.sessionId }); this.eventEmitter.emit(AppAnalyticsEvents.Track, { event: TelemetryEvents.ApplicationStarted, eventData: { @@ -82,6 +85,7 @@ implements OnApplicationBootstrap, IServerProvider { } const result = { ...info, + sessionId: this.sessionId, appVersion: SERVER_CONFIG.appVersion, osPlatform: process.platform, buildType: SERVER_CONFIG.buildType, diff --git a/redisinsight/api/src/modules/core/services/analytics/analytics.service.spec.ts b/redisinsight/api/src/modules/core/services/analytics/analytics.service.spec.ts index 0c0d2fbed8..7ef2986edd 100644 --- a/redisinsight/api/src/modules/core/services/analytics/analytics.service.spec.ts +++ b/redisinsight/api/src/modules/core/services/analytics/analytics.service.spec.ts @@ -33,6 +33,7 @@ const mockSettingsWithoutPermission = { describe('AnalyticsService', () => { let service: AnalyticsService; let settingsService: ISettingsProvider; + const sessionId = new Date().getTime(); beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -58,7 +59,7 @@ describe('AnalyticsService', () => { describe('initialize', () => { it('should set anonymousId', () => { - service.initialize(mockAnonymousId); + service.initialize({ anonymousId: mockAnonymousId, sessionId }); const anonymousId = service.getAnonymousId(); @@ -69,7 +70,7 @@ describe('AnalyticsService', () => { describe('sendEvent', () => { beforeEach(() => { mockAnalyticsTrack = jest.fn(); - service.initialize(mockAnonymousId); + service.initialize({ anonymousId: mockAnonymousId, sessionId }); }); it('should send event with anonymousId if permission are granted', async () => { settingsService.getSettings = jest @@ -84,6 +85,7 @@ describe('AnalyticsService', () => { expect(mockAnalyticsTrack).toHaveBeenCalledWith({ anonymousId: mockAnonymousId, + integrations: { Amplitude: { session_id: sessionId } }, event: TelemetryEvents.ApplicationStarted, properties: {}, }); @@ -114,6 +116,7 @@ describe('AnalyticsService', () => { expect(mockAnalyticsTrack).toHaveBeenCalledWith({ anonymousId: NON_TRACKING_ANONYMOUS_ID, + integrations: { Amplitude: { session_id: sessionId } }, event: TelemetryEvents.ApplicationStarted, properties: {}, }); diff --git a/redisinsight/api/src/modules/core/services/analytics/analytics.service.ts b/redisinsight/api/src/modules/core/services/analytics/analytics.service.ts index 5171196740..4fd2ca49ac 100644 --- a/redisinsight/api/src/modules/core/services/analytics/analytics.service.ts +++ b/redisinsight/api/src/modules/core/services/analytics/analytics.service.ts @@ -15,10 +15,17 @@ export interface ITelemetryEvent { nonTracking: boolean; } +export interface ITelemetryInitEvent { + anonymousId: string; + sessionId: number; +} + @Injectable() export class AnalyticsService { private anonymousId: string = NON_TRACKING_ANONYMOUS_ID; + private sessionId: number = -1; + private analytics; constructor( @@ -31,7 +38,9 @@ export class AnalyticsService { } @OnEvent(AppAnalyticsEvents.Initialize) - public initialize(anonymousId: string) { + public initialize(payload: ITelemetryInitEvent) { + const { anonymousId, sessionId } = payload; + this.sessionId = sessionId; this.anonymousId = anonymousId; this.analytics = new Analytics(ANALYTICS_CONFIG.writeKey); } @@ -55,6 +64,7 @@ export class AnalyticsService { if (isAnalyticsGranted) { this.analytics.track({ anonymousId: this.anonymousId, + integrations: { Amplitude: { session_id: this.sessionId } }, event, properties: { ...eventData, @@ -63,6 +73,7 @@ export class AnalyticsService { } else if (nonTracking) { this.analytics.track({ anonymousId: NON_TRACKING_ANONYMOUS_ID, + integrations: { Amplitude: { session_id: this.sessionId } }, event, properties: { ...eventData, diff --git a/redisinsight/api/test/api/info/GET-info.test.ts b/redisinsight/api/test/api/info/GET-info.test.ts index b45f26f762..21a6f33e0a 100644 --- a/redisinsight/api/test/api/info/GET-info.test.ts +++ b/redisinsight/api/test/api/info/GET-info.test.ts @@ -18,6 +18,7 @@ const responseSchema = Joi.object().keys({ osPlatform: Joi.string().required(), buildType: Joi.string().valid('ELECTRON', 'DOCKER_ON_PREMISE').required(), encryptionStrategies: Joi.array().items(Joi.string()), + sessionId: Joi.number().required(), }).required(); const mainCheckFn = async (testCase) => { diff --git a/redisinsight/ui/src/components/config/Config.tsx b/redisinsight/ui/src/components/config/Config.tsx index 4f620fefab..5f707de5cb 100644 --- a/redisinsight/ui/src/components/config/Config.tsx +++ b/redisinsight/ui/src/components/config/Config.tsx @@ -53,7 +53,7 @@ const Config = () => { if (serverInfo && checkIsAnalyticsGranted()) { (async () => { const telemetryService = getTelemetryService(segmentWriteKey) - await telemetryService.identify({ installationId: serverInfo.id }) + await telemetryService.identify({ installationId: serverInfo.id, sessionId: serverInfo.sessionId }) dispatch(setAnalyticsIdentified(true)) })() diff --git a/redisinsight/ui/src/telemetry/interfaces.ts b/redisinsight/ui/src/telemetry/interfaces.ts index fdf73aa93f..8a79d22508 100644 --- a/redisinsight/ui/src/telemetry/interfaces.ts +++ b/redisinsight/ui/src/telemetry/interfaces.ts @@ -2,6 +2,7 @@ import { TelemetryEvent } from './events' export interface ITelemetryIdentify { installationId: string; + sessionId: number; } export interface ITelemetryService { diff --git a/redisinsight/ui/src/telemetry/segment.ts b/redisinsight/ui/src/telemetry/segment.ts index 64ba7da500..6af779bf64 100644 --- a/redisinsight/ui/src/telemetry/segment.ts +++ b/redisinsight/ui/src/telemetry/segment.ts @@ -1,6 +1,6 @@ import { BrowserStorageItem } from 'uiSrc/constants' import { localStorageService } from 'uiSrc/services' -import { ITelemetryService, ITelemetryEvent } from './interfaces' +import { ITelemetryService, ITelemetryEvent, ITelemetryIdentify } from './interfaces' import loadSegmentAnalytics from './loadSegmentAnalytics' export const NON_TRACKING_ANONYMOUS_ID = 'UNSET' @@ -19,6 +19,8 @@ interface IContextPageInfo { export class SegmentTelemetryService implements ITelemetryService { private _anonymousId: string = '' + private _sessionId: number = -1 + private _getPageInfo = (): IContextPage => { const pageObject: IContextPage = {} @@ -52,7 +54,12 @@ export class SegmentTelemetryService implements ITelemetryService { context: { ip: '0.0.0.0', ...pageInfo - } + }, + integrations: { + Amplitude: { + session_id: this._sessionId, + } + }, }, resolve) } catch (e) { reject(e) @@ -60,7 +67,8 @@ export class SegmentTelemetryService implements ITelemetryService { }) } - async identify({ installationId }: { installationId: string }): Promise { + async identify({ installationId, sessionId }: ITelemetryIdentify): Promise { + this._sessionId = sessionId || -1 if (this._anonymousId) { return Promise.resolve() } @@ -94,7 +102,14 @@ export class SegmentTelemetryService implements ITelemetryService { async event({ event, properties }: ITelemetryEvent): Promise { return new Promise((resolve, reject) => { try { - window.analytics.track(event, properties, { context: { ip: '0.0.0.0', ...this._getPageInfo() } }, resolve) + window.analytics.track(event, properties, { + context: { ip: '0.0.0.0', ...this._getPageInfo() }, + integrations: { + Amplitude: { + session_id: this._sessionId, + } + }, + }, resolve) } catch (e) { reject(e) }