Skip to content

Commit 7a5098e

Browse files
committed
Merge remote-tracking branch 'origin/main' into use-actual-time-in-code
2 parents 2098658 + befb16b commit 7a5098e

32 files changed

Lines changed: 1224 additions & 402 deletions

File tree

api-models/src/main/scala/com/gu/mobile.notifications.client/models/Topic.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,30 @@ object TopicTypes {
2020
case object TagSeries extends TopicType { override val toString = "tag-series" }
2121
case object TagBlog extends TopicType { override val toString = "tag-blog" }
2222
case object FootballTeam extends TopicType { override val toString = "football-team" }
23+
/**
24+
* When a user is subscribed to a team, we will offer them a live activity
25+
* whenever that team is playing a match, as an alternative to push
26+
* notifications throughout the match. This live activity will be provided
27+
* via a single notification when the match starts, and users with app
28+
* versions that can support live activities will receive it by subscribing
29+
* to topics of type `FootballTeamLiveActivity`. Users with app versions that
30+
* do not support live activities will instead be subscribing to topics of
31+
* type `FootballTeam`, which will deliver notifications throughout the
32+
* match.
33+
*/
34+
case object FootballTeamLiveActivity extends TopicType { override val toString = "football-team-live-activity" }
2335
case object FootballMatch extends TopicType { override val toString = "football-match" }
36+
/**
37+
* A user may visit a match info page before a live activity for that match
38+
* is available, as we only create it a couple of hours before the match
39+
* starts. We still want them to be able to subscribe and get that live
40+
* activity when it's ready, so we'll allow them to sign up to this
41+
* notification topic instead. The live activity will then be provided via a
42+
* single notification when the match starts. Users with app versions that do
43+
* not support live activities will instead be subscribing to topics of type
44+
* `FootballMatch`, which will deliver notifications throughout the match.
45+
*/
46+
case object FootballMatchLiveActivity extends TopicType { override val toString = "football-match-live-activity" }
2447
case object User extends TopicType { override val toString = "user-type" }
2548
case object Newsstand extends TopicType { override val toString = "newsstand" }
2649
}

api-models/src/main/scala/com/gu/mobile.notifications.client/models/liveActitivites/BroadcastContentState.scala

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,30 +33,57 @@ case object PreMatch extends MatchStatus { val status = "PRE_MATCH" }
3333
case object FirstHalf extends MatchStatus { val status = "FIRST_HALF" }
3434
case object HalfTime extends MatchStatus { val status = "HALF_TIME" }
3535
case object SecondHalf extends MatchStatus { val status = "SECOND_HALF" }
36+
case object ExtraTimeToBePlayed extends MatchStatus { val status = "EXTRA_TIME_TO_BE_PLAYED" }
3637
case object ExtraTimeFirstHalf extends MatchStatus { val status = "EXTRA_TIME_FIRST_HALF" }
3738
case object ExtraTimeHalfTime extends MatchStatus { val status = "EXTRA_TIME_HALF_TIME" }
3839
case object ExtraTimeSecondHalf extends MatchStatus { val status = "EXTRA_TIME_SECOND_HALF" }
40+
case object PenaltiesToBePlayed extends MatchStatus { val status = "PENALTIES_TO_BE_PLAYED" }
3941
case object Penalties extends MatchStatus { val status = "PENALTIES" }
4042
case object FullTime extends MatchStatus { val status = "FULL_TIME" }
4143
case object Postponed extends MatchStatus { val status = "POSTPONED" }
44+
case object Suspended extends MatchStatus { val status = "SUSPENDED" }
4245
case object Abandoned extends MatchStatus { val status = "ABANDONED" }
46+
case object Resumed extends MatchStatus { val status = "RESUMED" }
47+
case object Cancelled extends MatchStatus { val status = "CANCELLED" }
4348
// @formatter:on
4449

4550
object MatchStatus {
46-
def fromString(s: String): MatchStatus = s match {
47-
case "Pre-match" | "prematch" => PreMatch
48-
case "MatchInProgress" | "1st Half" | "first_half" => FirstHalf
49-
case "HalfTime" | "Half Time" | "half_time" => HalfTime
50-
case "2nd Half" | "second_half" => SecondHalf
51-
case "Extra Time" | "ET 1st Half" => ExtraTimeFirstHalf
52-
case "ET Half Time" => ExtraTimeHalfTime
53-
case "ET 2nd Half" => ExtraTimeSecondHalf
54-
case "Penalties" => Penalties
55-
case "MatchComplete" | "MC" | "Full Time" | "Result" => FullTime
56-
case "Postponed" => Postponed
57-
case "Abandoned" => Abandoned
58-
case _ => Scheduled
59-
}
51+
def fromString(s: String): MatchStatus = statuses.getOrElse(s, Scheduled)
52+
53+
// How PA match status are handled here differs from push notification mappings
54+
private val statuses: Map[String, MatchStatus] = Map(
55+
("Fixture", PreMatch), //
56+
("-", PreMatch), // seen in the wild
57+
("New", PreMatch), //
58+
59+
("KO", FirstHalf), // The Match has started (Kicked Off).
60+
61+
("HT", HalfTime), // The Referee has blown the whistle for Half Time.
62+
63+
("SHS", SecondHalf), // The Second Half of the Match has Started.
64+
65+
("FT", FullTime), // The Referee has blown the whistle for Full Time.
66+
("Result", FullTime), // The Result is official.
67+
("MC", FullTime), // Match has been Completed.
68+
69+
("FTET", ExtraTimeToBePlayed), // Full Time, Extra Time it to be played.
70+
("ETS", ExtraTimeFirstHalf), // Extra Time has Started.
71+
("ETHT", ExtraTimeHalfTime), // Extra Time Half Time has been called.
72+
("ETSHS", ExtraTimeSecondHalf), // Extra Time, Second Half has Started.
73+
("ETFT", ExtraTimeSecondHalf), // Extra Time, Full Time has been blown.
74+
75+
("FTPT", PenaltiesToBePlayed), // Full Time, Penalties are To be played.
76+
("ETFTPT", PenaltiesToBePlayed), // Extra Time, Full Time, Penalties are To be played.
77+
("PT", Penalties), // Penalty ShooT Out has started.
78+
("PTFT", Penalties), // Penalty ShooT Full Time.
79+
80+
// edge cases
81+
("Suspended", Suspended), // Match has been Suspended.
82+
("Resumed", Resumed), // Match has been Resumed. // todo can match phase be determined by match minute?
83+
("Abandoned", Abandoned), // Match has been Abandoned.
84+
("Postponed", Postponed), // A Match has been Postponed.
85+
("Cancelled", Cancelled) // A Match has been Cancelled.
86+
)
6087
}
6188

6289
case class Competition(
@@ -87,13 +114,13 @@ case class FootballMatchContentState(
87114
awayTeam: TeamState,
88115
competition: Competition,
89116
commentary: Option[String] = None,
90-
lineupsAvailable: Option[Boolean] = None,
117+
lineupsAvailable: Boolean = false,
91118
currentMinute: Option[Int] = None,
92119
currentPeriodStartTime: Option[Long] = None,
93-
articleUrl: Option[String] = None
120+
articleUrl: Option[String] = None,
121+
matchInfoUrl: String
94122
) extends ContentState
95123

96-
97124
object FootballContentJsonFormats {
98125
// MatchStatus format must be defined first since FootballMatchContentState depends on it
99126
implicit val matchStatusFormat: Format[MatchStatus] = Format(
@@ -105,13 +132,18 @@ object FootballContentJsonFormats {
105132
case "FIRST_HALF" => JsSuccess(FirstHalf)
106133
case "HALF_TIME" => JsSuccess(HalfTime)
107134
case "SECOND_HALF" => JsSuccess(SecondHalf)
135+
case "EXTRA_TIME_TO_BE_PLAYED" => JsSuccess(ExtraTimeToBePlayed)
108136
case "EXTRA_TIME_FIRST_HALF" => JsSuccess(ExtraTimeFirstHalf)
109137
case "EXTRA_TIME_HALF_TIME" => JsSuccess(ExtraTimeHalfTime)
110138
case "EXTRA_TIME_SECOND_HALF" => JsSuccess(ExtraTimeSecondHalf)
139+
case "PENALTIES_TO_BE_PLAYED" => JsSuccess(PenaltiesToBePlayed)
111140
case "PENALTIES" => JsSuccess(Penalties)
112141
case "FULL_TIME" => JsSuccess(FullTime)
142+
case "SUSPENDED" => JsSuccess(Suspended)
143+
case "RESUMED" => JsSuccess(Resumed)
113144
case "POSTPONED" => JsSuccess(Postponed)
114145
case "ABANDONED" => JsSuccess(Abandoned)
146+
case "CANCELLED" => JsSuccess(Cancelled)
115147
case other => JsError(s"Unknown match status: $other")
116148
}
117149
case _ => JsError("Expected a JSON string for MatchStatus")

api-models/src/main/scala/com/gu/mobile.notifications.client/models/liveActitivites/LiveActivityPayloads.scala

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@ import com.gu.mobile.notifications.client.models.Payload
1111
* Channel Manager and Broadcast lambdas.
1212
**/
1313

14-
sealed trait EventSource
15-
case object FootballLambda extends EventSource
14+
sealed trait EventSource {
15+
def asString: String
16+
}
17+
case object FootballLambdaEventSource extends EventSource { val asString = "football-lambda" }
18+
case object ChannelManagerLambdaEventSource extends EventSource { val asString = "channel-manager-lambda" }
1619

1720
object EventSource {
1821
implicit val format: Format[EventSource] = new Format[EventSource] {
1922
def writes(source: EventSource): JsValue = source match {
20-
case FootballLambda => JsString("football-lambda")
23+
case FootballLambdaEventSource => JsString("football-lambda")
24+
case ChannelManagerLambdaEventSource => JsString("channel-manager-lambda")
2125
}
2226

2327
def reads(json: JsValue): JsResult[EventSource] = json match {
24-
case JsString("football-lambda") => JsSuccess(FootballLambda)
28+
case JsString("football-lambda") => JsSuccess(FootballLambdaEventSource)
29+
case JsString("channel-manager-lambda") => JsSuccess(ChannelManagerLambdaEventSource)
2530
case JsString(other) => JsError(s"Invalid EventSource: $other")
2631
case _ => JsError("EventSource must be a string")
2732
}

build.sbt

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -248,33 +248,33 @@ lazy val apiModels = {
248248

249249
def lambda(projectName: String, directoryName: String, mainClassName: Option[String] = None): Project =
250250
Project(projectName, file(directoryName))
251-
.enablePlugins(AssemblyPlugin)
252-
.settings(
253-
organization := "com.gu",
254-
resolvers ++= Seq(
255-
"Guardian GitHub Releases" at "https://guardian.github.com/maven/repo-releases",
256-
"snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
257-
),
258-
libraryDependencies ++= Seq(
259-
"com.amazonaws" % "aws-lambda-java-core" % "1.4.0",
260-
"org.slf4j" % "slf4j-api" % slf4jVersion,
261-
"com.gu" %% "simple-configuration-core" % simpleConfigurationVersion,
262-
"com.gu" %% "simple-configuration-ssm" % simpleConfigurationVersion,
263-
"ch.qos.logback" % "logback-classic" % logbackVersion,
264-
"net.logstash.logback" % "logstash-logback-encoder" % "8.1",
265-
specs2 % Test
266-
),
267-
assemblyJarName := s"$projectName.jar",
268-
assembly / assemblyMergeStrategy := {
269-
case "META-INF/MANIFEST.MF" => MergeStrategy.discard
270-
case _ => MergeStrategy.first
271-
},
272-
Test / run / fork := true,
273-
scalacOptions := compilerOptions,
274-
mainClass := mainClassName,
275-
// Workaround Mockito causes deadlock on SBT classloaders: https://github.com/sbt/sbt/issues/3022
276-
Test / parallelExecution := false
277-
)
251+
.enablePlugins(AssemblyPlugin)
252+
.settings(
253+
organization := "com.gu",
254+
resolvers ++= Seq(
255+
"Guardian GitHub Releases" at "https://guardian.github.com/maven/repo-releases",
256+
"snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
257+
),
258+
libraryDependencies ++= Seq(
259+
"com.amazonaws" % "aws-lambda-java-core" % "1.4.0",
260+
"org.slf4j" % "slf4j-api" % slf4jVersion,
261+
"com.gu" %% "simple-configuration-core" % simpleConfigurationVersion,
262+
"com.gu" %% "simple-configuration-ssm" % simpleConfigurationVersion,
263+
"ch.qos.logback" % "logback-classic" % logbackVersion,
264+
"net.logstash.logback" % "logstash-logback-encoder" % "8.1",
265+
specs2 % Test
266+
),
267+
assemblyJarName := s"$projectName.jar",
268+
assembly / assemblyMergeStrategy := {
269+
case "META-INF/MANIFEST.MF" => MergeStrategy.discard
270+
case _ => MergeStrategy.first
271+
},
272+
Test / run / fork := true,
273+
scalacOptions := compilerOptions,
274+
mainClass := mainClassName,
275+
// Workaround Mockito causes deadlock on SBT classloaders: https://github.com/sbt/sbt/issues/3022
276+
Test / parallelExecution := false
277+
)
278278

279279
lazy val schedulelambda = lambda("schedule", "schedulelambda")
280280
.dependsOn(commonscheduledynamodb)
@@ -301,8 +301,16 @@ lazy val schedulelambda = lambda("schedule", "schedulelambda")
301301
)
302302
}
303303

304-
lazy val football = lambda("football", "football")
304+
lazy val commonEventBusPusher = project
305305
.dependsOn(apiModels % "test->test", apiModels % "compile->compile")
306+
.settings(Seq(
307+
libraryDependencies ++= Seq(
308+
"software.amazon.awssdk" % "eventbridge" % "2.20.162"
309+
),
310+
))
311+
312+
lazy val football = lambda("football", "football")
313+
.dependsOn(commonEventBusPusher)
306314
.settings(
307315
resolvers += "Guardian GitHub Releases" at "https://guardian.github.com/maven/repo-releases",
308316
libraryDependencies ++= Seq(
@@ -318,7 +326,6 @@ lazy val football = lambda("football", "football")
318326
"io.netty" % "netty-codec-http" % nettyVersion,
319327
"io.netty" % "netty-codec-http2" % nettyVersion,
320328
"io.netty" % "netty-common" % nettyVersion,
321-
"software.amazon.awssdk" % "eventbridge" % "2.20.162"
322329
),
323330
excludeDependencies ++= Seq(
324331
ExclusionRule("org.playframework", "play-ahc-ws_2.13"),
@@ -432,7 +439,7 @@ lazy val notificationworkerlambda = lambda("notificationworkerlambda", "notifica
432439
// Hopefully this workaround can be removed once play-json-extensions either updates to Play 3.0 or is merged into play-json
433440
ExclusionRule(organization = "com.typesafe.play")
434441
),
435-
)
442+
)
436443

437444
lazy val fakebreakingnewslambda = lambda("fakebreakingnewslambda", "fakebreakingnewslambda", Some("fakebreakingnews.LocalRun"))
438445
.dependsOn(common)
@@ -462,7 +469,7 @@ lazy val reportExtractor = lambda("reportextractor", "reportextractor", Some("co
462469

463470
lazy val liveactivities = lambda("liveactivities", "liveactivities", Some("com.gu.liveactivities.LambdaLocalRun"))
464471
.dependsOn(common)
465-
.dependsOn(apiModels % "test->test", apiModels % "compile->compile")
472+
.dependsOn(commonEventBusPusher)
466473
.settings(LocalDynamoDBLiveActivities.settings)
467474
.settings(
468475
libraryDependencies ++= Seq(

cdk/bin/cdk.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,10 @@ export const liveActivitiesCodeProps: GuStackProps = {
194194
};
195195

196196
new LiveActivities(app, 'LiveActivities-CODE', liveActivitiesCodeProps);
197+
198+
export const liveActivitiesProdProps: GuStackProps = {
199+
stack: 'mobile-notifications',
200+
stage: 'PROD',
201+
};
202+
203+
new LiveActivities(app, 'LiveActivities-PROD', liveActivitiesProdProps);

0 commit comments

Comments
 (0)