-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
feat(dashboard): restore email editor 'for' block #7483
feat(dashboard): restore email editor 'for' block #7483
Conversation
✅ Deploy Preview for dev-web-novu ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
✅ Deploy Preview for dashboard-v2-novu-staging ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Outdated
Show resolved
Hide resolved
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Outdated
Show resolved
Hide resolved
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work, i left a couple of comments about my opinion of responsibility spread in the code and the meaning of it.
@@ -39,12 +39,12 @@ export class HydrateEmailSchemaUseCase { | |||
index: number | |||
) { | |||
content[index] = { | |||
type: 'text', | |||
type: 'variable', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was there a specific reason for this change? When parsing the variable
type from Maily, we need to convert it to text
because Liquid is responsible for variable parsing.
in addition, this helps us avoid any side effects from Maily's variable parsing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is required for the 'for' logic down the line. The output from this hydrate schema usecase looks something like this:
{
"type": "for",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "comments: "
},
{
"type": "variable", <--- This needs to be variable not text
"text": "{{ payload.comments.text }}"
}
],
"attrs": {
"textAlign": "left"
}
}
],
}
If it would be replaced by type: text
here, I wouldn't be able to know which node is variable
and which text
, therefore I wouldn't be able to transform the variable here from {{ payload.comments.text }}
to {{ payload.comments[0].text }}
apps/api/src/app/workflows-v2/usecases/generate-preview/generate-preview.usecase.ts
Outdated
Show resolved
Hide resolved
apps/api/src/app/workflows-v2/usecases/generate-preview/transform-maily-content-to-liquid.ts
Outdated
Show resolved
Hide resolved
@novu/client
@novu/node
@novu/notification-center
@novu/headless
novu
@novu/providers
@novu/shared
commit: |
apps/api/src/app/environments-v1/usecases/output-renderers/hydrate-email-schema.usecase.ts
Outdated
Show resolved
Hide resolved
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Outdated
Show resolved
Hide resolved
} | ||
|
||
private hasEach(node: TipTapNode): node is TipTapNode & { attrs: { each: unknown } } { | ||
return !!(node.attrs && 'each' in node.attrs); | ||
private async handleShowNode( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Show is an Attribute, not a node. So to be precise we should name this as handleShowAttr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
even more precise would be handleNodeByShowAttr because here we decide if to show a Node based on an attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to disagree here, I think this way the naming is more common and understandable.
We are processing nodes and the for
and show
are a specific type of nodes. We are not processing the attribute only, we are processing an entire node which has attr = "for"
, similar as step.type = "email"
.
When you first time look at this method, its clear that if the node is of type for
you do something with the node - you dont need to know about some internal property name such as attr
or anything:
private async processNode(node: TipTapNode, variables: FullPayloadForRender, parent?: TipTapNode): Promise<void> {
if (this.hasShow(node)) {
await this.handleShowNode(node, variables, parent);
}
if (this.hasEach(node)) {
await this.handleEachNode(node, variables, parent);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about handleEach, handleShow?
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Outdated
Show resolved
Hide resolved
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Show resolved
Hide resolved
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Outdated
Show resolved
Hide resolved
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Outdated
Show resolved
Hide resolved
.../api/src/app/environments-v1/usecases/output-renderers/expand-email-editor-schema.usecase.ts
Outdated
Show resolved
Hide resolved
apps/api/src/app/environments-v1/usecases/output-renderers/hydrate-email-schema.usecase.ts
Outdated
Show resolved
Hide resolved
apps/api/src/app/workflows-v2/util/template-parser/liquid-parser.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the validation became even more complex, this is fine for now but i wonder if we could make the validation work without this change.
|
||
return this.regularExpansion(eachObject, templateContent); | ||
} | ||
const iterableArray = this.getValueByPath(variables, iterablePath) as [{ [key: string]: unknown }]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we do not need to getValueByPath, id suggest doing the following:
go to variableAttributeConfig
add a new entry, { attr: MailyAttrsEnum.EACH, flag: MailyAttrsEnum.EACH }
the result will be that in the pipeline we will wrap each attribute (node.attrs.each) with {{...}}
then you could get the value from the variable using liquid js.
const iterableArray = this.getValueByPath(variables, iterablePath) as [{ [key: string]: unknown }]; | |
const iterableArray = await parseLiquid(iterablePath as string, variables); |
let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, iterableArray accepts and returns this:
// variables, iterablePath = "payload.comments"
const iterableArray = this.getValueByPath(variables, iterablePath) as [{ [key: string]: unknown }];
/**
* iterableArray = [
* {
* name: '{{payload.comments.name}}',
* },
* {
* name: '{{payload.comments.name}}',
* },
* {
* name: '{{payload.comments.name}}',
* }
*]
**/
What you are suggesting, would return just the iterable path string, not the actual array:
const iterableArray = await parseLiquid(iterablePath as string, variables);
// iterableArray = payload.comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It currently doesnt work like this because we dont have for block {{ payload.comments }}
anywhere in the maily json, we have just each: {{ payload.comments}}
. That said its interesting idea to explore I will have a look if we can do this separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for
semantics representation aside, this one should work, doesn't it?
we have each: {{ payload.comments}}
with data variables: {"payload":{"comments":[" a_1 "," a_2 "," a_3 "]}}
once we execute const iterableArray = await parseLiquid(iterablePath as string, variables);
we should get the array [" a_1 "," a_2 "," a_3 "]
if (showIfKey === undefined) { | ||
return; | ||
} | ||
private addIndexesToPlaceholders(nodes: TipTapNode[], iterablePath: string, index: number): TipTapNode[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you think about?
private addIndexesToPlaceholders(nodes: TipTapNode[], iterablePath: string, index: number): TipTapNode[] { | |
private addIndexesToLiquidOutput(nodes: TipTapNode[], iterablePath: string, index: number): TipTapNode[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the placeholders ({{payload.something}}
) are not liquid output right? They are more Liquid input, since the parsing by Liquid happens later and this is usecase is an input for that:
const parsedTipTap = await this.parseTipTapNodeByLiquid(expandedMailyContent, renderCommand);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Liquid JS calls the value with filters wrapped with {{...}}
an Output i using Output instead of placeholder would help us unify and help us to understand each other better.
and the main logic in this function is
newNode.text = nodePlaceholder.replace(iterablePath,
${iterablePath}[${index}]);
which creates a liquid js Output.
Let me know what you think about it.
In addition, apologies for missing this earlier I noticed that we are using string.replace
. Instead, we should use wrapInLiquidOutput
whenever we have a Maily fallback
attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What changed? Why was the change needed?
{{payload.comments}}
, item is{{payload.comments.id}}
High-level overview of how the parsing flow goes so we can reference it later when refactoring:
for-loop.mov