diff --git a/app/Livewire/Notifications/Slack.php b/app/Livewire/Notifications/Slack.php index 58cab54941..c101941ec6 100644 --- a/app/Livewire/Notifications/Slack.php +++ b/app/Livewire/Notifications/Slack.php @@ -29,6 +29,9 @@ class Slack extends Component #[Validate(['nullable', new SafeWebhookUrl])] public ?string $slackWebhookUrl = null; + #[Validate(['boolean'])] + public bool $includeProjectNameInTitle = false; + #[Validate(['boolean'])] public bool $deploymentSuccessSlackNotifications = false; @@ -90,6 +93,7 @@ public function syncData(bool $toModel = false) $this->authorize('update', $this->settings); $this->settings->slack_enabled = $this->slackEnabled; $this->settings->slack_webhook_url = $this->slackWebhookUrl; + $this->settings->include_project_name_in_title = $this->includeProjectNameInTitle; $this->settings->deployment_success_slack_notifications = $this->deploymentSuccessSlackNotifications; $this->settings->deployment_failure_slack_notifications = $this->deploymentFailureSlackNotifications; @@ -113,6 +117,7 @@ public function syncData(bool $toModel = false) $this->slackWebhookUrl = auth()->user()->can('update', $this->settings) ? $this->settings->slack_webhook_url : null; + $this->includeProjectNameInTitle = $this->settings->include_project_name_in_title; $this->deploymentSuccessSlackNotifications = $this->settings->deployment_success_slack_notifications; $this->deploymentFailureSlackNotifications = $this->settings->deployment_failure_slack_notifications; diff --git a/app/Models/SlackNotificationSettings.php b/app/Models/SlackNotificationSettings.php index 62603685e9..eac32b970c 100644 --- a/app/Models/SlackNotificationSettings.php +++ b/app/Models/SlackNotificationSettings.php @@ -16,6 +16,7 @@ class SlackNotificationSettings extends Model 'slack_enabled', 'slack_webhook_url', + 'include_project_name_in_title', 'deployment_success_slack_notifications', 'deployment_failure_slack_notifications', @@ -40,6 +41,7 @@ class SlackNotificationSettings extends Model protected $casts = [ 'slack_enabled' => 'boolean', 'slack_webhook_url' => 'encrypted', + 'include_project_name_in_title' => 'boolean', 'deployment_success_slack_notifications' => 'boolean', 'deployment_failure_slack_notifications' => 'boolean', diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index 8fff7f03bb..04e0e5c0c0 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -182,7 +182,8 @@ public function toSlack(): SlackMessage return new SlackMessage( title: $title, description: $description, - color: SlackMessage::errorColor() + color: SlackMessage::errorColor(), + projectName: data_get($this->application, 'environment.project.name'), ); } diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 415df5831d..1c1b1e2dd4 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -202,7 +202,8 @@ public function toSlack(): SlackMessage return new SlackMessage( title: $title, description: $description, - color: SlackMessage::successColor() + color: SlackMessage::successColor(), + projectName: data_get($this->application, 'environment.project.name'), ); } diff --git a/app/Notifications/Channels/SlackChannel.php b/app/Notifications/Channels/SlackChannel.php index cddb7a5611..42b1ac828e 100644 --- a/app/Notifications/Channels/SlackChannel.php +++ b/app/Notifications/Channels/SlackChannel.php @@ -19,6 +19,10 @@ public function send(SendsSlack $notifiable, Notification $notification): void return; } + if ($slackSettings->include_project_name_in_title) { + $message->prependProjectNameToTitle(); + } + SendMessageToSlackJob::dispatch($message, $slackSettings->slack_webhook_url); } } diff --git a/app/Notifications/Dto/SlackMessage.php b/app/Notifications/Dto/SlackMessage.php index 879bf65472..b0bc09c24e 100644 --- a/app/Notifications/Dto/SlackMessage.php +++ b/app/Notifications/Dto/SlackMessage.php @@ -7,9 +7,19 @@ class SlackMessage public function __construct( public string $title, public string $description, - public string $color = '#0099ff' + public string $color = '#0099ff', + public ?string $projectName = null, ) {} + public function prependProjectNameToTitle(): void + { + if ($this->projectName === null || $this->projectName === '') { + return; + } + + $this->title = "{$this->projectName}: {$this->title}"; + } + public static function infoColor(): string { return '#0099ff'; diff --git a/database/migrations/2026_07_14_000000_add_include_project_name_in_title_to_slack_notification_settings.php b/database/migrations/2026_07_14_000000_add_include_project_name_in_title_to_slack_notification_settings.php new file mode 100644 index 0000000000..bab0c8b3e8 --- /dev/null +++ b/database/migrations/2026_07_14_000000_add_include_project_name_in_title_to_slack_notification_settings.php @@ -0,0 +1,22 @@ +boolean('include_project_name_in_title')->default(false); + }); + } + + public function down(): void + { + Schema::table('slack_notification_settings', function (Blueprint $table) { + $table->dropColumn('include_project_name_in_title'); + }); + } +}; diff --git a/resources/views/livewire/notifications/slack.blade.php b/resources/views/livewire/notifications/slack.blade.php index 9ab7522918..0a32852c71 100644 --- a/resources/views/livewire/notifications/slack.blade.php +++ b/resources/views/livewire/notifications/slack.blade.php @@ -45,6 +45,9 @@ label="Deployment Success" /> + diff --git a/tests/Feature/SlackNotificationProjectNameTest.php b/tests/Feature/SlackNotificationProjectNameTest.php new file mode 100644 index 0000000000..c767eaee2e --- /dev/null +++ b/tests/Feature/SlackNotificationProjectNameTest.php @@ -0,0 +1,78 @@ + 0]); +}); + +it('prefixes a Slack title with the project name when enabled', function () { + Queue::fake(); + + $team = Team::factory()->create(); + $project = Project::factory()->create([ + 'name' => 'My Project', + 'team_id' => $team->id, + ]); + $environment = Environment::factory()->create(['project_id' => $project->id]); + $application = Application::factory()->create(['environment_id' => $environment->id]); + + expect($team->slackNotificationSettings->include_project_name_in_title)->toBeFalse(); + + $team->slackNotificationSettings->update([ + 'slack_enabled' => true, + 'slack_webhook_url' => 'https://hooks.slack.com/services/test', + 'include_project_name_in_title' => true, + ]); + $notification = new DeploymentFailed($application, 'deployment-uuid'); + $message = $notification->toSlack(); + + app(SlackChannel::class)->send($team, $notification); + + expect($message->projectName)->toBe('My Project'); + Queue::assertPushed(SendMessageToSlackJob::class, function (SendMessageToSlackJob $job) { + $message = (new ReflectionProperty($job, 'message'))->getValue($job); + + return $message->title === 'My Project: Deployment failed'; + }); +}); + +it('keeps the original Slack title when the project name option is disabled', function () { + Queue::fake(); + + $team = Team::factory()->create(); + $project = Project::factory()->create([ + 'name' => 'My Project', + 'team_id' => $team->id, + ]); + $environment = Environment::factory()->create(['project_id' => $project->id]); + $application = Application::factory()->create(['environment_id' => $environment->id]); + $team->slackNotificationSettings->update([ + 'slack_enabled' => true, + 'slack_webhook_url' => 'https://hooks.slack.com/services/test', + 'include_project_name_in_title' => false, + ]); + $notification = new DeploymentSuccess($application, 'deployment-uuid'); + $message = $notification->toSlack(); + + app(SlackChannel::class)->send($team, $notification); + + expect($message->projectName)->toBe('My Project'); + Queue::assertPushed(SendMessageToSlackJob::class, function (SendMessageToSlackJob $job) { + $message = (new ReflectionProperty($job, 'message'))->getValue($job); + + return $message->title === 'New version successfully deployed'; + }); +});