From a41563a1838e496cb0bd175a648c750c889ed825 Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Thu, 28 Nov 2024 09:17:45 +0100
Subject: [PATCH 1/5] Fix pricing custom plan
---
client/components/pages/pricing/CustomPlan.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/components/pages/pricing/CustomPlan.vue b/client/components/pages/pricing/CustomPlan.vue
index 53b454122..d0bab23ed 100644
--- a/client/components/pages/pricing/CustomPlan.vue
+++ b/client/components/pages/pricing/CustomPlan.vue
@@ -159,7 +159,7 @@
$200
- starting from {{ isYearly ? 'per year' : 'per month' }}
+ starting from per month
From e2c6af69e69236ca4c9a8ab57d518c45541d9031 Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Thu, 28 Nov 2024 10:22:33 +0100
Subject: [PATCH 2/5] [ESC-389]form-requires-to-answer
---
api/app/Http/Requests/AnswerFormRequest.php | 12 +-
api/tests/Feature/Forms/FormLogicTest.php | 235 ++++++++++++++++++++
2 files changed, 245 insertions(+), 2 deletions(-)
diff --git a/api/app/Http/Requests/AnswerFormRequest.php b/api/app/Http/Requests/AnswerFormRequest.php
index 8bfc10ee9..61d67f4c8 100644
--- a/api/app/Http/Requests/AnswerFormRequest.php
+++ b/api/app/Http/Requests/AnswerFormRequest.php
@@ -66,12 +66,20 @@ public function rules()
foreach ($selectionFields as $field) {
if (isset($data[$field['id']]) && is_array($data[$field['id']])) {
$data[$field['id']] = array_map(function ($val) use ($field) {
+ // Find the option by exact ID match first
$tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($val) {
- return $op['id'] ?? $op['value'] === $val;
+ return $op['id'] === $val || $op['name'] === $val;
});
- return isset($tmpop['name']) ? $tmpop['name'] : '';
+ // Return the original value if no match found
+ return isset($tmpop['name']) ? $tmpop['name'] : $val;
}, $data[$field['id']]);
+ } elseif (isset($data[$field['id']])) {
+ // Handle single select values
+ $tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($field, $data) {
+ return $op['id'] === $data[$field['id']] || $op['name'] === $data[$field['id']];
+ });
+ $data[$field['id']] = isset($tmpop['name']) ? $tmpop['name'] : $data[$field['id']];
}
}
if (FormLogicPropertyResolver::isRequired($property, $data)) {
diff --git a/api/tests/Feature/Forms/FormLogicTest.php b/api/tests/Feature/Forms/FormLogicTest.php
index 456824547..81d038c4d 100644
--- a/api/tests/Feature/Forms/FormLogicTest.php
+++ b/api/tests/Feature/Forms/FormLogicTest.php
@@ -113,3 +113,238 @@
],
]);
});
+
+it('preserves multi-select values during validation with logic conditions', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+
+ // Create a form with a multi-select field and a text field that has logic based on the multi-select
+ $form = $this->createForm($user, $workspace, [
+ 'properties' => [
+ [
+ 'id' => 'multi_select_field',
+ 'name' => 'Multi Select Field',
+ 'type' => 'multi_select',
+ 'hidden' => false,
+ 'required' => true,
+ 'multi_select' => [
+ 'options' => [
+ ['id' => 'option1', 'name' => 'Option 1'],
+ ['id' => 'option2', 'name' => 'Option 2'],
+ ['id' => 'option3', 'name' => 'Option 3']
+ ]
+ ]
+ ],
+ [
+ 'id' => 'text_field',
+ 'name' => 'Text Field',
+ 'type' => 'text',
+ 'hidden' => false,
+ 'required' => false,
+ 'logic' => [
+ 'conditions' => [
+ 'operatorIdentifier' => 'and',
+ 'children' => [
+ [
+ 'identifier' => 'multi_select',
+ 'value' => [
+ 'operator' => 'contains',
+ 'property_meta' => [
+ 'id' => 'multi_select_field',
+ 'type' => 'multi_select'
+ ],
+ 'value' => 'Option 1'
+ ]
+ ]
+ ]
+ ],
+ 'actions' => ['require-answer']
+ ]
+ ]
+ ]
+ ]);
+
+ // Submit form data with multi-select values
+ $formData = [
+ 'multi_select_field' => ['Option 1', 'Option 2']
+ ];
+
+ ray($formData)->blue('Original form data');
+
+ $response = $this->postJson(route('forms.answer', $form->slug), $formData);
+
+ // The validation should fail because text_field is required when Option 1 is selected
+ $response->assertStatus(422)
+ ->assertJson([
+ 'message' => 'The Text Field field is required.',
+ 'errors' => [
+ 'text_field' => ['The Text Field field is required.']
+ ]
+ ]);
+
+ // Check that the multi-select values were preserved in the validation data
+ ray($response->json())->purple('Response data');
+ expect($response->json('errors.multi_select_field'))->toBeNull();
+});
+
+it('correctly handles multi-select values with complex form logic', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+
+ // Create form with the specific fields from your example
+ $form = $this->createForm($user, $workspace, [
+ 'properties' => [
+ [
+ 'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
+ 'name' => 'Which event would you like to join?',
+ 'type' => 'multi_select',
+ 'hidden' => false,
+ 'required' => true,
+ 'multi_select' => [
+ 'options' => [
+ ['id' => 'Ashkelon Run (March 21)', 'name' => 'Ashkelon Run (March 21)'],
+ ['id' => 'Jerusalem Marathon (April 4)', 'name' => 'Jerusalem Marathon (April 4)'],
+ ['id' => 'Neither', 'name' => 'Neither']
+ ]
+ ]
+ ],
+ [
+ 'id' => '0ca51469-6bda-40f4-831c-084f066643d7',
+ 'name' => 'Jerusalem Marathon - Run Options',
+ 'type' => 'select',
+ 'hidden' => true,
+ 'required' => false,
+ 'select' => [
+ 'options' => [
+ ['id' => '10km (Most popular)', 'name' => '10km (Most popular)']
+ ]
+ ],
+ 'logic' => [
+ 'conditions' => [
+ 'operatorIdentifier' => 'and',
+ 'children' => [
+ [
+ 'identifier' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
+ 'value' => [
+ 'operator' => 'contains',
+ 'property_meta' => [
+ 'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
+ 'type' => 'multi_select'
+ ],
+ 'value' => 'Jerusalem Marathon (April 4)'
+ ]
+ ]
+ ]
+ ],
+ 'actions' => ['require-answer', 'show-block']
+ ]
+ ]
+ ]
+ ]);
+
+ // Submit form data matching your payload
+ $formData = [
+ '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Jerusalem Marathon (April 4)'],
+ '0ca51469-6bda-40f4-831c-084f066643d7' => '10km (Most popular)'
+ ];
+
+ ray($formData)->blue('Original form data');
+
+ $response = $this->postJson(route('forms.answer', $form->slug), $formData);
+
+ ray($response->json())->purple('Response data');
+
+ // Should be successful since all required fields are filled
+ $response->assertSuccessful()
+ ->assertJson([
+ 'type' => 'success',
+ 'message' => 'Form submission saved.'
+ ]);
+
+ // Now let's verify the saved submission data
+ $submission = $form->submissions()->first();
+ expect($submission->data['93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b'])->toBe(['Jerusalem Marathon (April 4)']);
+});
+
+it('preserves multi-select values when building validation rules', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+
+ // Create form with the exact fields from your real form
+ $form = $this->createForm($user, $workspace, [
+ 'properties' => [
+ [
+ 'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
+ 'name' => 'Which event would you like to join?',
+ 'type' => 'multi_select',
+ 'required' => true,
+ 'multi_select' => [
+ 'options' => [
+ ['id' => 'Jerusalem Marathon (April 4)', 'name' => 'Jerusalem Marathon (April 4)'],
+ ['id' => 'Ashkelon Run (March 21)', 'name' => 'Ashkelon Run (March 21)']
+ ]
+ ]
+ ],
+ [
+ 'id' => '72565901-c345-427a-b988-0ce3de9ad9f4',
+ 'name' => 'Additional Days',
+ 'type' => 'multi_select',
+ 'required' => false,
+ 'multi_select' => [
+ 'options' => [
+ ['id' => 'Thursday', 'name' => 'Thursday'],
+ ['id' => 'Sunday', 'name' => 'Sunday']
+ ]
+ ],
+ 'logic' => [
+ 'conditions' => [
+ 'operatorIdentifier' => 'and',
+ 'children' => [
+ [
+ 'identifier' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
+ 'value' => [
+ 'operator' => 'contains',
+ 'property_meta' => [
+ 'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b',
+ 'type' => 'multi_select'
+ ],
+ 'value' => 'Ashkelon Run (March 21)'
+ ]
+ ]
+ ]
+ ],
+ 'actions' => ['require-answer']
+ ]
+ ]
+ ]
+ ]);
+
+ // Submit form data with Jerusalem Marathon
+ $formData = [
+ '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Jerusalem Marathon (April 4)']
+ ];
+
+ ray($formData)->blue('Original form data');
+
+ $response = $this->postJson(route('forms.answer', $form->slug), $formData);
+
+ ray($response->json())->purple('Response data');
+
+ // Should be successful since Jerusalem Marathon doesn't require Additional Days
+ $response->assertSuccessful();
+
+ // Now try with Ashkelon Run which requires Additional Days
+ $formData = [
+ '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Ashkelon Run (March 21)']
+ ];
+
+ $response = $this->postJson(route('forms.answer', $form->slug), $formData);
+
+ // Should fail because Additional Days is required when Ashkelon Run is selected
+ $response->assertStatus(422)
+ ->assertJson([
+ 'errors' => [
+ '72565901-c345-427a-b988-0ce3de9ad9f4' => ['The Additional Days field is required.']
+ ]
+ ]);
+});
From 13c40f6e548e3decefe7e08932f5a6258cc1cdee Mon Sep 17 00:00:00 2001
From: Chirag Chhatrala <60499540+chiragchhatrala@users.noreply.github.com>
Date: Thu, 28 Nov 2024 20:42:24 +0530
Subject: [PATCH 3/5] Support for Rich Text Input (#630)
* Support for Rich Text Input
* rich text support for prefilled
* apply Purify for rich_text
---------
Co-authored-by: Julien Nahum
---
api/app/Http/Requests/AnswerFormRequest.php | 6 ++++++
client/components/open/forms/OpenFormField.vue | 1 +
.../open/forms/fields/components/FieldOptions.vue | 9 ++++++++-
client/components/open/tables/OpenTable.vue | 1 +
client/components/open/tables/components/OpenText.vue | 4 +---
client/data/blocks_types.json | 9 +++++++++
6 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/api/app/Http/Requests/AnswerFormRequest.php b/api/app/Http/Requests/AnswerFormRequest.php
index 61d67f4c8..d9dec34f6 100644
--- a/api/app/Http/Requests/AnswerFormRequest.php
+++ b/api/app/Http/Requests/AnswerFormRequest.php
@@ -14,6 +14,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
+use Stevebauman\Purify\Facades\Purify;
class AnswerFormRequest extends FormRequest
{
@@ -175,6 +176,7 @@ private function getPropertyRules($property): array
{
switch ($property['type']) {
case 'text':
+ case 'rich_text':
case 'signature':
return ['string'];
case 'number':
@@ -282,6 +284,10 @@ protected function prepareForValidation()
if ($property['type'] === 'phone_number' && (!isset($property['use_simple_text_input']) || !$property['use_simple_text_input']) && $receivedValue && in_array($receivedValue, $countryCodeMapper)) {
$mergeData[$property['id']] = null;
}
+
+ if ($property['type'] === 'rich_text' && $receivedValue) {
+ $mergeData[$property['id']] = Purify::clean($receivedValue);
+ }
});
$this->merge($mergeData);
diff --git a/client/components/open/forms/OpenFormField.vue b/client/components/open/forms/OpenFormField.vue
index 1d1747821..f145cdfe8 100644
--- a/client/components/open/forms/OpenFormField.vue
+++ b/client/components/open/forms/OpenFormField.vue
@@ -210,6 +210,7 @@ export default {
}
return {
text: 'TextInput',
+ rich_text: 'RichTextAreaInput',
number: 'TextInput',
rating: 'RatingInput',
scale: 'ScaleInput',
diff --git a/client/components/open/forms/fields/components/FieldOptions.vue b/client/components/open/forms/fields/components/FieldOptions.vue
index cb05c3a00..cbfba69f3 100644
--- a/client/components/open/forms/fields/components/FieldOptions.vue
+++ b/client/components/open/forms/fields/components/FieldOptions.vue
@@ -448,8 +448,15 @@
:multiple="field.multiple === true"
:move-to-form-assets="true"
/>
+
-
- {{ value }}
-
+