Add Discord allowed mentions configuration
This commit is contained in:
@@ -21,6 +21,11 @@ class DeliverNotificationAction implements ShouldQueue
|
|||||||
|
|
||||||
private const int DISCORD_CONTENT_LIMIT = 2000;
|
private const int DISCORD_CONTENT_LIMIT = 2000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
private const array DISCORD_ALLOWED_MENTION_TYPES = ['everyone', 'roles', 'users'];
|
||||||
|
|
||||||
public int $tries = 3;
|
public int $tries = 3;
|
||||||
|
|
||||||
public int $timeout = 15;
|
public int $timeout = 15;
|
||||||
@@ -280,11 +285,53 @@ class DeliverNotificationAction implements ShouldQueue
|
|||||||
->post($notificationAction->delivery_url, [
|
->post($notificationAction->delivery_url, [
|
||||||
'content' => $message,
|
'content' => $message,
|
||||||
'allowed_mentions' => [
|
'allowed_mentions' => [
|
||||||
'parse' => [],
|
'parse' => $this->discordAllowedMentions($notificationAction),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function discordAllowedMentions(NotificationAction $notificationAction): array
|
||||||
|
{
|
||||||
|
$configuredAllowedMentions = data_get(
|
||||||
|
$notificationAction->channel_configuration,
|
||||||
|
'allowed_mentions',
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! is_array($configuredAllowedMentions)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedMentions = [];
|
||||||
|
|
||||||
|
foreach ($configuredAllowedMentions as $allowedMention) {
|
||||||
|
if (! is_string($allowedMention)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedMention = Str::lower(trim($allowedMention));
|
||||||
|
|
||||||
|
if ($allowedMention === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! in_array($allowedMention, self::DISCORD_ALLOWED_MENTION_TYPES, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($allowedMention, $allowedMentions, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedMentions[] = $allowedMention;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $allowedMentions;
|
||||||
|
}
|
||||||
|
|
||||||
private function sendWebhookRequest(NotificationAction $notificationAction): Response
|
private function sendWebhookRequest(NotificationAction $notificationAction): Response
|
||||||
{
|
{
|
||||||
return Http::acceptJson()
|
return Http::acceptJson()
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ use Illuminate\Validation\Rule;
|
|||||||
|
|
||||||
class StoreProjectNotificationActionRequest extends FormRequest
|
class StoreProjectNotificationActionRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
private const array DISCORD_ALLOWED_MENTION_TYPES = ['everyone', 'roles', 'users'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* Determine if the user is authorized to make this request.
|
||||||
*/
|
*/
|
||||||
@@ -29,6 +34,9 @@ class StoreProjectNotificationActionRequest extends FormRequest
|
|||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'event_type' => ['required', 'string', Rule::in($this->eventTypes())],
|
'event_type' => ['required', 'string', Rule::in($this->eventTypes())],
|
||||||
'channel' => ['required', 'string', Rule::in($this->channels())],
|
'channel' => ['required', 'string', Rule::in($this->channels())],
|
||||||
|
'channel_configuration' => ['nullable', 'array'],
|
||||||
|
'channel_configuration.allowed_mentions' => ['nullable', 'array'],
|
||||||
|
'channel_configuration.allowed_mentions.*' => $this->allowedMentionRules(),
|
||||||
'enabled' => ['required', 'boolean'],
|
'enabled' => ['required', 'boolean'],
|
||||||
'delivery_url' => ['required', 'string', 'max:2048', 'url'],
|
'delivery_url' => ['required', 'string', 'max:2048', 'url'],
|
||||||
'message_template' => $this->messageTemplateRules(),
|
'message_template' => $this->messageTemplateRules(),
|
||||||
@@ -68,4 +76,16 @@ class StoreProjectNotificationActionRequest extends FormRequest
|
|||||||
|
|
||||||
return ['nullable', 'string', 'max:2000'];
|
return ['nullable', 'string', 'max:2000'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, ValidationRule|string>
|
||||||
|
*/
|
||||||
|
private function allowedMentionRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'nullable',
|
||||||
|
'string',
|
||||||
|
Rule::in(self::DISCORD_ALLOWED_MENTION_TYPES),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ use Illuminate\Validation\Rule;
|
|||||||
|
|
||||||
class UpdateProjectNotificationActionRequest extends FormRequest
|
class UpdateProjectNotificationActionRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
private const array DISCORD_ALLOWED_MENTION_TYPES = ['everyone', 'roles', 'users'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* Determine if the user is authorized to make this request.
|
||||||
*/
|
*/
|
||||||
@@ -29,6 +34,9 @@ class UpdateProjectNotificationActionRequest extends FormRequest
|
|||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'event_type' => ['required', 'string', Rule::in($this->eventTypes())],
|
'event_type' => ['required', 'string', Rule::in($this->eventTypes())],
|
||||||
'channel' => ['required', 'string', Rule::in($this->channels())],
|
'channel' => ['required', 'string', Rule::in($this->channels())],
|
||||||
|
'channel_configuration' => ['nullable', 'array'],
|
||||||
|
'channel_configuration.allowed_mentions' => ['nullable', 'array'],
|
||||||
|
'channel_configuration.allowed_mentions.*' => $this->allowedMentionRules(),
|
||||||
'enabled' => ['required', 'boolean'],
|
'enabled' => ['required', 'boolean'],
|
||||||
'delivery_url' => ['nullable', 'string', 'max:2048', 'url'],
|
'delivery_url' => ['nullable', 'string', 'max:2048', 'url'],
|
||||||
'message_template' => $this->messageTemplateRules(),
|
'message_template' => $this->messageTemplateRules(),
|
||||||
@@ -68,4 +76,16 @@ class UpdateProjectNotificationActionRequest extends FormRequest
|
|||||||
|
|
||||||
return ['nullable', 'string', 'max:2000'];
|
return ['nullable', 'string', 'max:2000'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, ValidationRule|string>
|
||||||
|
*/
|
||||||
|
private function allowedMentionRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'nullable',
|
||||||
|
'string',
|
||||||
|
Rule::in(self::DISCORD_ALLOWED_MENTION_TYPES),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,23 @@ const timestampFormatter = new Intl.DateTimeFormat(undefined, {
|
|||||||
dateStyle: 'medium',
|
dateStyle: 'medium',
|
||||||
timeStyle: 'short',
|
timeStyle: 'short',
|
||||||
});
|
});
|
||||||
|
const discordAllowedMentionOptions: Array<{
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
value: 'users',
|
||||||
|
label: 'User mentions (@username)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'roles',
|
||||||
|
label: 'Role mentions (@role)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'everyone',
|
||||||
|
label: '@everyone and @here',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const breadcrumbs: BreadcrumbItem[] = [
|
const breadcrumbs: BreadcrumbItem[] = [
|
||||||
{
|
{
|
||||||
@@ -67,6 +84,41 @@ props.notificationActions.forEach((notificationAction) => {
|
|||||||
const selectedChannel = (notificationAction: NotificationActionModel): string =>
|
const selectedChannel = (notificationAction: NotificationActionModel): string =>
|
||||||
editChannelState[notificationAction.id] ?? notificationAction.channel;
|
editChannelState[notificationAction.id] ?? notificationAction.channel;
|
||||||
|
|
||||||
|
const notificationActionAllowedMentions = (
|
||||||
|
notificationAction: NotificationActionModel,
|
||||||
|
): string[] => {
|
||||||
|
const allowedMentions =
|
||||||
|
notificationAction.channel_configuration['allowed_mentions'];
|
||||||
|
|
||||||
|
if (!Array.isArray(allowedMentions)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowedMentions
|
||||||
|
.filter(
|
||||||
|
(allowedMention): allowedMention is string =>
|
||||||
|
typeof allowedMention === 'string',
|
||||||
|
)
|
||||||
|
.map((allowedMention) => allowedMention.trim().toLowerCase())
|
||||||
|
.filter((allowedMention) => allowedMention !== '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const allowedMentionsError = (
|
||||||
|
errors: Record<string, string>,
|
||||||
|
): string | undefined => {
|
||||||
|
const generalError = errors['channel_configuration.allowed_mentions'];
|
||||||
|
|
||||||
|
if (generalError !== undefined) {
|
||||||
|
return generalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemErrorKey = Object.keys(errors).find((key) =>
|
||||||
|
key.startsWith('channel_configuration.allowed_mentions.'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return itemErrorKey === undefined ? undefined : errors[itemErrorKey];
|
||||||
|
};
|
||||||
|
|
||||||
const notificationActionTimestamp = (
|
const notificationActionTimestamp = (
|
||||||
notificationAction: NotificationActionModel,
|
notificationAction: NotificationActionModel,
|
||||||
): string => {
|
): string => {
|
||||||
@@ -249,6 +301,52 @@ const notificationActionTimestamp = (
|
|||||||
<InputError :message="errors.delivery_url" />
|
<InputError :message="errors.delivery_url" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label>Allowed mentions</Label>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'grid gap-2',
|
||||||
|
createChannelState.value !==
|
||||||
|
'discord'
|
||||||
|
? 'hidden'
|
||||||
|
: '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
v-for="allowedMentionOption in discordAllowedMentionOptions"
|
||||||
|
:key="`create-allowed-mention-${allowedMentionOption.value}`"
|
||||||
|
class="border-border flex items-center gap-2 rounded-lg border px-3 py-2"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="channel_configuration[allowed_mentions][]"
|
||||||
|
:value="
|
||||||
|
allowedMentionOption.value
|
||||||
|
"
|
||||||
|
:disabled="
|
||||||
|
createChannelState.value !==
|
||||||
|
'discord'
|
||||||
|
"
|
||||||
|
class="border-input text-primary focus:ring-ring size-4 rounded"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{ allowedMentionOption.label }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted-foreground text-sm">
|
||||||
|
{{
|
||||||
|
createChannelState.value ===
|
||||||
|
'discord'
|
||||||
|
? 'Pick which Discord mention types are allowed. Leave all unchecked to keep mentions fully disabled.'
|
||||||
|
: 'Allowed mentions are only used when the channel is Discord.'
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<InputError
|
||||||
|
:message="allowedMentionsError(errors)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label
|
<Label
|
||||||
for="create-notification-message-template"
|
for="create-notification-message-template"
|
||||||
@@ -529,6 +627,59 @@ const notificationActionTimestamp = (
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<Label>Allowed mentions</Label>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'grid gap-2',
|
||||||
|
selectedChannel(notificationAction) !==
|
||||||
|
'discord'
|
||||||
|
? 'hidden'
|
||||||
|
: '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
v-for="allowedMentionOption in discordAllowedMentionOptions"
|
||||||
|
:key="`notification-allowed-mention-${notificationAction.id}-${allowedMentionOption.value}`"
|
||||||
|
class="border-border flex items-center gap-2 rounded-lg border px-3 py-2"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="channel_configuration[allowed_mentions][]"
|
||||||
|
:value="
|
||||||
|
allowedMentionOption.value
|
||||||
|
"
|
||||||
|
:default-checked="
|
||||||
|
notificationActionAllowedMentions(
|
||||||
|
notificationAction,
|
||||||
|
).includes(
|
||||||
|
allowedMentionOption.value,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:disabled="
|
||||||
|
selectedChannel(notificationAction) !==
|
||||||
|
'discord'
|
||||||
|
"
|
||||||
|
class="border-input text-primary focus:ring-ring size-4 rounded"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{ allowedMentionOption.label }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted-foreground text-sm">
|
||||||
|
{{
|
||||||
|
selectedChannel(notificationAction) ===
|
||||||
|
'discord'
|
||||||
|
? 'Pick which Discord mention types are allowed. Leave all unchecked to keep mentions fully disabled.'
|
||||||
|
: 'Allowed mentions are only used when the channel is Discord.'
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<InputError
|
||||||
|
:message="allowedMentionsError(errors)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<Label
|
<Label
|
||||||
:for="
|
:for="
|
||||||
|
|||||||
@@ -106,6 +106,44 @@ class DeliverNotificationActionTest extends TestCase
|
|||||||
$this->assertNotNull($deliveryLog->delivered_at);
|
$this->assertNotNull($deliveryLog->delivered_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure Discord deliveries include configured allowed mention parse values.
|
||||||
|
*/
|
||||||
|
public function test_discord_delivery_includes_configured_allowed_mentions(): void
|
||||||
|
{
|
||||||
|
Http::preventStrayRequests();
|
||||||
|
Http::fake([
|
||||||
|
'https://discord.com/api/webhooks/123456/discord-alerts' => Http::response('', 204),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notificationAction = NotificationAction::factory()->discord()->create([
|
||||||
|
'delivery_url' => 'https://discord.com/api/webhooks/123456/discord-alerts',
|
||||||
|
'channel_configuration' => [
|
||||||
|
'allowed_mentions' => ['users', 'roles', 'users', 'EVERYONE', 'invalid'],
|
||||||
|
],
|
||||||
|
'message_template' => '$ticket_key moved to $status_to',
|
||||||
|
]);
|
||||||
|
$deliveryLog = $this->createPendingDeliveryLog($notificationAction);
|
||||||
|
|
||||||
|
(new DeliverNotificationAction(
|
||||||
|
notificationActionId: $notificationAction->id,
|
||||||
|
payload: $this->payload(),
|
||||||
|
notificationDeliveryLogId: $deliveryLog->id,
|
||||||
|
))->handle();
|
||||||
|
|
||||||
|
Http::assertSentCount(1);
|
||||||
|
Http::assertSent(function (Request $request): bool {
|
||||||
|
return $request->method() === 'POST'
|
||||||
|
&& $request->url() === 'https://discord.com/api/webhooks/123456/discord-alerts'
|
||||||
|
&& $request->data() === [
|
||||||
|
'content' => 'CORE-42 moved to Doing',
|
||||||
|
'allowed_mentions' => [
|
||||||
|
'parse' => ['users', 'roles', 'everyone'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure retryable webhook responses release the job and update the delivery log.
|
* Ensure retryable webhook responses release the job and update the delivery log.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
|||||||
->for($project)
|
->for($project)
|
||||||
->create([
|
->create([
|
||||||
'name' => 'Discord alerts',
|
'name' => 'Discord alerts',
|
||||||
|
'channel_configuration' => [
|
||||||
|
'allowed_mentions' => ['users', 'roles'],
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||||
@@ -41,6 +44,8 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
|||||||
->where('project.id', $project->id)
|
->where('project.id', $project->id)
|
||||||
->where('notificationActions.0.name', 'Discord alerts')
|
->where('notificationActions.0.name', 'Discord alerts')
|
||||||
->where('notificationActions.0.channel', NotificationChannel::Discord->value)
|
->where('notificationActions.0.channel', NotificationChannel::Discord->value)
|
||||||
|
->where('notificationActions.0.channel_configuration.allowed_mentions.0', 'users')
|
||||||
|
->where('notificationActions.0.channel_configuration.allowed_mentions.1', 'roles')
|
||||||
->where('notificationActions.0.event_type', NotificationEventType::TicketStatusChanged->value)
|
->where('notificationActions.0.event_type', NotificationEventType::TicketStatusChanged->value)
|
||||||
->where('notificationActions.0.enabled', true)
|
->where('notificationActions.0.enabled', true)
|
||||||
->where('notificationActions.0.has_delivery_url', true)
|
->where('notificationActions.0.has_delivery_url', true)
|
||||||
@@ -83,6 +88,9 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
|||||||
'name' => 'Discord alerts',
|
'name' => 'Discord alerts',
|
||||||
'event_type' => NotificationEventType::TicketStatusChanged->value,
|
'event_type' => NotificationEventType::TicketStatusChanged->value,
|
||||||
'channel' => NotificationChannel::Discord->value,
|
'channel' => NotificationChannel::Discord->value,
|
||||||
|
'channel_configuration' => [
|
||||||
|
'allowed_mentions' => ['users', 'roles'],
|
||||||
|
],
|
||||||
'enabled' => '1',
|
'enabled' => '1',
|
||||||
'delivery_url' => 'https://discord.com/api/webhooks/123456/discord-alerts',
|
'delivery_url' => 'https://discord.com/api/webhooks/123456/discord-alerts',
|
||||||
'message_template' => '$ticket_key moved from $status_from to $status_to by $actor_name: $url',
|
'message_template' => '$ticket_key moved from $status_from to $status_to by $actor_name: $url',
|
||||||
@@ -95,6 +103,10 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
|||||||
$this->assertSame($project->id, $notificationAction->project_id);
|
$this->assertSame($project->id, $notificationAction->project_id);
|
||||||
$this->assertSame(NotificationChannel::Discord, $notificationAction->channel);
|
$this->assertSame(NotificationChannel::Discord, $notificationAction->channel);
|
||||||
$this->assertSame(NotificationEventType::TicketStatusChanged, $notificationAction->event_type);
|
$this->assertSame(NotificationEventType::TicketStatusChanged, $notificationAction->event_type);
|
||||||
|
$this->assertSame(
|
||||||
|
['allowed_mentions' => ['users', 'roles']],
|
||||||
|
$notificationAction->channel_configuration,
|
||||||
|
);
|
||||||
$this->assertTrue($notificationAction->enabled);
|
$this->assertTrue($notificationAction->enabled);
|
||||||
$this->assertSame(
|
$this->assertSame(
|
||||||
'https://discord.com/api/webhooks/123456/discord-alerts',
|
'https://discord.com/api/webhooks/123456/discord-alerts',
|
||||||
@@ -124,6 +136,9 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
|||||||
'name' => 'Ops escalation alerts',
|
'name' => 'Ops escalation alerts',
|
||||||
'event_type' => NotificationEventType::TicketStatusChanged->value,
|
'event_type' => NotificationEventType::TicketStatusChanged->value,
|
||||||
'channel' => NotificationChannel::Discord->value,
|
'channel' => NotificationChannel::Discord->value,
|
||||||
|
'channel_configuration' => [
|
||||||
|
'allowed_mentions' => ['users', 'everyone'],
|
||||||
|
],
|
||||||
'enabled' => '1',
|
'enabled' => '1',
|
||||||
'delivery_url' => '',
|
'delivery_url' => '',
|
||||||
'message_template' => '$ticket_key moved to $status_to: $url',
|
'message_template' => '$ticket_key moved to $status_to: $url',
|
||||||
@@ -138,6 +153,10 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
|||||||
'https://discord.com/api/webhooks/123456/original-secret',
|
'https://discord.com/api/webhooks/123456/original-secret',
|
||||||
$notificationAction->delivery_url,
|
$notificationAction->delivery_url,
|
||||||
);
|
);
|
||||||
|
$this->assertSame(
|
||||||
|
['allowed_mentions' => ['users', 'everyone']],
|
||||||
|
$notificationAction->channel_configuration,
|
||||||
|
);
|
||||||
$this->assertSame('$ticket_key moved to $status_to: $url', $notificationAction->message_template);
|
$this->assertSame('$ticket_key moved to $status_to: $url', $notificationAction->message_template);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +276,9 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
|||||||
'channel' => NotificationChannel::Discord->value,
|
'channel' => NotificationChannel::Discord->value,
|
||||||
'enabled' => 'definitely-not-a-boolean',
|
'enabled' => 'definitely-not-a-boolean',
|
||||||
'delivery_url' => 'not-a-url',
|
'delivery_url' => 'not-a-url',
|
||||||
|
'channel_configuration' => [
|
||||||
|
'allowed_mentions' => ['not-valid'],
|
||||||
|
],
|
||||||
'message_template' => '',
|
'message_template' => '',
|
||||||
])
|
])
|
||||||
->assertRedirect(route('projects.settings.notifications', $project))
|
->assertRedirect(route('projects.settings.notifications', $project))
|
||||||
@@ -265,6 +287,7 @@ class ProjectNotificationSettingsControllerTest extends TestCase
|
|||||||
'event_type',
|
'event_type',
|
||||||
'enabled',
|
'enabled',
|
||||||
'delivery_url',
|
'delivery_url',
|
||||||
|
'channel_configuration.allowed_mentions.0',
|
||||||
'message_template',
|
'message_template',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user