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 @@ - 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 }} - +