Skip to content
Permalink
Browse files

refactor(payments): move amplitude pings into plain module

- removes amplitude-middleware from Redux store, converts to a plain
  module of exported functions for pings

- send amplitude pings directly from apiClient functions

- update components using amplitude pings to call functions directly
  rather than dispatching Redux actions

- improved test coverage & type annotations for apiClient, flow-events,
  and amplitude pings

- misc linting & test log noise cleanups

issue #3526
  • Loading branch information
lmorchard committed Dec 16, 2019
1 parent bfdf0cb commit 03fea43e439e5a5a3fecfe44a4e64e778a7c005d
Showing with 876 additions and 555 deletions.
  1. +4 −2 packages/fxa-payments-server/src/App.test.tsx
  2. +2 −0 packages/fxa-payments-server/src/components/PaymentForm/index.test.tsx
  3. +30 −40 packages/fxa-payments-server/src/{store/amplitude-middleware.test.ts → lib/amplitude.test.ts}
  4. +262 −0 packages/fxa-payments-server/src/lib/amplitude.ts
  5. +307 −35 packages/fxa-payments-server/src/lib/apiClient.test.ts
  6. +124 −38 packages/fxa-payments-server/src/lib/apiClient.ts
  7. +0 −2 packages/fxa-payments-server/src/lib/config.ts
  8. +21 −0 packages/fxa-payments-server/src/lib/flow-event.test.ts
  9. +9 −0 packages/fxa-payments-server/src/lib/sentry.test.js
  10. +12 −1 packages/fxa-payments-server/src/lib/test-utils.tsx
  11. +13 −6 packages/fxa-payments-server/src/routes/Product/SubscriptionCreate/index.tsx
  12. +0 −2 packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/index.stories.tsx
  13. +17 −16 packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/index.test.tsx
  14. +6 −8 packages/fxa-payments-server/src/routes/Product/SubscriptionUpgrade/index.tsx
  15. +0 −4 packages/fxa-payments-server/src/routes/Product/index.stories.tsx
  16. +2 −1 packages/fxa-payments-server/src/routes/Product/index.test.tsx
  17. +1 −18 packages/fxa-payments-server/src/routes/Product/index.tsx
  18. +13 −6 packages/fxa-payments-server/src/routes/Subscriptions/PaymentUpdateForm.tsx
  19. +5 −20 packages/fxa-payments-server/src/routes/Subscriptions/SubscriptionItem.tsx
  20. +0 −6 packages/fxa-payments-server/src/routes/Subscriptions/index.stories.tsx
  21. +23 −24 packages/fxa-payments-server/src/routes/Subscriptions/index.test.tsx
  22. +6 −27 packages/fxa-payments-server/src/routes/Subscriptions/index.tsx
  23. +16 −3 packages/fxa-payments-server/src/store/actions/api.ts
  24. +0 −34 packages/fxa-payments-server/src/store/actions/index.test.ts
  25. +1 −4 packages/fxa-payments-server/src/store/actions/index.ts
  26. +0 −24 packages/fxa-payments-server/src/store/actions/metrics.ts
  27. +0 −232 packages/fxa-payments-server/src/store/amplitude-middleware.ts
  28. +0 −2 packages/fxa-payments-server/src/store/index.tsx
  29. +2 −0 packages/fxa-payments-server/src/store/sequences.test.ts
@@ -6,6 +6,8 @@ import { defaultAppContextValue } from './lib/test-utils';
import { AppErrorBoundary, AppErrorDialog } from './App';
import { AppContext } from './lib/AppContext';

jest.mock('./lib/sentry');

// TODO: backfill general App component tests
// describe('App', () => {});

@@ -14,11 +16,11 @@ describe('App/AppErrorBoundary', () => {
// HACK: Swallow the exception thrown by BadComponent - it bubbles up
// unnecesarily to jest and makes noise.
jest.spyOn(console, 'error');
global.console.error.mockImplementation(() => {});
(global.console.error as jest.Mock).mockImplementation(() => {});
});

afterEach(() => {
global.console.error.mockRestore();
(global.console.error as jest.Mock).mockRestore();
});

it('renders children that do not cause exceptions', () => {
@@ -4,6 +4,8 @@ import '@testing-library/jest-dom/extend-expect';
import waitForExpect from 'wait-for-expect';
import { Omit } from '../../lib/types';

jest.mock('../../lib/sentry');

import {
mockStripeElementOnChangeFns,
mockStripeElementOnBlurFns,
@@ -1,39 +1,16 @@
import { Middleware, MiddlewareAPI, Dispatch, AnyAction } from 'redux';
import { AmplitudeMiddleware } from './amplitude-middleware';
import FlowEvent from '../lib/flow-event';
jest.mock('../lib/flow-event');
jest.mock('../lib/config', () => ({
config: { sentry: { dsn: '' } },
}));

let next: Dispatch<AnyAction>;
let dispatch: Dispatch<AnyAction>;
let getState: any;
let invoke: Function;
jest.mock('./sentry');

const create = (
middleware: Middleware,
store: MiddlewareAPI<Dispatch<AnyAction>, any>,
next: Dispatch<AnyAction>
) => {
return (action: any) => middleware(store)(next)(action);
};
import * as Amplitude from './amplitude';

beforeEach(() => {
(<jest.Mock>FlowEvent.logAmplitudeEvent).mockClear();
next = jest.fn();
dispatch = jest.fn();
getState = jest.fn();
invoke = create(AmplitudeMiddleware, { dispatch, getState }, next);
});

it('should dispatch the next action', () => {
invoke({ type: 'testo' });
expect(next).toBeCalled();
});

it('should call logAmplitudeEvent with the correct event group and type names', () => {
const testCases = [
const testCases: Array<[keyof typeof Amplitude, ...string[][]]> = [
['manageSubscriptionsMounted', ['subManage', 'view']],
['manageSubscriptionsEngaged', ['subManage', 'engage']],
['createSubscriptionMounted', ['subPaySetup', 'view']],
@@ -53,31 +30,45 @@ it('should call logAmplitudeEvent with the correct event group and type names',
['updatePaymentMounted', ['subPayManage', 'view']],
['updatePaymentEngaged', ['subPayManage', 'engage']],
['updatePayment_PENDING', ['subPayManage', 'submit']],
['updatePayment_FULFILLED', ['subPayManage', 'success']],
['updatePayment_REJECTED', ['subPayManage', 'fail']],
['cancelSubscriptionMounted', ['subCancel', 'view']],
['cancelSubscriptionEngaged', ['subCancel', 'engage']],
['cancelSubscription_PENDING', ['subCancel', 'submit']],
[
'updatePayment_FULFILLED',
['subPayManage', 'success'],
['subPayManage', 'complete'],
'cancelSubscription_FULFILLED',
['subCancel', 'success'],
['subCancel', 'complete'],
],
['updatePayment_REJECTED', ['subPayManage', 'fail']],
['cancelSubscription_REJECTED', ['subCancel', 'fail']],
];

for (const [actionType, ...expectedArgs] of testCases) {
invoke({ type: actionType });
Amplitude[actionType]({});

for (const args of expectedArgs as string[][]) {
for (const args of expectedArgs) {
expect(FlowEvent.logAmplitudeEvent).toBeCalledWith(...args, {});
}

(<jest.Mock>FlowEvent.logAmplitudeEvent).mockClear();
}
});

it('should capture error during logAmplitudeEvent', () => {
(<jest.Mock>FlowEvent.logAmplitudeEvent).mockImplementationOnce(() => {
throw 'oopsie';
});
expect(() => {
Amplitude.createSubscription_PENDING({
plan_id: '123xyz_hourly',
product_id: '123xyz',
});
}).not.toThrow();
});

it('should call logAmplitudeEvent with subscription plan info', () => {
const plan = { plan_id: '123xyz_hourly', product_id: '123xyz' };
invoke({
type: 'createSubscription_PENDING',
meta: { plan },
});
Amplitude.createSubscription_PENDING(plan);
const eventProps = (<jest.Mock>(
FlowEvent.logAmplitudeEvent
)).mock.calls[0].pop();
@@ -91,10 +82,9 @@ it('should call logAmplitudeEvent with subscription plan info', () => {
it('should call logAmplitudeEvent with reason for failure on fail event', () => {
const plan = { plan_id: '123xyz_hourly', product_id: '123xyz' };
const payload = { message: 'oopsie daisies' };
invoke({
type: 'createSubscription_REJECTED',
meta: { plan },
payload,
Amplitude.createSubscription_REJECTED({
...plan,
error: payload,
});
const eventProps = (<jest.Mock>(
FlowEvent.logAmplitudeEvent

0 comments on commit 03fea43

Please sign in to comment.
You can’t perform that action at this time.