Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions kahuna/public/js/image/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ image.controller('ImageCtrl', [
ctrl.canUserEdit = editable;
});

ctrl.objectHasEntries = obj => obj && Object.keys(obj).length > 0;

const usages = imageUsagesService.getUsages(ctrl.image);
const usagesCount$ = usages.count$;

Expand Down
26 changes: 26 additions & 0 deletions kahuna/public/js/image/view.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,32 @@
class="image-details__delete-crops"
gr-image="ctrl.image"
gr-on-delete="ctrl.onCropsDeleted()"></gr-delete-crops>

<div class="image-info image-details--scroll" style="margin-top: auto; max-height: 50%">
<div ng-repeat="(title, idToDetailMap) in ctrl.image.data.parentAndChildDetails">
<div class="image-info__group image-info__group--bottom" aria-label="{{title}}"
ng-if="ctrl.objectHasEntries(idToDetailMap)">
<dl class="image-info__group--dl">
<dt class="image-info__heading">{{title}}</dt>
<dd class="image-info__heading--crops"
ng-repeat="(id, detail) in idToDetailMap">
<a class="image-crop"
ui-sref="image({imageId: id})"
aria-label="View {{title}}">
<img class="image-crop__image"
alt="{{title}} {{id}} thumbnail"
ng-src="{{detail.thumbnail}}" />
<div class="flex-container image-crop__more-info">
<span ng-if="detail.dimensions">{{detail.dimensions.width}} &times; {{detail.dimensions.height}}</span>
<span class="flex-spacer"></span>
<span class="image-crop__creator" title="Added by {{detail.addedBy}} at {{detail.addedAt | date:'medium'}}">{{detail.addedBy | getInitials}}</span>
</div>
</a>
</dd>
</dl>
</div>
</div>
</div>
</div>

<div class="image-details image-details--full-image" role="complementary" aria-label="Image information">
Expand Down
8 changes: 7 additions & 1 deletion kahuna/public/stylesheets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2102,7 +2102,8 @@ FIXME: what to do with touch devices
}

.image-details__delete-crops {
width: 100%;
width: 100%;
outline: 1px solid #565656;
}

.image-details:after {
Expand All @@ -2122,6 +2123,11 @@ FIXME: what to do with touch devices
border-bottom: 1px solid #565656;
}

.image-info__group--bottom {
border-bottom: 0;
border-top: 1px solid #565656;
}

.image-info__group--last {
border-bottom: 0;
clear: both;
Expand Down
20 changes: 18 additions & 2 deletions media-api/app/controllers/MediaApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controllers

import org.apache.pekko.stream.scaladsl.StreamConverters
import com.google.common.net.HttpHeaders
import com.gu.mediaservice.lib.ImageStorageProps
import com.gu.mediaservice.{GridClient, JsonDiff}
import com.gu.mediaservice.lib.argo._
import com.gu.mediaservice.lib.argo.model.{Action, _}
Expand All @@ -15,6 +16,7 @@ import com.gu.mediaservice.lib.logging.{LogMarker, MarkerMap}
import com.gu.mediaservice.lib.metadata.SoftDeletedMetadataTable
import com.gu.mediaservice.lib.play.RequestLoggingFilter
import com.gu.mediaservice.model._
import com.gu.mediaservice.model.usage.{DerivativeUsageStatus, ReplacedUsageStatus}
import com.gu.mediaservice.syntax.MessageSubjects
import lib._
import lib.elasticsearch._
Expand Down Expand Up @@ -573,6 +575,16 @@ class MediaApi(
val deleteImagePermission = authorisation.isUploaderOrHasPermission(request.user, source.instance.uploadedBy, DeleteImagePermission)
val deleteCropsOrUsagePermission = canUserDeleteCropsOrUsages(request.user)

import JodaWrites._
implicit val jsonDetailsWrites: OWrites[RelationDetail] = Json.writes[RelationDetail]
val getRelationDetails = elasticSearch.getRelationDetails(id, imageResponse.getSecureThumbUrl)_
val relationDetails = Map(
"Replacement for" -> source.instance.identifiers.get(ImageStorageProps.replacesMediaIdIdentifierKey).map(getRelationDetails),
"Replaced by" -> source.instance.usages.filter(_.status == ReplacedUsageStatus).flatMap(_.childUsageMetadata.map(_.childMediaId)).map(getRelationDetails),
"Derivative of" -> source.instance.identifiers.get(ImageStorageProps.derivativeOfMediaIdsIdentifierKey).toList.flatMap(_.split(",").map(_.trim)).map(getRelationDetails),
"Derivatives" -> source.instance.usages.filter(_.status == DerivativeUsageStatus).flatMap(_.childUsageMetadata.map(_.childMediaId)).map(getRelationDetails)
).view.mapValues(_.iterator.toMap).toMap

val (imageData, imageLinks, imageActions) = imageResponse.create(
id,
source,
Expand All @@ -583,8 +595,12 @@ class MediaApi(
request.user.accessor.tier
)

Some((source.instance, imageData, imageLinks, imageActions))

Some((
source.instance,
imageData.asInstanceOf[JsObject] + ("parentAndChildDetails" -> Json.toJson(relationDetails)),
imageLinks,
imageActions
))
case _ => None
}
}
Expand Down
20 changes: 12 additions & 8 deletions media-api/app/lib/ImageResponse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,11 @@ class ImageResponse(config: MediaApiConfig, s3Client: S3Client, usageQuota: Usag

val pngFileUri = image.optimisedPng.map(_.file)

val fileUri = image.source.file

val imageUrl = s3Client.signUrl(config.imageBucket, fileUri, image, imageType = Source)
val imageUrl = s3Client.signUrl(config.imageBucket, image.source.file, image, imageType = Source)
val pngUrl: Option[String] = pngFileUri
.map(s3Client.signUrl(config.imageBucket, _, image, imageType = OptimisedPng))

def s3SignedThumbUrl = s3Client.signUrl(config.thumbBucket, fileUri, image, imageType = Thumbnail)

val thumbUrl = config.cloudFrontDomainThumbBucket
.flatMap(s3Client.signedCloudFrontUrl(_, fileUri.getPath.drop(1)))
.getOrElse(s3SignedThumbUrl)
val thumbUrl: String = getSecureThumbUrl(image)

val validityMap = checkUsageRestrictions(source, ImageExtras.validityMap(image, withWritePermission))
val valid = ImageExtras.isValid(validityMap)
Expand Down Expand Up @@ -130,6 +124,16 @@ class ImageResponse(config: MediaApiConfig, s3Client: S3Client, usageQuota: Usag
(data, links, actions)
}

def getSecureThumbUrl(image: Image) = {
val fileUri: URI = image.source.file

def s3SignedThumbUrl = s3Client.signUrl(config.thumbBucket, fileUri, image, imageType = Thumbnail)

config.cloudFrontDomainThumbBucket
.flatMap(s3Client.signedCloudFrontUrl(_, fileUri.getPath.drop(1)))
.getOrElse(s3SignedThumbUrl)
}

private def downloadLink(id: String) = Link("download", s"${config.rootUri}/images/$id/download")
private def downloadOptimisedLink(id: String) = Link("downloadOptimised", s"${config.rootUri}/images/$id/downloadOptimised?{&width,height,quality}")

Expand Down
25 changes: 22 additions & 3 deletions media-api/app/lib/elasticsearch/ElasticSearch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.gu.mediaservice.lib.auth.Authentication.Principal
import com.gu.mediaservice.lib.elasticsearch.{CompletionPreview, ElasticSearchClient, ElasticSearchConfig, MigrationStatusProvider, Running}
import com.gu.mediaservice.lib.logging.{GridLogging, LogMarker, MarkerMap}
import com.gu.mediaservice.lib.metrics.FutureSyntax
import com.gu.mediaservice.model.{Agencies, Agency, AwaitingReviewForSyndication, Image}
import com.gu.mediaservice.model.{Agencies, Agency, AwaitingReviewForSyndication, Dimensions, Image}
import com.sksamuel.elastic4s.ElasticDsl
import com.sksamuel.elastic4s.ElasticDsl._
import com.sksamuel.elastic4s.requests.get.{GetRequest, GetResponse}
Expand All @@ -27,8 +27,8 @@ import scalaz.NonEmptyList
import scalaz.syntax.std.list._

import java.util.concurrent.TimeUnit
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.concurrent.{Await, ExecutionContext, Future, TimeoutException}

class ElasticSearch(
val config: MediaApiConfig,
Expand Down Expand Up @@ -280,6 +280,25 @@ class ElasticSearch(
}
}

def getRelationDetails(mediaIdThisIsFor: String, getSecureThumbUrl: Image => String)(
id: String
)(implicit ex: ExecutionContext, logMarker:MarkerMap = MarkerMap()): (String, Option[RelationDetail]) = {
try {
id -> Await.result(getImageById(id), 5.seconds).map{image =>
RelationDetail(
thumbnail = getSecureThumbUrl(image),
addedBy = image.uploadedBy,
addedAt = image.uploadTime,
dimensions = image.source.orientedDimensions.orElse(image.source.dimensions)
)
}
} catch {
case e: TimeoutException =>
logger.error(logMarker, s"Timeout getting image $id (when finding relation details for $mediaIdThisIsFor)", e)
id -> None
}
}

def usageForSupplier(id: String, numDays: Int)(implicit ex: ExecutionContext, logMarker: LogMarker): Future[SupplierUsageSummary] = {
val supplier = Agencies.get(id)
val supplierName = supplier.supplier
Expand Down
8 changes: 7 additions & 1 deletion media-api/app/lib/elasticsearch/ElasticSearchModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package lib.elasticsearch
import com.gu.mediaservice.lib.auth.{Authentication, Tier}
import com.gu.mediaservice.lib.formatting.{parseDateFromQuery, printDateTime}
import com.gu.mediaservice.model.usage.UsageStatus
import com.gu.mediaservice.model.{Image, PrintUsageFilters, SyndicationStatus}
import com.gu.mediaservice.model.{Dimensions, Image, PrintUsageFilters, SyndicationStatus}
import lib.querysyntax.{Condition, Parser}
import org.joda.time.DateTime
import play.api.libs.json.{Json, OWrites}
Expand Down Expand Up @@ -54,6 +54,12 @@ object AggregateSearchParams {
)
}
}
case class RelationDetail(
thumbnail: String,
addedBy: String,
addedAt: DateTime,
dimensions: Option[Dimensions]
)

case class SearchParams(
query: Option[String] = None,
Expand Down
Loading