feat(cdp): followup native executor (#34769)

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Marcus Hof
2025-07-30 14:15:35 +02:00
committed by GitHub
parent 486d6818b5
commit ddeafc5efc
5 changed files with 427 additions and 128 deletions

View File

@@ -123,6 +123,7 @@
"@types/adm-zip": "^0.4.34",
"@types/babel__core": "^7.1.19",
"@types/babel__standalone": "^7.1.4",
"@types/chance": "^1.1.7",
"@types/faker": "^5.5.7",
"@types/generic-pool": "^3.1.9",
"@types/ioredis": "^4.26.4",
@@ -159,6 +160,7 @@
"supertest": "^7.0.0",
"ts-node": "^10.9.1",
"tsc-alias": "^1.8.16",
"chance": "^1.1.13",
"tsconfig-paths": "^4.2.0"
},
"cyclotron": {

View File

@@ -1,28 +1,111 @@
import { template } from './webhook.template'
import { SAMPLE_GLOBALS } from '~/cdp/_tests/fixtures'
import { NATIVE_HOG_FUNCTIONS_BY_ID } from '../../index'
import { DestinationTester, generateTestData } from '../../test/test-helpers'
const template = NATIVE_HOG_FUNCTIONS_BY_ID['native-webhook']
describe(`${template.name} template`, () => {
const tester = new DestinationTester(template!)
beforeEach(() => {
tester.beforeEach()
})
afterEach(() => {
tester.afterEach()
})
describe('native webhook template', () => {
it('should work with default mapping', async () => {
const mockRequest = jest.fn().mockResolvedValue({
status: 200,
json: () => Promise.resolve({ message: 'Success' }),
text: () => Promise.resolve(JSON.stringify({ message: 'Success' })),
headers: {},
})
const payload = {
url: 'https://example.com/webhook',
method: 'POST',
body: { event: '{event}', person: '{person}' },
headers: { 'Content-Type': 'application/json' },
debug_mode: true,
}
const response = await tester.invoke(SAMPLE_GLOBALS, payload)
await template.perform(mockRequest, { payload })
expect(response.logs).toMatchInlineSnapshot(`
[
{
"level": "debug",
"message": "config, {"method":"POST","body":{"event":{"uuid":"uuid","event":"test","distinct_id":"distinct_id","properties":{"email":"test@posthog.com"},"timestamp":"","elements_chain":"","url":""},"person":{"id":"person-id","name":"person-name","properties":{"email":"example@posthog.com"},"url":"https://us.posthog.com/projects/1/persons/1234"}},"headers":{"Content-Type":"application/json"},"debug":false,"debug_mode":true,"url":"https://example.com/webhook"}",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "debug",
"message": "endpoint, https://example.com/webhook",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "debug",
"message": "options, {"method":"POST","headers":{"Content-Type":"application/json"},"json":{"event":{"uuid":"uuid","event":"test","distinct_id":"distinct_id","properties":{"email":"test@posthog.com"},"timestamp":"","elements_chain":"","url":""},"person":{"id":"person-id","name":"person-name","properties":{"email":"example@posthog.com"},"url":"https://us.posthog.com/projects/1/persons/1234"}}}",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "debug",
"message": "fetchOptions, {"method":"POST","headers":{"User-Agent":"PostHog.com/1.0","Content-Type":"application/json"},"body":"{\\"event\\":{\\"uuid\\":\\"uuid\\",\\"event\\":\\"test\\",\\"distinct_id\\":\\"distinct_id\\",\\"properties\\":{\\"email\\":\\"test@posthog.com\\"},\\"timestamp\\":\\"\\",\\"elements_chain\\":\\"\\",\\"url\\":\\"\\"},\\"person\\":{\\"id\\":\\"person-id\\",\\"name\\":\\"person-name\\",\\"properties\\":{\\"email\\":\\"example@posthog.com\\"},\\"url\\":\\"https://us.posthog.com/projects/1/persons/1234\\"}}"}",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "debug",
"message": "convertedResponse, 200, {"status":"OK"}, {"status":"OK"}, {"content-type":"application/json"}",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "info",
"message": "Function completed in [REPLACED]",
"timestamp": "2025-01-01T00:00:00.000Z",
},
]
`)
})
expect(mockRequest).toHaveBeenCalledTimes(1)
expect(mockRequest).toHaveBeenCalledWith('https://example.com/webhook', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
json: { event: '{event}', person: '{person}' },
it('should handle a failing request', async () => {
const inputs = generateTestData(template.id, template.inputs_schema)
tester.mockFetchResponse({
status: 400,
body: { status: 'ERROR' },
headers: { 'content-type': 'application/json' },
})
const response = await tester.invoke(SAMPLE_GLOBALS, { ...inputs, debug_mode: true })
expect(response.logs).toMatchInlineSnapshot(`
[
{
"level": "debug",
"message": "config, {"method":"POST","body":{"event":{"uuid":"uuid","event":"test","distinct_id":"distinct_id","properties":{"email":"test@posthog.com"},"timestamp":"","elements_chain":"","url":""},"person":{"id":"person-id","name":"person-name","properties":{"email":"example@posthog.com"},"url":"https://us.posthog.com/projects/1/persons/1234"}},"headers":{"Content-Type":"application/json"},"debug":false,"debug_mode":true,"url":"http://jaj.mu/iroti"}",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "debug",
"message": "endpoint, http://jaj.mu/iroti",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "debug",
"message": "options, {"method":"POST","headers":{"Content-Type":"application/json"},"json":{"event":{"uuid":"uuid","event":"test","distinct_id":"distinct_id","properties":{"email":"test@posthog.com"},"timestamp":"","elements_chain":"","url":""},"person":{"id":"person-id","name":"person-name","properties":{"email":"example@posthog.com"},"url":"https://us.posthog.com/projects/1/persons/1234"}}}",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "debug",
"message": "fetchOptions, {"method":"POST","headers":{"User-Agent":"PostHog.com/1.0","Content-Type":"application/json"},"body":"{\\"event\\":{\\"uuid\\":\\"uuid\\",\\"event\\":\\"test\\",\\"distinct_id\\":\\"distinct_id\\",\\"properties\\":{\\"email\\":\\"test@posthog.com\\"},\\"timestamp\\":\\"\\",\\"elements_chain\\":\\"\\",\\"url\\":\\"\\"},\\"person\\":{\\"id\\":\\"person-id\\",\\"name\\":\\"person-name\\",\\"properties\\":{\\"email\\":\\"example@posthog.com\\"},\\"url\\":\\"https://us.posthog.com/projects/1/persons/1234\\"}}"}",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "warn",
"message": "HTTP request failed with status 400 ({"status":"ERROR"}). ",
"timestamp": "2025-01-01T00:00:00.000Z",
},
{
"level": "error",
"message": "Function failed: Error executing function on event uuid: Request failed with status 400 ({"status":"ERROR"})",
"timestamp": "2025-01-01T00:00:00.000Z",
},
]
`)
})
})

View File

@@ -4,7 +4,7 @@ import { HogFunctionTemplate, NativeTemplate } from '../types'
import { allComingSoonTemplates } from './_destinations/coming-soon/coming-soon-destinations.template'
import { template as googleAdsTemplate } from './_destinations/google_ads/google.template'
import { template as linearTemplate } from './_destinations/linear/linear.template'
import { template as nativeWebhook } from './_destinations/native-webhook/webhook.template'
import { template as nativeWebhookTemplate } from './_destinations/native-webhook/webhook.template'
import { template as redditAdsTemplate } from './_destinations/reddit_ads/reddit.template'
import { template as snapchatAdsTemplate } from './_destinations/snapchat_ads/snapchat.template'
import { template as tiktokAdsTemplate } from './_destinations/tiktok_ads/tiktok.template'
@@ -49,7 +49,7 @@ export const HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS: HogFunctionTemplate[] = [
urlNormalizationTemplate,
]
export const NATIVE_HOG_FUNCTIONS: NativeTemplate[] = [nativeWebhook].map((plugin) => ({
export const NATIVE_HOG_FUNCTIONS: NativeTemplate[] = [nativeWebhookTemplate].map((plugin) => ({
...plugin,
code_language: 'javascript',
code: 'return event;',

View File

@@ -1,5 +1,8 @@
import Chance from 'chance'
import merge from 'deepmerge'
import { DateTime, Settings } from 'luxon'
import { NativeDestinationExecutorService } from '~/cdp/services/native-destination-executor.service'
import { defaultConfig } from '~/config/config'
import { CyclotronInputType } from '~/schema/cyclotron'
import { GeoIp, GeoIPService } from '~/utils/geoip'
@@ -7,14 +10,17 @@ import { GeoIp, GeoIPService } from '~/utils/geoip'
import { Hub } from '../../../types'
import { cleanNullValues } from '../../hog-transformations/transformation-functions'
import { HogExecutorService } from '../../services/hog-executor.service'
import { HogInputsService } from '../../services/hog-inputs.service'
import {
CyclotronJobInvocationHogFunction,
CyclotronJobInvocationResult,
HogFunctionInputSchemaType,
HogFunctionInvocationGlobals,
HogFunctionInvocationGlobalsWithInputs,
HogFunctionTemplate,
HogFunctionTemplateCompiled,
HogFunctionType,
NativeTemplate,
} from '../../types'
import { cloneInvocation } from '../../utils/invocation-utils'
import { createInvocation } from '../../utils/invocation-utils'
@@ -27,6 +33,87 @@ export type DeepPartialHogFunctionInvocationGlobals = {
request?: HogFunctionInvocationGlobals['request']
}
const compileObject = async (obj: any): Promise<any> => {
if (Array.isArray(obj)) {
return Promise.all(obj.map((item) => compileObject(item)))
} else if (typeof obj === 'object') {
const res: Record<string, any> = {}
for (const [key, value] of Object.entries(obj)) {
res[key] = await compileObject(value)
}
return res
} else if (typeof obj === 'string') {
return await compileHog(`return f'${obj}'`)
} else {
return undefined
}
}
const compileInputs = async (
template: HogFunctionTemplate | NativeTemplate,
_inputs: Record<string, any>
): Promise<Record<string, CyclotronInputType>> => {
const defaultInputs = template.inputs_schema.reduce((acc, input) => {
if (typeof input.default !== 'undefined') {
acc[input.key] = input.default
}
return acc
}, {} as Record<string, CyclotronInputType>)
const allInputs = { ...defaultInputs, ..._inputs }
// Don't compile inputs that don't suppport templating
const compiledEntries = await Promise.all(
Object.entries(allInputs).map(async ([key, value]) => {
const schema = template.inputs_schema.find((input) => input.key === key)
if (schema?.templating === false) {
return [key, value]
}
return [key, await compileObject(value)]
})
)
return compiledEntries.reduce((acc, [key, value]) => {
acc[key] = {
value: allInputs[key],
bytecode: value,
}
return acc
}, {} as Record<string, CyclotronInputType>)
}
const createGlobals = (
globals: DeepPartialHogFunctionInvocationGlobals = {}
): HogFunctionInvocationGlobalsWithInputs => {
return {
...globals,
inputs: {},
project: { id: 1, name: 'project-name', url: 'https://us.posthog.com/projects/1' },
event: {
uuid: 'event-id',
event: 'event-name',
distinct_id: 'distinct-id',
properties: { $current_url: 'https://example.com', ...globals.event?.properties },
timestamp: '2024-01-01T00:00:00Z',
elements_chain: '',
url: 'https://us.posthog.com/projects/1/events/1234',
...globals.event,
},
person: {
id: 'person-id',
name: 'person-name',
properties: { email: 'example@posthog.com', ...globals.person?.properties },
url: 'https://us.posthog.com/projects/1/persons/1234',
...globals.person,
},
source: {
url: 'https://us.posthog.com/hog_functions/1234',
name: 'hog-function-name',
...globals.source,
},
}
}
export class TemplateTester {
public template: HogFunctionTemplateCompiled
private executor: HogExecutorService
@@ -70,79 +157,7 @@ export class TemplateTester {
}
createGlobals(globals: DeepPartialHogFunctionInvocationGlobals = {}): HogFunctionInvocationGlobalsWithInputs {
return {
...globals,
inputs: {},
project: { id: 1, name: 'project-name', url: 'https://us.posthog.com/projects/1' },
event: {
uuid: 'event-id',
event: 'event-name',
distinct_id: 'distinct-id',
properties: { $current_url: 'https://example.com', ...globals.event?.properties },
timestamp: '2024-01-01T00:00:00Z',
elements_chain: '',
url: 'https://us.posthog.com/projects/1/events/1234',
...globals.event,
},
person: {
id: 'person-id',
name: 'person-name',
properties: { email: 'example@posthog.com', ...globals.person?.properties },
url: 'https://us.posthog.com/projects/1/persons/1234',
...globals.person,
},
source: {
url: 'https://us.posthog.com/hog_functions/1234',
name: 'hog-function-name',
...globals.source,
},
}
}
private async compileObject(obj: any): Promise<any> {
if (Array.isArray(obj)) {
return Promise.all(obj.map((item) => this.compileObject(item)))
} else if (typeof obj === 'object') {
const res: Record<string, any> = {}
for (const [key, value] of Object.entries(obj)) {
res[key] = await this.compileObject(value)
}
return res
} else if (typeof obj === 'string') {
return await compileHog(`return f'${obj}'`)
} else {
return undefined
}
}
private async compileInputs(_inputs: Record<string, any>): Promise<Record<string, CyclotronInputType>> {
const defaultInputs = this.template.inputs_schema.reduce((acc, input) => {
if (typeof input.default !== 'undefined') {
acc[input.key] = input.default
}
return acc
}, {} as Record<string, CyclotronInputType>)
const allInputs = { ...defaultInputs, ..._inputs }
// Don't compile inputs that don't suppport templating
const compiledEntries = await Promise.all(
Object.entries(allInputs).map(async ([key, value]) => {
const schema = this.template.inputs_schema.find((input) => input.key === key)
if (schema?.templating === false) {
return [key, value]
}
return [key, await this.compileObject(value)]
})
)
return compiledEntries.reduce((acc, [key, value]) => {
acc[key] = {
value: allInputs[key],
bytecode: value,
}
return acc
}, {} as Record<string, CyclotronInputType>)
return createGlobals(globals)
}
async invoke(
@@ -153,7 +168,7 @@ export class TemplateTester {
throw new Error('Mapping templates found. Use invokeMapping instead.')
}
const compiledInputs = await this.compileInputs(_inputs)
const compiledInputs = await compileInputs(this.template, _inputs)
const globals = this.createGlobals(_globals)
const { code, ...partialTemplate } = this.template
@@ -196,7 +211,7 @@ export class TemplateTester {
throw new Error('No mapping templates found')
}
const compiledInputs = await this.compileInputs(_inputs)
const compiledInputs = await compileInputs(this.template, _inputs)
const compiledMappingInputs = {
...this.template.mapping_templates.find((mapping) => mapping.name === mapping_name),
@@ -215,7 +230,7 @@ export class TemplateTester {
return {
key: input.key,
value,
bytecode: await this.compileObject(value),
bytecode: await compileObject(value),
}
})
)
@@ -270,6 +285,95 @@ export class TemplateTester {
}
}
export class DestinationTester {
private executor: NativeDestinationExecutorService
private inputsService: HogInputsService
private mockFetch = jest.fn()
constructor(private template: NativeTemplate) {
this.template = template
this.executor = new NativeDestinationExecutorService({} as any)
this.inputsService = new HogInputsService({} as any)
this.executor.fetch = this.mockFetch
this.mockFetch.mockResolvedValue({
status: 200,
json: () => Promise.resolve({ status: 'OK' }),
text: () => Promise.resolve(JSON.stringify({ status: 'OK' })),
headers: { 'content-type': 'application/json' },
})
}
createGlobals(globals: DeepPartialHogFunctionInvocationGlobals = {}): HogFunctionInvocationGlobalsWithInputs {
return createGlobals(globals)
}
mockFetchResponse(response?: { status?: number; body?: Record<string, any>; headers?: Record<string, string> }) {
const defaultResponse = {
status: 200,
body: { status: 'OK' },
headers: { 'content-type': 'application/json' },
}
const finalResponse = { ...defaultResponse, ...response }
this.mockFetch.mockResolvedValue({
status: finalResponse.status,
json: () => Promise.resolve(finalResponse.body),
text: () => Promise.resolve(JSON.stringify(finalResponse.body)),
headers: finalResponse.headers,
})
}
beforeEach() {
Settings.defaultZone = 'UTC'
const fixedTime = DateTime.fromISO('2025-01-01T00:00:00Z').toJSDate()
jest.spyOn(Date, 'now').mockReturnValue(fixedTime.getTime())
}
afterEach() {
Settings.defaultZone = 'system'
jest.useRealTimers()
}
async invoke(globals: HogFunctionInvocationGlobals, inputs: Record<string, any>) {
const compiledInputs = await compileInputs(this.template, inputs)
const globalsWithInputs = await this.inputsService.buildInputsWithGlobals(
{
...this.template,
inputs: compiledInputs,
} as unknown as HogFunctionType,
this.createGlobals(globals)
)
const invocation = createInvocation(globalsWithInputs, {
...this.template,
template_id: this.template.id,
hog: 'return event',
bytecode: [],
team_id: 1,
enabled: true,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
deleted: false,
inputs: compiledInputs,
is_addon_required: false,
})
const result = await this.executor.execute(invocation)
result.logs.forEach((x) => {
if (typeof x.message === 'string' && x.message.includes('Function completed in')) {
x.message = 'Function completed in [REPLACED]'
}
})
result.invocation.id = 'invocation-id'
return result
}
}
export const createAdDestinationPayload = (
globals?: DeepPartialHogFunctionInvocationGlobals
): DeepPartialHogFunctionInvocationGlobals => {
@@ -304,3 +408,97 @@ export const createAdDestinationPayload = (
return defaultPayload
}
export const generateTestData = (
seedName: string,
input_schema: HogFunctionInputSchemaType[],
requiredFieldsOnly: boolean = false
): Record<string, any> => {
const generateValue = (input: HogFunctionInputSchemaType): any => {
const chance = new Chance(seedName)
if (Array.isArray(input.choices)) {
const choice = chance.pickone(input.choices)
return choice.value
}
const getFormat = (input: HogFunctionInputSchemaType): string => {
if (input.key === 'url') {
return 'uri'
} else if (input.key === 'email') {
return 'email'
} else if (input.key === 'uuid') {
return 'uuid'
} else if (input.key === 'phone') {
return 'phone'
}
return 'string'
}
let val: any
switch (input.type) {
case 'boolean':
val = chance.bool()
break
case 'number':
val = chance.integer()
break
default:
// covers string
switch (getFormat(input)) {
case 'date': {
const d = chance.date()
val = [d.getFullYear(), d.getMonth() + 1, d.getDate()]
.map((v) => String(v).padStart(2, '0'))
.join('-')
break
}
case 'date-time':
val = chance.date().toISOString()
break
case 'email':
val = chance.email()
break
case 'hostname':
val = chance.domain()
break
case 'ipv4':
val = chance.ip()
break
case 'ipv6':
val = chance.ipv6()
break
case 'time': {
const d = chance.date()
val = [d.getHours(), d.getMinutes(), d.getSeconds()]
.map((v) => String(v).padStart(2, '0'))
.join(':')
break
}
case 'uri':
val = chance.url()
break
case 'uuid':
val = chance.guid()
break
case 'phone':
val = chance.phone()
break
default:
val = chance.string()
break
}
break
}
return val
}
const inputs = input_schema.reduce((acc, input) => {
if (input.required || requiredFieldsOnly === false) {
acc[input.key] = input.default ?? generateValue(input)
}
return acc
}, {} as Record<string, any>)
return inputs
}

86
pnpm-lock.yaml generated
View File

@@ -161,7 +161,7 @@ importers:
version: 3.12.1
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.10.14(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
version: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
parcel:
specifier: ^2.13.3
version: 2.13.3(@swc/helpers@0.5.15)(cssnano@7.0.6(postcss@8.5.6))(postcss@8.5.6)(relateurl@0.2.7)(svgo@3.3.2)(terser@5.19.1)(typescript@5.2.2)
@@ -1459,6 +1459,9 @@ importers:
'@types/babel__standalone':
specifier: ^7.1.4
version: 7.1.4
'@types/chance':
specifier: ^1.1.7
version: 1.1.7
'@types/faker':
specifier: ^5.5.7
version: 5.5.9
@@ -1516,6 +1519,9 @@ importers:
babel-eslint:
specifier: ^10.1.0
version: 10.1.0(eslint@8.57.0)
chance:
specifier: ^1.1.13
version: 1.1.13
deepmerge:
specifier: ^4.2.2
version: 4.3.1
@@ -1923,7 +1929,7 @@ importers:
version: 8.57.0
jest:
specifier: '*'
version: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.10.14(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
version: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
kea:
specifier: '*'
version: 3.1.5(react@18.2.0)
@@ -1977,7 +1983,7 @@ importers:
version: 3.1.3
jest:
specifier: '*'
version: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.10.14(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
version: 29.7.0(@types/node@22.15.17)(ts-node@10.9.1(@swc/core@1.11.4(@swc/helpers@0.5.15))(@types/node@22.15.17)(typescript@5.2.2))
kea:
specifier: '*'
version: 3.1.5(react@18.2.0)
@@ -7585,6 +7591,9 @@ packages:
'@types/caseless@0.12.5':
resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==}
'@types/chance@1.1.7':
resolution: {integrity: sha512-40you9610GTQPJyvjMBgmj9wiDO6qXhbfjizNYod/fmvLSfUUxURAJMTD8tjmbcZSsyYE5iEUox61AAcCjW/wQ==}
'@types/chart.js@2.9.37':
resolution: {integrity: sha512-9bosRfHhkXxKYfrw94EmyDQcdjMaQPkU1fH2tDxu8DWXxf1mjzWQAV4laJF51ZbC2ycYwNDvIm1rGez8Bug0vg==}
@@ -9013,6 +9022,9 @@ packages:
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
chance@1.1.13:
resolution: {integrity: sha512-V6lQCljcLznE7tUYUM9EOAnnKXbctE6j/rdQkYOHIWbfGQbrzTsAXNW9CdU5XCo4ArXQCj/rb6HgxPlmGJcaUg==}
char-regex@1.0.2:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
engines: {node: '>=10'}
@@ -18071,7 +18083,7 @@ snapshots:
'@babel/traverse': 7.28.0
'@babel/types': 7.28.1
convert-source-map: 2.0.0
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -18181,7 +18193,7 @@ snapshots:
'@babel/core': 7.26.0
'@babel/helper-compilation-targets': 7.25.9
'@babel/helper-plugin-utils': 7.24.7
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@@ -18192,7 +18204,7 @@ snapshots:
'@babel/core': 7.28.0
'@babel/helper-compilation-targets': 7.25.9
'@babel/helper-plugin-utils': 7.24.7
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@@ -18203,7 +18215,7 @@ snapshots:
'@babel/core': 7.26.0
'@babel/helper-compilation-targets': 7.25.9
'@babel/helper-plugin-utils': 7.24.7
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@@ -18214,7 +18226,7 @@ snapshots:
'@babel/core': 7.28.0
'@babel/helper-compilation-targets': 7.25.9
'@babel/helper-plugin-utils': 7.24.7
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@@ -18225,7 +18237,7 @@ snapshots:
'@babel/core': 7.26.0
'@babel/helper-compilation-targets': 7.25.9
'@babel/helper-plugin-utils': 7.24.7
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@@ -18236,7 +18248,7 @@ snapshots:
'@babel/core': 7.28.0
'@babel/helper-compilation-targets': 7.25.9
'@babel/helper-plugin-utils': 7.24.7
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@@ -19675,7 +19687,7 @@ snapshots:
'@babel/parser': 7.28.0
'@babel/template': 7.27.2
'@babel/types': 7.28.1
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -20232,7 +20244,7 @@ snapshots:
'@eslint/eslintrc@2.1.4':
dependencies:
ajv: 6.12.6
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
espree: 9.6.1
globals: 13.23.0
ignore: 5.2.4
@@ -20362,7 +20374,7 @@ snapshots:
'@humanwhocodes/config-array@0.11.14':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -20779,7 +20791,7 @@ snapshots:
'@open-draft/until': 1.0.3
'@types/debug': 4.1.7
'@xmldom/xmldom': 0.8.6
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
headers-polyfill: 3.2.5
outvariant: 1.4.0
strict-event-emitter: 0.2.8
@@ -24162,7 +24174,7 @@ snapshots:
'@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.2.2)(webpack@5.88.2)':
dependencies:
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
endent: 2.1.0
find-cache-dir: 3.3.2
flat-cache: 3.2.0
@@ -24966,6 +24978,8 @@ snapshots:
'@types/caseless@0.12.5': {}
'@types/chance@1.1.7': {}
'@types/chart.js@2.9.37':
dependencies:
moment: 2.29.4
@@ -25590,7 +25604,7 @@ snapshots:
dependencies:
'@typescript-eslint/typescript-estree': 7.1.1(typescript@5.2.2)
'@typescript-eslint/utils': 7.1.1(eslint@8.57.0)(typescript@5.2.2)
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
eslint: 8.57.0
ts-api-utils: 1.0.2(typescript@5.2.2)
optionalDependencies:
@@ -25604,7 +25618,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 7.1.1
'@typescript-eslint/visitor-keys': 7.1.1
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
@@ -25842,7 +25856,7 @@ snapshots:
agent-base@6.0.2:
dependencies:
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -25850,7 +25864,7 @@ snapshots:
agentkeepalive@4.3.0:
dependencies:
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
depd: 2.0.0
humanize-ms: 1.2.1
transitivePeerDependencies:
@@ -26715,6 +26729,8 @@ snapshots:
chalk@5.4.1: {}
chance@1.1.13: {}
char-regex@1.0.2: {}
char-regex@2.0.1: {}
@@ -27815,7 +27831,7 @@ snapshots:
detect-port@1.5.1:
dependencies:
address: 1.2.2
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -28193,7 +28209,7 @@ snapshots:
esbuild-register@3.5.0(esbuild@0.18.20):
dependencies:
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
esbuild: 0.18.20
transitivePeerDependencies:
- supports-color
@@ -28767,7 +28783,7 @@ snapshots:
dependencies:
chalk: 4.1.2
commander: 5.1.0
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -29542,14 +29558,14 @@ snapshots:
dependencies:
'@tootallnate/once': 2.0.0
agent-base: 6.0.2
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -29562,21 +29578,21 @@ snapshots:
https-proxy-agent@4.0.0:
dependencies:
agent-base: 5.1.1
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
@@ -30053,7 +30069,7 @@ snapshots:
istanbul-lib-source-maps@4.0.1:
dependencies:
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
istanbul-lib-coverage: 3.2.0
source-map: 0.6.1
transitivePeerDependencies:
@@ -30930,7 +30946,7 @@ snapshots:
dependencies:
chalk: 5.4.1
commander: 13.1.0
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
execa: 8.0.1
lilconfig: 3.1.3
listr2: 8.3.3
@@ -31321,7 +31337,7 @@ snapshots:
micromark@2.11.4:
dependencies:
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
parse-entities: 2.0.0
transitivePeerDependencies:
- supports-color
@@ -33235,7 +33251,7 @@ snapshots:
puppeteer-core@2.1.1:
dependencies:
'@types/mime-types': 2.1.1
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
extract-zip: 1.7.0
https-proxy-agent: 4.0.0
mime: 2.6.0
@@ -34209,7 +34225,7 @@ snapshots:
retry-request@4.2.2:
dependencies:
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
extend: 3.0.2
transitivePeerDependencies:
- supports-color
@@ -34646,7 +34662,7 @@ snapshots:
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.4
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
socks: 2.8.3
transitivePeerDependencies:
- supports-color
@@ -35095,7 +35111,7 @@ snapshots:
dependencies:
component-emitter: 1.3.1
cookiejar: 2.1.4
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
fast-safe-stringify: 2.1.1
form-data: 4.0.1
formidable: 3.5.1
@@ -35984,7 +36000,7 @@ snapshots:
dependencies:
chalk: 2.4.2
commander: 3.0.2
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.1(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color