Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
119622d
feat(notifications): add BulletinButton to proto and Rust service
esaugomez31 Feb 11, 2026
30f94ce
feat(notifications): add BulletinButton support to API
esaugomez31 Feb 11, 2026
c7a7f16
test(notifications): add e2e tests for BulletinButton
esaugomez31 Feb 11, 2026
2b7d7fc
fix(notifications): add missing imports in BulletinButton tests
esaugomez31 Feb 11, 2026
d88b8ed
fix(dev): remove trailing newline from supergraph.graphql
esaugomez31 Feb 11, 2026
6e4935f
fix: remove extra blank lines in notifications subgraph schema
esaugomez31 Feb 18, 2026
b22aaa4
fix(notifications): match generated SDL blank lines in subgraph schema
esaugomez31 Feb 20, 2026
52e6106
fix(ci): pin DOCKER_API_VERSION=1.44 for GA runner compatibility
esaugomez31 Feb 20, 2026
312de21
Revert "fix(ci): pin DOCKER_API_VERSION=1.44 for GA runner compatibil…
esaugomez31 Feb 24, 2026
bbff5e3
refactor(notifications): move label from BulletinButton to action typ…
esaugomez31 Feb 26, 2026
401f113
refactor(notifications): move label from BulletinButton to action typ…
esaugomez31 Feb 26, 2026
d453b07
test(notifications): update e2e tests for label in action types
esaugomez31 Feb 26, 2026
424a366
fix(notifications): remove trailing newline from supergraph.graphql
esaugomez31 Feb 26, 2026
f52009d
refactor(notifications): use nullish coalescing and rename variable f…
esaugomez31 Feb 26, 2026
4aa1862
fix(notifications): validate label with trim before setting on action
esaugomez31 Feb 26, 2026
96f3234
test(notifications): rename serde_ test prefix to serialize_/deserial…
esaugomez31 Feb 26, 2026
8d8f10b
test(notifications): add e2e test for label priority when both action…
esaugomez31 Feb 26, 2026
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
169 changes: 169 additions & 0 deletions bats/core/notifications/notifications.bats
Comment thread
grimen marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,172 @@ setup_file() {
count=$(graphql_output '.data.me.unacknowledgedStatefulNotificationsWithoutBulletinEnabledCount')
[[ $count -eq 2 ]] || exit 1
}

@test "notifications: bulletin with label and external url action" {
admin_token="$(read_value 'admin.token')"

variables=$(
jq -n \
'{
input: {
localizedNotificationContents: [
{
language: "en",
title: "New feature available",
body: "Check out our latest update"
}
],
shouldSendPush: false,
shouldAddToHistory: true,
shouldAddToBulletin: true,
openExternalUrl: {
url: "https://example.com/update",
label: "Learn more"
},
icon: "BELL"
}
}'
)

exec_admin_graphql "$admin_token" 'marketing-notification-trigger' "$variables"

local action_label
local action_url
local icon
for i in {1..10}; do
exec_graphql 'alice' 'list-unacknowledged-stateful-notifications-with-bulletin-enabled'
action_label=$(graphql_output '.data.me.unacknowledgedStatefulNotificationsWithBulletinEnabled.nodes[0].action.label')
[[ "$action_label" = "Learn more" ]] && break;
sleep 1
done
[[ "$action_label" = "Learn more" ]] || exit 1

action_url=$(graphql_output '.data.me.unacknowledgedStatefulNotificationsWithBulletinEnabled.nodes[0].action.url')
[[ "$action_url" = "https://example.com/update" ]] || exit 1

icon=$(graphql_output '.data.me.unacknowledgedStatefulNotificationsWithBulletinEnabled.nodes[0].icon')
[[ "$icon" = "BELL" ]] || exit 1
}

@test "notifications: bulletin without label has null action label" {
admin_token="$(read_value 'admin.token')"

variables=$(
jq -n \
'{
input: {
localizedNotificationContents: [
{
language: "en",
title: "Simple notification",
body: "No label here"
}
],
shouldSendPush: false,
shouldAddToHistory: true,
shouldAddToBulletin: true,
}
}'
)

exec_admin_graphql "$admin_token" 'marketing-notification-trigger' "$variables"

local action
local title
for i in {1..10}; do
exec_graphql 'alice' 'list-stateful-notifications' '{"first": 1}'
title=$(graphql_output '.data.me.statefulNotifications.nodes[0].title')
[[ "$title" = "Simple notification" ]] && break;
sleep 1
done
[[ "$title" = "Simple notification" ]] || exit 1

action=$(graphql_output '.data.me.statefulNotifications.nodes[0].action')
[[ "$action" = "null" ]] || exit 1
}

@test "notifications: bulletin with label and deep link action" {
admin_token="$(read_value 'admin.token')"

variables=$(
jq -n \
'{
input: {
localizedNotificationContents: [
{
language: "en",
title: "Complete your profile",
body: "Set up your account to get started"
}
],
shouldSendPush: false,
shouldAddToHistory: true,
shouldAddToBulletin: true,
openDeepLink: {
screen: "SETTINGS",
label: "Go to settings"
}
}
}'
)

exec_admin_graphql "$admin_token" 'marketing-notification-trigger' "$variables"

local action_label
local deep_link
for i in {1..10}; do
exec_graphql 'alice' 'list-unacknowledged-stateful-notifications-with-bulletin-enabled'
action_label=$(graphql_output '.data.me.unacknowledgedStatefulNotificationsWithBulletinEnabled.nodes[0].action.label')
[[ "$action_label" = "Go to settings" ]] && break;
sleep 1
done
[[ "$action_label" = "Go to settings" ]] || exit 1

deep_link=$(graphql_output '.data.me.unacknowledgedStatefulNotificationsWithBulletinEnabled.nodes[0].action.deepLink')
[[ "$deep_link" = "/settings" ]] || exit 1
}

@test "notifications: bulletin with label on both deep link and external url uses deep link" {
admin_token="$(read_value 'admin.token')"

variables=$(
jq -n \
'{
input: {
localizedNotificationContents: [
{
language: "en",
title: "Both actions label",
body: "Deep link label should take priority"
}
],
shouldSendPush: false,
shouldAddToHistory: true,
shouldAddToBulletin: true,
openDeepLink: {
screen: "SETTINGS",
label: "Deep link label"
},
openExternalUrl: {
url: "https://example.com/fallback",
label: "External label"
}
}
}'
)

exec_admin_graphql "$admin_token" 'marketing-notification-trigger' "$variables"

local action_label
local action_url
for i in {1..10}; do
exec_graphql 'alice' 'list-unacknowledged-stateful-notifications-with-bulletin-enabled'
action_label=$(graphql_output '.data.me.unacknowledgedStatefulNotificationsWithBulletinEnabled.nodes[0].action.label')
[[ "$action_label" = "External label" ]] && break;
sleep 1
done
[[ "$action_label" = "External label" ]] || exit 1

action_url=$(graphql_output '.data.me.unacknowledgedStatefulNotificationsWithBulletinEnabled.nodes[0].action.url')
[[ "$action_url" = "https://example.com/fallback" ]] || exit 1
}
11 changes: 11 additions & 0 deletions bats/gql/list-stateful-notifications-without-bulletin-enabled.gql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ query listStatefulNotificationsWithoutBulletinEnabled($first: Int = 2, $after: S
acknowledgedAt
createdAt
bulletinEnabled
action {
... on OpenDeepLinkAction {
deepLink
label
}
... on OpenExternalLinkAction {
url
label
}
}
icon
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions bats/gql/list-stateful-notifications.gql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ query listStatefulNotifications($first: Int = 2, $after: String = null) {
acknowledgedAt
createdAt
bulletinEnabled
action {
... on OpenDeepLinkAction {
deepLink
label
}
... on OpenExternalLinkAction {
url
label
}
}
icon
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ query listUnacknowledgedStatefulNotificationsWithBulletinEnabled($first: Int = 2
deepLink
acknowledgedAt
createdAt
action {
... on OpenDeepLinkAction {
deepLink
label
}
... on OpenExternalLinkAction {
url
label
}
}
icon
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions core/api/src/app/admin/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ type AdminTriggerMarketingNotificationArgs = {
| {
screen: DeepLinkScreen | undefined
action: DeepLinkAction | undefined
label: string | undefined
}
| undefined
openExternalUrl:
| {
url: string
label: string | undefined
}
| undefined
shouldSendPush: boolean
Expand Down
2 changes: 2 additions & 0 deletions core/api/src/domain/notifications/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,13 @@ type TriggerMarketingNotificationArgs = {
| {
screen: DeepLinkScreen | undefined
action: DeepLinkAction | undefined
label: string | undefined
}
| undefined
openExternalUrl:
| {
url: string
label: string | undefined
}
| undefined
shouldSendPush: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const OpenDeepLinkInput = GT.Input({
action: {
type: DeepLinkAction,
},
label: {
type: GT.String,
},
}),
})

Expand All @@ -42,6 +45,9 @@ const OpenExternalUrlInput = GT.Input({
url: {
type: GT.NonNull(ExternalUrl),
},
label: {
type: GT.String,
},
}),
})

Expand Down Expand Up @@ -93,9 +99,10 @@ const MarketingNotificationTriggerMutation = GT.Field<
| {
screen: DeepLinkScreen | Error | undefined
action: DeepLinkAction | Error | undefined
label: string | undefined
}
| undefined
openExternalUrl: { url: string | Error } | undefined
openExternalUrl: { url: string | Error; label: string | undefined } | undefined
localizedNotificationContents: {
title: string
body: string
Expand Down Expand Up @@ -145,6 +152,7 @@ const MarketingNotificationTriggerMutation = GT.Field<
nonErrorOpenDeepLink = {
screen: openDeepLink.screen,
action: openDeepLink.action,
label: openDeepLink.label,
}
}

Expand All @@ -159,6 +167,7 @@ const MarketingNotificationTriggerMutation = GT.Field<
}
nonErrorOpenExternalUrl = {
url: openExternalUrl.url,
label: openExternalUrl.label,
}
}

Expand Down
2 changes: 2 additions & 0 deletions core/api/src/graphql/admin/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,12 @@ scalar OnChainTxHash

input OpenDeepLinkInput {
action: DeepLinkAction
label: String
screen: DeepLinkScreen
}

input OpenExternalUrlInput {
label: String
url: ExternalUrl!
}

Expand Down
5 changes: 5 additions & 0 deletions core/api/src/services/notifications/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,11 @@ export const NotificationsService = (): INotificationsService => {
if (externalUrl !== undefined) action.setExternalUrl(externalUrl)
}

const label = openExternalUrl?.label || openDeepLink?.label
if (action && typeof label === "string" && label.trim()) {
action.setLabel(label)
}

const protoIcon = icon ? iconToGrpcIcon(icon) : undefined

const marketingNotificationRequests: Promise<HandleNotificationEventResponse>[] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ message Action {
DeepLink deep_link = 1;
string external_url = 2;
}
optional string label = 3;
}

message DeepLink {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,4 +371,4 @@ var NotificationsServiceService = exports.NotificationsServiceService = {
},
};

exports.NotificationsServiceClient = grpc.makeGenericClientConstructor(NotificationsServiceService);
exports.NotificationsServiceClient = grpc.makeGenericClientConstructor(NotificationsServiceService, 'NotificationsService');
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,11 @@ export class Action extends jspb.Message {
getExternalUrl(): string;
setExternalUrl(value: string): Action;

hasLabel(): boolean;
clearLabel(): void;
getLabel(): string | undefined;
setLabel(value: string): Action;

getDataCase(): Action.DataCase;

serializeBinary(): Uint8Array;
Expand All @@ -989,6 +994,7 @@ export namespace Action {
export type AsObject = {
deepLink?: DeepLink.AsObject,
externalUrl: string,
label?: string,
}

export enum DataCase {
Expand Down
Loading
Loading