Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically add/remove leases when a rights category is selected for an image/s based on application level config #4358

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
34 changes: 32 additions & 2 deletions common-lib/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,32 @@ usageRights.applicable = [
#-----------------------------------------------------------------------------------------
usageRights.stdUserExcluded = []

#--------------------------------------------------------------------------------------------
# List of leases that should be associated with an image when a rights category is selected
# (on upload or image edit)
# Format should be:
# usageRights.leases = [ (array)
# {
# category: "<<category-id>>",
# type: "allow-use | deny-use | allow-syndication | deny-syndication",
# startDate: "TODAY | UPLOAD | TAKEN | TXDATE", <- other than today all entries map to image metadata field
# duration: <<int nos years>>, <- optional and will be indefinite if excluded
# notes: "<<text string>>" <- optional
# },
# ...
# ]
#--------------------------------------------------------------------------------------------
usageRights.leases = [
AndyKilmory marked this conversation as resolved.
Show resolved Hide resolved
{
category: "screengrab",
type: "allow-use",
startDate: "UPLOAD",
duration: 5,
notes: "test lease"
}
]


usageRightsConfigProvider = {
className: "com.gu.mediaservice.lib.config.RuntimeUsageRightsConfig"
config {
Expand Down Expand Up @@ -123,9 +149,13 @@ usageRightsConfigProvider = {
# }
# can be left blank or excluded if not required
# -------------------------------------------------------
usageInstructions {
}
usageRestrictions {
contract-photographer = "This image has restrictions - see special instructions for details"
handout = "This image can only be used in that context from which it originates - or you'll get told off!"
}
usageInstructions {
contract-photographer = "You'll need to ask the photographer nicely if you want to use this image"
obituary = "Make sure the person is dead before you use this image"
}

# -------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import java.util.Date
import scala.collection.JavaConverters._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.util.Random

trait Metric[A] {
def recordOne(value: A, dimensions: List[Dimension] = Nil): Unit
Expand Down Expand Up @@ -45,7 +46,8 @@ abstract class CloudWatchMetrics(

private val client: AmazonCloudWatch = config.withAWSCredentials(AmazonCloudWatchClientBuilder.standard()).build()

private[CloudWatchMetrics] val metricsActor = actorSystem.actorOf(MetricsActor.props(namespace, client), "metricsactor")
private val random = new Random()
private[CloudWatchMetrics] val metricsActor = actorSystem.actorOf(MetricsActor.props(namespace, client), s"metricsactor-${random.alphanumeric.take(8).mkString}")

applicationLifecycle.addStopHook(() => (metricsActor ? MetricsActor.Shutdown)(Timeout(5.seconds)))

Expand Down
71 changes: 71 additions & 0 deletions kahuna/public/js/common/usageRightsUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// -using config lease definitions to create leases for image based on chosen rights category-
export function createCategoryLeases(leaseDefs, image) {
const leaseTypes = ["allow-use", "deny-use", "allow-syndication", "deny-syndication"];
const leases = [];
leaseDefs.forEach((leaseDef) => {
//-establish start date: TODAY | UPLOAD | TAKEN | TXDATE-
const startDteType = leaseDef.startDate ?? "NONE";
let startDate = undefined;
switch (startDteType) {
case ("TODAY"):
startDate = new Date();
break;
case ("UPLOAD"):
startDate = new Date(image.data.uploadTime);
break;
case ("TAKEN"):
if (image.data.metadata.dateTaken) {
startDate = new Date(image.data.metadata.dateTaken);
}
break;
case ("TXDATE"):
if (image.data.metadata.domainMetadata &&
image.data.metadata.domainMetadata.programmes &&
image.data.metadata.domainMetadata.programmes.originalTxDate) {
startDate = new Date(image.data.metadata.domainMetadata.programmes.originalTxDate);
}
break;
}
// -check we have acceptable type and startDate-
if (leaseTypes.includes(leaseDef.type ?? "") && startDate) {
const lease = {};
lease["access"] = leaseDef.type;
lease["createdAt"] = (new Date()).toISOString();
lease["leasedBy"] = "Usage_Rights_Category";
lease["startDate"] = startDate.toISOString();
lease["notes"] = leaseDef.notes ?? "";

if (leaseDef.duration) {
let endDate = startDate;
endDate.setFullYear(endDate.getFullYear() + leaseDef.duration);
lease["endDate"] = endDate.toISOString();
}
lease["mediaId"] = image.data.id;
leases.push(lease);
}
});
return leases;
}

/* ******************************************************************************
Remove any leases from image that have same type as any rights-cat applied leases
********************************************************************************* */
export function removeCategoryLeases(categories, image, removeRights) {
const mtchCats = categories.filter(cat => cat.value === removeRights);
if (mtchCats.length === 0) {
return [];
}
const removeCat = mtchCats[0];
if (removeCat.leases.length === 0) {
return [];
}
const removeLeases = [];
image.data.leases.data.leases.forEach(lease => {
const mtches = removeCat.leases.filter(catLease => catLease.type === lease.access);
if (mtches.length > 0) {
removeLeases.push(lease);
}
});

return removeLeases;
}
63 changes: 60 additions & 3 deletions kahuna/public/js/edits/image-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {imageService} from '../image/service';
import '../services/label';
import {imageAccessor} from '../services/image-accessor';
import {usageRightsEditor} from '../usage-rights/usage-rights-editor';
import { createCategoryLeases, removeCategoryLeases } from '../common/usageRightsUtils.js';
import {metadataTemplates} from "../metadata-templates/metadata-templates";
import {leases} from '../leases/leases';
import {archiver} from '../components/gr-archiver-status/gr-archiver-status';
Expand Down Expand Up @@ -273,6 +274,7 @@ imageEditor.controller('ImageEditorCtrl', [
const image = ctrl.image;
const resource = image.data.userMetadata.data.usageRights;
editsService.update(resource, data, image);
batchSetLeasesFromUsageRights(image, data.category);
});
}

Expand Down Expand Up @@ -316,10 +318,65 @@ imageEditor.controller('ImageEditorCtrl', [
ctrl.showUsageRights = ctrl.usageRightsCategory === undefined;
}

function batchSetLeasesFromUsageRights(image, rightsCat) {
const category = ctrl.categories.find(cat => cat.value === rightsCat);
if (!category || image.data.usageRights.category === rightsCat) {
return;
}
if (category.leases.length === 0) {
// possibility of removal only
if (!image.data.usageRights.category) {
return;
}
const removeLeases = removeCategoryLeases(ctrl.categories, image, image.data.usageRights.category);
if (removeLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:delete-leases', {
catLeases: removeLeases,
batch: false
});
}
return;
}
const catLeases = createCategoryLeases(category.leases, image);
if (catLeases.length === 0) {
// possibility of remove only of leases due to missing date info on image
if (!image.data.usageRights.category) {
return;
}
const removeLeases = removeCategoryLeases(ctrl.categories, image, image.data.usageRights.category);
if (removeLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:delete-leases', {
catLeases: removeLeases,
batch: false
});
}
return;
}
$rootScope.$broadcast('events:rights-category:add-leases', {
catLeases: catLeases,
batch: false
});
}

function batchApplyUsageRights() {
$rootScope.$broadcast(batchApplyUsageRightsEvent, {
data: ctrl.usageRights.data
});
$rootScope.$broadcast(batchApplyUsageRightsEvent, {
data: ctrl.usageRights.data
});

//-rights category derived leases-
const mtchingRightsCats = ctrl.categories.filter(c => c.value == ctrl.usageRights.data.category);
if (mtchingRightsCats.length > 0) {
const rightsCat = mtchingRightsCats[0];
if (rightsCat.leases.length > 0) {
const catLeases = createCategoryLeases(rightsCat.leases, ctrl.image);
if (catLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:add-leases', {
catLeases: catLeases,
batch: true
});
}
}
}
}

function openCollectionTree() {
Expand Down
26 changes: 26 additions & 0 deletions kahuna/public/js/leases/leases.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,32 @@ leases.controller('LeasesCtrl', [
// which also isn't ideal, but isn't quadratic either.
const batchAddLeasesEvent = 'events:batch-apply:add-leases';
const batchRemoveLeasesEvent = 'events:batch-apply:remove-leases';
const rightsCatAddLeasesEvent = 'events:rights-category:add-leases';
const rightsCatDeleteLeasesEvent = 'events:rights-category:delete-leases';

//-handle rights cat assigned lease-
$scope.$on(rightsCatAddLeasesEvent,
(e, payload) => {
let matchImages = ctrl.images.filter(img => img.data.id === payload.catLeases[0].mediaId);
if (angular.isDefined(matchImages.toArray)) {
matchImages = matchImages.toArray();
};
if (matchImages.length || payload.batch) {
leaseService.replace(matchImages[0], payload.catLeases);
}
}
);

//-handle deletion of leases from previous rights category-
$scope.$on(rightsCatDeleteLeasesEvent,
(e, payload) => {
if (payload.catLeases && 0 < payload.catLeases.length) {
AndyKilmory marked this conversation as resolved.
Show resolved Hide resolved
payload.catLeases.forEach(lease => {
leaseService.deleteLease(lease, ctrl.images);
});
}
}
);

if (Boolean(ctrl.withBatch)) {
$scope.$on(batchAddLeasesEvent,
Expand Down
36 changes: 36 additions & 0 deletions kahuna/public/js/usage-rights/usage-rights-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {List} from 'immutable';

import '../services/image-list';

import { createCategoryLeases, removeCategoryLeases } from '../common/usageRightsUtils.js';

import template from './usage-rights-editor.html';
import './usage-rights-editor.css';

Expand Down Expand Up @@ -202,6 +204,10 @@ usageRightsEditor.controller(
const resource = image.data.userMetadata.data.usageRights;
return editsService.update(resource, data, image, true);
},
({ image }) => {
const prevRights = (0 < ctrl.usageRights.size) ? ctrl.usageRights.first().data.category : "";
return setLeasesFromUsageRights(image, prevRights);
},
({ image }) => setMetadataFromUsageRights(image, true),
({ image }) => image.get()
],'images-updated');
Expand All @@ -227,6 +233,36 @@ usageRightsEditor.controller(
'Unexpected error';
}

function setLeasesFromUsageRights(image, prevRights) {
if (ctrl.category.leases.length === 0) {
// possibility of removal only
const removeLeases = removeCategoryLeases(ctrl.categories, image, prevRights);
if (removeLeases && removeLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:delete-leases', {
catLeases: removeLeases,
batch: false
});
}
return;
}
const catLeases = createCategoryLeases(ctrl.category.leases, image);
if (catLeases.length === 0) {
// possibility of removal only - missing tx date etc.
const removeLeases = removeCategoryLeases(ctrl.categories, image, prevRights);
if (removeLeases && removeLeases.length > 0) {
$rootScope.$broadcast('events:rights-category:delete-leases', {
catLeases: removeLeases,
batch: false
});
}
return;
}
$rootScope.$broadcast('events:rights-category:add-leases', {
catLeases: catLeases,
batch: false
});
}

// HACK: This should probably live somewhere else, but it's the least intrusive
// here. This updates the metadata based on the usage rights to stop users having
// to enter content twice.
Expand Down
3 changes: 3 additions & 0 deletions metadata-editor/app/controllers/EditsApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.gu.mediaservice.lib.config.{RuntimeUsageRightsConfig, UsageRightsConf
import com.gu.mediaservice.model._
import lib.EditsConfig
import model.UsageRightsProperty
import model.UsageRightsLease
import play.api.libs.json._
import play.api.mvc.Security.AuthenticatedRequest
import play.api.mvc.{AnyContent, BaseController, ControllerComponents}
Expand Down Expand Up @@ -75,6 +76,7 @@ case class CategoryResponse(
defaultRestrictions: Option[String],
caution: Option[String],
properties: List[UsageRightsProperty] = List(),
leases: Seq[UsageRightsLease] = Seq(),
usageRestrictions: Option[String],
usageSpecialInstructions: Option[String]
)
Expand All @@ -90,6 +92,7 @@ object CategoryResponse {
defaultRestrictions = u.defaultRestrictions,
caution = u.caution,
properties = UsageRightsProperty.getPropertiesForSpec(u, config.usageRightsConfig),
leases = UsageRightsLease.getLeasesForSpec(u, config.usageRightsLeases),
usageRestrictions = config.customUsageRestrictions.get(u.category),
usageSpecialInstructions = config.customSpecialInstructions.get(u.category)
)
Expand Down
4 changes: 3 additions & 1 deletion metadata-editor/app/lib/EditsConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package lib

import com.amazonaws.regions.{Region, RegionUtils}
import com.gu.mediaservice.lib.config.{CommonConfig, GridConfigResources}

import model.UsageRightsLease

class EditsConfig(resources: GridConfigResources) extends CommonConfig(resources) {
val dynamoRegion: Region = RegionUtils.getRegion(string("aws.region"))
Expand All @@ -19,6 +19,8 @@ class EditsConfig(resources: GridConfigResources) extends CommonConfig(resources
val kahunaUri: String = services.kahunaBaseUri
val loginUriTemplate: String = services.loginUriTemplate

val usageRightsLeases: Seq[UsageRightsLease] = configuration.getOptional[Seq[UsageRightsLease]]("usageRights.leases").getOrElse(Seq.empty)

val customSpecialInstructions: Map[String, String] =
configuration.getOptional[Map[String, String]]("usageInstructions").getOrElse(Map.empty)

Expand Down
Loading
Loading