From 969a9661f4933a3daaf81a9bc4ddc21ebae0896a Mon Sep 17 00:00:00 2001 From: Jason Salaber Date: Mon, 2 Dec 2024 14:39:50 -0500 Subject: [PATCH] fix: transform config to have proper Date objects --- dev-apps/nextjs/app-router/app/devcycle.tsx | 11 ++--- .../bucketing/__tests__/bucketing.test.ts | 14 +++++-- lib/shared/bucketing/src/bucketing.ts | 2 +- sdk/nextjs/src/common/transformConfig.ts | 40 +++++++++++++++++++ sdk/nextjs/src/pages/bucketing.ts | 3 +- sdk/nextjs/src/server/bucketing.ts | 6 ++- 6 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 sdk/nextjs/src/common/transformConfig.ts diff --git a/dev-apps/nextjs/app-router/app/devcycle.tsx b/dev-apps/nextjs/app-router/app/devcycle.tsx index 50466e1f6..ce62423d2 100644 --- a/dev-apps/nextjs/app-router/app/devcycle.tsx +++ b/dev-apps/nextjs/app-router/app/devcycle.tsx @@ -9,15 +9,16 @@ const getUserIdentity = async () => { } } -const { getVariableValue, getClientContext } = setupDevCycle( +const { getVariableValue, getClientContext } = setupDevCycle({ + serverSDKKey: process.env.NEXT_PUBLIC_DEVCYCLE_SERVER_SDK_KEY ?? '', // SDK Key. This will be public and sent to the client, so you MUST use the client SDK key. - process.env.NEXT_PUBLIC_DEVCYCLE_CLIENT_SDK_KEY ?? '', + clientSDKKey: process.env.NEXT_PUBLIC_DEVCYCLE_CLIENT_SDK_KEY ?? '', // pass your method for getting the user identity - getUserIdentity, + userGetter: getUserIdentity, // pass any options you want to use for the DevCycle SDK - { + options: { enableStreaming: process.env.NEXT_PUBLIC_ENABLE_STREAMING === '1', }, -) +}) export { getVariableValue, getClientContext, getUserIdentity } diff --git a/lib/shared/bucketing/__tests__/bucketing.test.ts b/lib/shared/bucketing/__tests__/bucketing.test.ts index 1b757d1fb..969356852 100644 --- a/lib/shared/bucketing/__tests__/bucketing.test.ts +++ b/lib/shared/bucketing/__tests__/bucketing.test.ts @@ -1419,7 +1419,9 @@ describe('Rollout Logic', () => { it('should handle stepped rollout with 0% start and 100% later stage', () => { const rollout: PublicRollout = { type: 'stepped', - startDate: new Date(), + startDate: new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 7, + ), startPercentage: 0, stages: [ { @@ -1453,10 +1455,12 @@ describe('Rollout Logic', () => { jest.useRealTimers() }) - it('should handle stepped rollout with 50% start and 100% later stage', () => { + it.only('should handle stepped rollout with 50% start and 100% later stage', () => { const rollout: PublicRollout = { type: 'stepped', - startDate: new Date(), + startDate: new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 7, + ), startPercentage: 0.5, stages: [ { @@ -1494,7 +1498,9 @@ describe('Rollout Logic', () => { it('should handle stepped rollout with 100% start and 0% later stage', () => { const rollout: PublicRollout = { type: 'stepped', - startDate: new Date(), + startDate: new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 7, + ), startPercentage: 1, stages: [ { diff --git a/lib/shared/bucketing/src/bucketing.ts b/lib/shared/bucketing/src/bucketing.ts index 03eda02ab..abf6dce06 100644 --- a/lib/shared/bucketing/src/bucketing.ts +++ b/lib/shared/bucketing/src/bucketing.ts @@ -105,7 +105,7 @@ export const getCurrentRolloutPercentage = ( : null) if (!currentStage) { - return start + return 0 } if (!nextStage || nextStage.type === 'discrete') { diff --git a/sdk/nextjs/src/common/transformConfig.ts b/sdk/nextjs/src/common/transformConfig.ts new file mode 100644 index 000000000..37c7e6afe --- /dev/null +++ b/sdk/nextjs/src/common/transformConfig.ts @@ -0,0 +1,40 @@ +import { ConfigBody } from '@devcycle/types' + +/** + * Transform the config to ensure all dates are valid Date objects + * @param configData + * @returns + */ +export const transformConfig = (configData: ConfigBody): ConfigBody => { + if (!configData.features || !configData.features.length) return configData + + configData.features = configData.features.map((feature) => { + if (!feature || !feature.configuration.targets.length) return feature + + feature.configuration.targets = feature.configuration.targets.map( + (target) => { + const { rollout } = target + if (!rollout) return target + + rollout.startDate = transformDate(rollout.startDate) + if (!rollout.stages || !rollout.stages.length) return target + + rollout.stages = rollout.stages.map((stage) => { + stage.date = transformDate(stage.date) + return stage + }) + return target + }, + ) + return feature + }) + return configData +} + +const transformDate = (date: unknown): Date => { + if (!date) throw new Error('Missing rollout date in config') + if (date instanceof Date) return date + if (typeof date === 'string' || typeof date === 'number') + return new Date(date) + throw new Error('Missing rollout date in config') +} diff --git a/sdk/nextjs/src/pages/bucketing.ts b/sdk/nextjs/src/pages/bucketing.ts index 778d6a31e..d7f78fe12 100644 --- a/sdk/nextjs/src/pages/bucketing.ts +++ b/sdk/nextjs/src/pages/bucketing.ts @@ -2,6 +2,7 @@ import { DevCycleUser, DVCPopulatedUser } from '@devcycle/js-client-sdk' import { generateBucketedConfig } from '@devcycle/bucketing' import { BucketedUserConfig, ConfigBody, ConfigSource } from '@devcycle/types' import { fetchCDNConfig, sdkConfigAPI } from './requests.js' +import { transformConfig } from '../common/transformConfig.js' class CDNConfigSource extends ConfigSource { async getConfig( @@ -52,7 +53,7 @@ const bucketOrFetchConfig = async ( return generateBucketedConfig({ user, - config, + config: transformConfig(config), }) } diff --git a/sdk/nextjs/src/server/bucketing.ts b/sdk/nextjs/src/server/bucketing.ts index 57453426d..c2ac660f0 100644 --- a/sdk/nextjs/src/server/bucketing.ts +++ b/sdk/nextjs/src/server/bucketing.ts @@ -7,6 +7,7 @@ import { DevCycleNextOptions, } from '../common/types' import { ConfigBody, ConfigSource } from '@devcycle/types' +import { transformConfig } from '../common/transformConfig' const getPopulatedUser = cache((user: DevCycleUser, userAgent?: string) => { return new DVCPopulatedUser( @@ -48,7 +49,10 @@ const generateBucketedConfigCached = cache( return { bucketedConfig: { - ...generateBucketedConfig({ user: populatedUser, config }), + ...generateBucketedConfig({ + user: populatedUser, + config: transformConfig(config), + }), clientSDKKey, sse: { url: config.sse