Tell scheduling apart from unpublishing in the history

Giving a record a future date was recorded as "unpublished", and announced
to webhooks as such. The history now compares before and after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 10:09:03 +02:00
parent 1fe1e67773
commit 70521d4bc9
7 changed files with 35 additions and 15 deletions

View File

@@ -51,6 +51,7 @@ class RevisionsRelationManager extends RelationManager
->color(fn (string $state) => match ($state) { ->color(fn (string $state) => match ($state) {
'published' => 'success', 'published' => 'success',
'unpublished' => 'warning', 'unpublished' => 'warning',
'scheduled' => 'info',
'created' => 'info', 'created' => 'info',
'deleted', 'erased' => 'danger', 'deleted', 'erased' => 'danger',
default => 'gray', default => 'gray',
@@ -77,7 +78,7 @@ class RevisionsRelationManager extends RelationManager
->filters([ ->filters([
SelectFilter::make('event') SelectFilter::make('event')
->label(__('panel.revisions.fields.event')) ->label(__('panel.revisions.fields.event'))
->options(fn () => collect(['created', 'updated', 'published', 'unpublished', 'deleted', 'restored', 'erased']) ->options(fn () => collect(['created', 'updated', 'published', 'scheduled', 'unpublished', 'deleted', 'restored', 'erased'])
->mapWithKeys(fn (string $e) => [$e => __("panel.revisions.events.{$e}")]) ->mapWithKeys(fn (string $e) => [$e => __("panel.revisions.events.{$e}")])
->all()), ->all()),
]) ])

View File

@@ -131,11 +131,23 @@ trait HasRevisions
// Ask the model itself: "published" means different things to a page // Ask the model itself: "published" means different things to a page
// with a status and a project with only a date. // with a status and a project with only a date.
if (method_exists($this, 'isPublishedNow')) { if (! method_exists($this, 'isPublishedNow')) {
return $this->isPublishedNow() ? 'published' : 'unpublished'; return 'updated';
} }
return 'updated'; // Before and after, because the change is what has a name: going live,
// coming off, or being given a date — which is neither, and which a
// history that only asked "is it live now?" called "unpublished".
$before = (clone $this)->setRawAttributes($this->getRawOriginal());
$now = $this->isPublishedNow();
$was = $before->isPublishedNow();
return match (true) {
$now && ! $was => 'published',
! $now && $was => 'unpublished',
! $now && method_exists($this, 'isScheduled') && $this->isScheduled() => 'scheduled',
default => 'updated',
};
} }
protected function revisionValue(mixed $value): mixed protected function revisionValue(mixed $value): mixed

View File

@@ -622,6 +622,7 @@ return [
'updated' => 'Editat', 'updated' => 'Editat',
'published' => 'Publicat', 'published' => 'Publicat',
'unpublished' => 'Despublicat', 'unpublished' => 'Despublicat',
'scheduled' => 'Programat',
'deleted' => 'Esborrat', 'deleted' => 'Esborrat',
'restored' => 'Recuperat', 'restored' => 'Recuperat',
'erased' => 'Eliminat del tot', 'erased' => 'Eliminat del tot',

View File

@@ -620,6 +620,7 @@ return [
'updated' => 'Edited', 'updated' => 'Edited',
'published' => 'Published', 'published' => 'Published',
'unpublished' => 'Unpublished', 'unpublished' => 'Unpublished',
'scheduled' => 'Scheduled',
'deleted' => 'Deleted', 'deleted' => 'Deleted',
'restored' => 'Recovered', 'restored' => 'Recovered',
'erased' => 'Erased for good', 'erased' => 'Erased for good',

View File

@@ -622,6 +622,7 @@ return [
'updated' => 'Editado', 'updated' => 'Editado',
'published' => 'Publicado', 'published' => 'Publicado',
'unpublished' => 'Despublicado', 'unpublished' => 'Despublicado',
'scheduled' => 'Programado',
'deleted' => 'Borrado', 'deleted' => 'Borrado',
'restored' => 'Recuperado', 'restored' => 'Recuperado',
'erased' => 'Eliminado del todo', 'erased' => 'Eliminado del todo',

View File

@@ -2795,7 +2795,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -2818,7 +2818,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -2841,7 +2841,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -2864,7 +2864,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -2887,7 +2887,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -2910,7 +2910,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -2933,7 +2933,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -2956,7 +2956,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -2979,7 +2979,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -3002,7 +3002,7 @@ paths:
- $ref: "#/components/parameters/PerPage" - $ref: "#/components/parameters/PerPage"
- name: event - name: event
in: query in: query
description: "`created`, `updated`, `published`, `unpublished`, `deleted`, `restored` or `erased`." description: "`created`, `updated`, `published`, `scheduled`, `unpublished`, `deleted`, `restored` or `erased`."
schema: { type: string } schema: { type: string }
responses: responses:
"200": "200":
@@ -5710,7 +5710,7 @@ components:
type: object type: object
properties: properties:
id: { type: integer } id: { type: integer }
event: { type: string, enum: [created, updated, published, unpublished, deleted, restored, erased] } event: { type: string, enum: [created, updated, published, scheduled, unpublished, deleted, restored, erased] }
author: author:
type: object type: object
properties: properties:

View File

@@ -158,6 +158,10 @@ class ReviewAndHistoryApiTest extends TestCase
->assertJsonPath('data.awaiting_review', false) ->assertJsonPath('data.awaiting_review', false)
->assertJsonPath('data.published_at', $at->toIso8601String()); ->assertJsonPath('data.published_at', $at->toIso8601String());
// A date in the future is neither live nor taken down, and the
// history says so.
$this->assertSame('scheduled', $project->revisions()->first()->event);
$this->postJson("/api/v1/projects/{$project->id}/unpublish") $this->postJson("/api/v1/projects/{$project->id}/unpublish")
->assertOk() ->assertOk()
->assertJsonPath('data.published_at', null); ->assertJsonPath('data.published_at', null);