From ef83ffcf7778872f529f55337ae8c39956297e5e Mon Sep 17 00:00:00 2001 From: Favour Olayinka Date: Sat, 3 Feb 2024 12:50:57 +0100 Subject: [PATCH] 3a703 admin edit submission (#305) * wip: admin submission edit feature * wip: refresh form submission after update * wip: connect submissions page data to store * Fixed the submission loading issue * test: admin edit submission feature test * Fix pending submission, editabe submission & more (#306) --------- Co-authored-by: Julien Nahum --- .../Forms/FormSubmissionController.php | 18 +++++ .../Middleware/Form/ResolveFormMiddleware.php | 26 +++++++ app/Jobs/Form/StoreFormSubmissionJob.php | 9 +++ .../open/components/EditSubmissionModal.vue | 42 +++++++++++ .../open/components/RecordOperations.vue | 22 ++++-- .../open/forms/OpenCompleteForm.vue | 5 +- client/components/open/forms/OpenForm.vue | 10 ++- .../open/forms/components/FormSubmissions.vue | 70 +++++++++---------- client/components/open/tables/OpenTable.vue | 5 +- client/composables/forms/pendingSubmission.js | 5 +- client/pages/forms/[slug]/show.vue | 1 + .../pages/forms/[slug]/show/submissions.vue | 5 ++ client/stores/records.js | 58 ++++----------- routes/api.php | 2 + .../Submissions/EditSubmissionTest.php | 61 ++++++++++++++++ 15 files changed, 245 insertions(+), 94 deletions(-) create mode 100644 app/Http/Middleware/Form/ResolveFormMiddleware.php create mode 100644 client/components/open/components/EditSubmissionModal.vue create mode 100644 tests/Feature/Submissions/EditSubmissionTest.php diff --git a/app/Http/Controllers/Forms/FormSubmissionController.php b/app/Http/Controllers/Forms/FormSubmissionController.php index 2eec417..1c4e007 100644 --- a/app/Http/Controllers/Forms/FormSubmissionController.php +++ b/app/Http/Controllers/Forms/FormSubmissionController.php @@ -6,7 +6,11 @@ use App\Http\Controllers\Controller; use App\Http\Resources\FormSubmissionResource; use App\Models\Forms\Form; use App\Exports\FormSubmissionExport; +use App\Http\Requests\AnswerFormRequest; +use App\Jobs\Form\StoreFormSubmissionJob; +use App\Models\Forms\FormSubmission; use App\Service\Forms\FormSubmissionFormatter; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Maatwebsite\Excel\Facades\Excel; @@ -27,6 +31,20 @@ class FormSubmissionController extends Controller return FormSubmissionResource::collection($form->submissions()->paginate(100)); } + public function update(AnswerFormRequest $request, $id, $submissionId) + { + $form = $request->form; + $this->authorize('update', $form); + $job = new StoreFormSubmissionJob($request->form, $request->validated()); + $job->setSubmissionId($submissionId)->handle(); + + $data = new FormSubmissionResource(FormSubmission::findOrFail($submissionId)); + return $this->success([ + 'message' => 'Record successfully updated.', + 'data' => $data + ]); + } + public function export(string $id) { $form = Form::findOrFail((int) $id); diff --git a/app/Http/Middleware/Form/ResolveFormMiddleware.php b/app/Http/Middleware/Form/ResolveFormMiddleware.php new file mode 100644 index 0000000..b2a6875 --- /dev/null +++ b/app/Http/Middleware/Form/ResolveFormMiddleware.php @@ -0,0 +1,26 @@ +route($routeParamName))->firstOrFail(); + $request->merge([ + 'form' => $form, + ]); + return $next($request); + } +} diff --git a/app/Jobs/Form/StoreFormSubmissionJob.php b/app/Jobs/Form/StoreFormSubmissionJob.php index 198bb2c..871ba9a 100644 --- a/app/Jobs/Form/StoreFormSubmissionJob.php +++ b/app/Jobs/Form/StoreFormSubmissionJob.php @@ -56,6 +56,12 @@ class StoreFormSubmissionJob implements ShouldQueue return $this->submissionId; } + public function setSubmissionId(int $id) + { + $this->submissionId = $id; + return $this; + } + private function storeSubmission(array $formData) { // Create or update record @@ -76,6 +82,9 @@ class StoreFormSubmissionJob implements ShouldQueue */ private function submissionToUpdate(): ?FormSubmission { + if($this->submissionId){ + return $this->form->submissions()->findOrFail($this->submissionId); + } if ($this->form->editable_submissions && isset($this->submissionData['submission_id']) && $this->submissionData['submission_id']) { $submissionId = $this->submissionData['submission_id'] ? Hashids::decode($this->submissionData['submission_id']) : false; $submissionId = $submissionId[0] ?? null; diff --git a/client/components/open/components/EditSubmissionModal.vue b/client/components/open/components/EditSubmissionModal.vue new file mode 100644 index 0000000..746ec2d --- /dev/null +++ b/client/components/open/components/EditSubmissionModal.vue @@ -0,0 +1,42 @@ + + \ No newline at end of file diff --git a/client/components/open/components/RecordOperations.vue b/client/components/open/components/RecordOperations.vue index 19c79d9..75d52bc 100644 --- a/client/components/open/components/RecordOperations.vue +++ b/client/components/open/components/RecordOperations.vue @@ -1,5 +1,13 @@ diff --git a/client/stores/records.js b/client/stores/records.js index b5d7c1f..7dde38f 100644 --- a/client/stores/records.js +++ b/client/stores/records.js @@ -1,49 +1,21 @@ import { defineStore } from 'pinia' - -export const namespaced = true +import { useContentStore } from '~/composables/stores/useContentStore' /** * Loads records from database */ -export const useRecordsStore = defineStore('records', { - state: () => ({ - content: [], - loading: false - }), - getters: { - getById: (state) => (id) => { - if (state.content.length === 0) return null - return state.content.find(item => item.submission_id === id) - } - }, - actions: { - set (items) { - this.content = items - }, - addOrUpdate (item) { - this.content = this.content.filter((val) => val.id !== item.id) - this.content.push(item) - }, - remove (itemId) { - this.content = this.content.filter((val) => val.id !== itemId) - }, - startLoading () { - this.loading = true - }, - stopLoading () { - this.loading = false - }, - resetState () { - this.set([]) - this.stopLoading() - }, - loadRecord (request) { - this.set([]) - this.startLoading() - return request.then((data) => { - this.addOrUpdate(data) - this.stopLoading() - }) - } +export const useRecordsStore = defineStore('records', ()=>{ + + const contentStore = useContentStore() + + const loadRecord = (request)=> { + contentStore.resetState() + contentStore.startLoading() + return request.then((data) => { + contentStore.save(data) + contentStore.stopLoading() + }) } -}) \ No newline at end of file + return {...contentStore, loadRecord} + +}) diff --git a/routes/api.php b/routes/api.php index eaa87d6..9ae138f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -18,6 +18,7 @@ use App\Http\Controllers\Forms\RecordController; use App\Http\Controllers\WorkspaceController; use App\Http\Controllers\TemplateController; use App\Http\Controllers\Forms\Integration\FormZapierWebhookController; +use App\Http\Middleware\Form\ResolveFormMiddleware; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Storage; @@ -85,6 +86,7 @@ Route::group(['middleware' => 'auth:api'], function () { Route::delete('/{id}', [FormController::class, 'destroy'])->name('destroy'); Route::get('/{id}/submissions', [FormSubmissionController::class, 'submissions'])->name('submissions'); + Route::put('/{id}/submissions/{submission_id}', [FormSubmissionController::class, 'update'])->name('submissions.update')->middleware([ResolveFormMiddleware::class]); Route::get('/{id}/submissions/export', [FormSubmissionController::class, 'export'])->name('submissions.export'); Route::get('/{id}/submissions/file/{filename}', [FormSubmissionController::class, 'submissionFile']) ->middleware('signed') diff --git a/tests/Feature/Submissions/EditSubmissionTest.php b/tests/Feature/Submissions/EditSubmissionTest.php new file mode 100644 index 0000000..5bf2b0f --- /dev/null +++ b/tests/Feature/Submissions/EditSubmissionTest.php @@ -0,0 +1,61 @@ +actingAsUser(); + $workspace = $this->createUserWorkspace($user); + $form = $this->makeForm($user, $workspace); + $form = $this->createForm($user, $workspace, [ + 'closes_at' => \Carbon\Carbon::now()->addDays(1)->toDateTimeString(), + ]); + $formData = FormSubmissionDataFactory::generateSubmissionData($form, ['text' => 'John']); + $textFieldId = array_keys($formData)[0]; + $updatedFormData = $formData; + $updatedFormTextValue = "Updated text"; + $updatedFormData[$textFieldId] = $updatedFormTextValue; + $this->postJson(route('forms.answer', $form->slug), $formData) + ->assertSuccessful() + ->assertJson([ + 'type' => 'success', + 'message' => 'Form submission saved.' + ]); + $submission = $form->submissions()->first(); + $updateResponse = $this->putJson(route('open.forms.submissions.update', ['id'=>$form->id, 'submission_id' => $submission->id]), $updatedFormData) + ->assertSuccessful() + ->assertJson([ + 'type' => 'success', + 'message' => 'Record successfully updated.' + ]); + $expectedTextString = $updateResponse->json('data')['data'][$textFieldId]; + expect($expectedTextString)->toBe($updatedFormTextValue); + $updatedSubmission = $form->submissions()->first(); + expect($updatedSubmission->data[$textFieldId])->toBe($updatedFormTextValue); +}); + +it('cannot update form submission as non admin', function () { + $secondUser =$this->createUser(); + $user = $this->actingAsUser(); + $workspace = $this->createUserWorkspace($user); + $form = $this->makeForm($user, $workspace); + $form = $this->createForm($user, $workspace, [ + 'closes_at' => \Carbon\Carbon::now()->addDays(1)->toDateTimeString(), + ]); + $formData = FormSubmissionDataFactory::generateSubmissionData($form, ['text' => 'John']); + $textFieldId = array_keys($formData)[0]; + $updatedFormData = $formData; + $updatedFormTextValue = "Updated text"; + $updatedFormData[$textFieldId] = $updatedFormTextValue; + $this->postJson(route('forms.answer', $form->slug), $formData) + ->assertSuccessful() + ->assertJson([ + 'type' => 'success', + 'message' => 'Form submission saved.' + ]); + $submission = $form->submissions()->first(); + $this->actingAs($secondUser); + $updateResponse = $this->putJson(route('open.forms.submissions.update', ['id'=>$form->id, 'submission_id' => $submission->id]), $updatedFormData) + ->assertStatus(403); +}); + +