Skip to content

Commit

Permalink
feat(message-system): add ab testing message
Browse files Browse the repository at this point in the history
  • Loading branch information
adderpositive committed Nov 12, 2024
1 parent 35433e7 commit fd73d0e
Show file tree
Hide file tree
Showing 18 changed files with 715 additions and 263 deletions.
20 changes: 18 additions & 2 deletions docs/features/message-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ There are multiple ways of displaying message to a user:
- messages on specific places in app (e.g. settings page, banner in account page)
- feature
- disabling some feature with an explanation message
- distribution
- possibility to use AB testing on features/components

## Implementation

Expand Down Expand Up @@ -205,7 +207,7 @@ Structure of config, types and optionality of specific keys can be found in the
- critical (red)
*/
"variant": "warning",
// Options: banner, modal, context, feature
// Options: banner, modal, context, feature, distribution
"category": "banner",
/*
- Message in language of Suite app is shown to a user.
Expand Down Expand Up @@ -260,12 +262,26 @@ Structure of config, types and optionality of specific keys can be found in the
"coins.btc"
]
}
// Used only for feature
// Used only for feature
"feature": [
{
"domain": "coinjoin",
"flag": false
}
],
/*
- Should be used only with category: "distribution"
- min distribution items are two
*/
"groups": [
{
"variant": "A",
"percentage": 30
},
{
"variant": "B",
"percentage": 70
}
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export const getRootReducer = (selectedAccount = BTC_ACCOUNT, fees = DEFAULT_FEE
feature: [],
},
dismissedMessages: {},
validExperiments: [],
},
() => ({}),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { AppState } from 'src/reducers/store';

import messageSystemMiddleware from '../messageSystemMiddleware';

// Type annotation as workaround for typecheck error "The inferred type of 'default' cannot be named..."
// Type annotation as workaround for type-check error "The inferred type of 'default' cannot be named..."
const messageSystemReducer: Reducer = prepareMessageSystemReducer(extraDependencies);
const deviceReducer = prepareDeviceReducer(extraDependencies);

Expand Down Expand Up @@ -71,7 +71,7 @@ const initStore = (preloadedState: State) => {
};

describe('Message system middleware', () => {
it('prepares valid messages for being displayed', async () => {
it('prepares valid messages for being displayed', () => {
const message1 = {
id: '22e6444d-a586-4593-bc8d-5d013f193eba',
category: 'banner',
Expand All @@ -96,9 +96,10 @@ describe('Message system middleware', () => {
message3,
message4,
]);
jest.spyOn(messageSystemUtils, 'getValidExperiments').mockImplementation(() => []);

const store = initStore(getInitialState(undefined, undefined));
await store.dispatch({
store.dispatch({
type: messageSystemActions.fetchSuccessUpdate.type,
payload: { config: { sequence: 1 }, timestamp: 0 },
});
Expand All @@ -118,14 +119,61 @@ describe('Message system middleware', () => {
feature: [message4.id],
},
},
{
type: messageSystemActions.updateValidExperiments.type,
payload: [],
},
]);
});

it('saves messages even if there are no valid messages', async () => {
it('saves messages even if there are no valid messages', () => {
jest.spyOn(messageSystemUtils, 'getValidMessages').mockImplementation(() => []);
jest.spyOn(messageSystemUtils, 'getValidExperiments').mockImplementation(() => []);

const store = initStore(getInitialState(undefined, undefined));
store.dispatch({
type: messageSystemActions.fetchSuccessUpdate.type,
payload: { config: { sequence: 1 }, timestamp: 0 },
});

const result = store.getActions();
expect(result).toEqual([
{
type: messageSystemActions.fetchSuccessUpdate.type,
payload: { config: { sequence: 1 }, timestamp: 0 },
},
{
type: messageSystemActions.updateValidMessages.type,
payload: { banner: [], context: [], modal: [], feature: [] },
},
{
type: messageSystemActions.updateValidExperiments.type,
payload: [],
},
]);
});

it('test of experiment action', () => {
const experiment1 = {
id: '3bed56a4-ecd8-4e0f-9e5f-014b484c2afa',
groups: [
{
variant: 'A',
percentage: 25,
},
{
variant: 'B',
percentage: 75,
},
],
};

jest.spyOn(messageSystemUtils, 'getValidExperiments').mockImplementation(() => [
experiment1,
]);

const store = initStore(getInitialState(undefined, undefined));
await store.dispatch({
store.dispatch({
type: messageSystemActions.fetchSuccessUpdate.type,
payload: { config: { sequence: 1 }, timestamp: 0 },
});
Expand All @@ -140,6 +188,10 @@ describe('Message system middleware', () => {
type: messageSystemActions.updateValidMessages.type,
payload: { banner: [], context: [], modal: [], feature: [] },
},
{
type: messageSystemActions.updateValidExperiments.type,
payload: [experiment1.id],
},
]);
});
});
12 changes: 11 additions & 1 deletion packages/suite/src/middlewares/suite/messageSystemMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
messageSystemActions,
categorizeMessages,
getValidMessages,
getValidExperiments,
} from '@suite-common/message-system';

import { SUITE } from 'src/actions/suite/constants';
Expand Down Expand Up @@ -43,10 +44,19 @@ const messageSystemMiddleware =
enabledNetworks,
},
});

const categorizedValidMessages = categorizeMessages(validMessages);

const validExperiments = getValidExperiments(config, {
device,
transport,
settings: {
tor: getIsTorEnabled(torStatus),
enabledNetworks,
},
}).map(item => item.id);

api.dispatch(messageSystemActions.updateValidMessages(categorizedValidMessages));
api.dispatch(messageSystemActions.updateValidExperiments(validExperiments));
}

return action;
Expand Down
Loading

0 comments on commit fd73d0e

Please sign in to comment.