[Telemetry] Only send from active window (#123909)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Alejandro Fernández Haro 2022-01-31 12:47:20 +01:00 committed by GitHub
parent a791ed6043
commit bc6e30d740
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 7 deletions

View file

@ -186,6 +186,10 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
};
}
public stop() {
this.telemetrySender?.stop();
}
private getTelemetryServicePublicApis(): TelemetryServicePublicApis {
const telemetryService = this.telemetryService!;
return {

View file

@ -66,6 +66,29 @@ describe('TelemetrySender', () => {
});
describe('shouldSendReport', () => {
let hasFocus: jest.SpyInstance;
beforeEach(() => {
hasFocus = jest.spyOn(document, 'hasFocus');
hasFocus.mockReturnValue(true); // Return true by default for all tests;
});
afterEach(() => {
hasFocus.mockRestore();
});
it('returns false if the page is not visible', async () => {
hasFocus.mockReturnValue(false);
const telemetryService = mockTelemetryService();
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
telemetryService.fetchLastReported = jest.fn().mockResolvedValue(Date.now());
const telemetrySender = new TelemetrySender(telemetryService);
const shouldSendReport = await telemetrySender['shouldSendReport']();
expect(shouldSendReport).toBe(false);
expect(telemetryService.getIsOptedIn).toBeCalledTimes(0);
expect(telemetryService.fetchLastReported).toBeCalledTimes(0);
});
it('returns false whenever optIn is false', async () => {
const telemetryService = mockTelemetryService();
telemetryService.getIsOptedIn = jest.fn().mockReturnValue(false);
@ -372,9 +395,11 @@ describe('TelemetrySender', () => {
it('calls sendIfDue every 60000 ms', () => {
const telemetryService = mockTelemetryService();
const telemetrySender = new TelemetrySender(telemetryService);
telemetrySender['sendIfDue'] = jest.fn().mockResolvedValue(void 0);
telemetrySender.startChecking();
expect(setInterval).toBeCalledTimes(1);
expect(setInterval).toBeCalledWith(telemetrySender['sendIfDue'], 60000);
expect(telemetrySender['sendIfDue']).toHaveBeenCalledTimes(0);
jest.advanceTimersByTime(60000);
expect(telemetrySender['sendIfDue']).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -6,6 +6,9 @@
* Side Public License, v 1.
*/
import type { Subscription } from 'rxjs';
import { fromEvent, interval, merge } from 'rxjs';
import { exhaustMap } from 'rxjs/operators';
import { LOCALSTORAGE_KEY, PAYLOAD_CONTENT_ENCODING } from '../../common/constants';
import { TelemetryService } from './telemetry_service';
import { Storage } from '../../../kibana_utils/public';
@ -16,7 +19,7 @@ export class TelemetrySender {
private readonly telemetryService: TelemetryService;
private lastReported?: number;
private readonly storage: Storage;
private intervalId: number = 0; // setInterval returns a positive integer, 0 means no interval is set
private sendIfDue$?: Subscription;
private retryCount: number = 0;
static getRetryDelay(retryCount: number) {
@ -62,11 +65,21 @@ export class TelemetrySender {
};
/**
* Using configuration and the lastReported dates, it decides whether a new telemetry report should be sent.
* Returns `true` when the page is visible and active in the browser.
*/
private isActiveWindow = () => {
// Using `document.hasFocus()` instead of `document.visibilityState` because the latter may return "visible"
// if 2 windows are open side-by-side because they are "technically" visible.
return document.hasFocus();
};
/**
* Using configuration, page visibility state and the lastReported dates,
* it decides whether a new telemetry report should be sent.
* @returns `true` if a new report should be sent. `false` otherwise.
*/
private shouldSendReport = async (): Promise<boolean> => {
if (this.telemetryService.canSendTelemetry()) {
if (this.isActiveWindow() && this.telemetryService.canSendTelemetry()) {
return await this.isReportDue();
}
@ -122,8 +135,20 @@ export class TelemetrySender {
};
public startChecking = () => {
if (this.intervalId === 0) {
this.intervalId = window.setInterval(this.sendIfDue, 60000);
if (!this.sendIfDue$) {
// Trigger sendIfDue...
this.sendIfDue$ = merge(
// ... periodically
interval(60000),
// ... when it regains `focus`
fromEvent(window, 'focus') // Using `window` instead of `document` because Chrome only emits on the first one.
)
.pipe(exhaustMap(this.sendIfDue))
.subscribe();
}
};
public stop = () => {
this.sendIfDue$?.unsubscribe();
};
}