Skip to content

Commit

Permalink
feat: enable JEXLs for options in choice and multiple choice questions
Browse files Browse the repository at this point in the history
  • Loading branch information
StephanH90 committed Aug 16, 2024
1 parent e286822 commit adc8c8c
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<li class="cfb-option-row" data-test-row={{concat "option-" (add @i 1)}}>
<ValidatedForm @model={{@row}} as |f|>
<div uk-grid class="uk-grid-small uk-flex uk-flex-top" id={{@row.slug}}>
<div class="uk-width-auto uk-flex uk-flex-middle">
{{#if @canReorder}}
<span
data-test-sort-handle
uk-icon="menu"
class="uk-sortable-handle uk-margin-small-right"
role="button"
></span>
{{/if}}
{{#if (is-empty @row.id)}}
<button
data-test-delete-row
type="button"
class="uk-icon-button"
uk-icon="trash"
title={{t "caluma.form-builder.options.delete"}}
{{on "click" (fn @deleteRow @row)}}
>
</button>
{{else}}
<button
data-test-archive-row
type="button"
class="uk-icon-button"
uk-icon={{if @row.isArchived "refresh" "close"}}
title={{t
(concat
"caluma.form-builder.options."
(if @row.isArchived "restore" "archive")
)
}}
{{on
"click"
(fn (changeset-set @row "isArchived") (not @row.isArchived))
}}
>
</button>
{{/if}}
</div>
<div class="uk-width-expand">
<f.input
@name="label"
@inputName={{concat "option-" (add @i 1) "-label"}}
@required={{true}}
@disabled={{@row.isArchived}}
@submitted={{@submitted}}
@on-update={{@updateLabel}}
/>
</div>
<div class="uk-width-1-4">
<f.input
@name="slug"
@inputName={{concat "option-" (add @i 1) "-slug"}}
@required={{true}}
@disabled={{not (is-empty @row.id)}}
@submitted={{@submitted}}
@renderComponent={{component
"cfb-slug-input"
hidePrefix=true
prefix=@model.slug
onUnlink=(fn (mut @row.slugUnlinked) true)
}}
/>
</div>
<div class="uk-width-auto">
<button
data-test-toggle-jexl
type="button"
class="uk-icon-button"
uk-icon={{if this.showJexl "triangle-up" "triangle-down"}}
title={{t "caluma.form-builder.options.show-jexl"}}
{{on "click" (fn (mut this.showJexl) (not this.showJexl))}}
/>
</div>
{{#if this.showJexl}}
<div class="uk-width-4-4">
<f.input
@label={{t "caluma.form-builder.question.isHidden"}}
@name="isHidden"
@renderComponent={{component "cfb-code-editor" language="jexl"}}
/>
</div>
{{/if}}
</div>
</ValidatedForm>
</li>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

export default class CfbFormEditorQuestionOption extends Component {
@tracked showJexl = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -363,11 +363,11 @@ export default class CfbFormEditorQuestion extends Component {
(changeset.get("options") || [])
.filter((option) => option.get("isDirty"))
.map(async (option) => {
const { label, slug, isArchived } = option;
const { label, slug, isArchived, isHidden } = option;

await this.apollo.mutate({
mutation: saveOptionMutation,
variables: { input: { label, slug, isArchived } },
variables: { input: { label, slug, isArchived, isHidden } },
});
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,80 +8,13 @@
class="uk-list uk-list-divider uk-form-controls uk-margin-small-top"
>
{{#each @value as |row i|}}
<li class="cfb-option-row" data-test-row={{concat "option-" (add i 1)}}>
<ValidatedForm @model={{row}} as |f|>
<div
uk-grid
class="uk-grid-small uk-flex uk-flex-top"
id={{row.slug}}
>
<div class="uk-width-auto uk-flex uk-flex-middle">
{{#if this.canReorder}}
<span
data-test-sort-handle
uk-icon="menu"
class="uk-sortable-handle uk-margin-small-right"
role="button"
></span>
{{/if}}
{{#if (is-empty row.id)}}
<button
data-test-delete-row
type="button"
class="uk-icon-button"
uk-icon="trash"
title={{t "caluma.form-builder.options.delete"}}
{{on "click" (fn this.deleteRow row)}}
>
</button>
{{else}}
<button
data-test-archive-row
type="button"
class="uk-icon-button"
uk-icon={{if row.isArchived "refresh" "close"}}
title={{t
(concat
"caluma.form-builder.options."
(if row.isArchived "restore" "archive")
)
}}
{{on
"click"
(fn (changeset-set row "isArchived") (not row.isArchived))
}}
>
</button>
{{/if}}
</div>
<div class="uk-width-expand">
<f.input
@name="label"
@inputName={{concat "option-" (add i 1) "-label"}}
@required={{true}}
@disabled={{row.isArchived}}
@submitted={{@submitted}}
@on-update={{this.updateLabel}}
/>
</div>
<div class="uk-width-1-4">
<f.input
@name="slug"
@inputName={{concat "option-" (add i 1) "-slug"}}
@required={{true}}
@disabled={{not (is-empty row.id)}}
@submitted={{@submitted}}
@renderComponent={{component
"cfb-slug-input"
hidePrefix=true
prefix=@model.slug
onUnlink=(fn (mut row.slugUnlinked) true)
}}
/>
</div>
</div>
</ValidatedForm>
</li>
<CfbFormEditor::QuestionOption
@row={{row}}
@i={{i}}
@canReorder={{this.canReorder}}
@deleteRow={{fn this.deleteRow row}}
@updateLabel={{this.updateLabel}}
/>
{{/each}}
<li class="uk-text-center">
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mutation SaveOption($input: SaveOptionInput!) {
id
label
slug
isHidden
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ query FormEditorQuestion($slug: String!) {
label
slug
isArchived
isHidden
}
}
}
Expand All @@ -93,6 +94,7 @@ query FormEditorQuestion($slug: String!) {
label
slug
isArchived
isHidden
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/form/addon/gql/fragments/field.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fragment SimpleQuestion on Question {
slug
label
isArchived
isHidden
}
}
}
Expand All @@ -89,6 +90,7 @@ fragment SimpleQuestion on Question {
slug
label
isArchived
isHidden
}
}
}
Expand Down
41 changes: 37 additions & 4 deletions packages/form/addon/lib/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,10 @@ export default class Field extends Base {
* - DynamicMultipleChoiceQuestion
*
* This will also return the disabled state of the option. An option can only
* be disabled, if it is an old value used in a dynamic question.
* be disabled in any of the following cases:
* - it is a dynamic option which is no longer available
* - the option has been removed from the form using the form builder
* - the option is no longer visible due to its JEXL
*
* @property {null|Object[]} options
*/
Expand All @@ -352,9 +355,39 @@ export default class Field extends Base {
const selected =
(this.question.isMultipleChoice ? this.value : [this.value]) || [];

const options = this.question.options.filter(
(option) => !option.disabled || selected.includes(option.slug),
);
const options = this.question.options
.filter((option) => !option.disabled || selected.includes(option.slug))
.map((option) => {
if (!option.isHidden) {
return option;
}

try {
const isHidden = this.document.jexl.evalSync(
option.isHidden,
this.jexlContext,
);

if (selected.includes(option.slug) && isHidden) {
return {
...option,
_isHidden: isHidden,
disabled: true,
};
}
return {
...option,
_isHidden: isHidden,
};
} catch (error) {
throw new Error(
`Error while evaluating \`isHidden\` expression on Option\`${option.slug}\`: ${error.message}`,
);
}
})
.filter(
({ _isHidden, disabled }) => !_isHidden || (_isHidden && disabled),
);

const hasUnknownValue = !selected.every((slug) =>
options.find((option) => option.slug === slug),
Expand Down
13 changes: 8 additions & 5 deletions packages/form/addon/lib/question.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,14 @@ export default class Question extends Base {
const key = camelize(this.raw.__typename.replace(/Question$/, "Options"));
const raw = this.isDynamic ? this[key] : this.raw[key];

return (raw?.edges || []).map(({ node: { label, slug, isArchived } }) => ({
label,
slug,
disabled: isArchived || false,
}));
return (raw?.edges || []).map(
({ node: { label, slug, isArchived, isHidden } }) => ({
label,
slug,
disabled: isArchived || false,
isHidden,
}),
);
}

/**
Expand Down
32 changes: 30 additions & 2 deletions packages/form/tests/unit/lib/field-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ module("Unit | Library | field", function (hooks) {
});

test("it can validate radio fields", async function (assert) {
assert.expect(1);
assert.expect(2);

const field = this.addField({
question: {
Expand All @@ -401,13 +401,22 @@ module("Unit | Library | field", function (hooks) {
node: {
slug: "option-1",
label: "Option 1",
isHidden: "false",
},
},
{
node: {
slug: "invalid-option",
label: "Invalid Option",
isArchived: true,
isHidden: "false",
},
},
{
node: {
slug: "hidden-option",
label: "Hidden Option",
isHidden: "true",
},
},
],
Expand All @@ -428,10 +437,16 @@ module("Unit | Library | field", function (hooks) {
assert.deepEqual(field.errors, [
'"Invalid Option" is not a valid value for this field',
]);

field.answer.value = "hidden-option";
await field.validate.perform();
assert.deepEqual(field.errors, [
'"Hidden Option" is not a valid value for this field',
]);
});

test("it can validate checkbox fields", async function (assert) {
assert.expect(2);
assert.expect(3);

const field = this.addField({
question: {
Expand All @@ -450,6 +465,13 @@ module("Unit | Library | field", function (hooks) {
isArchived: true,
},
},
{
node: {
slug: "hidden-option",
label: "Hidden Option",
isHidden: "true",
},
},
],
},
isHidden: "false",
Expand All @@ -475,6 +497,12 @@ module("Unit | Library | field", function (hooks) {
'"Invalid Option" is not a valid value for this field',
'"other-invalid-option" is not a valid value for this field',
]);

field.answer.value = ["hidden-option"];
await field.validate.perform();
assert.deepEqual(field.errors, [
'"Hidden Option" is not a valid value for this field',
]);
});

test("it can validate table fields", async function (assert) {
Expand Down
1 change: 1 addition & 0 deletions packages/testing/addon/mirage-graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3237,6 +3237,7 @@ input SaveOptionInput {
slug: String!
label: String!
isArchived: Boolean
isHidden: Boolean
meta: JSONString
clientMutationId: String
}
Expand Down

0 comments on commit adc8c8c

Please sign in to comment.