Skip to content

fix: fix flush policies reference copy, add BackgroundPolicy #838

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ You must pass at least the `writeKey`. Additional configuration options are list
| `logger` | undefined | Custom logger instance to expose internal Segment client logging. |
| `flushAt` | 20 | How many events to accumulate before sending events to the backend. |
| `flushInterval` | 30 | In seconds, how often to send events to the backend. |
| `flushPolicies` | undefined | Add more granular control for when to flush, see [Adding or removing policies](#adding-or-removing-policies) |
| `flushPolicies` | undefined | Add more granular control for when to flush, see [Adding or removing policies](#adding-or-removing-policies). **Mutually exclusive with flushAt/flushInterval** |
| `maxBatchSize` | 1000 | How many events to send to the API at once |
| `trackAppLifecycleEvents` | false | Enable automatic tracking for [app lifecycle events](https://segment.com/docs/connections/spec/mobile/#lifecycle-events): application installed, opened, updated, backgrounded) |
| `trackDeepLinks` | false | Enable automatic tracking for when the user opens the app via a deep link (Note: Requires additional setup on iOS, [see instructions](#ios-deep-link-tracking-setup)) |
Expand Down Expand Up @@ -601,7 +601,7 @@ Refer to the following table for Plugins you can use to meet your tracking needs

## Controlling Upload With Flush Policies

To more granurily control when events are uploaded you can use `FlushPolicies`
To more granurily control when events are uploaded you can use `FlushPolicies`. **This will override any setting on `flushAt` and `flushInterval`, but you can use `CountFlushPolicy` and `TimerFlushPolicy` to have the same behaviour respectively.**

A Flush Policy defines the strategy for deciding when to flush, this can be on an interval, on a certain time of day, after receiving a certain number of events or even after receiving a particular event. This gives you even more flexibility on when to send event to Segment.

Expand All @@ -626,6 +626,7 @@ We have several standard FlushPolicies:
- `CountFlushPolicy` triggers whenever a certain number of events is reached
- `TimerFlushPolicy` triggers on an interval of milliseconds
- `StartupFlushPolicy` triggers on client startup only
- `BackgroundFlushPolicy` triggers when the app goes into the background/inactive.

## Adding or removing policies

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/__mocks__/react-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { PlatformOSType } from 'react-native';
export const AppState = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
currentState: 'active',
};

export const Linking = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const createTestClient = (
config: {
writeKey: 'mock-write-key',
autoAddSegmentDestination: false,
flushInterval: 0,
...config,
},
logger: getMockLogger(),
Expand Down
77 changes: 77 additions & 0 deletions packages/core/src/__tests__/analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AppStateStatus } from 'react-native';
import { AppState } from 'react-native';
import { SegmentClient } from '../analytics';
import { ErrorType, SegmentError } from '../errors';
import { CountFlushPolicy, TimerFlushPolicy } from '../flushPolicies';
import { getMockLogger } from './__helpers__/mockLogger';
import { MockSegmentStore } from './__helpers__/mockSegmentStore';

Expand Down Expand Up @@ -172,4 +173,80 @@ describe('SegmentClient', () => {
expect(errorHandler).toHaveBeenCalledWith(error);
});
});

describe('Flush Policies', () => {
it('creates the default flush policies when config is empty', () => {
client = new SegmentClient({
...clientArgs,
config: {
...clientArgs.config,
flushAt: undefined,
flushInterval: undefined,
},
});
const flushPolicies = client.getFlushPolicies();
expect(flushPolicies.length).toBe(2);
});

it('setting flush policies is mutually exclusive with flushAt/Interval', () => {
client = new SegmentClient({
...clientArgs,
config: {
...clientArgs.config,
flushAt: 5,
flushInterval: 30,
flushPolicies: [new CountFlushPolicy(1)],
},
});
const flushPolicies = client.getFlushPolicies();
expect(flushPolicies.length).toBe(1);
});

it('setting flushAt/Interval to 0 should make the client have no uploads', () => {
client = new SegmentClient({
...clientArgs,
config: {
...clientArgs.config,
flushAt: 0,
flushInterval: 0,
},
});
const flushPolicies = client.getFlushPolicies();
expect(flushPolicies.length).toBe(0);
});

it('setting an empty array of policies should make the client have no uploads', () => {
client = new SegmentClient({
...clientArgs,
config: {
...clientArgs.config,
flushAt: undefined,
flushInterval: undefined,
flushPolicies: [],
},
});
const flushPolicies = client.getFlushPolicies();
expect(flushPolicies.length).toBe(0);
});

it('can add and remove policies, does not mutate original array', () => {
const policies = [new CountFlushPolicy(1), new TimerFlushPolicy(200)];
client = new SegmentClient({
...clientArgs,
config: {
...clientArgs.config,
flushAt: undefined,
flushInterval: undefined,
flushPolicies: policies,
},
});
expect(client.getFlushPolicies().length).toBe(policies.length);

client.removeFlushPolicy(...policies);
expect(client.getFlushPolicies().length).toBe(0);

client.addFlushPolicy(...policies);
expect(client.getFlushPolicies().length).toBe(policies.length);
});
});
});
1 change: 1 addition & 0 deletions packages/core/src/__tests__/internal/fetchSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('internal #getSettings', () => {
config: {
writeKey: '123-456',
defaultSettings: defaultIntegrationSettings,
flushInterval: 0,
},
logger: getMockLogger(),
store: store,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/__tests__/methods/flush.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('methods #flush', () => {
writeKey: '123-456',
autoAddSegmentDestination: false,
trackAppLifecycleEvents: false,
flushInterval: 0,
},
logger: getMockLogger(),
store: store,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/__tests__/methods/group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('methods #group', () => {
const clientArgs = {
config: {
writeKey: 'mock-write-key',
flushInterval: 0,
},
logger: getMockLogger(),
store: store,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/__tests__/methods/screen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('methods #screen', () => {
const clientArgs = {
config: {
writeKey: 'mock-write-key',
flushInterval: 0,
},
logger: getMockLogger(),
store: store,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/__tests__/methods/track.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('methods #track', () => {
const clientArgs = {
config: {
writeKey: 'mock-write-key',
flushInterval: 0,
},
logger: getMockLogger(),
store: store,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/__tests__/timeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('timeline', () => {
const clientArgs = {
config: {
writeKey: 'mock-write-key',
flushInterval: 0,
},
logger: getMockLogger(),
store: store,
Expand Down
49 changes: 30 additions & 19 deletions packages/core/src/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
AppStateStatus,
NativeEventSubscription,
} from 'react-native';
import { settingsCDN, workspaceDestinationFilterKey } from './constants';
import {
settingsCDN,
workspaceDestinationFilterKey,
defaultFlushInterval,
defaultFlushAt,
} from './constants';
import { getContext } from './context';
import {
applyRawEventData,
Expand Down Expand Up @@ -703,25 +708,31 @@ export class SegmentClient {
* trigger flush
*/
private setupFlushPolicies() {
const flushPolicies = this.config.flushPolicies ?? [];

// Compatibility with older arguments
if (
this.config.flushAt !== undefined &&
this.config.flushAt !== null &&
this.config.flushAt > 0
) {
flushPolicies.push(new CountFlushPolicy(this.config.flushAt));
}
const flushPolicies = [];

if (
this.config.flushInterval !== undefined &&
this.config.flushInterval !== null &&
this.config.flushInterval > 0
) {
flushPolicies.push(
new TimerFlushPolicy(this.config.flushInterval * 1000)
);
// If there are zero policies or flushAt/flushInterval use the defaults:
if (this.config.flushPolicies !== undefined) {
flushPolicies.push(...this.config.flushPolicies);
} else {
if (
this.config.flushAt === undefined ||
(this.config.flushAt !== null && this.config.flushAt > 0)
) {
flushPolicies.push(
new CountFlushPolicy(this.config.flushAt ?? defaultFlushAt)
);
}

if (
this.config.flushInterval === undefined ||
(this.config.flushInterval !== null && this.config.flushInterval > 0)
) {
flushPolicies.push(
new TimerFlushPolicy(
(this.config.flushInterval ?? defaultFlushInterval) * 1000
)
);
}
}

this.flushPolicyExecuter = new FlushPolicyExecuter(flushPolicies, () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ export const settingsCDN = 'https://cdn-settings.segment.com/v1/projects';

export const defaultConfig: Config = {
writeKey: '',
flushAt: 20,
flushInterval: 30,
maxBatchSize: 1000,
trackDeepLinks: false,
trackAppLifecycleEvents: false,
autoAddSegmentDestination: true,
};

export const workspaceDestinationFilterKey = '';

export const defaultFlushAt = 20;
export const defaultFlushInterval = 30;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { AppState, AppStateStatus } from 'react-native';
import { BackgroundFlushPolicy } from '../background-flush-policy';

describe('BackgroundFlushPolicy', () => {
it('triggers a flush when reaching limit', () => {
let updateCallback = (_val: AppStateStatus) => {
return;
};

const addSpy = jest
.spyOn(AppState, 'addEventListener')
.mockImplementation((_action, callback) => {
updateCallback = callback;
return { remove: jest.fn() };
});

const policy = new BackgroundFlushPolicy();
policy.start();
const observer = jest.fn();

policy.shouldFlush.onChange(observer);

expect(addSpy).toHaveBeenCalledTimes(1);

updateCallback('background');
expect(observer).toHaveBeenCalledWith(true);
observer.mockClear();

updateCallback('active');
expect(observer).not.toHaveBeenCalled();

updateCallback('inactive');
expect(observer).toHaveBeenCalledWith(true);
});
});
38 changes: 38 additions & 0 deletions packages/core/src/flushPolicies/background-flush-policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
AppState,
AppStateStatus,
NativeEventSubscription,
} from 'react-native';
import type { SegmentEvent } from '../types';
import { FlushPolicyBase } from './types';

/**
* StatupFlushPolicy triggers a flush right away on client startup
*/
export class BackgroundFlushPolicy extends FlushPolicyBase {
private appStateSubscription?: NativeEventSubscription;
private appState: AppStateStatus = AppState.currentState;

start() {
this.appStateSubscription = AppState.addEventListener(
'change',
(nextAppState) => {
if (
this.appState === 'active' &&
['inactive', 'background'].includes(nextAppState)
) {
// When the app goes into the background we will trigger a flush
this.shouldFlush.value = true;
}
}
);
}

onEvent(_event: SegmentEvent): void {
// Nothing to do
}

end(): void {
this.appStateSubscription?.remove();
}
}
2 changes: 1 addition & 1 deletion packages/core/src/flushPolicies/flush-policy-executer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class FlushPolicyExecuter {
private onFlush: () => void;

constructor(policies: FlushPolicy[], onFlush: () => void) {
this.policies = policies;
this.policies = [...policies];
this.onFlush = onFlush;

// Now listen to changes on the flush policies shouldFlush
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/flushPolicies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './types';
export * from './count-flush-policy';
export * from './timer-flush-policy';
export * from './startup-flush-policy';
export * from './background-flush-policy';
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('SegmentDestination', () => {
config: {
writeKey: '123-456',
maxBatchSize: 2,
flushInterval: 0,
},
store,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('ClevertapPlugin ', () => {
config: {
writeKey: '123-456',
trackApplicationLifecycleEvents: true,
flushInterval: 0,
},
store,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('DeviceTokenPlugin', () => {
config: {
writeKey: '123-456',
trackApplicationLifecycleEvents: true,
flushInterval: 0,
},
store,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('MixpanelPlugin', () => {
config: {
writeKey: '123-456',
trackApplicationLifecycleEvents: true,
flushInterval: 0,
},
store,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('#alias', () => {
config: {
writeKey: '123-456',
trackApplicationLifecycleEvents: true,
flushInterval: 0,
},
store,
};
Expand Down