Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Forms/Connectors/Webhook.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private function examplePayload(): string
$latestSubmission = null;

if (User::current()->can('viewSubmissions', $form)) {
$latestSubmission = $form->querySubmissions()->whereNull('partial')->orderBy('date', 'desc')->first();
$latestSubmission = $form->querySubmissions()->whereStatus('finalized')->orderBy('date', 'desc')->first();
}

return json_encode([
Expand Down
2 changes: 1 addition & 1 deletion src/Forms/Fieldtype.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public function getIndexItems($request)
return [
'id' => $form->handle(),
'title' => __($form->title()),
'submissions' => $form->submissions()->count(),
'submissions' => $form->querySubmissions()->whereStatus('finalized')->count(),
];
};

Expand Down
2 changes: 1 addition & 1 deletion src/Forms/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ private function submissionLimitReached(): bool

private function submissionCount(): int
{
$query = $this->querySubmissions()->whereNull('partial');
$query = $this->querySubmissions()->whereStatus('finalized');

if ($start = $this->submissionLimitPeriodStart()) {
$query->where('date', '>=', $start);
Expand Down
10 changes: 10 additions & 0 deletions src/Forms/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,23 @@ public function submissions()
{
$query = $this->form()->querySubmissions();

$this->queryStatus($query);
$this->queryConditions($query);
$this->queryScopes($query);
$this->queryOrderBys($query);

return $this->output($this->results($query));
}

protected function queryStatus($query)
{
if ($this->isQueryingCondition('status') || $this->isQueryingCondition('partial')) {
return;
}

$query->whereStatus('finalized');
}

/**
* Get the sort order for a collection.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private function latestSubmission(Form $form): ?Submission
return null;
}

return $form->querySubmissions()->whereNull('partial')->orderBy('date', 'desc')->first();
return $form->querySubmissions()->whereStatus('finalized')->orderBy('date', 'desc')->first();
}

private function sampleSubmission(Form $form, FakeSubmissionGenerator $generator): Submission
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Controllers/CP/Forms/FormsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function index(Request $request)
'id' => $form->handle(),
'title' => __($form->title()),
'status' => $form->status(),
'submissions' => $canViewSubmissions ? $form->querySubmissions()->where('site', Site::selected())->whereNull('partial')->count() : null,
'submissions' => $canViewSubmissions ? $form->querySubmissions()->where('site', Site::selected())->whereStatus('finalized')->count() : null,
'connections' => $canEdit ? FormConnector::all()->sum(fn ($connector) => $connector->forForm($form)->count() ?? 0) : null,
'show_url' => $form->showUrl(),
'submissions_url' => $form->submissionsUrl(),
Expand Down
2 changes: 1 addition & 1 deletion src/Jobs/DeletePartialFormSubmissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function handle(): void
$threshold = now()->subDays($days);

FormSubmission::query()
->where('partial', true)
->whereStatus('partial')
->where('date', '<', $threshold)
->get()
->each->delete();
Expand Down
5 changes: 1 addition & 4 deletions src/Query/Scopes/Filters/SubmissionStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ public function autoApply()

public function apply($query, $values)
{
match ($values['status']) {
'partial' => $query->where('partial', true),
default => $query->where('partial', '!=', true),
};
$query->whereStatus($values['status']);
}

public function badge($values)
Expand Down
10 changes: 10 additions & 0 deletions src/Stache/Query/SubmissionQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ public function whereIn($column, $values, $boolean = 'and')
return parent::whereIn($column, $values, $boolean);
}

public function whereStatus(string $status)
{
return match ($status) {
'any' => $this,
'finalized' => $this->where('partial', '!=', true),
'partial' => $this->where('partial', true),
default => throw new \Exception("Invalid status [$status]"),
};
}

public function orderBy($column, $direction = 'asc')
{
if ($column === 'datestamp') {
Expand Down
43 changes: 43 additions & 0 deletions tests/Feature/Forms/ExportSubmissionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,49 @@ public function it_exports_only_the_requested_columns()
->assertDontSee('email');
}

#[Test]
public function it_exports_all_submissions_including_partials()
{
$form = $this->createFormWithSubmissions();
FormSubmission::make()->form($form)->id('1700000004')->data(['name' => 'Delta'])->asPartial()->save();

$response = $this
->actingAs(tap(User::make()->makeSuper())->save())
->export($form, 'csv')
->assertOk();

$this->assertEqualsCanonicalizing(['Alice', 'Bravo', 'Charlie', 'Delta'], $this->namesFromCsv($response->getContent()));
}

#[Test]
public function it_exports_all_submissions_including_partials_when_sorted()
{
$form = $this->createFormWithSubmissions();
FormSubmission::make()->form($form)->id('1700000004')->data(['name' => 'Delta'])->asPartial()->save();

$response = $this
->actingAs(tap(User::make()->makeSuper())->save())
->export($form, 'csv', ['sort' => 'name', 'order' => 'asc'])
->assertOk();

$this->assertEquals(['Alice', 'Bravo', 'Charlie', 'Delta'], $this->namesFromCsv($response->getContent()));
}

#[Test]
public function it_exports_submissions_filtered_by_status()
{
$form = $this->createFormWithSubmissions();
FormSubmission::make()->form($form)->id('1700000004')->data(['name' => 'Delta'])->asPartial()->save();

$this->actingAs(tap(User::make()->makeSuper())->save());

$finalized = $this->export($form, 'csv', ['sort' => 'name', 'filters' => base64_encode(json_encode(['submission_status' => ['status' => 'finalized']]))]);
$this->assertEquals(['Alice', 'Bravo', 'Charlie'], $this->namesFromCsv($finalized->getContent()));

$partial = $this->export($form, 'csv', ['sort' => 'name', 'filters' => base64_encode(json_encode(['submission_status' => ['status' => 'partial']]))]);
$this->assertEquals(['Delta'], $this->namesFromCsv($partial->getContent()));
}

private function createFormWithSubmissions()
{
$blueprint = Blueprint::makeFromFields(['name' => ['type' => 'text']]);
Expand Down
19 changes: 19 additions & 0 deletions tests/Feature/Forms/ViewFormListingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,23 @@ private function makeSubmission($form, $site = null)
$submission->data(['name' => 'John Doe']);
$submission->save();
}

#[Test]
public function it_excludes_partial_submissions_from_the_submission_count()
{
$this->setTestRoles(['test' => ['access cp', 'view form submissions']]);
$user = tap(User::make()->assignRole('test'))->save();
$form = tap(Form::make('test'))->save();
$this->makeSubmission($form);
$form->makeSubmission()->asPartial()->save();

$this
->actingAs($user)
->get(cp_route('forms.index'))
->assertSuccessful()
->assertInertia(fn ($page) => $page
->component('forms/Index')
->where('forms.0.submissions', 1)
);
}
}
30 changes: 30 additions & 0 deletions tests/Forms/FieldtypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Tests\Forms;

use Illuminate\Http\Request;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Form;
use Statamic\Facades\User;
use Statamic\Forms\Fieldtype;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

class FieldtypeTest extends TestCase
{
use PreventSavingStacheItemsToDisk;

#[Test]
public function the_index_items_submission_count_excludes_partial_submissions()
{
$this->actingAs(tap(User::make()->makeSuper())->save());

$form = tap(Form::make('test'))->save();
$form->makeSubmission()->save();
$form->makeSubmission()->asPartial()->save();

$items = (new Fieldtype)->getIndexItems(new Request(['paginate' => false]));

$this->assertEquals(1, $items->firstWhere('id', 'test')['submissions']);
}
}
58 changes: 58 additions & 0 deletions tests/Forms/SubmissionQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,62 @@ public function submissions_are_found_using_offset()
$submissions = FormSubmission::query()->offset(1)->get();
$this->assertEquals(['b', 'c'], $submissions->map->get('id')->all());
}

private function createSubmissionsWithStatuses()
{
$form = tap(Form::make('test'))->save();
FormSubmission::make()->form($form)->data(['ref' => 'a'])->save();
FormSubmission::make()->form($form)->data(['ref' => 'b', 'partial' => true])->save();
FormSubmission::make()->form($form)->data(['ref' => 'c'])->save();
FormSubmission::make()->form($form)->data(['ref' => 'd', 'partial' => true])->save();

return $form;
}

private function ids($submissions)
{
return $submissions->map->get('ref')->sort()->values()->all();
}

#[Test]
public function it_filters_by_status()
{
$this->createSubmissionsWithStatuses();

$this->assertEquals(['a', 'c'], $this->ids(FormSubmission::query()->whereStatus('finalized')->get()));
$this->assertEquals(['b', 'd'], $this->ids(FormSubmission::query()->whereStatus('partial')->get()));
$this->assertEquals(['a', 'b', 'c', 'd'], $this->ids(FormSubmission::query()->whereStatus('any')->get()));
}

#[Test]
public function it_throws_an_exception_for_an_invalid_status()
{
$this->expectExceptionMessage('Invalid status [foo]');

FormSubmission::query()->whereStatus('foo');
}

#[Test]
public function the_repository_query_does_not_filter_by_status_by_default()
{
$form = $this->createSubmissionsWithStatuses();

$this->assertEquals(['a', 'b', 'c', 'd'], $this->ids(FormSubmission::query()->get()));
$this->assertEquals(['a', 'b', 'c', 'd'], $this->ids(FormSubmission::all()));
$this->assertEquals(['a', 'b', 'c', 'd'], $this->ids(FormSubmission::whereForm('test')));

$partial = FormSubmission::query()->where('partial', true)->first();
$this->assertEquals($partial, FormSubmission::find($partial->id()));
$this->assertEquals($partial, $form->submission($partial->id()));
}

#[Test]
public function the_form_query_does_not_filter_by_status_by_default()
{
$form = $this->createSubmissionsWithStatuses();

$this->assertEquals(['a', 'b', 'c', 'd'], $this->ids($form->querySubmissions()->get()));
$this->assertEquals(['a', 'b', 'c', 'd'], $this->ids($form->submissions()));
$this->assertEquals(['b', 'd'], $this->ids($form->querySubmissions()->whereStatus('partial')->get()));
}
}
14 changes: 14 additions & 0 deletions tests/Forms/SubmissionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Statamic\Events\SubmissionSaved;
use Statamic\Events\SubmissionSaving;
use Statamic\Facades\Form;
use Statamic\Facades\FormSubmission;
use Statamic\Facades\Site;
use Statamic\Forms\Connectors\Webhooks\SendWebhook;
use Statamic\Forms\CreateAssetsFromFileUploads;
Expand Down Expand Up @@ -566,4 +567,17 @@ public function it_clones_internal_collections()
$this->assertEquals('A', $submission->getSupplement('bar'));
$this->assertEquals('B', $clone->getSupplement('bar'));
}

#[Test]
public function deleting_the_form_deletes_partial_submissions_too()
{
$form = tap(Form::make('contact_us'))->save();
$finalized = tap($form->makeSubmission())->save();
$partial = tap($form->makeSubmission()->asPartial())->save();

$form->delete();

$this->assertNull(FormSubmission::find($finalized->id()));
$this->assertNull(FormSubmission::find($partial->id()));
}
}
28 changes: 28 additions & 0 deletions tests/Tags/Form/FormSubmissionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Tests\Tags\Form;

use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Form;

class FormSubmissionsTest extends FormTestCase
{
Expand Down Expand Up @@ -37,4 +38,31 @@ public function it_renders_submissions()
$this->assertStringContainsString('Email: san@holo.com<br>Message: foo', $output);
$this->assertStringContainsString('Email: foba@bett.com<br>Message: bar', $output);
}

#[Test]
public function it_only_renders_finalized_submissions_by_default()
{
$this->createSubmissions();

$output = $this->tag('{{ form:submissions in="contact" }}[{{ message }}]{{ /form:submissions }}');

$this->assertEquals('[finalized]', trim($output));
}

#[Test]
public function it_renders_submissions_filtered_by_status()
{
$this->createSubmissions();

$this->assertEquals('[partial]', trim($this->tag('{{ form:submissions in="contact" status:is="partial" }}[{{ message }}]{{ /form:submissions }}')));
$this->assertEquals('[finalized]', trim($this->tag('{{ form:submissions in="contact" status:is="finalized" }}[{{ message }}]{{ /form:submissions }}')));
$this->assertEquals('[finalized][partial]', trim($this->tag('{{ form:submissions in="contact" status:is="any" sort="message" }}[{{ message }}]{{ /form:submissions }}')));
}

private function createSubmissions()
{
$form = Form::find('contact');
$form->makeSubmission()->data(['email' => 'san@holo.com', 'message' => 'finalized'])->save();
$form->makeSubmission()->data(['email' => 'foba@bett.com', 'message' => 'partial'])->asPartial()->save();
}
}
Loading