Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ON-43061 # Added processInjectableResource to submissionService #92

Merged
merged 5 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- `submissionService.processInjectablesInCustomResource()`

## [7.0.0] - 2024-08-13

### Removed
Expand Down
207 changes: 207 additions & 0 deletions src/replaceCustomValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,210 @@ export function replaceInjectablesWithSubmissionValues(
hadAllInjectablesReplaced,
}
}

/**
* Process a resource with injectable element values to turn a single resource
* (could be a single) into multiple resources. e.g.
* `"{ELEMENT:Children|Child_Name} {ELEMENT:Family_Name}"` with the following
* submission data:
*
* ```json
* {
* "Family_Name": "Smith",
* "Children": [
* {
* "Child_Name": "John"
* },
* {
* "Child_Name": "Jane"
* }
* ]
* }
* ```
*
* Would result in the following resources:
*
* - `"John Smith"`
* - `"Jane Smith"`
*
* #### Example
*
* ```js
* const emailAddresses = processInjectablesInCustomResource({
* resource: '{ELEMENT:People|Email_Address}',
* submission: {
* People: [
* {
* Email_Address: 'user@oneblink.io',
* },
* {
* Email_Address: 'admin@oneblink.io',
* },
* ],
* },
* formElements: [
* {
* id: '18dcd3e0-6e2f-462e-803b-e24562d9fa6d',
* type: 'repeatableSet',
* name: 'People',
* label: 'People',
* elements: [
* {
* id: 'd0902113-3f77-4070-adbd-ca3ae95ce091',
* type: 'email',
* name: 'Email_Address',
* label: 'Email_Address',
* },
* ],
* },
* ],
* replaceRootInjectables: (resource, submission, formElements) => {
* const { text } = replaceInjectablesWithElementValues(resource, {
* submission,
* formElements,
* excludeNestedElements: true,
* // other options
* })
* return text
* },
* })
* // emailAddresses === ["user@oneblink.io", "admin@oneblink.io"]
* ```
*
* @param options
* @returns
*/
export function processInjectablesInCustomResource<T>({
resource,
submission,
formElements,
replaceRootInjectables,
prepareNestedInjectables = (resource, prepare) =>
prepare(String(resource)) as T,
}: {
/** The resource that contains properties that support injection or a string */
resource: T
/** The form submission data to process */
submission: SubmissionTypes.S3SubmissionData['submission']
/** The form elements to process */
formElements: FormTypes.FormElement[]
/**
* A function to inject values, this allows custom formatters to be used.
* Return `undefined` to prevent the injection from recursively continuing.
*
* @param resource The current resource that contains properties that support
* injection or a string
* @param submission The current form submission data to process (may be an
* entry in a repeatable set)
* @param formElements The current form elements to process (may be the
* elements from a repeatable set)
* @returns
*/
replaceRootInjectables: (
resource: T,
submission: SubmissionTypes.S3SubmissionData['submission'],
formElements: FormTypes.FormElement[],
) =>
| [injectedText: string, resourceKey: string, newResource: T]
| string
| undefined
/**
* An optional function to replace nested injectables when creating multiple
* resources from repeatable sets. Only required if the `resource` param is
* not a `string`.
*
* @param resource The current resource that contains properties that support
* injection or a string
* @param prepare A function to prepare the resource string(s) for another
* iteration
* @returns
*/
prepareNestedInjectables?: (
resource: T,
preparer: (resourceText: string) => string,
) => T
}): Map<string, T> {
const newResources: Map<string, T> = new Map<string, T>()

const injectorResult = replaceRootInjectables(
resource,
submission,
formElements,
)
if (!injectorResult) {
return newResources
}

const [text, resourceKey, newResource] =
typeof injectorResult === 'string'
? [injectorResult, injectorResult, injectorResult as T]
: injectorResult

// Find nested form elements
const matches: Map<string, boolean> = new Map()
matchElementsTagRegex(
mymattcarroll marked this conversation as resolved.
Show resolved Hide resolved
{
text,
excludeNestedElements: false,
},
({ elementName }) => {
const [repeatableSetElementName, ...elementNames] = elementName.split('|')
matches.set(repeatableSetElementName, !!elementNames.length)
},
)

if (matches.size) {
matches.forEach((hasNestedFormElements, repeatableSetElementName) => {
if (hasNestedFormElements) {
// Attempt to create a new resource for each entry in the repeatable set.
const entries = submission?.[repeatableSetElementName]
if (Array.isArray(entries)) {
const repeatableSetElement = findFormElement(
formElements,
(formElement) => {
return (
'name' in formElement &&
formElement.name === repeatableSetElementName
)
},
)
if (
repeatableSetElement &&
'elements' in repeatableSetElement &&
Array.isArray(repeatableSetElement.elements)
) {
for (const entry of entries) {
const replacedResource = prepareNestedInjectables(
newResource,
(resourceText) => {
return resourceText.replaceAll(
`{ELEMENT:${repeatableSetElementName}|`,
'{ELEMENT:',
)
},
)
const nestedResources = processInjectablesInCustomResource<T>({
resource: replacedResource,
submission: entry,
formElements: repeatableSetElement.elements,
replaceRootInjectables,
prepareNestedInjectables,
})
if (nestedResources.size) {
nestedResources.forEach((nestedResource, nestedResourceKey) => {
if (!newResources.has(nestedResourceKey)) {
newResources.set(nestedResourceKey, nestedResource)
}
})
}
}
}
}
}
})
} else {
newResources.set(resourceKey, newResource)
}

return newResources
}
80 changes: 80 additions & 0 deletions tests/__snapshots__/submissionService.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`processInjectablesInCustomResource should correctly replace nested injectables 1`] = `
Map {
"John is part of the Smith family" => "John is part of the Smith family",
"Jane is part of the Smith family" => "Jane is part of the Smith family",
}
`;

exports[`processInjectablesInCustomResource should handle missing element values gracefully 1`] = `Map {}`;

exports[`processInjectablesInCustomResource should handle multiple replacements correctly 1`] = `
Map {
"Smith Smith" => "Smith Smith",
}
`;

exports[`processInjectablesInCustomResource should handle single element without iteration 1`] = `
Map {
"Smith" => "Smith",
}
`;

exports[`processInjectablesInCustomResource should replace injectable values correctly 1`] = `
Map {
"John Smith" => "John Smith",
"Jane Smith" => "Jane Smith",
}
`;

exports[`processInjectablesInCustomResource should replace injectable values correctly when resource is not a string 1`] = `
[
{
"book_title": "The Adventures of Superman",
"favorite_sentence": "It was a bright cold day in April, and the clocks were striking thirteen.",
},
{
"book_title": "The Adventures of Wonder Woman",
"favorite_sentence": "It was a bright cold day in April, and the clocks were striking thirteen.",
},
{
"book_title": "The Mysterious Case of Sherlock Holmes",
"favorite_sentence": "Elementary, my dear Watson.",
},
{
"book_title": "The Mysterious Case of Hercule Poirot",
"favorite_sentence": "Elementary, my dear Watson.",
},
{
"book_title": "Strange Times in Gotham",
"favorite_sentence": "It was the best of times, it was the worst of times.",
},
{
"book_title": "Strange Times in Metropolis",
"favorite_sentence": "It was the best of times, it was the worst of times.",
},
{
"book_title": "The Journey of Superman",
"favorite_sentence": "Not all those who wander are lost.",
},
{
"book_title": "The Journey of Wonder Woman",
"favorite_sentence": "Not all those who wander are lost.",
},
{
"book_title": "Brave Nights",
"favorite_sentence": "To be, or not to be, that is the question.",
},
{
"book_title": "Mysterious Nights",
"favorite_sentence": "To be, or not to be, that is the question.",
},
]
`;

exports[`processInjectablesInCustomResource should return an empty array if no injectables are present 1`] = `
Map {
"No injectables here" => "No injectables here",
}
`;
Loading