Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6370760

Browse filesBrowse files
committed
feat: pricing model diffing 2 - diff features & usage meters
1 parent 7e46c6f commit 6370760
Copy full SHA for 6370760

File tree

Expand file treeCollapse file tree

2 files changed

+313
-1
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+313
-1
lines changed
Open diff view settings
Collapse file

‎platform/flowglad-next/src/utils/pricingModels/diffing.test.ts‎

Copy file name to clipboardExpand all lines: platform/flowglad-next/src/utils/pricingModels/diffing.test.ts
+247-1Lines changed: 247 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { describe, expect, it } from 'vitest'
2-
import { diffSluggedResources, type SluggedResource } from './diffing'
2+
import { FeatureType, UsageMeterAggregationType } from '@/types'
3+
import {
4+
diffFeatures,
5+
diffSluggedResources,
6+
diffUsageMeters,
7+
type FeatureDiffInput,
8+
type SluggedResource,
9+
type UsageMeterDiffInput,
10+
} from './diffing'
311

412
type SlugAndName = SluggedResource<{ name: string }>
513

@@ -181,3 +189,241 @@ describe('diffSluggedResources', () => {
181189
expect(result.toUpdate[0].existing.slug).toBe('minimal')
182190
})
183191
})
192+
193+
describe('diffFeatures', () => {
194+
it('should use diffSluggedResources to compute diff', () => {
195+
// Setup: existing has feature, proposed is empty
196+
const existing: FeatureDiffInput[] = [
197+
{
198+
slug: 'foo',
199+
name: 'Foo Feature',
200+
description: 'A test feature',
201+
type: FeatureType.Toggle,
202+
active: true,
203+
},
204+
]
205+
const proposed: FeatureDiffInput[] = []
206+
207+
const result = diffFeatures(existing, proposed)
208+
209+
// Expectation: toRemove contains the feature
210+
expect(result.toRemove).toHaveLength(1)
211+
expect(result.toRemove[0].slug).toBe('foo')
212+
expect(result.toCreate).toEqual([])
213+
expect(result.toUpdate).toEqual([])
214+
})
215+
216+
it('should handle feature updates', () => {
217+
// Setup: existing and proposed both have same slug but different name
218+
const existing: FeatureDiffInput[] = [
219+
{
220+
slug: 'foo',
221+
name: 'Old Name',
222+
description: 'A test feature',
223+
type: FeatureType.Toggle,
224+
active: true,
225+
},
226+
]
227+
const proposed: FeatureDiffInput[] = [
228+
{
229+
slug: 'foo',
230+
name: 'New Name',
231+
description: 'A test feature',
232+
type: FeatureType.Toggle,
233+
active: true,
234+
},
235+
]
236+
237+
const result = diffFeatures(existing, proposed)
238+
239+
// Expectation: toUpdate contains the feature with name change
240+
expect(result.toRemove).toEqual([])
241+
expect(result.toCreate).toEqual([])
242+
expect(result.toUpdate).toHaveLength(1)
243+
expect(result.toUpdate[0].existing.name).toBe('Old Name')
244+
expect(result.toUpdate[0].proposed.name).toBe('New Name')
245+
})
246+
247+
it('should handle feature creation', () => {
248+
// Setup: proposed has new feature not in existing
249+
const existing: FeatureDiffInput[] = []
250+
const proposed: FeatureDiffInput[] = [
251+
{
252+
slug: 'new-feature',
253+
name: 'New Feature',
254+
description: 'A new feature',
255+
type: FeatureType.Toggle,
256+
active: true,
257+
},
258+
]
259+
260+
const result = diffFeatures(existing, proposed)
261+
262+
// Expectation: toCreate contains the new feature
263+
expect(result.toRemove).toEqual([])
264+
expect(result.toCreate).toHaveLength(1)
265+
expect(result.toCreate[0].slug).toBe('new-feature')
266+
expect(result.toUpdate).toEqual([])
267+
})
268+
269+
it('should handle mixed changes for features', () => {
270+
// Setup: remove one, update one, create one
271+
const existing: FeatureDiffInput[] = [
272+
{
273+
slug: 'remove-me',
274+
name: 'Remove',
275+
description: 'Will be removed',
276+
type: FeatureType.Toggle,
277+
active: true,
278+
},
279+
{
280+
slug: 'update-me',
281+
name: 'Old',
282+
description: 'Will be updated',
283+
type: FeatureType.Toggle,
284+
active: true,
285+
},
286+
]
287+
const proposed: FeatureDiffInput[] = [
288+
{
289+
slug: 'update-me',
290+
name: 'New',
291+
description: 'Will be updated',
292+
type: FeatureType.Toggle,
293+
active: false,
294+
},
295+
{
296+
slug: 'create-me',
297+
name: 'Create',
298+
description: 'Will be created',
299+
type: FeatureType.Toggle,
300+
active: true,
301+
},
302+
]
303+
304+
const result = diffFeatures(existing, proposed)
305+
306+
// Expectations
307+
expect(result.toRemove).toHaveLength(1)
308+
expect(result.toRemove[0].slug).toBe('remove-me')
309+
expect(result.toCreate).toHaveLength(1)
310+
expect(result.toCreate[0].slug).toBe('create-me')
311+
expect(result.toUpdate).toHaveLength(1)
312+
expect(result.toUpdate[0].existing.name).toBe('Old')
313+
expect(result.toUpdate[0].proposed.name).toBe('New')
314+
})
315+
316+
// TODO: after validation is implemented, add tests for permitted feature update fields
317+
})
318+
319+
describe('diffUsageMeters', () => {
320+
it('should use diffSluggedResources to compute diff', () => {
321+
// Setup: existing has usage meter, proposed is empty
322+
const existing: UsageMeterDiffInput[] = [
323+
{
324+
slug: 'foo',
325+
name: 'Foo Meter',
326+
aggregationType: UsageMeterAggregationType.Sum,
327+
},
328+
]
329+
const proposed: UsageMeterDiffInput[] = []
330+
331+
const result = diffUsageMeters(existing, proposed)
332+
333+
// Expectation: toRemove contains the usage meter
334+
expect(result.toRemove).toHaveLength(1)
335+
expect(result.toRemove[0].slug).toBe('foo')
336+
expect(result.toCreate).toEqual([])
337+
expect(result.toUpdate).toEqual([])
338+
})
339+
340+
it('should handle usage meter updates', () => {
341+
// Setup: existing and proposed both have same slug but different name
342+
const existing: UsageMeterDiffInput[] = [
343+
{
344+
slug: 'foo',
345+
name: 'Old Name',
346+
aggregationType: UsageMeterAggregationType.Sum,
347+
},
348+
]
349+
const proposed: UsageMeterDiffInput[] = [
350+
{
351+
slug: 'foo',
352+
name: 'New Name',
353+
aggregationType: UsageMeterAggregationType.Sum,
354+
},
355+
]
356+
357+
const result = diffUsageMeters(existing, proposed)
358+
359+
// Expectation: toUpdate contains the usage meter with name change
360+
expect(result.toRemove).toEqual([])
361+
expect(result.toCreate).toEqual([])
362+
expect(result.toUpdate).toHaveLength(1)
363+
expect(result.toUpdate[0].existing.name).toBe('Old Name')
364+
expect(result.toUpdate[0].proposed.name).toBe('New Name')
365+
})
366+
367+
it('should handle usage meter creation', () => {
368+
// Setup: proposed has new usage meter not in existing
369+
const existing: UsageMeterDiffInput[] = []
370+
const proposed: UsageMeterDiffInput[] = [
371+
{
372+
slug: 'new-meter',
373+
name: 'New Meter',
374+
aggregationType:
375+
UsageMeterAggregationType.CountDistinctProperties,
376+
},
377+
]
378+
379+
const result = diffUsageMeters(existing, proposed)
380+
381+
// Expectation: toCreate contains the new usage meter
382+
expect(result.toRemove).toEqual([])
383+
expect(result.toCreate).toHaveLength(1)
384+
expect(result.toCreate[0].slug).toBe('new-meter')
385+
expect(result.toUpdate).toEqual([])
386+
})
387+
388+
it('should handle mixed changes for usage meters', () => {
389+
// Setup: remove one, update one, create one
390+
const existing: UsageMeterDiffInput[] = [
391+
{
392+
slug: 'remove-me',
393+
name: 'Remove',
394+
aggregationType: UsageMeterAggregationType.Sum,
395+
},
396+
{
397+
slug: 'update-me',
398+
name: 'Old',
399+
aggregationType: UsageMeterAggregationType.Sum,
400+
},
401+
]
402+
const proposed: UsageMeterDiffInput[] = [
403+
{
404+
slug: 'update-me',
405+
name: 'New',
406+
aggregationType:
407+
UsageMeterAggregationType.CountDistinctProperties,
408+
},
409+
{
410+
slug: 'create-me',
411+
name: 'Create',
412+
aggregationType: UsageMeterAggregationType.Sum,
413+
},
414+
]
415+
416+
const result = diffUsageMeters(existing, proposed)
417+
418+
// Expectations
419+
expect(result.toRemove).toHaveLength(1)
420+
expect(result.toRemove[0].slug).toBe('remove-me')
421+
expect(result.toCreate).toHaveLength(1)
422+
expect(result.toCreate[0].slug).toBe('create-me')
423+
expect(result.toUpdate).toHaveLength(1)
424+
expect(result.toUpdate[0].existing.name).toBe('Old')
425+
expect(result.toUpdate[0].proposed.name).toBe('New')
426+
})
427+
428+
// TODO: after validation is implemented, add tests for permitted usage meter update fields
429+
})
Collapse file

‎platform/flowglad-next/src/utils/pricingModels/diffing.ts‎

Copy file name to clipboardExpand all lines: platform/flowglad-next/src/utils/pricingModels/diffing.ts
+66Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,25 @@
55
* (features, products, usage meters) to identify what needs to be created, updated, or removed.
66
*/
77

8+
import type { SetupPricingModelInput } from './setupSchemas'
9+
810
/**
911
* A resource with a slug identifier.
1012
*/
1113
export type SluggedResource<T> = T & { slug: string }
1214

15+
/**
16+
* Input type for feature diffing - extracted from SetupPricingModelInput.
17+
*/
18+
export type FeatureDiffInput =
19+
SetupPricingModelInput['features'][number]
20+
21+
/**
22+
* Input type for usage meter diffing - extracted from SetupPricingModelInput.
23+
*/
24+
export type UsageMeterDiffInput =
25+
SetupPricingModelInput['usageMeters'][number]
26+
1327
/**
1428
* Result of diffing two arrays of slugged resources.
1529
*
@@ -108,3 +122,55 @@ export const diffSluggedResources = <T extends { slug: string }>(
108122
toUpdate,
109123
}
110124
}
125+
126+
/**
127+
* Diffs feature arrays to identify which features need to be removed, created, or updated.
128+
*
129+
* Features are compared by their slug field. The function uses the generic
130+
* `diffSluggedResources` utility to perform the comparison.
131+
*
132+
* @param existing - Array of existing features
133+
* @param proposed - Array of proposed features
134+
* @returns A DiffResult containing features to remove, create, and update
135+
*
136+
* @example
137+
* ```typescript
138+
* const existing = [{ slug: 'feature-a', name: 'Feature A', type: 'toggle', active: true }]
139+
* const proposed = [{ slug: 'feature-a', name: 'Feature A Updated', type: 'toggle', active: true }]
140+
* const diff = diffFeatures(existing, proposed)
141+
* // diff.toUpdate will contain the feature with name change
142+
* ```
143+
*/
144+
export const diffFeatures = (
145+
existing: FeatureDiffInput[],
146+
proposed: FeatureDiffInput[]
147+
): DiffResult<FeatureDiffInput> => {
148+
return diffSluggedResources(existing, proposed)
149+
}
150+
151+
/**
152+
* Diffs usage meter arrays to identify which usage meters need to be removed, created, or updated.
153+
*
154+
* Usage meters are compared by their slug field. The function uses the generic
155+
* `diffSluggedResources` utility to perform the comparison.
156+
*
157+
* Note: Usage meter removal is not allowed and will cause validation errors in later stages.
158+
*
159+
* @param existing - Array of existing usage meters
160+
* @param proposed - Array of proposed usage meters
161+
* @returns A DiffResult containing usage meters to remove, create, and update
162+
*
163+
* @example
164+
* ```typescript
165+
* const existing = [{ slug: 'api-calls', name: 'API Calls', aggregationType: 'sum' }]
166+
* const proposed = [{ slug: 'api-calls', name: 'API Requests', aggregationType: 'sum' }]
167+
* const diff = diffUsageMeters(existing, proposed)
168+
* // diff.toUpdate will contain the usage meter with name change
169+
* ```
170+
*/
171+
export const diffUsageMeters = (
172+
existing: UsageMeterDiffInput[],
173+
proposed: UsageMeterDiffInput[]
174+
): DiffResult<UsageMeterDiffInput> => {
175+
return diffSluggedResources(existing, proposed)
176+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.