[Feature Flags] Set RUM transaction.outcome (#200576)

This commit is contained in:
Alejandro Fernández Haro 2024-11-21 16:40:52 +01:00 committed by GitHub
parent 30e075a1b6
commit f9287a9545
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 70 additions and 6 deletions

View file

@ -8,10 +8,10 @@
*/
import { firstValueFrom } from 'rxjs';
import { apm } from '@elastic/apm-rum';
import { Transaction, apm } from '@elastic/apm-rum';
import { type Client, OpenFeature, type Provider } from '@openfeature/web-sdk';
import { coreContextMock } from '@kbn/core-base-browser-mocks';
import type { FeatureFlagsStart } from '@kbn/core-feature-flags-browser';
import type { FeatureFlagsSetup, FeatureFlagsStart } from '@kbn/core-feature-flags-browser';
import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks';
import type { InternalInjectedMetadataSetup } from '@kbn/core-injected-metadata-browser-internal';
import { FeatureFlagsService } from '..';
@ -63,7 +63,7 @@ describe('FeatureFlagsService Browser', () => {
test('awaits initialization in the start context', async () => {
const { setProvider } = featureFlagsService.setup({ injectedMetadata });
let externalResolve: Function = () => void 0;
const spy = jest.spyOn(OpenFeature, 'setProviderAndWait').mockImplementation(async () => {
const spy = jest.spyOn(OpenFeature, 'setProviderAndWait').mockImplementationOnce(async () => {
await new Promise((resolve) => {
externalResolve = resolve;
});
@ -80,7 +80,7 @@ describe('FeatureFlagsService Browser', () => {
test('do not hold for too long during initialization', async () => {
const { setProvider } = featureFlagsService.setup({ injectedMetadata });
const spy = jest.spyOn(OpenFeature, 'setProviderAndWait').mockImplementation(async () => {
const spy = jest.spyOn(OpenFeature, 'setProviderAndWait').mockImplementationOnce(async () => {
await new Promise(() => {}); // never resolves
});
const apmCaptureErrorSpy = jest.spyOn(apm, 'captureError');
@ -95,6 +95,60 @@ describe('FeatureFlagsService Browser', () => {
expect.stringContaining('The feature flags provider took too long to initialize.')
);
});
describe('APM instrumentation', () => {
const fakeProvider = { metadata: { name: 'fake provider' } } as Provider;
let setProvider: FeatureFlagsSetup['setProvider'];
let apmSpy: jest.SpyInstance<Transaction | undefined>;
let setProviderSpy: jest.SpyInstance<Promise<void>>;
beforeEach(() => {
const setup = featureFlagsService.setup({ injectedMetadata });
setProvider = setup.setProvider;
setProviderSpy = jest.spyOn(OpenFeature, 'setProviderAndWait');
apmSpy = jest.spyOn(apm, 'startTransaction');
});
test('starts an APM transaction to track the time it takes to set a provider', () => {
expect.assertions(1);
setProvider(fakeProvider);
expect(apmSpy).toHaveBeenCalledWith('set-provider', 'feature-flags');
});
test('APM transaction tracks success', async () => {
expect.assertions(4);
setProviderSpy.mockResolvedValueOnce();
setProvider(fakeProvider);
const transaction = apmSpy.mock.results[0].value;
const endTransactionSpy = jest.spyOn(transaction, 'end');
expect(transaction.outcome).toBeUndefined();
expect(endTransactionSpy).toHaveBeenCalledTimes(0);
await setProviderSpy.mock.results[0].value.catch(() => {});
expect(transaction.outcome).toBe('success');
expect(endTransactionSpy).toHaveBeenCalledTimes(1);
});
test('APM transaction tracks failures', async () => {
expect.assertions(5);
const apmCaptureErrorSpy = jest.spyOn(apm, 'captureError');
const error = new Error('Something went terribly wrong');
setProviderSpy.mockRejectedValueOnce(error);
setProvider(fakeProvider);
const transaction = apmSpy.mock.results[0].value;
const endTransactionSpy = jest.spyOn(transaction, 'end');
expect(transaction.outcome).toBeUndefined();
expect(endTransactionSpy).toHaveBeenCalledTimes(0);
await setProviderSpy.mock.results[0].value.catch(() => {});
expect(apmCaptureErrorSpy).toHaveBeenCalledWith(error);
expect(transaction.outcome).toBe('failure');
expect(endTransactionSpy).toHaveBeenCalledTimes(1);
});
});
});
describe('context handling', () => {

View file

@ -71,11 +71,21 @@ export class FeatureFlagsService {
const transaction = apm.startTransaction('set-provider', 'feature-flags');
this.isProviderReadyPromise = OpenFeature.setProviderAndWait(provider);
this.isProviderReadyPromise
.then(() => transaction?.end())
.then(() => {
if (transaction) {
// @ts-expect-error RUM types are not correct
transaction.outcome = 'success';
transaction.end();
}
})
.catch((err) => {
this.logger.error(err);
apm.captureError(err);
transaction?.end();
if (transaction) {
// @ts-expect-error RUM types are not correct
transaction.outcome = 'failure';
transaction.end();
}
});
},
appendContext: (contextToAppend) => this.appendContext(contextToAppend),