-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* move poller outside * added cohort fetches * remove unused imports * always update cohort and fix test * added cohort server url * remove streamTest file * added fetch key to base64 and memberId convert to set * fixed type * added cohort to eval context * fixed bugs, moved configs, surface cohort errors to flag pollers * storage update only after all cohort loads * added tests * added tests * add ci test with secrets from env * fix cohortPoller.test.ts * not use environment * added .env instr, added tests * cleanup unnecessary check * lint * fix test node version matrix * polish test and add macos-13 * updated gh action node versions to current lts's * remove unsupported node v24 * added serverZone config option * parameterize test * moved config util code under util * added eu test * increase cohort fetch timeout * update to flag poller loads new cohort, cohort updater polls updates * fix client start and stop, cleanup * fixed tests * fixed typo, env, and err msg * fix gh action * added streamer test, added streamer onInitUpdate, clearer logic * add test, add return types, move a util func * fix null cohortUpdater when no cohort configs * fix poller interval and comments * fix relative imports * add cohortRequestDelayMillis, use sleep util, skip retry if maxCohortSize error * unused imports * fix relative imports attempt 2 * Log error on eval, dont init fail on if cohort fail, add tests * fix lint * add no config integration test * change default maxCohortSize * changed configs * fix lint * add test, fix comment
- Loading branch information
Showing
40 changed files
with
4,135 additions
and
696 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
# Ignore generated files | ||
gen | ||
.env* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
To setup for running test on local, create a `.env` file with following | ||
contents, and replace `{API_KEY}` and `{SECRET_KEY}` for the project in test: | ||
|
||
``` | ||
API_KEY={API_KEY} | ||
SECRET_KEY={SECRET_KEY} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { HttpClient } from '@amplitude/experiment-core'; | ||
|
||
import { Cohort } from '../../types/cohort'; | ||
|
||
export type GetCohortOptions = { | ||
libraryName: string; | ||
libraryVersion: string; | ||
cohortId: string; | ||
maxCohortSize: number; | ||
lastModified?: number; | ||
timeoutMillis?: number; | ||
}; | ||
|
||
export interface CohortApi { | ||
/** | ||
* Calls /sdk/v1/cohort/<cohortId> with query params maxCohortSize and lastModified if specified. | ||
* Returns a promise that | ||
* resolves to a | ||
* Cohort if the cohort downloads successfully or | ||
* undefined if cohort has no change since lastModified timestamp and | ||
* throws an error if download failed. | ||
* @param options | ||
*/ | ||
getCohort(options?: GetCohortOptions): Promise<Cohort>; | ||
} | ||
|
||
export class CohortClientRequestError extends Error {} // 4xx errors except 429 | ||
export class CohortMaxSizeExceededError extends CohortClientRequestError {} // 413 error | ||
export class CohortDownloadError extends Error {} // All other errors | ||
|
||
export class SdkCohortApi implements CohortApi { | ||
private readonly cohortApiKey; | ||
private readonly serverUrl; | ||
private readonly httpClient; | ||
|
||
constructor(cohortApiKey: string, serverUrl: string, httpClient: HttpClient) { | ||
this.cohortApiKey = cohortApiKey; | ||
this.serverUrl = serverUrl; | ||
this.httpClient = httpClient; | ||
} | ||
|
||
public async getCohort( | ||
options?: GetCohortOptions, | ||
): Promise<Cohort | undefined> { | ||
const headers: Record<string, string> = { | ||
Authorization: `Basic ${this.cohortApiKey}`, | ||
}; | ||
if (options?.libraryName && options?.libraryVersion) { | ||
headers[ | ||
'X-Amp-Exp-Library' | ||
] = `${options.libraryName}/${options.libraryVersion}`; | ||
} | ||
|
||
const reqUrl = `${this.serverUrl}/sdk/v1/cohort/${ | ||
options.cohortId | ||
}?maxCohortSize=${options.maxCohortSize}${ | ||
options.lastModified ? `&lastModified=${options.lastModified}` : '' | ||
}`; | ||
const response = await this.httpClient.request({ | ||
requestUrl: reqUrl, | ||
method: 'GET', | ||
headers: headers, | ||
timeoutMillis: options?.timeoutMillis, | ||
}); | ||
|
||
// Check status code. | ||
// 200: download success. | ||
// 204: no change. | ||
// 413: cohort larger than maxCohortSize | ||
if (response.status == 200) { | ||
const cohort: Cohort = JSON.parse(response.body) as Cohort; | ||
if (Array.isArray(cohort.memberIds)) { | ||
cohort.memberIds = new Set<string>(cohort.memberIds); | ||
} | ||
return cohort; | ||
} else if (response.status == 204) { | ||
return undefined; | ||
} else if (response.status == 413) { | ||
throw new CohortMaxSizeExceededError( | ||
`Cohort size > ${options.maxCohortSize}`, | ||
); | ||
} else if ( | ||
400 <= response.status && | ||
response.status < 500 && | ||
response.status != 429 | ||
) { | ||
// Any 4xx other than 429. | ||
throw new CohortClientRequestError( | ||
`Cohort client error response status ${response.status}, body ${response.body}`, | ||
); | ||
} else { | ||
throw new CohortDownloadError( | ||
`Cohort error response status ${response.status}, body ${response.body}`, | ||
); | ||
} | ||
} | ||
} |
Oops, something went wrong.