Skip to content

Commit

Permalink
fix: parse inputs following schema when forking workflows
Browse files Browse the repository at this point in the history
  • Loading branch information
marian2js committed Mar 29, 2023
1 parent c854534 commit bd0caaa
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 4 deletions.
27 changes: 24 additions & 3 deletions apps/api/src/workflows/services/workflow.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseService } from '@app/common/base/base.service'
import { replaceTemplateFields } from '@app/definitions/utils/field.utils'
import { fixObjectTypes, replaceTemplateFields } from '@app/definitions/utils/field.utils'
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common'
import { DeepPartial, DeleteOneOptions, UpdateOneOptions } from '@ptc-org/nestjs-query-core'
import { ReturnModelType } from '@typegoose/typegoose'
Expand Down Expand Up @@ -224,7 +224,11 @@ export class WorkflowService extends BaseService<Workflow> {
name: trigger.name,
inputs: {
...templateInputs,
...replaceTemplateFields(idsMap, trigger.inputs ?? {}, templateInputs),
...replaceTemplateFields(
idsMap,
trigger.inputs ?? {},
fixObjectTypes(templateInputs, workflow.templateSchema ?? {}),
),
},
credentials: credentialsForTrigger?.id,
schedule: {
Expand Down Expand Up @@ -265,6 +269,19 @@ export class WorkflowService extends BaseService<Workflow> {
)

const previousAction = previousActionMap.get(action.id)
this.logger.log(
`Forking action with template inputs: ${JSON.stringify(templateInputs)}; schema: ${JSON.stringify(
workflow.templateSchema,
)}; fixed types: ${JSON.stringify(
fixObjectTypes(templateInputs, workflow.templateSchema ?? {}),
)}; result: ${JSON.stringify(
replaceTemplateFields(
idsMap,
action.inputs ?? {},
fixObjectTypes(templateInputs, workflow.templateSchema ?? {}),
),
)}`,
)
const forkedAction = await this.workflowActionService.createOne({
owner: user._id,
workflow: forkedWorkflow._id,
Expand All @@ -274,7 +291,11 @@ export class WorkflowService extends BaseService<Workflow> {
name: action.name,
inputs: {
...templateInputs,
...replaceTemplateFields(idsMap, action.inputs ?? {}, templateInputs),
...replaceTemplateFields(
idsMap,
action.inputs ?? {},
fixObjectTypes(templateInputs, workflow.templateSchema ?? {}),
),
},
previousAction: idsMap.get(previousAction?.id ?? '') as any,
previousActionCondition: previousAction?.condition,
Expand Down
188 changes: 187 additions & 1 deletion libs/definitions/src/utils/field.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getInterpolatedVariables, replaceTemplateFields } from './field.utils'
import { JSONSchema7 } from 'json-schema'
import { fixObjectTypes, getInterpolatedVariables, replaceTemplateFields } from './field.utils'

describe('FieldUtils', () => {
describe('getInterpolatedVariables', () => {
Expand Down Expand Up @@ -125,4 +126,189 @@ describe('FieldUtils', () => {
})
})
})

describe('fixObjectTypes', () => {
const schema: JSONSchema7 = {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'integer' },
active: { type: 'boolean' },
score: { type: 'number' },
hobbies: {
type: 'array',
items: { type: 'string' },
},
address: {
type: 'object',
properties: {
street: { type: 'string' },
city: { type: 'string' },
},
},
friends: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'integer' },
active: { type: 'boolean' },
},
},
},
},
}

it('should fix object types according to schema', () => {
const input = {
name: 'John Doe',
age: '30',
active: 'true',
score: '100.5',
hobbies: ['reading', 'sports'],
address: {
street: '123 Main St',
city: 'New York',
},
}

const expectedOutput = {
name: 'John Doe',
age: 30,
active: true,
score: 100.5,
hobbies: ['reading', 'sports'],
address: {
street: '123 Main St',
city: 'New York',
},
}

expect(fixObjectTypes(input, schema)).toEqual(expectedOutput)
})

it('should not change the value of items not following the schema', () => {
const input = {
name: 'John Doe',
age: '{{template.age}}',
active: '{{template.active}}',
score: '{{template.score}}',
hobbies: '{{template.hobbies}}',
address: '{{template.address}}',
}

const expectedOutput = {
name: 'John Doe',
age: '{{template.age}}',
active: '{{template.active}}',
score: '{{template.score}}',
hobbies: '{{template.hobbies}}',
address: '{{template.address}}',
}

expect(fixObjectTypes(input, schema)).toEqual(expectedOutput)
})

it('should not modify values with the correct type', () => {
const input = {
name: 'Jane Doe',
age: 28,
active: false,
hobbies: ['painting', 'music'],
address: {
street: '456 Elm St',
city: 'Los Angeles',
},
}

expect(fixObjectTypes(input, schema)).toEqual(input)
})

it('should handle nested objects and arrays', () => {
const input = {
name: 'John Doe',
age: '30',
active: 'true',
hobbies: ['reading', 'sports'],
address: {
street: '123 Main St',
city: 'New York',
},
friends: [
{
name: 'Alice',
age: '35',
active: 'false',
},
{
name: 'Bob',
age: '32',
active: 'true',
},
],
}

const expectedOutput = {
name: 'John Doe',
age: 30,
active: true,
hobbies: ['reading', 'sports'],
address: {
street: '123 Main St',
city: 'New York',
},
friends: [
{
name: 'Alice',
age: 35,
active: false,
},
{
name: 'Bob',
age: 32,
active: true,
},
],
}

expect(fixObjectTypes(input, schema)).toEqual(expectedOutput)
})

it('should handle empty objects and arrays', () => {
const input = {
name: 'John Doe',
age: '30',
active: 'true',
hobbies: [],
address: {},
}

const expectedOutput = {
name: 'John Doe',
age: 30,
active: true,
hobbies: [],
address: {},
}

expect(fixObjectTypes(input, schema)).toEqual(expectedOutput)
})

it('should handle missing properties', () => {
const input = {
name: 'John Doe',
age: '30',
active: 'true',
}

const expectedOutput = {
name: 'John Doe',
age: 30,
active: true,
}

expect(fixObjectTypes(input, schema)).toEqual(expectedOutput)
})
})
})
49 changes: 49 additions & 0 deletions libs/definitions/src/utils/field.utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getAddress, isAddress } from 'ethers/lib/utils'
import { JSONSchema7, JSONSchema7TypeName } from 'json-schema'
import { VarEvm } from '../operation-evm'

export function hasInterpolation(str: string) {
Expand Down Expand Up @@ -135,3 +136,51 @@ export function replaceTemplateFields(
}
return result
}

export function fixObjectTypes(obj: Record<string, any>, schema: JSONSchema7): Record<string, any> {
if (!schema?.properties) {
return obj
}
if (!obj || typeof obj !== 'object') {
return obj
}
for (const [key, value] of Object.entries(obj)) {
const type: JSONSchema7TypeName = schema.properties[key] && (schema.properties[key] as any).type
switch (type) {
case 'object':
obj[key] = fixObjectTypes(value, schema.properties![key] as JSONSchema7)
break
case 'array':
if (Array.isArray(value)) {
obj[key] = value.map((item) => {
if (typeof item === 'object') {
return fixObjectTypes(item, (schema.properties![key] as any).items as JSONSchema7)
} else {
return item
}
})
} else {
obj[key] = value
}

break
case 'boolean':
obj[key] = value === 'true' ? true : value === 'false' ? false : value
break
case 'number':
obj[key] = Number.isFinite(Number(value)) ? Number(value) : value
break
case 'integer':
obj[key] = Number.isInteger(Number(value)) ? Number(value) : value
break
case 'null':
obj[key] = value === 'null' ? null : value
break
case 'string':
default:
obj[key] = value
break
}
}
return obj
}

0 comments on commit bd0caaa

Please sign in to comment.