-
Notifications
You must be signed in to change notification settings - Fork 20
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
feat: adds datasource status to sdk-client #590
Changes from 11 commits
60aae7c
7ef3b5c
6ef2c49
2849c1f
6f8c6b5
ff6dda5
c4879f5
d95111f
1c584d3
6028c89
90bbc69
a2787a6
ae3f473
1c24e1b
8dd7590
47b6b70
ddeb130
60ce6c5
7df3547
15adf36
11e430c
df41f81
57796af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export enum DataSourceErrorKind { | ||
/// An unexpected error, such as an uncaught exception, further | ||
/// described by the error message. | ||
Unknown, | ||
|
||
/// An I/O error such as a dropped connection. | ||
NetworkError, | ||
|
||
/// The LaunchDarkly service returned an HTTP response with an error | ||
/// status, available in the status code. | ||
ErrorResponse, | ||
|
||
/// The SDK received malformed data from the LaunchDarkly service. | ||
InvalidData, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* eslint-disable max-classes-per-file */ | ||
import { DataSourceErrorKind } from './DataSourceErrorKinds'; | ||
|
||
export class LDFileDataSourceError extends Error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Errors are not intended to be internal. In that the internal package should only be used by SDK implementations, but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe not expected to be constructed outside though. That part is likely fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Started fixing this, but running into some import/export configuration issue. Pushed most recent commit before having to leave for flight. |
||
constructor(message: string) { | ||
super(message); | ||
this.name = 'LaunchDarklyFileDataSourceError'; | ||
} | ||
} | ||
|
||
export class LDPollingError extends Error { | ||
public readonly kind: DataSourceErrorKind; | ||
public readonly status?: number; | ||
public readonly recoverable: boolean; | ||
|
||
constructor(kind: DataSourceErrorKind, message: string, status?: number, recoverable = true) { | ||
super(message); | ||
this.kind = kind; | ||
this.status = status; | ||
this.name = 'LaunchDarklyPollingError'; | ||
this.recoverable = recoverable; | ||
} | ||
} | ||
|
||
export class LDStreamingError extends Error { | ||
public readonly kind: DataSourceErrorKind; | ||
public readonly code?: number; | ||
public readonly recoverable: boolean; | ||
|
||
constructor(kind: DataSourceErrorKind, message: string, code?: number, recoverable = true) { | ||
super(message); | ||
this.kind = kind; | ||
this.code = code; | ||
this.name = 'LaunchDarklyStreamingError'; | ||
this.recoverable = recoverable; | ||
} | ||
} | ||
|
||
export type StreamingErrorHandler = (err: LDStreamingError) => void; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { DataSourceErrorKind } from './DataSourceErrorKinds'; | ||
import { | ||
LDFileDataSourceError, | ||
LDPollingError, | ||
LDStreamingError, | ||
StreamingErrorHandler, | ||
} from './errors'; | ||
|
||
export { | ||
DataSourceErrorKind, | ||
LDFileDataSourceError, | ||
LDPollingError, | ||
LDStreamingError, | ||
StreamingErrorHandler, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from './context'; | ||
export * from './datasource'; | ||
export * from './diagnostics'; | ||
export * from './evaluation'; | ||
export * from './events'; | ||
export * from './stream'; | ||
export * from './context'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
import StreamingProcessor from './StreamingProcessor'; | ||
import type { StreamingErrorHandler } from './types'; | ||
|
||
export { StreamingProcessor }; | ||
export type { StreamingErrorHandler }; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,11 +87,8 @@ describe('sdk-client storage', () => { | |
|
||
expect(mockPlatform.storage.get).toHaveBeenCalledWith(flagStorageKey); | ||
|
||
// 'change' should not have been emitted | ||
expect(emitter.emit).toHaveBeenCalledTimes(2); | ||
expect(emitter.emit).toHaveBeenNthCalledWith(1, 'change', context, defaultFlagKeys); | ||
expect(emitter.emit).toHaveBeenNthCalledWith( | ||
2, | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, defaultFlagKeys); | ||
expect(emitter.emit).toHaveBeenCalledWith( | ||
'error', | ||
context, | ||
expect.objectContaining({ message: 'test-error' }), | ||
|
@@ -140,15 +137,12 @@ describe('sdk-client storage', () => { | |
); | ||
|
||
// 'change' should not have been emitted | ||
expect(emitter.emit).toHaveBeenCalledTimes(2); | ||
expect(emitter.emit).toHaveBeenNthCalledWith( | ||
1, | ||
expect(emitter.emit).toHaveBeenCalledWith( | ||
'change', | ||
expect.objectContaining(toMulti(context)), | ||
defaultFlagKeys, | ||
); | ||
expect(emitter.emit).toHaveBeenNthCalledWith( | ||
2, | ||
expect(emitter.emit).toHaveBeenCalledWith( | ||
'error', | ||
expect.objectContaining(toMulti(context)), | ||
expect.objectContaining({ message: 'test-error' }), | ||
|
@@ -175,15 +169,17 @@ describe('sdk-client storage', () => { | |
|
||
// @ts-ignore | ||
emitter = ldc.emitter; | ||
jest.spyOn(emitter as LDEmitter, 'emit'); | ||
const spy = jest.spyOn(emitter as LDEmitter, 'emit'); | ||
|
||
// expect emission | ||
await ldc.identify(context); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, defaultFlagKeys); | ||
|
||
// expit no emission | ||
// clear the spy so we can tell if change was invoked again | ||
spy.mockClear(); | ||
// expect no emission | ||
await ldc.identify(context); | ||
|
||
expect(emitter.emit).toHaveBeenCalledTimes(1); | ||
expect(emitter.emit).not.toHaveBeenCalledWith('change', context, defaultFlagKeys); | ||
}); | ||
|
||
test('no storage, cold start from streaming', async () => { | ||
|
@@ -256,8 +252,8 @@ describe('sdk-client storage', () => { | |
JSON.stringify(putResponse), | ||
); | ||
|
||
expect(emitter.emit).toHaveBeenNthCalledWith(1, 'change', context, defaultFlagKeys); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I may need some explanation on these changes also, as the ordering of events mattered and that isn't tested anymore. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about that too. I can go through and get them all to be passing, but I don’t think there is any specification around when a change event should come out vs when a dataSourceStatus change should come out and that is leading to them all breaking as I added dataSourceStatus events. Perhaps an improved way could be to filter to just “change” and then do positional checking on the filtered output. |
||
expect(emitter.emit).toHaveBeenNthCalledWith(2, 'change', context, ['dev-test-flag']); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, defaultFlagKeys); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, ['dev-test-flag']); | ||
}); | ||
|
||
test('syncing storage when a flag is added', async () => { | ||
|
@@ -296,7 +292,7 @@ describe('sdk-client storage', () => { | |
flagStorageKey, | ||
JSON.stringify(putResponse), | ||
); | ||
expect(emitter.emit).toHaveBeenNthCalledWith(2, 'change', context, ['another-dev-test-flag']); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, ['another-dev-test-flag']); | ||
}); | ||
|
||
test('syncing storage when a flag is updated', async () => { | ||
|
@@ -319,7 +315,7 @@ describe('sdk-client storage', () => { | |
await jest.runAllTimersAsync(); | ||
|
||
expect(ldc.allFlags()).toMatchObject({ 'dev-test-flag': false }); | ||
expect(emitter.emit).toHaveBeenNthCalledWith(2, 'change', context, ['dev-test-flag']); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, ['dev-test-flag']); | ||
}); | ||
|
||
test('syncing storage on multiple flag operations', async () => { | ||
|
@@ -347,7 +343,7 @@ describe('sdk-client storage', () => { | |
|
||
expect(ldc.allFlags()).toMatchObject({ 'dev-test-flag': false, 'another-dev-test-flag': true }); | ||
expect(ldc.allFlags()).not.toHaveProperty('moonshot-demo'); | ||
expect(emitter.emit).toHaveBeenNthCalledWith(2, 'change', context, [ | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, [ | ||
'moonshot-demo', | ||
'dev-test-flag', | ||
'another-dev-test-flag', | ||
|
@@ -380,8 +376,7 @@ describe('sdk-client storage', () => { | |
); | ||
|
||
// we expect one change from the local storage init, but no further change from the PUT | ||
expect(emitter.emit).toHaveBeenCalledTimes(1); | ||
expect(emitter.emit).toHaveBeenNthCalledWith(1, 'change', context, defaultFlagKeys); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, defaultFlagKeys); | ||
|
||
// this is defaultPutResponse | ||
expect(ldc.allFlags()).toEqual({ | ||
|
@@ -423,7 +418,7 @@ describe('sdk-client storage', () => { | |
|
||
// both previous and current are true but inExperiment has changed | ||
// so a change event should be emitted | ||
expect(emitter.emit).toHaveBeenNthCalledWith(2, 'change', context, ['dev-test-flag']); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, ['dev-test-flag']); | ||
}); | ||
|
||
test('patch should emit change event', async () => { | ||
|
@@ -452,8 +447,7 @@ describe('sdk-client storage', () => { | |
expect(ldc.allFlags()).toMatchObject({ 'dev-test-flag': false }); | ||
expect(mockPlatform.storage.set).toHaveBeenCalledTimes(4); | ||
expect(flagsInStorage['dev-test-flag'].version).toEqual(patchResponse.version); | ||
expect(emitter.emit).toHaveBeenCalledTimes(2); | ||
expect(emitter.emit).toHaveBeenNthCalledWith(2, 'change', context, ['dev-test-flag']); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, ['dev-test-flag']); | ||
}); | ||
|
||
test('patch should add new flags', async () => { | ||
|
@@ -484,8 +478,7 @@ describe('sdk-client storage', () => { | |
expect.stringContaining(JSON.stringify(patchResponse)), | ||
); | ||
expect(flagsInStorage).toHaveProperty('another-dev-test-flag'); | ||
expect(emitter.emit).toHaveBeenCalledTimes(2); | ||
expect(emitter.emit).toHaveBeenNthCalledWith(2, 'change', context, ['another-dev-test-flag']); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, ['another-dev-test-flag']); | ||
}); | ||
|
||
test('patch should ignore older version', async () => { | ||
|
@@ -557,8 +550,7 @@ describe('sdk-client storage', () => { | |
expect.stringContaining('dev-test-flag'), | ||
); | ||
expect(flagsInStorage['dev-test-flag']).toMatchObject({ ...deleteResponse, deleted: true }); | ||
expect(emitter.emit).toHaveBeenCalledTimes(2); | ||
expect(emitter.emit).toHaveBeenNthCalledWith(2, 'change', context, ['dev-test-flag']); | ||
expect(emitter.emit).toHaveBeenCalledWith('change', context, ['dev-test-flag']); | ||
}); | ||
|
||
test('delete should not delete equal version', async () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: These moved to datasource/errors.ts