diff --git a/lang/en/messages.php b/lang/en/messages.php index f6339ba62df..968ceade458 100644 --- a/lang/en/messages.php +++ b/lang/en/messages.php @@ -139,7 +139,7 @@ 'form_configure_store_instructions' => 'Disable to stop storing submissions. Events and email notifications will still be sent.', 'form_configure_title_instructions' => 'Use a call to action, such as \'Contact Us\'.', 'form_create_description' => 'Get started by creating your first form.', - 'form_export_filtered_description' => 'Exports submissions with current filters and visible columns.', + 'form_export_filtered_description' => 'Exports submissions with current filters.', 'getting_started_widget_collections' => 'Collections hold the different content types that make up your site, helping you stay organized.', 'getting_started_widget_docs' => 'Discover everything Statamic can do, and learn how to use its powerful features the right way.', 'getting_started_widget_header' => 'Getting Started with Statamic', diff --git a/resources/js/pages/forms/Show.vue b/resources/js/pages/forms/Show.vue index d96bb35f47c..6b1524aac66 100644 --- a/resources/js/pages/forms/Show.vue +++ b/resources/js/pages/forms/Show.vue @@ -23,8 +23,7 @@ const listingParameters = ref({}); const hasFilteredScope = computed(() => { const params = listingParameters.value; - const hasSortOverride = (params.sort && params.sort !== 'datestamp') || (params.order && params.order !== 'desc'); - return !!(params.search || params.filters || hasSortOverride); + return !!(params.search || params.filters); }); function openExportModal() { @@ -40,18 +39,19 @@ function exportSubmissions() { let url = exporter.downloadUrl; + const params = listingParameters.value; + const query = new URLSearchParams(); + if (params.sort) query.set('sort', params.sort); + if (params.order) query.set('order', params.order); + if (exportScope.value === 'filtered') { - const params = listingParameters.value; - const query = new URLSearchParams(); if (params.search) query.set('search', params.search); - if (params.sort) query.set('sort', params.sort); - if (params.order) query.set('order', params.order); if (params.filters) query.set('filters', params.filters); - - const separator = url.includes('?') ? '&' : '?'; - url += separator + query.toString(); } + const separator = url.includes('?') ? '&' : '?'; + url += separator + query.toString(); + window.open(url, '_blank'); exportModalOpen.value = false; } diff --git a/tests/Feature/Forms/ExportSubmissionsTest.php b/tests/Feature/Forms/ExportSubmissionsTest.php new file mode 100644 index 00000000000..028b6616bda --- /dev/null +++ b/tests/Feature/Forms/ExportSubmissionsTest.php @@ -0,0 +1,94 @@ +fakeStacheDirectory.'/forms'; + } + + #[Test] + public function it_exports_submissions_sorted_by_a_column() + { + $form = $this->createFormWithSubmissions(); + + $response = $this + ->actingAs(tap(User::make()->makeSuper())->save()) + ->export($form, 'csv', ['sort' => 'name', 'order' => 'asc']) + ->assertOk(); + + $this->assertEquals(['Alice', 'Bravo', 'Charlie'], $this->namesFromCsv($response->getContent())); + } + + #[Test] + public function it_exports_submissions_sorted_in_the_requested_direction() + { + $form = $this->createFormWithSubmissions(); + + $response = $this + ->actingAs(tap(User::make()->makeSuper())->save()) + ->export($form, 'csv', ['sort' => 'name', 'order' => 'desc']) + ->assertOk(); + + $this->assertEquals(['Charlie', 'Bravo', 'Alice'], $this->namesFromCsv($response->getContent())); + } + + #[Test] + public function it_exports_submissions_sorted_by_the_listings_default_date_column() + { + $form = $this->createFormWithSubmissions(); + + $response = $this + ->actingAs(tap(User::make()->makeSuper())->save()) + ->export($form, 'csv', ['sort' => 'datestamp', 'order' => 'desc']) + ->assertOk(); + + $this->assertEquals(['Bravo', 'Alice', 'Charlie'], $this->namesFromCsv($response->getContent())); + } + + private function createFormWithSubmissions() + { + $blueprint = Blueprint::makeFromFields(['name' => ['type' => 'text']]); + Blueprint::partialMock()->shouldReceive('find')->with('forms.test')->andReturn($blueprint); + + $form = tap(Form::make('test'))->save(); + + // A submission's date is derived from its ID, so the IDs are set explicitly + // to keep the dates distinct and the resulting sort order deterministic. + collect(['Charlie' => '1700000001', 'Alice' => '1700000002', 'Bravo' => '1700000003']) + ->each(fn ($id, $name) => FormSubmission::make()->form($form)->id($id)->data(['name' => $name])->save()); + + return $form; + } + + private function export($form, $type, $params = []) + { + return $this->get(cp_route('forms.export', array_merge([ + 'form' => $form->handle(), + 'type' => $type, + ], $params))); + } + + private function namesFromCsv($csv) + { + return collect(explode("\n", trim($csv))) + ->slice(1) + ->map(fn ($line) => str_getcsv($line)[0]) + ->values() + ->all(); + } +}