Skip to content

Commit

Permalink
[Feature Flags] Set RUM transaction.outcome (elastic#200576)
Browse files Browse the repository at this point in the history
  • Loading branch information
afharo authored and CAWilson94 committed Dec 12, 2024
1 parent e825e07 commit d1ed666
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 '..';
Expand Down Expand Up @@ -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;
});
Expand All @@ -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');
Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit d1ed666

Please sign in to comment.