Open a TaskStream board from the panel's project form
Some checks failed
Tests / test (push) Has been cancelled
Tests / test-postgres (push) Has been cancelled
Tests / mcp (push) Has been cancelled
Tests / lint (push) Has been cancelled

A "TaskStream" section on the create form (toggle and optional key,
suggested from the slug) opens the board before saving, as the person
signed in, with the Pireon token of their session. A refusal shows on the
key field and a TaskStream outage as a notification; either way nothing is
saved. The created notification carries the link, and the edit page has
an "Open in TaskStream" button.

Documented in the user manual (and its PDF) and the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 23:01:55 +02:00
parent cdfc8763a5
commit b32d6bf001
10 changed files with 282 additions and 4 deletions

View File

@@ -4,10 +4,79 @@ declare(strict_types=1);
namespace App\Filament\Resources\Projects\Pages;
use App\Actions\Projects\OpenInTaskStream;
use App\Filament\Resources\Projects\ProjectResource;
use App\Models\Project;
use App\Services\Pireon\TokenStore;
use App\Services\TaskStream\TaskStreamClient;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
class CreateProject extends CreateRecord
{
protected static string $resource = ProjectResource::class;
/**
* With the TaskStream toggle on, the board is opened before the project
* is saved, as the person signed in (their Pireon token from the session).
*/
protected function handleRecordCreation(array $data): Model
{
$taskstream = (bool) Arr::pull($data, 'taskstream', false);
$key = Arr::pull($data, 'taskstream_key');
$project = new Project($data);
if ($taskstream) {
$this->openInTaskStream($project, filled($key) ? $key : null);
}
$project->save();
return $project;
}
protected function openInTaskStream(Project $project, ?string $key): void
{
try {
app(OpenInTaskStream::class)->handle($project, Auth::user(), app(TokenStore::class)->accessToken(), $key);
} catch (ValidationException $e) {
// The action names the API's fields; here they live under `data`.
throw ValidationException::withMessages(
collect($e->errors())->mapWithKeys(fn (array $messages, string $field) => ["data.{$field}" => $messages])->all(),
);
} catch (HttpException $e) {
Notification::make()->danger()->title($e->getMessage())->send();
throw (new Halt)->rollBackDatabaseTransaction();
}
}
protected function getCreatedNotification(): ?Notification
{
$notification = parent::getCreatedNotification();
$key = $this->getRecord()->taskstream_key;
if ($notification === null || $key === null) {
return $notification;
}
$url = app(TaskStreamClient::class)->projectUrl($key);
return $notification
->body(__('panel.projects.taskstream.created', ['url' => $url]))
->actions([
Action::make('taskstream')
->label(__('panel.projects.taskstream.open'))
->url($url, shouldOpenInNewTab: true),
])
->persistent();
}
}