diff --git a/bats/core/notifications/notifications.bats b/bats/core/notifications/notifications.bats index 56d153c0e7..905f6d7739 100644 --- a/bats/core/notifications/notifications.bats +++ b/bats/core/notifications/notifications.bats @@ -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 +} diff --git a/bats/gql/list-stateful-notifications-without-bulletin-enabled.gql b/bats/gql/list-stateful-notifications-without-bulletin-enabled.gql index 55145e887e..417eab01ed 100644 --- a/bats/gql/list-stateful-notifications-without-bulletin-enabled.gql +++ b/bats/gql/list-stateful-notifications-without-bulletin-enabled.gql @@ -14,6 +14,17 @@ query listStatefulNotificationsWithoutBulletinEnabled($first: Int = 2, $after: S acknowledgedAt createdAt bulletinEnabled + action { + ... on OpenDeepLinkAction { + deepLink + label + } + ... on OpenExternalLinkAction { + url + label + } + } + icon } } } diff --git a/bats/gql/list-stateful-notifications.gql b/bats/gql/list-stateful-notifications.gql index 3af9a7f90c..6febd45714 100644 --- a/bats/gql/list-stateful-notifications.gql +++ b/bats/gql/list-stateful-notifications.gql @@ -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 } } } diff --git a/bats/gql/list-unacknowledged-stateful-notifications-with-bulletin-enabled.gql b/bats/gql/list-unacknowledged-stateful-notifications-with-bulletin-enabled.gql index 4c56c0a8fb..b928f87c21 100644 --- a/bats/gql/list-unacknowledged-stateful-notifications-with-bulletin-enabled.gql +++ b/bats/gql/list-unacknowledged-stateful-notifications-with-bulletin-enabled.gql @@ -13,6 +13,17 @@ query listUnacknowledgedStatefulNotificationsWithBulletinEnabled($first: Int = 2 deepLink acknowledgedAt createdAt + action { + ... on OpenDeepLinkAction { + deepLink + label + } + ... on OpenExternalLinkAction { + url + label + } + } + icon } } } diff --git a/core/api/src/app/admin/index.types.d.ts b/core/api/src/app/admin/index.types.d.ts index e089d8e8d9..78f8b5285e 100644 --- a/core/api/src/app/admin/index.types.d.ts +++ b/core/api/src/app/admin/index.types.d.ts @@ -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 diff --git a/core/api/src/domain/notifications/index.types.d.ts b/core/api/src/domain/notifications/index.types.d.ts index 896c3fce42..7fb1bbd782 100644 --- a/core/api/src/domain/notifications/index.types.d.ts +++ b/core/api/src/domain/notifications/index.types.d.ts @@ -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 diff --git a/core/api/src/graphql/admin/root/mutation/marketing-notification-trigger.ts b/core/api/src/graphql/admin/root/mutation/marketing-notification-trigger.ts index 36ec89d1a4..80ee7aeece 100644 --- a/core/api/src/graphql/admin/root/mutation/marketing-notification-trigger.ts +++ b/core/api/src/graphql/admin/root/mutation/marketing-notification-trigger.ts @@ -33,6 +33,9 @@ const OpenDeepLinkInput = GT.Input({ action: { type: DeepLinkAction, }, + label: { + type: GT.String, + }, }), }) @@ -42,6 +45,9 @@ const OpenExternalUrlInput = GT.Input({ url: { type: GT.NonNull(ExternalUrl), }, + label: { + type: GT.String, + }, }), }) @@ -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 @@ -145,6 +152,7 @@ const MarketingNotificationTriggerMutation = GT.Field< nonErrorOpenDeepLink = { screen: openDeepLink.screen, action: openDeepLink.action, + label: openDeepLink.label, } } @@ -159,6 +167,7 @@ const MarketingNotificationTriggerMutation = GT.Field< } nonErrorOpenExternalUrl = { url: openExternalUrl.url, + label: openExternalUrl.label, } } diff --git a/core/api/src/graphql/admin/schema.graphql b/core/api/src/graphql/admin/schema.graphql index 7f9a020e2f..fd1d8b606d 100644 --- a/core/api/src/graphql/admin/schema.graphql +++ b/core/api/src/graphql/admin/schema.graphql @@ -426,10 +426,12 @@ scalar OnChainTxHash input OpenDeepLinkInput { action: DeepLinkAction + label: String screen: DeepLinkScreen } input OpenExternalUrlInput { + label: String url: ExternalUrl! } diff --git a/core/api/src/services/notifications/index.ts b/core/api/src/services/notifications/index.ts index 78debd639c..877c7abedc 100644 --- a/core/api/src/services/notifications/index.ts +++ b/core/api/src/services/notifications/index.ts @@ -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[] = [] diff --git a/core/api/src/services/notifications/proto/notifications.proto b/core/api/src/services/notifications/proto/notifications.proto index adf15bb8f3..1f14a60c54 100644 --- a/core/api/src/services/notifications/proto/notifications.proto +++ b/core/api/src/services/notifications/proto/notifications.proto @@ -255,6 +255,7 @@ message Action { DeepLink deep_link = 1; string external_url = 2; } + optional string label = 3; } message DeepLink { diff --git a/core/api/src/services/notifications/proto/notifications_grpc_pb.js b/core/api/src/services/notifications/proto/notifications_grpc_pb.js index fc1c710846..22d6fb4b19 100644 --- a/core/api/src/services/notifications/proto/notifications_grpc_pb.js +++ b/core/api/src/services/notifications/proto/notifications_grpc_pb.js @@ -371,4 +371,4 @@ var NotificationsServiceService = exports.NotificationsServiceService = { }, }; -exports.NotificationsServiceClient = grpc.makeGenericClientConstructor(NotificationsServiceService); +exports.NotificationsServiceClient = grpc.makeGenericClientConstructor(NotificationsServiceService, 'NotificationsService'); diff --git a/core/api/src/services/notifications/proto/notifications_pb.d.ts b/core/api/src/services/notifications/proto/notifications_pb.d.ts index 59f5a4e0b2..c986532966 100644 --- a/core/api/src/services/notifications/proto/notifications_pb.d.ts +++ b/core/api/src/services/notifications/proto/notifications_pb.d.ts @@ -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; @@ -989,6 +994,7 @@ export namespace Action { export type AsObject = { deepLink?: DeepLink.AsObject, externalUrl: string, + label?: string, } export enum DataCase { diff --git a/core/api/src/services/notifications/proto/notifications_pb.js b/core/api/src/services/notifications/proto/notifications_pb.js index f334b68454..5e8eda8242 100644 --- a/core/api/src/services/notifications/proto/notifications_pb.js +++ b/core/api/src/services/notifications/proto/notifications_pb.js @@ -923,9 +923,9 @@ proto.services.notifications.v1.ShouldSendNotificationRequest.prototype.toObject */ proto.services.notifications.v1.ShouldSendNotificationRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - channel: jspb.Message.getFieldWithDefault(msg, 2, 0), - category: jspb.Message.getFieldWithDefault(msg, 3, 0) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +channel: jspb.Message.getFieldWithDefault(msg, 2, 0), +category: jspb.Message.getFieldWithDefault(msg, 3, 0) }; if (includeInstance) { @@ -1113,8 +1113,8 @@ proto.services.notifications.v1.ShouldSendNotificationResponse.prototype.toObjec */ proto.services.notifications.v1.ShouldSendNotificationResponse.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - shouldSend: jspb.Message.getBooleanFieldWithDefault(msg, 2, false) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +shouldSend: jspb.Message.getBooleanFieldWithDefault(msg, 2, false) }; if (includeInstance) { @@ -1273,8 +1273,8 @@ proto.services.notifications.v1.EnableNotificationChannelRequest.prototype.toObj */ proto.services.notifications.v1.EnableNotificationChannelRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - channel: jspb.Message.getFieldWithDefault(msg, 2, 0) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +channel: jspb.Message.getFieldWithDefault(msg, 2, 0) }; if (includeInstance) { @@ -1433,7 +1433,7 @@ proto.services.notifications.v1.EnableNotificationChannelResponse.prototype.toOb */ proto.services.notifications.v1.EnableNotificationChannelResponse.toObject = function(includeInstance, msg) { var f, obj = { - notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) +notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) }; if (includeInstance) { @@ -1591,9 +1591,9 @@ proto.services.notifications.v1.NotificationSettings.prototype.toObject = functi */ proto.services.notifications.v1.NotificationSettings.toObject = function(includeInstance, msg) { var f, obj = { - push: (f = msg.getPush()) && proto.services.notifications.v1.ChannelNotificationSettings.toObject(includeInstance, f), - locale: jspb.Message.getFieldWithDefault(msg, 2, ""), - pushDeviceTokensList: (f = jspb.Message.getRepeatedField(msg, 3)) == null ? undefined : f +push: (f = msg.getPush()) && proto.services.notifications.v1.ChannelNotificationSettings.toObject(includeInstance, f), +locale: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f, +pushDeviceTokensList: (f = jspb.Message.getRepeatedField(msg, 3)) == null ? undefined : f }; if (includeInstance) { @@ -1846,8 +1846,8 @@ proto.services.notifications.v1.ChannelNotificationSettings.prototype.toObject = */ proto.services.notifications.v1.ChannelNotificationSettings.toObject = function(includeInstance, msg) { var f, obj = { - enabled: jspb.Message.getBooleanFieldWithDefault(msg, 1, false), - disabledCategoriesList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f +enabled: jspb.Message.getBooleanFieldWithDefault(msg, 1, false), +disabledCategoriesList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f }; if (includeInstance) { @@ -2027,8 +2027,8 @@ proto.services.notifications.v1.DisableNotificationChannelRequest.prototype.toOb */ proto.services.notifications.v1.DisableNotificationChannelRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - channel: jspb.Message.getFieldWithDefault(msg, 2, 0) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +channel: jspb.Message.getFieldWithDefault(msg, 2, 0) }; if (includeInstance) { @@ -2187,7 +2187,7 @@ proto.services.notifications.v1.DisableNotificationChannelResponse.prototype.toO */ proto.services.notifications.v1.DisableNotificationChannelResponse.toObject = function(includeInstance, msg) { var f, obj = { - notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) +notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) }; if (includeInstance) { @@ -2338,9 +2338,9 @@ proto.services.notifications.v1.DisableNotificationCategoryRequest.prototype.toO */ proto.services.notifications.v1.DisableNotificationCategoryRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - channel: jspb.Message.getFieldWithDefault(msg, 2, 0), - category: jspb.Message.getFieldWithDefault(msg, 3, 0) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +channel: jspb.Message.getFieldWithDefault(msg, 2, 0), +category: jspb.Message.getFieldWithDefault(msg, 3, 0) }; if (includeInstance) { @@ -2528,7 +2528,7 @@ proto.services.notifications.v1.DisableNotificationCategoryResponse.prototype.to */ proto.services.notifications.v1.DisableNotificationCategoryResponse.toObject = function(includeInstance, msg) { var f, obj = { - notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) +notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) }; if (includeInstance) { @@ -2679,9 +2679,9 @@ proto.services.notifications.v1.EnableNotificationCategoryRequest.prototype.toOb */ proto.services.notifications.v1.EnableNotificationCategoryRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - channel: jspb.Message.getFieldWithDefault(msg, 2, 0), - category: jspb.Message.getFieldWithDefault(msg, 3, 0) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +channel: jspb.Message.getFieldWithDefault(msg, 2, 0), +category: jspb.Message.getFieldWithDefault(msg, 3, 0) }; if (includeInstance) { @@ -2869,7 +2869,7 @@ proto.services.notifications.v1.EnableNotificationCategoryResponse.prototype.toO */ proto.services.notifications.v1.EnableNotificationCategoryResponse.toObject = function(includeInstance, msg) { var f, obj = { - notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) +notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) }; if (includeInstance) { @@ -3020,7 +3020,7 @@ proto.services.notifications.v1.GetNotificationSettingsRequest.prototype.toObjec */ proto.services.notifications.v1.GetNotificationSettingsRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, "") +userId: jspb.Message.getFieldWithDefault(msg, 1, "") }; if (includeInstance) { @@ -3150,7 +3150,7 @@ proto.services.notifications.v1.GetNotificationSettingsResponse.prototype.toObje */ proto.services.notifications.v1.GetNotificationSettingsResponse.toObject = function(includeInstance, msg) { var f, obj = { - notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) +notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) }; if (includeInstance) { @@ -3301,8 +3301,8 @@ proto.services.notifications.v1.UpdateUserLocaleRequest.prototype.toObject = fun */ proto.services.notifications.v1.UpdateUserLocaleRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - locale: jspb.Message.getFieldWithDefault(msg, 2, "") +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +locale: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -3461,7 +3461,7 @@ proto.services.notifications.v1.UpdateUserLocaleResponse.prototype.toObject = fu */ proto.services.notifications.v1.UpdateUserLocaleResponse.toObject = function(includeInstance, msg) { var f, obj = { - notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) +notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) }; if (includeInstance) { @@ -3612,8 +3612,8 @@ proto.services.notifications.v1.AddPushDeviceTokenRequest.prototype.toObject = f */ proto.services.notifications.v1.AddPushDeviceTokenRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - deviceToken: jspb.Message.getFieldWithDefault(msg, 2, "") +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +deviceToken: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -3772,7 +3772,7 @@ proto.services.notifications.v1.AddPushDeviceTokenResponse.prototype.toObject = */ proto.services.notifications.v1.AddPushDeviceTokenResponse.toObject = function(includeInstance, msg) { var f, obj = { - notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) +notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) }; if (includeInstance) { @@ -3923,8 +3923,8 @@ proto.services.notifications.v1.RemovePushDeviceTokenRequest.prototype.toObject */ proto.services.notifications.v1.RemovePushDeviceTokenRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - deviceToken: jspb.Message.getFieldWithDefault(msg, 2, "") +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +deviceToken: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -4083,7 +4083,7 @@ proto.services.notifications.v1.RemovePushDeviceTokenResponse.prototype.toObject */ proto.services.notifications.v1.RemovePushDeviceTokenResponse.toObject = function(includeInstance, msg) { var f, obj = { - notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) +notificationSettings: (f = msg.getNotificationSettings()) && proto.services.notifications.v1.NotificationSettings.toObject(includeInstance, f) }; if (includeInstance) { @@ -4234,8 +4234,8 @@ proto.services.notifications.v1.UpdateEmailAddressRequest.prototype.toObject = f */ proto.services.notifications.v1.UpdateEmailAddressRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - emailAddress: jspb.Message.getFieldWithDefault(msg, 2, "") +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +emailAddress: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -4495,7 +4495,7 @@ proto.services.notifications.v1.RemoveEmailAddressRequest.prototype.toObject = f */ proto.services.notifications.v1.RemoveEmailAddressRequest.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, "") +userId: jspb.Message.getFieldWithDefault(msg, 1, "") }; if (includeInstance) { @@ -4726,7 +4726,7 @@ proto.services.notifications.v1.HandleNotificationEventRequest.prototype.toObjec */ proto.services.notifications.v1.HandleNotificationEventRequest.toObject = function(includeInstance, msg) { var f, obj = { - event: (f = msg.getEvent()) && proto.services.notifications.v1.NotificationEvent.toObject(includeInstance, f) +event: (f = msg.getEvent()) && proto.services.notifications.v1.NotificationEvent.toObject(includeInstance, f) }; if (includeInstance) { @@ -5010,14 +5010,14 @@ proto.services.notifications.v1.NotificationEvent.prototype.toObject = function( */ proto.services.notifications.v1.NotificationEvent.toObject = function(includeInstance, msg) { var f, obj = { - circleGrew: (f = msg.getCircleGrew()) && proto.services.notifications.v1.CircleGrew.toObject(includeInstance, f), - circleThresholdReached: (f = msg.getCircleThresholdReached()) && proto.services.notifications.v1.CircleThresholdReached.toObject(includeInstance, f), - identityVerificationApproved: (f = msg.getIdentityVerificationApproved()) && proto.services.notifications.v1.IdentityVerificationApproved.toObject(includeInstance, f), - identityVerificationDeclined: (f = msg.getIdentityVerificationDeclined()) && proto.services.notifications.v1.IdentityVerificationDeclined.toObject(includeInstance, f), - identityVerificationReviewStarted: (f = msg.getIdentityVerificationReviewStarted()) && proto.services.notifications.v1.IdentityVerificationReviewStarted.toObject(includeInstance, f), - transactionOccurred: (f = msg.getTransactionOccurred()) && proto.services.notifications.v1.TransactionOccurred.toObject(includeInstance, f), - price: (f = msg.getPrice()) && proto.services.notifications.v1.PriceChanged.toObject(includeInstance, f), - marketingNotificationTriggered: (f = msg.getMarketingNotificationTriggered()) && proto.services.notifications.v1.MarketingNotificationTriggered.toObject(includeInstance, f) +circleGrew: (f = msg.getCircleGrew()) && proto.services.notifications.v1.CircleGrew.toObject(includeInstance, f), +circleThresholdReached: (f = msg.getCircleThresholdReached()) && proto.services.notifications.v1.CircleThresholdReached.toObject(includeInstance, f), +identityVerificationApproved: (f = msg.getIdentityVerificationApproved()) && proto.services.notifications.v1.IdentityVerificationApproved.toObject(includeInstance, f), +identityVerificationDeclined: (f = msg.getIdentityVerificationDeclined()) && proto.services.notifications.v1.IdentityVerificationDeclined.toObject(includeInstance, f), +identityVerificationReviewStarted: (f = msg.getIdentityVerificationReviewStarted()) && proto.services.notifications.v1.IdentityVerificationReviewStarted.toObject(includeInstance, f), +transactionOccurred: (f = msg.getTransactionOccurred()) && proto.services.notifications.v1.TransactionOccurred.toObject(includeInstance, f), +price: (f = msg.getPrice()) && proto.services.notifications.v1.PriceChanged.toObject(includeInstance, f), +marketingNotificationTriggered: (f = msg.getMarketingNotificationTriggered()) && proto.services.notifications.v1.MarketingNotificationTriggered.toObject(includeInstance, f) }; if (includeInstance) { @@ -5518,10 +5518,10 @@ proto.services.notifications.v1.CircleGrew.prototype.toObject = function(opt_inc */ proto.services.notifications.v1.CircleGrew.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - circleType: jspb.Message.getFieldWithDefault(msg, 2, 0), - thisMonthCircleSize: jspb.Message.getFieldWithDefault(msg, 3, 0), - allTimeCircleSize: jspb.Message.getFieldWithDefault(msg, 4, 0) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +circleType: jspb.Message.getFieldWithDefault(msg, 2, 0), +thisMonthCircleSize: jspb.Message.getFieldWithDefault(msg, 3, 0), +allTimeCircleSize: jspb.Message.getFieldWithDefault(msg, 4, 0) }; if (includeInstance) { @@ -5738,10 +5738,10 @@ proto.services.notifications.v1.CircleThresholdReached.prototype.toObject = func */ proto.services.notifications.v1.CircleThresholdReached.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - circleType: jspb.Message.getFieldWithDefault(msg, 2, 0), - timeFrame: jspb.Message.getFieldWithDefault(msg, 3, 0), - threshold: jspb.Message.getFieldWithDefault(msg, 4, 0) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +circleType: jspb.Message.getFieldWithDefault(msg, 2, 0), +timeFrame: jspb.Message.getFieldWithDefault(msg, 3, 0), +threshold: jspb.Message.getFieldWithDefault(msg, 4, 0) }; if (includeInstance) { @@ -5958,7 +5958,7 @@ proto.services.notifications.v1.IdentityVerificationApproved.prototype.toObject */ proto.services.notifications.v1.IdentityVerificationApproved.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, "") +userId: jspb.Message.getFieldWithDefault(msg, 1, "") }; if (includeInstance) { @@ -6088,8 +6088,8 @@ proto.services.notifications.v1.IdentityVerificationDeclined.prototype.toObject */ proto.services.notifications.v1.IdentityVerificationDeclined.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - declinedReason: jspb.Message.getFieldWithDefault(msg, 2, 0) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +declinedReason: jspb.Message.getFieldWithDefault(msg, 2, 0) }; if (includeInstance) { @@ -6248,7 +6248,7 @@ proto.services.notifications.v1.IdentityVerificationReviewStarted.prototype.toOb */ proto.services.notifications.v1.IdentityVerificationReviewStarted.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, "") +userId: jspb.Message.getFieldWithDefault(msg, 1, "") }; if (includeInstance) { @@ -6378,10 +6378,10 @@ proto.services.notifications.v1.TransactionOccurred.prototype.toObject = functio */ proto.services.notifications.v1.TransactionOccurred.toObject = function(includeInstance, msg) { var f, obj = { - userId: jspb.Message.getFieldWithDefault(msg, 1, ""), - type: jspb.Message.getFieldWithDefault(msg, 2, 0), - settlementAmount: (f = msg.getSettlementAmount()) && proto.services.notifications.v1.Money.toObject(includeInstance, f), - displayAmount: (f = msg.getDisplayAmount()) && proto.services.notifications.v1.Money.toObject(includeInstance, f) +userId: jspb.Message.getFieldWithDefault(msg, 1, ""), +type: jspb.Message.getFieldWithDefault(msg, 2, 0), +settlementAmount: (f = msg.getSettlementAmount()) && proto.services.notifications.v1.Money.toObject(includeInstance, f), +displayAmount: (f = msg.getDisplayAmount()) && proto.services.notifications.v1.Money.toObject(includeInstance, f) }; if (includeInstance) { @@ -6640,8 +6640,8 @@ proto.services.notifications.v1.Money.prototype.toObject = function(opt_includeI */ proto.services.notifications.v1.Money.toObject = function(includeInstance, msg) { var f, obj = { - currencyCode: jspb.Message.getFieldWithDefault(msg, 1, ""), - minorUnits: jspb.Message.getFieldWithDefault(msg, 2, 0) +currencyCode: jspb.Message.getFieldWithDefault(msg, 1, ""), +minorUnits: jspb.Message.getFieldWithDefault(msg, 2, 0) }; if (includeInstance) { @@ -6800,9 +6800,9 @@ proto.services.notifications.v1.PriceChanged.prototype.toObject = function(opt_i */ proto.services.notifications.v1.PriceChanged.toObject = function(includeInstance, msg) { var f, obj = { - priceOfOneBitcoin: (f = msg.getPriceOfOneBitcoin()) && proto.services.notifications.v1.Money.toObject(includeInstance, f), - direction: jspb.Message.getFieldWithDefault(msg, 2, 0), - priceChangePercentage: jspb.Message.getFloatingPointFieldWithDefault(msg, 3, 0.0) +priceOfOneBitcoin: (f = msg.getPriceOfOneBitcoin()) && proto.services.notifications.v1.Money.toObject(includeInstance, f), +direction: jspb.Message.getFieldWithDefault(msg, 2, 0), +priceChangePercentage: jspb.Message.getFloatingPointFieldWithDefault(msg, 3, 0.0) }; if (includeInstance) { @@ -7018,13 +7018,13 @@ proto.services.notifications.v1.MarketingNotificationTriggered.prototype.toObjec */ proto.services.notifications.v1.MarketingNotificationTriggered.toObject = function(includeInstance, msg) { var f, obj = { - userIdsList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f, - localizedContentMap: (f = msg.getLocalizedContentMap()) ? f.toObject(includeInstance, proto.services.notifications.v1.LocalizedContent.toObject) : [], - shouldSendPush: jspb.Message.getBooleanFieldWithDefault(msg, 3, false), - shouldAddToHistory: jspb.Message.getBooleanFieldWithDefault(msg, 4, false), - shouldAddToBulletin: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), - action: (f = msg.getAction()) && proto.services.notifications.v1.Action.toObject(includeInstance, f), - icon: jspb.Message.getFieldWithDefault(msg, 7, 0) +userIdsList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f, +localizedContentMap: (f = msg.getLocalizedContentMap()) ? f.toObject(includeInstance, proto.services.notifications.v1.LocalizedContent.toObject) : [], +shouldSendPush: jspb.Message.getBooleanFieldWithDefault(msg, 3, false), +shouldAddToHistory: jspb.Message.getBooleanFieldWithDefault(msg, 4, false), +shouldAddToBulletin: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), +action: (f = msg.getAction()) && proto.services.notifications.v1.Action.toObject(includeInstance, f), +icon: (f = jspb.Message.getField(msg, 7)) == null ? undefined : f }; if (includeInstance) { @@ -7390,8 +7390,8 @@ proto.services.notifications.v1.LocalizedContent.prototype.toObject = function(o */ proto.services.notifications.v1.LocalizedContent.toObject = function(includeInstance, msg) { var f, obj = { - title: jspb.Message.getFieldWithDefault(msg, 1, ""), - body: jspb.Message.getFieldWithDefault(msg, 2, "") +title: jspb.Message.getFieldWithDefault(msg, 1, ""), +body: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -7576,8 +7576,9 @@ proto.services.notifications.v1.Action.prototype.toObject = function(opt_include */ proto.services.notifications.v1.Action.toObject = function(includeInstance, msg) { var f, obj = { - deepLink: (f = msg.getDeepLink()) && proto.services.notifications.v1.DeepLink.toObject(includeInstance, f), - externalUrl: jspb.Message.getFieldWithDefault(msg, 2, "") +deepLink: (f = msg.getDeepLink()) && proto.services.notifications.v1.DeepLink.toObject(includeInstance, f), +externalUrl: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f, +label: (f = jspb.Message.getField(msg, 3)) == null ? undefined : f }; if (includeInstance) { @@ -7623,6 +7624,10 @@ proto.services.notifications.v1.Action.deserializeBinaryFromReader = function(ms var value = /** @type {string} */ (reader.readString()); msg.setExternalUrl(value); break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setLabel(value); + break; default: reader.skipField(); break; @@ -7667,6 +7672,13 @@ proto.services.notifications.v1.Action.serializeBinaryToWriter = function(messag f ); } + f = /** @type {string} */ (jspb.Message.getField(message, 3)); + if (f != null) { + writer.writeString( + 3, + f + ); + } }; @@ -7743,6 +7755,42 @@ proto.services.notifications.v1.Action.prototype.hasExternalUrl = function() { }; +/** + * optional string label = 3; + * @return {string} + */ +proto.services.notifications.v1.Action.prototype.getLabel = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.services.notifications.v1.Action} returns this + */ +proto.services.notifications.v1.Action.prototype.setLabel = function(value) { + return jspb.Message.setField(this, 3, value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.services.notifications.v1.Action} returns this + */ +proto.services.notifications.v1.Action.prototype.clearLabel = function() { + return jspb.Message.setField(this, 3, undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.services.notifications.v1.Action.prototype.hasLabel = function() { + return jspb.Message.getField(this, 3) != null; +}; + + @@ -7775,8 +7823,8 @@ proto.services.notifications.v1.DeepLink.prototype.toObject = function(opt_inclu */ proto.services.notifications.v1.DeepLink.toObject = function(includeInstance, msg) { var f, obj = { - screen: jspb.Message.getFieldWithDefault(msg, 1, 0), - action: jspb.Message.getFieldWithDefault(msg, 2, 0) +screen: (f = jspb.Message.getField(msg, 1)) == null ? undefined : f, +action: (f = jspb.Message.getField(msg, 2)) == null ? undefined : f }; if (includeInstance) { diff --git a/core/notifications/proto/notifications.proto b/core/notifications/proto/notifications.proto index adf15bb8f3..1f14a60c54 100644 --- a/core/notifications/proto/notifications.proto +++ b/core/notifications/proto/notifications.proto @@ -255,6 +255,7 @@ message Action { DeepLink deep_link = 1; string external_url = 2; } + optional string label = 3; } message DeepLink { diff --git a/core/notifications/src/graphql/convert.rs b/core/notifications/src/graphql/convert.rs index 762ed1c749..b1eab4ce6f 100644 --- a/core/notifications/src/graphql/convert.rs +++ b/core/notifications/src/graphql/convert.rs @@ -21,14 +21,19 @@ impl From for types::StatefulNotification { action: notification.action().map(|a| match a { Action::OpenDeepLink(deep_link) => { types::NotificationAction::OpenDeepLinkAction(types::OpenDeepLinkAction { + label: deep_link.label.clone(), deep_link: deep_link.to_link_string(), }) } - Action::OpenExternalUrl(url) => types::NotificationAction::OpenExternalLinkAction( - types::OpenExternalLinkAction { - url: url.into_inner(), - }, - ), + Action::OpenExternalUrl(url) => { + let label = url.label.clone(); + types::NotificationAction::OpenExternalLinkAction( + types::OpenExternalLinkAction { + url: url.into_inner(), + label, + }, + ) + } }), icon: notification.icon().map(Into::into), } diff --git a/core/notifications/src/graphql/types.rs b/core/notifications/src/graphql/types.rs index c44bb19230..0eaaa52c14 100644 --- a/core/notifications/src/graphql/types.rs +++ b/core/notifications/src/graphql/types.rs @@ -41,11 +41,13 @@ impl ScalarType for Timestamp { #[derive(SimpleObject)] pub(super) struct OpenDeepLinkAction { pub deep_link: String, + pub label: Option, } #[derive(SimpleObject)] pub(super) struct OpenExternalLinkAction { pub url: String, + pub label: Option, } #[derive(Union)] diff --git a/core/notifications/src/grpc/server/convert.rs b/core/notifications/src/grpc/server/convert.rs index 9b6d3d2736..a66dc7126e 100644 --- a/core/notifications/src/grpc/server/convert.rs +++ b/core/notifications/src/grpc/server/convert.rs @@ -249,6 +249,8 @@ impl TryFrom for notification_event::Action { type Error = tonic::Status; fn try_from(action: proto::Action) -> Result { + let label = action.label; + match action.data { Some(proto::action::Data::DeepLink(deep_link)) => { let screen = if let Some(screen) = deep_link.screen { @@ -271,13 +273,17 @@ impl TryFrom for notification_event::Action { None }; - let dl = notification_event::DeepLink { screen, action }; + let dl = notification_event::DeepLink { + screen, + action, + label, + }; Ok(notification_event::Action::OpenDeepLink(dl)) } Some(proto::action::Data::ExternalUrl(url)) => { - Ok(notification_event::Action::OpenExternalUrl( - notification_event::ExternalUrl::from(url), - )) + let mut external_url = notification_event::ExternalUrl::from(url); + external_url.label = label; + Ok(notification_event::Action::OpenExternalUrl(external_url)) } None => Err(tonic::Status::new( tonic::Code::InvalidArgument, diff --git a/core/notifications/src/notification_event/circle_grew.rs b/core/notifications/src/notification_event/circle_grew.rs index e1825c3988..0e89ccdc8b 100644 --- a/core/notifications/src/notification_event/circle_grew.rs +++ b/core/notifications/src/notification_event/circle_grew.rs @@ -20,6 +20,7 @@ impl NotificationEvent for CircleGrew { Some(Action::OpenDeepLink(DeepLink { screen: Some(DeepLinkScreen::Circles), action: None, + label: None, })) } diff --git a/core/notifications/src/notification_event/circle_threshold_reached.rs b/core/notifications/src/notification_event/circle_threshold_reached.rs index afd73135b0..54dcb4304f 100644 --- a/core/notifications/src/notification_event/circle_threshold_reached.rs +++ b/core/notifications/src/notification_event/circle_threshold_reached.rs @@ -20,6 +20,7 @@ impl NotificationEvent for CircleThresholdReached { Some(Action::OpenDeepLink(DeepLink { screen: Some(DeepLinkScreen::Circles), action: None, + label: None, })) } diff --git a/core/notifications/src/notification_event/marketing_notification_triggered.rs b/core/notifications/src/notification_event/marketing_notification_triggered.rs index f9ecafa6e7..7713410118 100644 --- a/core/notifications/src/notification_event/marketing_notification_triggered.rs +++ b/core/notifications/src/notification_event/marketing_notification_triggered.rs @@ -59,3 +59,156 @@ impl NotificationEvent for MarketingNotificationTriggered { self.icon.clone() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::notification_event::{DeepLink, ExternalUrl}; + + fn default_event() -> MarketingNotificationTriggered { + let default_content = LocalizedStatefulMessage { + locale: GaloyLocale::from("en".to_string()), + title: "Test Title".to_string(), + body: "Test Body".to_string(), + }; + MarketingNotificationTriggered { + content: HashMap::new(), + default_content, + should_send_push: true, + should_add_to_history: true, + should_add_to_bulletin: true, + action: None, + icon: None, + } + } + + #[test] + fn action_label_returns_some_when_set_on_deep_link() { + let mut event = default_event(); + event.action = Some(Action::OpenDeepLink(DeepLink { + screen: None, + action: None, + label: Some("Deposit".to_string()), + })); + + let action = event.action().expect("should return action"); + assert_eq!(action.label(), Some("Deposit")); + } + + #[test] + fn action_label_returns_none_when_not_set() { + let mut event = default_event(); + event.action = Some(Action::OpenDeepLink(DeepLink { + screen: None, + action: None, + label: None, + })); + + let action = event.action().expect("should return action"); + assert_eq!(action.label(), None); + } + + #[test] + fn action_label_on_external_url() { + let mut event = default_event(); + event.action = Some(Action::OpenExternalUrl(ExternalUrl::from( + "https://example.com".to_string(), + ))); + + let action = event.action().expect("should return action"); + assert_eq!(action.label(), None); + } + + #[test] + fn push_msg_uses_default_content() { + let event = default_event(); + let msg = event.to_localized_push_msg(&GaloyLocale::from("en".to_string())); + assert_eq!(msg.title, "Test Title"); + assert_eq!(msg.body, "Test Body"); + } + + #[test] + fn push_msg_uses_localized_content_when_available() { + let mut event = default_event(); + let es_locale = GaloyLocale::from("es".to_string()); + event.content.insert( + es_locale.clone(), + LocalizedStatefulMessage { + locale: es_locale.clone(), + title: "Localized Title".to_string(), + body: "Localized Body".to_string(), + }, + ); + + let msg = event.to_localized_push_msg(&es_locale); + assert_eq!(msg.title, "Localized Title"); + assert_eq!(msg.body, "Localized Body"); + } + + #[test] + fn serialize_roundtrip_with_action_label() { + let mut event = default_event(); + event.action = Some(Action::OpenDeepLink(DeepLink { + screen: None, + action: None, + label: Some("See more".to_string()), + })); + + let json = serde_json::to_string(&event).expect("should serialize"); + let deserialized: MarketingNotificationTriggered = + serde_json::from_str(&json).expect("should deserialize"); + + let action = deserialized.action().expect("should have action"); + assert_eq!(action.label(), Some("See more")); + } + + #[test] + fn deserialize_backward_compat_without_label() { + let json = r#"{ + "content": {}, + "default_content": { + "locale": "en", + "title": "Old notification", + "body": "No label field" + }, + "should_send_push": false, + "should_add_to_history": true, + "should_add_to_bulletin": false + }"#; + + let event: MarketingNotificationTriggered = + serde_json::from_str(json).expect("should deserialize without label"); + assert!(event.action().is_none()); + assert!(event.icon().is_none()); + } + + #[test] + fn deserialize_backward_compat_external_url_as_string() { + let json = r#"{ + "content": {}, + "default_content": { + "locale": "en", + "title": "Test", + "body": "Test" + }, + "should_send_push": true, + "should_add_to_history": true, + "should_add_to_bulletin": false, + "action": { "OpenExternalUrl": "https://example.com" } + }"#; + + let event: MarketingNotificationTriggered = + serde_json::from_str(json).expect("should deserialize old ExternalUrl format"); + let action = event.action().expect("should have action"); + assert_eq!(action.label(), None); + } + + #[test] + fn icon_returns_value_when_set() { + let mut event = default_event(); + event.icon = Some(Icon::Bell); + + let icon = event.icon().expect("should return icon"); + assert!(matches!(icon, Icon::Bell)); + } +} diff --git a/core/notifications/src/notification_event/mod.rs b/core/notifications/src/notification_event/mod.rs index db3cc400e5..40d94f2cdc 100644 --- a/core/notifications/src/notification_event/mod.rs +++ b/core/notifications/src/notification_event/mod.rs @@ -81,6 +81,8 @@ pub enum Icon { pub struct DeepLink { pub screen: Option, pub action: Option, + #[serde(default)] + pub label: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -121,18 +123,57 @@ pub enum Action { OpenExternalUrl(ExternalUrl), } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ExternalUrl(String); +impl Action { + pub fn label(&self) -> Option<&str> { + match self { + Action::OpenDeepLink(dl) => dl.label.as_deref(), + Action::OpenExternalUrl(eu) => eu.label.as_deref(), + } + } +} + +#[derive(Debug, Serialize, Clone)] +pub struct ExternalUrl { + url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub label: Option, +} impl ExternalUrl { pub fn into_inner(self) -> String { - self.0 + self.url } } impl From for ExternalUrl { fn from(s: String) -> Self { - Self(s) + Self { + url: s, + label: None, + } + } +} + +impl<'de> Deserialize<'de> for ExternalUrl { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum ExternalUrlRepr { + Simple(String), + Struct { + url: String, + #[serde(default)] + label: Option, + }, + } + + match ExternalUrlRepr::deserialize(deserializer)? { + ExternalUrlRepr::Simple(url) => Ok(ExternalUrl { url, label: None }), + ExternalUrlRepr::Struct { url, label } => Ok(ExternalUrl { url, label }), + } } } diff --git a/core/notifications/subgraph/schema.graphql b/core/notifications/subgraph/schema.graphql index 842ca56e71..54552c14f3 100644 --- a/core/notifications/subgraph/schema.graphql +++ b/core/notifications/subgraph/schema.graphql @@ -61,10 +61,12 @@ union NotificationAction = OpenDeepLinkAction | OpenExternalLinkAction type OpenDeepLinkAction { deepLink: String! + label: String } type OpenExternalLinkAction { url: String! + label: String } """ diff --git a/dev/config/apollo-federation/supergraph.graphql b/dev/config/apollo-federation/supergraph.graphql index 4aacd5a640..87e74afe84 100644 --- a/dev/config/apollo-federation/supergraph.graphql +++ b/dev/config/apollo-federation/supergraph.graphql @@ -1586,12 +1586,14 @@ type OpenDeepLinkAction @join__type(graph: NOTIFICATIONS) { deepLink: String! + label: String } type OpenExternalLinkAction @join__type(graph: NOTIFICATIONS) { url: String! + label: String } """Information about pagination in a connection"""