Skip to content

Commit ba42964

Browse files
Generate events by FQL condition
1 parent e4957a5 commit ba42964

File tree

2 files changed

+408
-16
lines changed

2 files changed

+408
-16
lines changed

packages/cli/src/commands/generate-test-payload.ts

Lines changed: 138 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ import {
1212
isDirective,
1313
DestinationDefinition as CloudModeDestinationDefinition,
1414
InputField,
15-
AudienceDestinationDefinition
15+
AudienceDestinationDefinition,
16+
BaseActionDefinition
1617
} from '@segment/actions-core'
1718
import Chance from 'chance'
1819
import { getRawKeys } from '@segment/actions-core/mapping-kit/value-keys'
19-
import { set } from 'lodash'
20+
import { get, set } from 'lodash'
21+
import { ErrorCondition, GroupCondition, parseFql } from '@segment/destination-subscriptions'
22+
import { reconstructSegmentEvent } from '../lib/event-generator'
23+
2024
export default class GenerateTestPayload extends Command {
2125
private spinner: ora.Ora = ora()
2226
private chance: Chance.Chance = new Chance('Payload')
@@ -154,6 +158,7 @@ export default class GenerateTestPayload extends Command {
154158

155159
try {
156160
let settings: unknown
161+
let auth: unknown
157162
if ((destination as BrowserDestinationDefinition).mode == 'device') {
158163
// Generate sample settings based on destination settings schema
159164
const destinationSettings = (destination as BrowserDestinationDefinition).settings
@@ -162,7 +167,7 @@ export default class GenerateTestPayload extends Command {
162167
const destinationSettings = (destination as CloudModeDestinationDefinition).authentication?.fields
163168
settings = this.generateSampleFromSchema(destinationSettings || {})
164169
if ((destination as CloudModeDestinationDefinition).authentication?.scheme === 'oauth2') {
165-
settings = {
170+
auth = {
166171
oauth: {
167172
accessToken: 'YOUR_ACCESS_TOKEN',
168173
refreshToken: 'YOUR_REFRESH_TOKEN'
@@ -189,19 +194,32 @@ export default class GenerateTestPayload extends Command {
189194
}
190195
}
191196

192-
// Generate sample payload based on the fields
193-
const payload = this.generateSamplePayloadFromMapping(mapping)
197+
const defaultSubscription = (action as BaseActionDefinition).defaultSubscription
198+
199+
// Generate sample payload based on the fields.
200+
const payload = this.generateSamplePayloadFromMapping(mapping, fields, defaultSubscription)
194201

195202
// if audience settings exist, add them to the payload
196-
if (audienceSettings) {
203+
if (Object.keys(audienceSettings).length > 0) {
197204
set(payload, 'context.personas.audience_settings', this.generateSampleFromSchema(audienceSettings || {}))
198205
}
199206

200207
// Generate final sample request
201208
const sampleRequest = {
202209
settings,
203210
mapping,
204-
payload
211+
payload,
212+
auth,
213+
features: {
214+
feature1: true,
215+
feature2: false
216+
},
217+
subscriptionMetadata: {
218+
actionConfigId: 'YOUR_ACTION_CONFIG_ID',
219+
destinationConfigId: 'YOUR_DESTINATION_CONFIG_ID',
220+
actionId: 'YOUR_ACTION_ID',
221+
sourceId: 'YOUR_SOURCE_ID'
222+
}
205223
}
206224

207225
this.spinner.succeed(`Generated test payload for action: ${actionSlug}`)
@@ -249,15 +267,18 @@ export default class GenerateTestPayload extends Command {
249267
}
250268
}
251269

252-
generateSamplePayloadFromMapping(mapping: Record<string, any>): Record<string, any> {
270+
generateSamplePayloadFromMapping(
271+
mapping: Record<string, any>,
272+
fields: Record<string, InputField>,
273+
defaultSubscription?: string
274+
): Record<string, any> {
253275
const chance = new Chance('payload')
276+
254277
const payload: Record<string, any> = {
255278
userId: chance.guid(),
256279
anonymousId: chance.guid(),
257280
event: 'Example Event',
258-
type: 'track',
259281
timestamp: new Date().toISOString(),
260-
properties: {},
261282
context: {
262283
ip: chance.ip(),
263284
userAgent:
@@ -280,19 +301,120 @@ export default class GenerateTestPayload extends Command {
280301
}
281302

282303
// Add properties based on mapping with better values
283-
for (const [_, value] of Object.entries(mapping)) {
304+
for (const [key, value] of Object.entries(mapping)) {
284305
if (isDirective(value)) {
285-
const [key] = getRawKeys(value)
286-
const path = key.replace('$.', '')
287-
set(payload, path, this.generateValueByFieldName(path))
306+
const [pathKey] = getRawKeys(value)
307+
const path = pathKey.replace('$.', '')
308+
const fieldDefinition = fields[key]
309+
const existingValue = get(payload, path)
310+
const newValue = this.setTestData(fieldDefinition, key)
311+
if (typeof existingValue === 'object' && existingValue !== null && !Array.isArray(existingValue)) {
312+
set(payload, path, { ...existingValue, ...newValue })
313+
} else {
314+
set(payload, path, newValue)
315+
}
316+
}
317+
}
318+
319+
if (defaultSubscription) {
320+
const parsed = parseFql(defaultSubscription)
321+
if ((parsed as ErrorCondition).error) {
322+
this.log(chalk.red(`Failed to parse FQL: ${(parsed as ErrorCondition).error}`))
323+
} else {
324+
const groupCondition = parsed as GroupCondition
325+
return reconstructSegmentEvent(groupCondition.children, payload)
288326
}
289327
}
290328

291329
return payload
292330
}
293331

294-
generateValueByFieldName(fieldName: string): any {
295-
const lowerFieldName = fieldName.toLowerCase()
332+
setTestData(fieldDefinition: Omit<InputField, 'Description'>, fieldName: string) {
333+
const chance = this.chance
334+
const { type, format, choices, multiple } = fieldDefinition
335+
336+
if (Array.isArray(choices)) {
337+
if (typeof choices[0] === 'object' && 'value' in choices[0]) {
338+
return choices[0].value
339+
}
340+
341+
return choices[0]
342+
}
343+
let val: any
344+
switch (type) {
345+
case 'boolean':
346+
val = chance.bool()
347+
break
348+
case 'datetime':
349+
val = '2021-02-01T00:00:00.000Z'
350+
break
351+
case 'integer':
352+
val = chance.integer()
353+
break
354+
case 'number':
355+
val = chance.floating({ fixed: 2 })
356+
break
357+
case 'text':
358+
val = chance.sentence()
359+
break
360+
case 'object':
361+
if (fieldDefinition.properties) {
362+
val = {}
363+
for (const [key, prop] of Object.entries(fieldDefinition.properties)) {
364+
val[key] = this.setTestData(prop as Omit<InputField, 'Description'>, key)
365+
}
366+
}
367+
break
368+
default:
369+
// covers string
370+
switch (format) {
371+
case 'date': {
372+
const d = chance.date()
373+
val = [d.getFullYear(), d.getMonth() + 1, d.getDate()].map((v) => String(v).padStart(2, '0')).join('-')
374+
break
375+
}
376+
case 'date-time':
377+
val = chance.date().toISOString()
378+
break
379+
case 'email':
380+
val = chance.email()
381+
break
382+
case 'hostname':
383+
val = chance.domain()
384+
break
385+
case 'ipv4':
386+
val = chance.ip()
387+
break
388+
case 'ipv6':
389+
val = chance.ipv6()
390+
break
391+
case 'time': {
392+
const d = chance.date()
393+
val = [d.getHours(), d.getMinutes(), d.getSeconds()].map((v) => String(v).padStart(2, '0')).join(':')
394+
break
395+
}
396+
case 'uri':
397+
val = chance.url()
398+
break
399+
case 'uuid':
400+
val = chance.guid()
401+
break
402+
default:
403+
val = this.generateValueByFieldName(fieldName)
404+
break
405+
}
406+
break
407+
}
408+
409+
if (multiple) {
410+
val = [val]
411+
}
412+
413+
return val
414+
}
415+
416+
generateValueByFieldName(fieldKey: string): any {
417+
const lowerFieldName = fieldKey.toLowerCase()
296418

297419
// Check for common field name patterns
298420
if (lowerFieldName.includes('email')) {

0 commit comments

Comments
 (0)