Skip to content

Commit 56e765a

Browse files
authored
Merge pull request #1432 from guardian/an/fp/add-new-ui-for-video-player-format
Add new UI to select Video Player Format
2 parents dca0747 + 088aeab commit 56e765a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1498
-553
lines changed

app/controllers/UploadController.scala

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,29 +99,23 @@ class UploadController(
9999
latestAssets.sortBy(ClientAsset.getVersion).reverse
100100
}
101101

102-
def create: Action[AnyContent] = LookupPermissions { implicit raw =>
102+
def create: Action[AnyContent] = APIAuthAction { implicit raw =>
103103
parse(raw) { req: UploadRequest =>
104-
if (req.selfHost && !raw.permissions.addSelfHostedAsset) {
105-
Unauthorized(
106-
s"User ${raw.user.email} is not authorised with permissions to upload self-hosted asset"
107-
)
108-
} else {
109-
log.info(
110-
s"Request for upload under atom ${req.atomId}. filename=${req.filename}. size=${req.size}, selfHosted=${req.selfHost}"
111-
)
104+
log.info(
105+
s"Request for upload under atom ${req.atomId}. filename=${req.filename}. size=${req.size}, selfHosted=${req.selfHost}"
106+
)
112107

113-
val thriftAtom = getPreviewAtom(req.atomId)
114-
val atom = MediaAtom.fromThrift(thriftAtom)
115-
val assetVersion =
116-
MediaAtomHelpers.getNextAssetVersion(thriftAtom.tdata)
108+
val thriftAtom = getPreviewAtom(req.atomId)
109+
val atom = MediaAtom.fromThrift(thriftAtom)
110+
val assetVersion =
111+
MediaAtomHelpers.getNextAssetVersion(thriftAtom.tdata)
117112

118-
val upload = start(atom, raw.user.email, req, assetVersion)
113+
val upload = start(atom, raw.user.email, req, assetVersion)
119114

120-
log.info(
121-
s"Upload created under atom ${req.atomId}. upload=${upload.id}. parts=${upload.parts.size}, selfHosted=${upload.metadata.selfHost}"
122-
)
123-
Ok(Json.toJson(upload))
124-
}
115+
log.info(
116+
s"Upload created under atom ${req.atomId}. upload=${upload.id}. parts=${upload.parts.size}, selfHosted=${upload.metadata.selfHost}"
117+
)
118+
Ok(Json.toJson(upload))
125119
}
126120
}
127121

app/data/AtomListStore.scala

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@ package data
22

33
import com.gu.atom.data.PreviewDynamoDataStore
44
import com.gu.media.CapiAccess
5-
import com.gu.media.model.{ContentChangeDetails, Image, MediaAtom}
5+
import com.gu.media.model.Platform.{Url, Youtube}
6+
import com.gu.media.model.VideoPlayerFormat.Loop
7+
import com.gu.media.model.{
8+
ContentChangeDetails,
9+
Image,
10+
MediaAtom,
11+
Platform,
12+
VideoPlayerFormat
13+
}
614
import com.gu.media.util.TestFilters
715
import model.commands.CommandExceptions.AtomDataStoreError
816
import model.{MediaAtomList, MediaAtomSummary}
@@ -14,7 +22,7 @@ trait AtomListStore {
1422
search: Option[String],
1523
limit: Option[Int],
1624
shouldUseCreatedDateForSort: Boolean,
17-
mediaPlatform: Option[String],
25+
platformFilter: Option[String],
1826
orderByOldest: Boolean
1927
): MediaAtomList
2028
}
@@ -30,7 +38,7 @@ class CapiBackedAtomListStore(capi: CapiAccess)
3038
search: Option[String],
3139
limit: Option[Int],
3240
shouldUseCreatedDateForSort: Boolean,
33-
mediaPlatform: Option[String],
41+
platformFilter: Option[String],
3442
orderByOldest: Boolean
3543
): MediaAtomList = {
3644
val pagination = Pagination.option(CapiMaxPageSize, limit)
@@ -40,7 +48,7 @@ class CapiBackedAtomListStore(capi: CapiAccess)
4048
case false => Map.empty
4149
}
4250

43-
val mediaPlatformFilter = mediaPlatform match {
51+
val mediaPlatformFilter = platformFilter match {
4452
case Some(mPlatform) => Map("media-platform" -> mPlatform)
4553
case _ => Map.empty
4654
}
@@ -142,38 +150,42 @@ class CapiBackedAtomListStore(capi: CapiAccess)
142150
(asset \ "version").as[Long]
143151
}
144152

145-
val mediaPlatforms = (atom \ "assets")
146-
.as[JsArray]
147-
.value
148-
.flatMap { asset =>
149-
(asset \ "platform").asOpt[String].map(_.toLowerCase)
150-
}
151-
.toList
152-
.distinct
153+
val atomPlatform =
154+
(atom \ "platform").asOpt[Platform]
153155

154-
val currentAsset = (atom \ "assets").as[JsArray].value.find { asset =>
156+
val activeAsset = (atom \ "assets").as[JsArray].value.find { asset =>
155157
val assetVersion = (asset \ "version").as[Long]
156158
activeVersion.contains(assetVersion)
157159
}
158160

159-
val currentMediaPlatform = currentAsset.flatMap { asset =>
160-
(asset \ "platform").asOpt[String].map(_.toLowerCase)
161+
val activeAssetPlatform = activeAsset.map { asset =>
162+
(asset \ "platform").as[Platform]
161163
}
162164

163-
// sort media platforms so the current one is first
164-
val sortedMediaPlatforms = currentMediaPlatform match {
165-
case Some(current) => current :: mediaPlatforms.filter(_ != current)
166-
case None => mediaPlatforms
167-
}
165+
val firstAssetPlatform =
166+
(atom \ "assets").as[JsArray].value.headOption.map { asset =>
167+
(asset \ "platform").as[Platform]
168+
}
169+
170+
val platform = Platform.getPlatform(
171+
atomPlatform,
172+
activeAssetPlatform,
173+
firstAssetPlatform
174+
)
175+
176+
val videoPlayerFormat =
177+
(atom \ "metadata" \ "selfHost" \ "videoPlayerFormat")
178+
.asOpt[VideoPlayerFormat]
179+
.orElse(if (platform == Url) Some(Loop) else None)
168180

169181
Some(
170182
MediaAtomSummary(
171183
id,
172184
title,
173185
posterImage,
174186
contentChangeDetails,
175-
sortedMediaPlatforms,
176-
currentMediaPlatform
187+
platform,
188+
videoPlayerFormat
177189
)
178190
)
179191
}
@@ -242,27 +254,13 @@ class DynamoBackedAtomListStore(store: PreviewDynamoDataStore)
242254
}
243255

244256
private def fromAtom(atom: MediaAtom): MediaAtomSummary = {
245-
val versions = atom.assets.map(_.version).toSet
246-
val currentAsset = atom.assets.find(asset =>
247-
asset.version == atom.activeVersion.getOrElse(versions.max)
248-
)
249-
val mediaPlatforms = atom.assets.map(_.platform.name.toLowerCase).distinct
250-
val currentMediaPlatform =
251-
currentAsset.map(_.platform.name).map(_.toLowerCase)
252-
253-
// sort media platforms so the current one is first
254-
val sortedMediaPlatforms = currentMediaPlatform match {
255-
case Some(current) => current :: mediaPlatforms.filter(_ != current)
256-
case None => mediaPlatforms
257-
}
258-
259257
MediaAtomSummary(
260258
atom.id,
261259
atom.title,
262260
atom.posterImage,
263261
atom.contentChangeDetails,
264-
sortedMediaPlatforms,
265-
currentMediaPlatform
262+
atom.platform.getOrElse(Youtube),
263+
atom.videoPlayerFormat
266264
)
267265
}
268266
}

app/model/MediaAtomSummary.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package model
22

3-
import com.gu.media.model.{Image, ContentChangeDetails}
3+
import com.gu.media.model.{
4+
ContentChangeDetails,
5+
Image,
6+
Platform,
7+
VideoPlayerFormat
8+
}
49
import com.gu.ai.x.play.json.Encoders._
510
import com.gu.ai.x.play.json.Jsonx
611
import play.api.libs.json.Format
@@ -12,8 +17,8 @@ case class MediaAtomSummary(
1217
title: String,
1318
posterImage: Option[Image],
1419
contentChangeDetails: ContentChangeDetails,
15-
mediaPlatforms: List[String],
16-
currentMediaPlatform: Option[String]
20+
platform: Platform,
21+
videoPlayerFormat: Option[VideoPlayerFormat]
1722
)
1823

1924
object MediaAtomList {

common/src/main/scala/com/gu/media/Permissions.scala

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,18 @@ import com.gu.permissions.PermissionDefinition
1010

1111
case class Permissions(
1212
deleteAtom: Boolean = false,
13-
addSelfHostedAsset: Boolean = false,
1413
setVideosOnAllChannelsPublic: Boolean = false,
15-
pinboard: Boolean = false,
16-
videoPlayerFormat: Boolean = false
14+
pinboard: Boolean = false
1715
)
1816
object Permissions {
1917
implicit val format: Format[Permissions] = Jsonx.formatCaseClass[Permissions]
2018

2119
val app = "atom-maker"
2220
val basicAccess = PermissionDefinition("media_atom_maker_access", app)
2321
val deleteAtom = PermissionDefinition("delete_atom", app)
24-
val addSelfHostedAsset = PermissionDefinition("add_self_hosted_asset", app)
2522
val setVideosOnAllChannelsPublic =
2623
PermissionDefinition("set_videos_on_all_channels_public", app)
2724
val pinboard = PermissionDefinition("pinboard", "pinboard")
28-
val videoPlayerFormat = PermissionDefinition("video_player_format", app)
2925
}
3026

3127
class MediaAtomMakerPermissionsProvider(
@@ -41,11 +37,9 @@ class MediaAtomMakerPermissionsProvider(
4137

4238
def getAll(user: PandaUser): Permissions = Permissions(
4339
deleteAtom = hasPermission(deleteAtom, user),
44-
addSelfHostedAsset = hasPermission(addSelfHostedAsset, user),
4540
setVideosOnAllChannelsPublic =
4641
hasPermission(setVideosOnAllChannelsPublic, user),
47-
pinboard = hasPermission(pinboard, user),
48-
videoPlayerFormat = hasPermission(videoPlayerFormat, user)
42+
pinboard = hasPermission(pinboard, user)
4943
)
5044

5145
def getStatusPermissions(user: PandaUser): Permissions =

common/src/main/scala/com/gu/media/model/MediaAtom.scala

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import com.gu.contentatom.thrift.{
1515
AtomType => ThriftAtomType,
1616
Flags => ThriftFlags
1717
}
18+
import com.gu.media.model.Platform.Url
19+
import com.gu.media.model.VideoPlayerFormat.Loop
1820
import com.gu.media.util.MediaAtomImplicits
1921
import com.gu.media.youtube.{
2022
MediaAtomYoutubeDescriptionHandler,
@@ -278,16 +280,19 @@ object MediaAtom extends MediaAtomImplicits {
278280
def fromThrift(atom: ThriftAtom) = {
279281
val data = atom.tdata
280282

283+
val assets = data.assets.map(Asset.fromThrift).toList
284+
val activeVersion = data.activeVersion
281285
val youtubeDescription: Option[String] =
282286
MediaAtomYoutubeDescriptionHandler.getYoutubeDescription(data)
287+
val platform = getPlatform(data, activeVersion, assets)
283288

284289
MediaAtom(
285290
id = atom.id,
286291
labels = atom.labels.toList,
287292
contentChangeDetails =
288293
ContentChangeDetails.fromThrift(atom.contentChangeDetails),
289-
assets = data.assets.map(Asset.fromThrift).toList,
290-
activeVersion = data.activeVersion,
294+
assets = assets,
295+
activeVersion = activeVersion,
291296
title = data.title,
292297
category = Category.fromThrift(data.category),
293298
plutoData = data.metadata.flatMap(_.pluto).map(PlutoData.fromThrift),
@@ -319,11 +324,46 @@ object MediaAtom extends MediaAtomImplicits {
319324
youtubeTitle =
320325
data.metadata.flatMap(_.youtube).map(_.title).getOrElse(data.title),
321326
youtubeDescription = youtubeDescription,
322-
videoPlayerFormat = data.metadata
323-
.flatMap(_.selfHost)
324-
.flatMap(_.videoPlayerFormat)
325-
.map(VideoPlayerFormat.fromThrift),
326-
platform = data.platform.map(Platform.fromThrift)
327+
videoPlayerFormat = getVideoPlayerFormat(data.metadata, platform),
328+
platform = platform
329+
)
330+
}
331+
332+
/** will derive videoPlayerFormat if it is missing
333+
* @param metadata
334+
* @param platform
335+
* @return
336+
*/
337+
def getVideoPlayerFormat(
338+
metadata: Option[ThriftMetadata],
339+
platform: Option[Platform]
340+
): Option[VideoPlayerFormat] = {
341+
metadata
342+
.flatMap(_.selfHost)
343+
.flatMap(_.videoPlayerFormat)
344+
.map(VideoPlayerFormat.fromThrift)
345+
.orElse(if (platform.contains(Url)) Some(Loop) else None)
346+
}
347+
348+
/** will derive atom-level platform if it is missing
349+
* @param data
350+
* @param activeVersion
351+
* @param assets
352+
* @return
353+
*/
354+
def getPlatform(
355+
data: ThriftMediaAtom,
356+
activeVersion: Option[Long],
357+
assets: List[Asset]
358+
): Option[Platform] = {
359+
Option(
360+
Platform.getPlatform(
361+
data.platform.map(Platform.fromThrift),
362+
activeVersion
363+
.flatMap(activeVersion => assets.find(_.version == activeVersion))
364+
.map(_.platform),
365+
assets.headOption.map(_.platform)
366+
)
327367
)
328368
}
329369
}

common/src/main/scala/com/gu/media/model/Platform.scala

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ object Platform {
1616
case object Url extends Platform { val name = "Url" }
1717

1818
val platformReads = Reads[Platform](json => {
19-
json.as[String] match {
20-
case "Youtube" => JsSuccess(Youtube)
21-
case "Facebook" => JsSuccess(Facebook)
22-
case "Dailymotion" => JsSuccess(Dailymotion)
23-
case "Mainstream" => JsSuccess(Mainstream)
24-
case "Url" => JsSuccess(Url)
19+
json.as[String].toLowerCase match {
20+
case "youtube" => JsSuccess(Youtube)
21+
case "facebook" => JsSuccess(Facebook)
22+
case "dailymotion" => JsSuccess(Dailymotion)
23+
case "mainstream" => JsSuccess(Mainstream)
24+
case "url" => JsSuccess(Url)
2525
}
2626
})
2727

@@ -35,4 +35,30 @@ object Platform {
3535
private val types = List(Youtube, Facebook, Dailymotion, Mainstream, Url)
3636

3737
def fromThrift(p: ThriftPlatform) = types.find(_.name == p.name).get
38+
39+
/** The atom-level platform field tells us authoritatively if atom is for
40+
* Youtube or Self-hosted video.
41+
*
42+
* To derive an atom-level platform field from models with potentially
43+
* missing data:
44+
* - use atom level platform if provided
45+
* - otherwise use the platform field of the active asset
46+
* - otherwise use the platform field of the first asset, if there are any
47+
* assets
48+
* - otherwise default to Youtube
49+
* @param atomPlatform
50+
* @param activeAssetPlatform
51+
* @param firstAssetPlatform
52+
* @return
53+
*/
54+
def getPlatform(
55+
atomPlatform: Option[Platform],
56+
activeAssetPlatform: Option[Platform],
57+
firstAssetPlatform: Option[Platform]
58+
): Platform =
59+
atomPlatform
60+
.orElse(activeAssetPlatform)
61+
.orElse(firstAssetPlatform)
62+
.getOrElse(Youtube)
63+
3864
}

common/src/main/scala/com/gu/media/model/VideoPlayerFormat.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ object VideoPlayerFormat {
1616
case object Cinemagraph extends VideoPlayerFormat { val name = "Cinemagraph" }
1717

1818
val videoPlayerFormatReads = Reads[VideoPlayerFormat](json => {
19-
json.as[String] match {
20-
case "Default" => JsSuccess(Default)
21-
case "Loop" => JsSuccess(Loop)
22-
case "Cinemagraph" => JsSuccess(Cinemagraph)
19+
json.as[String].toLowerCase match {
20+
case "default" => JsSuccess(Default)
21+
case "loop" => JsSuccess(Loop)
22+
case "cinemagraph" => JsSuccess(Cinemagraph)
2323
}
2424
})
2525

0 commit comments

Comments
 (0)