From 1b15597e0e1d4afdebf2581d83d86ded7bb49db5 Mon Sep 17 00:00:00 2001 From: Chirag <103994754+chiragnotionforms@users.noreply.github.com> Date: Wed, 25 Jan 2023 20:40:33 +0530 Subject: [PATCH] Fix logic on hidden, loading on edit submission (#75) --- app/Jobs/Form/StoreFormSubmissionJob.php | 2 +- app/Mail/Forms/SubmissionConfirmationMail.php | 2 +- app/Rules/FormPropertyLogicRule.php | 34 ++++++++--- .../Forms/FormLogicConditionChecker.php | 4 +- .../Forms/FormLogicPropertyResolver.php | 25 ++++++++ app/Service/Forms/FormSubmissionFormatter.php | 24 +++++++- .../js/components/open/forms/OpenForm.vue | 18 +++--- resources/js/pages/forms/show-public.vue | 17 ++++-- resources/js/store/modules/open/records.js | 58 +++++++++++++++++++ tests/Feature/Forms/FormPropertyLogicTest.php | 2 +- 10 files changed, 159 insertions(+), 27 deletions(-) create mode 100644 resources/js/store/modules/open/records.js diff --git a/app/Jobs/Form/StoreFormSubmissionJob.php b/app/Jobs/Form/StoreFormSubmissionJob.php index a280bf0..1dda78d 100644 --- a/app/Jobs/Form/StoreFormSubmissionJob.php +++ b/app/Jobs/Form/StoreFormSubmissionJob.php @@ -45,7 +45,7 @@ class StoreFormSubmissionJob implements ShouldQueue $this->storeSubmission($formData); - $formData["submission_id"] = $this->submissionData['submission_id'] ?? ''; + $formData["submission_id"] = $this->submissionData['submission_id'] ?? null; FormSubmitted::dispatch($this->form, $formData); } diff --git a/app/Mail/Forms/SubmissionConfirmationMail.php b/app/Mail/Forms/SubmissionConfirmationMail.php index f3a2d3f..9c2f30e 100644 --- a/app/Mail/Forms/SubmissionConfirmationMail.php +++ b/app/Mail/Forms/SubmissionConfirmationMail.php @@ -43,7 +43,7 @@ class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue 'fields' => $formatter->getFieldsWithValue(), 'form' => $form, 'noBranding' => $form->no_branding, - 'submission_id' => isset($this->event->data['submission_id']) ? $this->event->data['submission_id'] : '' + 'submission_id' => $this->event->data['submission_id'] ?? null ]); } diff --git a/app/Rules/FormPropertyLogicRule.php b/app/Rules/FormPropertyLogicRule.php index 5d3d300..fae01c0 100644 --- a/app/Rules/FormPropertyLogicRule.php +++ b/app/Rules/FormPropertyLogicRule.php @@ -339,7 +339,7 @@ class FormPropertyLogicRule implements Rule, DataAwareRule ] ], 'does_not_contain' => [ - 'expected_type' => 'object', + 'expected_type' => ['object', 'string'], 'format' => [ 'type' => 'uuid', ] @@ -472,6 +472,7 @@ class FormPropertyLogicRule implements Rule, DataAwareRule private $isConditionCorrect = true; private $isActionCorrect = true; + private $conditionErrors = []; private $field = []; private $data = []; @@ -480,26 +481,31 @@ class FormPropertyLogicRule implements Rule, DataAwareRule if (!isset($condition['value'])) { $this->isConditionCorrect = false; + $this->conditionErrors[] = 'missing condition body'; return; } if (!isset($condition['value']['property_meta'])) { $this->isConditionCorrect = false; + $this->conditionErrors[] = 'missing condition property'; return; } if (!isset($condition['value']['property_meta']['type'])) { $this->isConditionCorrect = false; + $this->conditionErrors[] = 'missing condition property type'; return; } if (!isset($condition['value']['operator'])) { $this->isConditionCorrect = false; + $this->conditionErrors[] = 'missing condition operator'; return; } if (!isset($condition['value']['value'])) { $this->isConditionCorrect = false; + $this->conditionErrors[] = 'missing condition value'; return; } @@ -509,14 +515,18 @@ class FormPropertyLogicRule implements Rule, DataAwareRule if (!isset(self::CONDITION_MAPPING[$typeField])) { $this->isConditionCorrect = false; + $this->conditionErrors[] = 'configuration not found for condition type'; return; } if (!isset(self::CONDITION_MAPPING[$typeField]['comparators'][$operator])) { $this->isConditionCorrect = false; + $this->conditionErrors[] = 'configuration not found for condition operator'; return; } + // TODO: find what's causing the issue when saving this validation rule :( + $type = self::CONDITION_MAPPING[$typeField]['comparators'][$operator]['expected_type']; if (is_array($type)) { @@ -532,6 +542,7 @@ class FormPropertyLogicRule implements Rule, DataAwareRule } else { if (!$this->valueHasCorrectType($type, $value)) { $this->isConditionCorrect = false; + $this->conditionErrors[] = 'wrong type of condition value'; } } } @@ -553,16 +564,19 @@ class FormPropertyLogicRule implements Rule, DataAwareRule { if (isset($conditions['operatorIdentifier'])) { if (($conditions['operatorIdentifier'] !== 'and') && ($conditions['operatorIdentifier'] !== 'or')) { + $this->conditionErrors[] = 'missing operator'; $this->isConditionCorrect = false; return; } if (isset($conditions['operatorIdentifier']['children'])) { + $this->conditionErrors[] = 'extra condition'; $this->isConditionCorrect = false; return; } if (!is_array($conditions['children'])) { + $this->conditionErrors[] = 'wrong sub-condition type'; $this->isConditionCorrect = false; return; } @@ -616,14 +630,17 @@ class FormPropertyLogicRule implements Rule, DataAwareRule */ public function message() { - $errorList = []; - if(!$this->isConditionCorrect){ - $errorList[] = "The logic conditions for ".$this->field['name']." are not complete."; + $message = null; + if (! $this->isConditionCorrect) { + $message = 'The logic conditions for '.$this->field['name'].' are not complete.'; + } else if (! $this->isActionCorrect) { + $message = 'The logic actions for '.$this->field['name'].' are not valid.'; } - if(!$this->isActionCorrect){ - $errorList[] = "The logic actions for ".$this->field['name']." are not valid."; + if (count($this->conditionErrors) > 0) { + return $message . ' Error detail(s): '.implode(', ', $this->conditionErrors); } - return $errorList; + + return $message; } /** @@ -635,6 +652,9 @@ class FormPropertyLogicRule implements Rule, DataAwareRule public function setData($data) { $this->data = $data; + $this->isConditionCorrect = true; + $this->isActionCorrect = true; + $this->conditionErrors = []; return $this; } diff --git a/app/Service/Forms/FormLogicConditionChecker.php b/app/Service/Forms/FormLogicConditionChecker.php index d2edd7f..fef7625 100644 --- a/app/Service/Forms/FormLogicConditionChecker.php +++ b/app/Service/Forms/FormLogicConditionChecker.php @@ -94,7 +94,7 @@ class FormLogicConditionChecker return str_starts_with($fieldValue, $condition['value']); } - private function checkendsWith ($condition, $fieldValue): bool { + private function checkEndsWith ($condition, $fieldValue): bool { return str_ends_with($fieldValue, $condition['value']); } @@ -206,7 +206,7 @@ class FormLogicConditionChecker case 'starts_with': return $this->checkStartsWith($propertyCondition, $value); case 'ends_with': - return $this->checkendsWith($propertyCondition, $value); + return $this->checkEndsWith($propertyCondition, $value); case 'is_empty': return $this->checkIsEmpty($propertyCondition, $value); case 'is_not_empty': diff --git a/app/Service/Forms/FormLogicPropertyResolver.php b/app/Service/Forms/FormLogicPropertyResolver.php index 0897525..6e3f6f8 100644 --- a/app/Service/Forms/FormLogicPropertyResolver.php +++ b/app/Service/Forms/FormLogicPropertyResolver.php @@ -23,6 +23,11 @@ class FormLogicPropertyResolver return (new self($property, $values))->shouldBeRequired(); } + public static function isHidden(array $property, array $values): bool + { + return (new self($property, $values))->shouldBeHidden(); + } + public function shouldBeRequired(): bool { if(!isset($this->property['required'])){ @@ -42,4 +47,24 @@ class FormLogicPropertyResolver return $this->property['required']; } } + + public function shouldBeHidden(): bool + { + if (! isset($this->property['hidden'])) { + return false; + } + + if (!$this->logic) { + return $this->property['hidden']; + } + + $conditionsMet = FormLogicConditionChecker::conditionsMet($this->logic['conditions'], $this->formData); + if ($conditionsMet && $this->property['hidden'] && count($this->logic['actions']) > 0 && in_array('show-block', $this->logic['actions'])) { + return false; + } elseif ($conditionsMet && !$this->property['hidden'] && count($this->logic['actions']) > 0 && in_array('hide-block', $this->logic['actions'])) { + return true; + } else { + return $this->property['hidden']; + } + } } diff --git a/app/Service/Forms/FormSubmissionFormatter.php b/app/Service/Forms/FormSubmissionFormatter.php index e4416d4..c13bb25 100644 --- a/app/Service/Forms/FormSubmissionFormatter.php +++ b/app/Service/Forms/FormSubmissionFormatter.php @@ -29,8 +29,14 @@ class FormSubmissionFormatter private $showRemovedFields = false; + /** + * Logic resolver needs an array id => value, so we create it here + */ + private $idFormData = null; + public function __construct(private Form $form, private array $formData) { + $this->initIdFormData(); } public function createLinks() @@ -88,9 +94,9 @@ class FormSubmissionFormatter continue; } - // If should hide hidden fields + // If hide hidden fields if (!$this->showHiddenFields) { - if (isset($field['hidden']) && $field['hidden']) { + if (FormLogicPropertyResolver::isHidden($field, $this->idFormData)) { continue; } } @@ -149,7 +155,7 @@ class FormSubmissionFormatter // If hide hidden fields if (!$this->showHiddenFields) { - if (isset($field['hidden']) && $field['hidden']) { + if (FormLogicPropertyResolver::isHidden($field, $this->idFormData)) { continue; } } @@ -204,4 +210,16 @@ class FormSubmissionFormatter return $transformedFields; } + private function initIdFormData() { + $formProperties = collect($this->form->properties); + foreach ($this->formData as $key => $value) { + $property = $formProperties->first(function ($item) use ($key) { + return $item['id'] == $key; + }); + if ($property) { + $this->idFormData[$property['id']] = $value; + } + } + } + } diff --git a/resources/js/components/open/forms/OpenForm.vue b/resources/js/components/open/forms/OpenForm.vue index 3a4f1d4..4d3e8c2 100644 --- a/resources/js/components/open/forms/OpenForm.vue +++ b/resources/js/components/open/forms/OpenForm.vue @@ -256,7 +256,7 @@ export default { } if (this.form.editable_submissions && this.form.submission_id) { - this.dataForm["submission_id"] = this.form.submission_id + this.dataForm.submission_id = this.form.submission_id } this.$emit('submit', this.dataForm, this.onSubmissionFailure) @@ -290,18 +290,20 @@ export default { } }, async getSubmissionData() { - if (!this.form || !this.form.editable_submissions || !this.form.submission_id) { - return null - } - const response = await axios.get('/api/forms/' + this.form.slug + '/submissions/' + this.form.submission_id) - return response.data + if (!this.form || !this.form.editable_submissions || !this.form.submission_id) { return null } + await this.$store.dispatch('open/records/loadRecord', + axios.get('/api/forms/' + this.form.slug + '/submissions/' + this.form.submission_id).then((response) => { + return { submission_id: this.form.submission_id, ...response.data.data } + }) + ) + return this.$store.getters['open/records/getById'](this.form.submission_id) }, async initForm() { if (this.isPublicFormPage && this.form.editable_submissions) { - let urlParam = new URLSearchParams(window.location.search) + const urlParam = new URLSearchParams(window.location.search) if (urlParam && urlParam.get('submission_id')) { this.form.submission_id = urlParam.get('submission_id') - const {data} = await this.getSubmissionData() + const data = await this.getSubmissionData() if (data !== null && data) { this.dataForm = new Form(data) return diff --git a/resources/js/pages/forms/show-public.vue b/resources/js/pages/forms/show-public.vue index 8d50af1..d252881 100644 --- a/resources/js/pages/forms/show-public.vue +++ b/resources/js/pages/forms/show-public.vue @@ -34,7 +34,16 @@

- + @@ -125,7 +134,6 @@ export default { data () { return { - loading: false, submitted: false } }, @@ -160,7 +168,8 @@ export default { computed: { ...mapState({ forms: state => state['open/forms'].content, - formLoading: state => state['open/forms'].loading + formLoading: state => state['open/forms'].loading, + recordLoading: state => state['open/records'].loading }), formSlug () { return this.$route.params.slug @@ -175,7 +184,7 @@ export default { return this.form ? this.form.title : 'Create beautiful forms' }, metaDescription () { - return (this.form && this.form.description) ? this.form.description.substring(0,160) : null + return (this.form && this.form.description) ? this.form.description.substring(0, 160) : null }, metaImage () { return (this.form && this.form.cover_picture) ? this.form.cover_picture : null diff --git a/resources/js/store/modules/open/records.js b/resources/js/store/modules/open/records.js new file mode 100644 index 0000000..8bb0e88 --- /dev/null +++ b/resources/js/store/modules/open/records.js @@ -0,0 +1,58 @@ +import axios from 'axios' + +export const namespaced = true +export const workspaceEndpoint = '/api/open/records/' + +/** + * Loads records from database + */ + +// state +export const state = { + content: [], + loading: false +} + +// getters +export const getters = { + getById: (state) => (id) => { + if (state.content.length === 0) return null + return state.content.find(item => item.submission_id === id) + } +} + +// mutations +export const mutations = { + set (state, items) { + state.content = items + }, + addOrUpdate (state, item) { + state.content = state.content.filter((val) => val.id !== item.id) + state.content.push(item) + }, + remove (state, itemId) { + state.content = state.content.filter((val) => val.id !== itemId) + }, + startLoading () { + state.loading = true + }, + stopLoading () { + state.loading = false + } +} + +// actions +export const actions = { + resetState (context) { + context.commit('set', []) + context.commit('stopLoading') + }, + loadRecord (context, request) { + context.commit('set', []) + context.commit('startLoading') + return request.then((data) => { + context.commit('addOrUpdate', data) + context.commit('stopLoading') + }) + } +} \ No newline at end of file diff --git a/tests/Feature/Forms/FormPropertyLogicTest.php b/tests/Feature/Forms/FormPropertyLogicTest.php index 4c625e9..430aea1 100644 --- a/tests/Feature/Forms/FormPropertyLogicTest.php +++ b/tests/Feature/Forms/FormPropertyLogicTest.php @@ -134,5 +134,5 @@ it('can validate form logic rules for conditions', function () { $validatorObj = $this->app['validator']->make($data, $rules); $this->assertFalse($validatorObj->passes()); - expect($validatorObj->errors()->messages()['properties.0.logic'][0])->toBe("The logic conditions for Name are not complete."); + expect($validatorObj->errors()->messages()['properties.0.logic'][0])->toBe("The logic conditions for Name are not complete. Error detail(s): missing condition value"); }); \ No newline at end of file