-
Notifications
You must be signed in to change notification settings - Fork 36
Data loader [WIP] #467
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
Data loader [WIP] #467
Changes from 11 commits
e76ce72
b9bafb8
82f8f15
9c60fd6
1eebd47
d9b78ab
f8ad479
ef41f91
3074bed
f892c4c
3d74837
08d252e
71db86e
71f8662
edece89
2758f22
b600457
75e631a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| export const splitDefinitions = { | ||
| p1__split: { | ||
| 'name': 'p1__split', | ||
| 'status': 'ACTIVE', | ||
| 'conditions': [] | ||
| }, | ||
| p2__split: { | ||
| 'name': 'p2__split', | ||
| 'status': 'ACTIVE', | ||
| 'conditions': [] | ||
| }, | ||
| p3__split: { | ||
| 'name': 'p3__split', | ||
| 'status': 'ACTIVE', | ||
| 'conditions': [] | ||
| }, | ||
| }; | ||
|
|
||
| export const splitSerializedDefinitions = (function () { | ||
| return Object.keys(splitDefinitions).reduce((acum, splitName) => { | ||
| acum[splitName] = JSON.stringify(splitDefinitions[splitName]); | ||
| return acum; | ||
| }, {}); | ||
| }()); | ||
|
|
||
| export const segmentsDefinitions = { | ||
| segment_1: { | ||
| 'name': 'segment_1', | ||
| 'added': ['[email protected]'], | ||
| }, | ||
| }; | ||
|
|
||
| export const segmentsSerializedDefinitions = (function () { | ||
| return Object.keys(segmentsDefinitions).reduce((acum, segmentName) => { | ||
| acum[segmentName] = JSON.stringify(segmentsDefinitions[segmentName]); | ||
| return acum; | ||
| }, {}); | ||
| }()); | ||
|
|
||
| export const preloadedDataWithSegments = { | ||
| since: 1457552620999, | ||
| splitsData: splitSerializedDefinitions, | ||
| segmentsData: segmentsSerializedDefinitions | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /** | ||
| * Factory of data loaders | ||
| * | ||
| * @TODO update comment | ||
| * @param {Object} preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader | ||
| * and extended with a `mySegmentsData` property. | ||
| */ | ||
| export function dataLoaderFactory(preloadedData = {}) { | ||
|
|
||
| /** | ||
| * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function | ||
| * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js) | ||
| * | ||
| * @param {Object} storage storage for client-side | ||
| * @param {Object} userId main user key defined at the SDK config | ||
| */ | ||
| return function loadData(storage, userId) { | ||
| // Do not load data if current preloadedData is empty | ||
| if (Object.keys(preloadedData).length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| const { segmentsData = {}, since = 0, splitsData = {} } = preloadedData; | ||
|
||
|
|
||
| const currentSince = storage.splits.getChangeNumber(); | ||
|
||
|
|
||
| // Do not load data if current localStorage data is more recent | ||
| if (since <= currentSince) { | ||
|
||
| return; | ||
| } | ||
| // cleaning up the localStorage data, since some cached splits might need be part of the preloaded data | ||
| storage.splits.flush(); | ||
| storage.splits.setChangeNumber(since); | ||
|
|
||
| // splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data | ||
| Object.keys(splitsData).forEach(splitName => { | ||
| storage.splits.addSplit(splitName, splitsData[splitName]); | ||
| }); | ||
|
|
||
| // add mySegments data | ||
| let userIdMySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId]; | ||
|
||
| if (!userIdMySegmentsData) { | ||
| // segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds | ||
| userIdMySegmentsData = Object.keys(segmentsData).filter(segmentName => { | ||
| const added = JSON.parse(segmentsData[segmentName]).added; | ||
|
||
| return added.indexOf(userId) > -1; | ||
| }); | ||
| } | ||
| storage.segments.resetSegments(userIdMySegmentsData); | ||
| }; | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,12 +16,13 @@ export const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days | |
| const BrowserStorageFactory = context => { | ||
| const settings = context.get(context.constants.SETTINGS); | ||
| const { storage } = settings; | ||
| let result; | ||
|
||
|
|
||
| switch (storage.type) { | ||
| case STORAGE_MEMORY: { | ||
| const keys = new KeyBuilder(settings); | ||
|
|
||
| return { | ||
| result = { | ||
| splits: new SplitCacheInMemory, | ||
| segments: new SegmentCacheInMemory(keys), | ||
| impressions: new ImpressionsCacheInMemory, | ||
|
|
@@ -57,13 +58,14 @@ const BrowserStorageFactory = context => { | |
| this.events.clear(); | ||
| } | ||
| }; | ||
| break; | ||
| } | ||
|
|
||
| case STORAGE_LOCALSTORAGE: { | ||
| const keys = new KeyBuilderLocalStorage(settings); | ||
| const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS; | ||
|
|
||
| return { | ||
| result = { | ||
| splits: new SplitCacheInLocalStorage(keys, expirationTimestamp, settings.sync.__splitFiltersValidation), | ||
| segments: new SegmentCacheInLocalStorage(keys), | ||
| impressions: new ImpressionsCacheInMemory, | ||
|
|
@@ -99,12 +101,20 @@ const BrowserStorageFactory = context => { | |
| this.events.clear(); | ||
| } | ||
| }; | ||
| break; | ||
| } | ||
|
|
||
| default: | ||
| throw new Error('Unsupported storage type'); | ||
| } | ||
|
|
||
| // load precached data into storage | ||
| if (storage.dataLoader) { | ||
| const key = settings.core.key; | ||
| storage.dataLoader(result, key); | ||
| } | ||
|
|
||
| return result; | ||
| }; | ||
|
|
||
| export default BrowserStorageFactory; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { isObject, isString } from '../lang'; | ||
| import logFactory from '../logger'; | ||
| const log = logFactory('', { | ||
| displayAllErrors: true | ||
|
||
| }); | ||
|
|
||
| function validateSinceData(maybeSince, method) { | ||
| if (maybeSince > -1) return true; | ||
|
||
| log.error(`${method}: preloadedData.since must be a positive number.`); | ||
| return false; | ||
| } | ||
|
|
||
| function validateSplitsData(maybeSplitsData, method) { | ||
| if (isObject(maybeSplitsData)) { | ||
| const splitNames = Object.keys(maybeSplitsData); | ||
| if (splitNames.length > 0 && splitNames.every(splitName => isString(maybeSplitsData[splitName]))) return true; | ||
| } | ||
| log.error(`${method}: preloadedData.splitsData must be a map of split names to their serialized definitions.`); | ||
|
Contributor
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 we should make this a separate validation. For a list with items, if not all have the right format (keep isString for now or go and add the basic validation for Splits already, to check the key parts of the JSON) we'll log an error for invalid values.
Contributor
Author
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. Thanks. It is a good idea to use the existing split validation utils |
||
| return false; | ||
| } | ||
|
|
||
| function validateMySegmentsData(maybeMySegmentsData, method) { | ||
| if (isObject(maybeMySegmentsData)) { | ||
| const userKeys = Object.keys(maybeMySegmentsData); | ||
| if (userKeys.length > 0 && userKeys.every(userKey => { | ||
| const segmentNames = maybeMySegmentsData[userKey]; | ||
| // an empty list is valid | ||
| return Array.isArray(segmentNames) && segmentNames.every(segmentName => isString(segmentName)); | ||
| })) return true; | ||
| } | ||
| log.error(`${method}: preloadedData.mySegmentsData must be a map of user keys to their list of segment names.`); | ||
|
Contributor
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. If list is empty it shouldn't be an error. |
||
| return false; | ||
| } | ||
|
|
||
| function validateSegmentsData(maybeSegmentsData, method) { | ||
| if (isObject(maybeSegmentsData)) { | ||
| const segmentNames = Object.keys(maybeSegmentsData); | ||
| if (segmentNames.length > 0 && segmentNames.every(segmentName => isString(maybeSegmentsData[segmentName]))) return true; | ||
| } | ||
| log.error(`${method}: preloadedData.segmentsData must be a map of segment names to their serialized definitions.`); | ||
|
Contributor
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. Same, if list is empty it shouldn't be an error. |
||
| return false; | ||
| } | ||
|
|
||
| export function validatePreloadedData(maybePreloadedData, method) { | ||
| if (!isObject(maybePreloadedData)) { | ||
| log.error(`${method}: preloadedData must be an object.`); | ||
| } else if ( | ||
| validateSinceData(maybePreloadedData.since, method) && | ||
| validateSplitsData(maybePreloadedData.splitsData, method) && | ||
| (!maybePreloadedData.mySegmentsData || validateMySegmentsData(maybePreloadedData.mySegmentsData, method)) && | ||
| (!maybePreloadedData.segmentsData || validateSegmentsData(maybePreloadedData.segmentsData, method)) | ||
| ) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,7 +41,7 @@ const initialState = String( | |
| localStorage.getItem(LS_KEY) : '' | ||
| ); | ||
|
|
||
| const createLog = (namespace, options = {}) => new Logger(namespace, merge(options, defaultOptions)); | ||
| const createLog = (namespace, options = {}) => new Logger(namespace, merge(defaultOptions, options)); | ||
|
||
|
|
||
| const ownLog = createLog('splitio-utils:logger'); | ||
|
|
||
|
|
||
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.
Be consistent with the 1 line if's on inputvalidation and remove the brackets. Or add them there, but choose one.