Skip to content

Commit 28d4281

Browse files
authored
Overriding extractors at the space level when they are enabled at the global level. (#74)
* Adding support for overriding extractors at the space level when they are enabled at the global level. * Switched to using radio buttons and icons for the page to enable/disable extractors at the space level. * Fixed accidental commit. * Added a default radio button for extractors in space to follow the global default. Order extractors by name in Space Extractors page. Moved radio buttons to the end of the form to make it easier to read. * Changed table header for space extractors to "Enabled by SuperAdmin" to be more clear. Added some titles text to explain each column. * Added update-space-extractors-selection Mongo update script. * Updated CHANGELOG.md.
1 parent 3bc1e37 commit 28d4281

File tree

9 files changed

+146
-54
lines changed

9 files changed

+146
-54
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77
## [Unreleased]
88

99
### Added
10+
- Users have more refined options to set extractors triggers at the space level. They can now follow global settings,
11+
disable and enable triggers.
1012
- Ability to set chunksize for clowder when downloading files. This is changed to 1MB from 8KB. This will result in
1113
faster downloads and less CPU usage at the cost of slightly more memory use.
1214
- Add endpoint and view page for the extractor metrics.

app/controllers/Spaces.scala

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,15 @@ class Spaces @Inject() (spaces: SpaceService, users: UserService, events: EventS
9696
case Some(s) => {
9797
// get list of registered extractors
9898
val runningExtractors: List[ExtractorInfo] = extractors.listExtractorsInfo(List.empty)
99-
// get list of extractors registered with a specific space
100-
val selectedExtractors: List[String] = spaces.getAllExtractors(id)
99+
// list of extractors enabled globally
101100
val globalSelections: List[String] = extractors.getEnabledExtractors()
102-
Ok(views.html.spaces.updateExtractors(runningExtractors, selectedExtractors, globalSelections, id, s.name))
101+
// get list of extractors registered with a specific space
102+
val selectedExtractors: Option[ExtractorsForSpace] = spaces.getAllExtractors(id)
103+
val (enabledInSpace, disabledInSpace) = spaces.getAllExtractors(id) match {
104+
case Some(extractorsForSpace) => (extractorsForSpace.enabled, extractorsForSpace.disabled)
105+
case None => (List.empty[String], List.empty[String])
106+
}
107+
Ok(views.html.spaces.updateExtractors(runningExtractors, enabledInSpace, disabledInSpace, globalSelections, id, s.name))
103108
}
104109
case None => InternalServerError(spaceTitle + " not found")
105110
}
@@ -111,7 +116,7 @@ class Spaces @Inject() (spaces: SpaceService, users: UserService, events: EventS
111116
def updateExtractors(id: UUID) = PermissionAction(Permission.EditSpace, Some(ResourceRef(ResourceRef.space, id)))(parse.multipartFormData) {
112117
implicit request =>
113118
implicit val user = request.user
114-
//form contains space id and list of extractors.
119+
// form contains space id and list of extractors.
115120
var space_id: String = ""
116121
var extractors: List[String] = Nil
117122

@@ -120,17 +125,36 @@ class Spaces @Inject() (spaces: SpaceService, users: UserService, events: EventS
120125
Logger.error("space id not defined")
121126
BadRequest(spaceTitle + " id not defined")
122127
} else {
123-
//space id passed as hidden parameter
128+
// space id passed as hidden parameter
124129
space_id = dataParts("space_id").head
125130
spaces.get(new UUID(space_id)) match {
126131
case Some(existing_space) => {
127-
//1. remove entry with extractors for this space from mongo
132+
// FIXME by splitting the operation in two separate queries we run into transaction issues if there is
133+
// a hickup between the two. We should try to do the two db queries in one.
134+
135+
// 1. remove entry with extractors for this space from mongo
128136
spaces.deleteAllExtractors(existing_space.id)
129-
//2. if extractors are selected, add them
130-
if (dataParts.isDefinedAt("extractors")) {
131-
extractors = dataParts("extractors").toList
132-
extractors.map(spaces.addExtractor(existing_space.id, _))
137+
// 2. if extractors are selected, add them
138+
val prefix = "extractors-"
139+
extractors = dataParts.keysIterator.filter(_.startsWith(prefix)).toList
140+
extractors.foreach { extractor =>
141+
// get the first entry and ignore all others (there should only be one)
142+
val name = extractor.substring(prefix.length)
143+
val value = dataParts(extractor)(0)
144+
if (value.equals("default")) {
145+
spaces.setDefaultExtractor(existing_space.id, name)
146+
} else if (value.equals("enabled")) {
147+
spaces.enableExtractor(existing_space.id, name)
148+
} else if (value.equals("disabled")) {
149+
spaces.disableExtractor(existing_space.id, name)
150+
} else {
151+
Logger.error("Wrong value for update space extractor form")
152+
}
133153
}
154+
// if (dataParts.isDefinedAt("extractors-override")) {
155+
// extractors = dataParts("extractors-override").toList
156+
// extractors.map(spaces.disableExtractor(existing_space.id, _))
157+
// }
134158
Redirect(routes.Spaces.getSpace(new UUID(space_id)))
135159
}
136160
case None => {
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package models
22

33
/**
4-
* Information about extractors assigned to a space.
4+
* Information about extractors assigned to a space. This only represents extractors that were customized for a specific
5+
* space. For example enabled/disabled. By default we currently show all global extractors on each space and let users
6+
* enable/disable them if they so wish.
7+
*
8+
* TODO: If we wanted to have default parameters for an extractor defined by space, this could be one place to store them.
59
*/
610
case class ExtractorsForSpace(
711
spaceId:String,
8-
extractors:List[String]
12+
enabled: List[String] = List.empty[String],
13+
disabled: List[String] = List.empty[String]
914
)

app/services/RabbitmqPlugin.scala

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import java.io.IOException
44
import java.net.URI
55
import java.text.SimpleDateFormat
66
import java.net.URLEncoder
7+
import java.util
78

89
import akka.actor.{Actor, ActorRef, PoisonPill, Props}
910

@@ -23,6 +24,7 @@ import play.api.{Application, Logger, Plugin}
2324
import play.libs.Akka
2425
import securesocial.core.IdentityId
2526

27+
import scala.collection.mutable.ListBuffer
2628
import scala.concurrent.{Await, Future}
2729
import scala.util.Try
2830

@@ -314,15 +316,6 @@ class RabbitmqPlugin(application: Application) extends Plugin {
314316
private def contentTypeToRoutingKey(contentType: String) =
315317
contentType.replace(".", "_").replace("/", ".")
316318

317-
/**
318-
* Given a dataset, return the union of all extractors registered for each space the dataset is in.
319-
* @param dataset
320-
* @return list of active extractors
321-
*/
322-
private def getRegisteredExtractors(dataset: Dataset): List[String] = {
323-
dataset.spaces.flatMap(s => spacesService.getAllExtractors(s))
324-
}
325-
326319
/**
327320
* Check if given operation matches any existing records cached in ExtractorInfo.
328321
* Note, dataset operation is in the format of "x.y",
@@ -360,16 +353,26 @@ class RabbitmqPlugin(application: Application) extends Plugin {
360353
}
361354

362355
/**
363-
* Find all extractors enabled for the space the dataset belongs and the matched operation.
356+
* Find all extractors enabled/disabled for the space the dataset belongs and the matched operation.
364357
* @param dataset The dataset used to find which space to query for registered extractors.
365358
* @param operation The dataset operation requested.
366359
* @return A list of extractors IDs.
367360
*/
368-
private def getSpaceExtractorsByOperation(dataset: Dataset, operation: String): List[String] = {
369-
dataset.spaces.flatMap(s =>
370-
spacesService.getAllExtractors(s).flatMap(exId =>
371-
extractorsService.getExtractorInfo(exId)).filter(exInfo =>
372-
containsOperation(exInfo.process.dataset, operation) || containsOperation(exInfo.process.file, operation)).map(_.name))
361+
private def getSpaceExtractorsByOperation(dataset: Dataset, operation: String): (List[String], List[String]) = {
362+
var enabledExtractors = new ListBuffer[String]()
363+
var disabledExtractors = new ListBuffer[String]()
364+
dataset.spaces.map(space => {
365+
val extractors = spacesService.getAllExtractors(space)
366+
enabledExtractors.appendAll(extractors.get.enabled.flatMap { exId =>
367+
extractorsService.getExtractorInfo(exId).filter(exInfo =>
368+
containsOperation(exInfo.process.dataset, operation) || containsOperation(exInfo.process.file, operation)).map(_.name)
369+
})
370+
disabledExtractors.appendAll(extractors.get.disabled.flatMap { exId =>
371+
extractorsService.getExtractorInfo(exId).filter(exInfo =>
372+
containsOperation(exInfo.process.dataset, operation) || containsOperation(exInfo.process.file, operation)).map(_.name)
373+
})
374+
})
375+
(enabledExtractors.toList, disabledExtractors.toList)
373376
}
374377

375378
/**
@@ -418,12 +421,14 @@ class RabbitmqPlugin(application: Application) extends Plugin {
418421
else ""
419422
// get extractors enabled at the global level
420423
val globalExtractors = getGlobalExtractorsByOperation(operation)
421-
// get extractors enabled at the space level
422-
val spaceExtractors = getSpaceExtractorsByOperation(dataset, operation)
424+
// get extractors enabled/disabled at the space level
425+
val (enabledExtractors, disabledExtractors) = getSpaceExtractorsByOperation(dataset, operation)
423426
// get queues based on RabbitMQ bindings (old method).
424427
val queuesFromBindings = getQueuesFromBindings(routingKey)
425428
// take the union of queues so that we publish to a specific queue only once
426-
globalExtractors.toSet union spaceExtractors.toSet union queuesFromBindings.toSet
429+
val selected = (globalExtractors.toSet -- disabledExtractors.toSet) union enabledExtractors.toSet union queuesFromBindings.toSet
430+
Logger.debug("Extractors selected for submission: " + selected)
431+
selected
427432
}
428433

429434
/**

app/services/SpaceService.scala

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,29 @@ trait SpaceService {
275275
* If entry for spaceId already exists, will update list of extractors.
276276
* Otherwise will create and add a new document to the collection, with spaceId and extractor given.
277277
*/
278-
def addExtractor(spaceId: UUID, extractor:String)
278+
def enableExtractor(spaceId: UUID, extractor: String)
279+
280+
/**
281+
* Disable extractors within the space. This is used to override global selections.
282+
* @param spaceId
283+
* @param extractor
284+
*/
285+
def disableExtractor(spaceId: UUID, extractor: String)
286+
287+
288+
/**
289+
* Follow the global setting for whether to trigger an extractor or not.
290+
* @param spaceId
291+
* @param extractor
292+
*/
293+
def setDefaultExtractor(spaceId: UUID, extractor: String)
279294

280295
/**
281-
* Get all extractors for this space id.
296+
* Get all extractors for this space id. This is the union of all enabled and disabled extractors for this space.
297+
* If a user never manually enabled or disabled an extractor for a space it will not be returned, but the extractor
298+
* might still be enabled/disabled at the instance level.
282299
*/
283-
def getAllExtractors(spaceId: UUID): List[String]
300+
def getAllExtractors(spaceId: UUID): Option[ExtractorsForSpace]
284301

285302
/**
286303
* Delete an entire entry with extractors for this space id.

app/services/mongodb/MongoDBExtractorService.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ class MongoDBExtractorService extends ExtractorService {
170170
def listExtractorsInfo(categories: List[String]): List[ExtractorInfo] = {
171171
var list_queue = List[ExtractorInfo]()
172172

173-
val allDocs = ExtractorInfoDAO.findAll()
173+
val allDocs = ExtractorInfoDAO.findAll().sort(orderBy = MongoDBObject("name" -> -1))
174174
for (doc <- allDocs) {
175175
// If no categories are specified, return all extractor names
176176
var category_match = categories.isEmpty

app/services/mongodb/MongoDBSpaceService.scala

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ package services.mongodb
44
import javax.inject.{Inject, Singleton}
55
import api.Permission
66
import api.Permission.Permission
7-
import com.mongodb.casbah.Imports._
7+
import com.mongodb.casbah.Imports.{$and, _}
88
import com.mongodb.casbah.WriteConcern
99
import com.mongodb.casbah.commons.MongoDBObject
1010
import com.mongodb.DBObject
11-
import com.novus.salat.dao.{SalatDAO, ModelCompanion}
11+
import com.novus.salat.dao.{ModelCompanion, SalatDAO}
1212
import models._
1313
import org.bson.types.ObjectId
1414
import play.api.Logger
@@ -675,24 +675,29 @@ class MongoDBSpaceService @Inject() (
675675
* If entry for this spaceId already exists, adds extractor to it.
676676
* Otherwise, creates a new entry with spaceId and extractor.
677677
*/
678-
def addExtractor (spaceId: UUID, extractor: String) {
679-
//will add extractor to the list of extractors for this space, only if it's not there.
678+
def enableExtractor(spaceId: UUID, extractor: String) {
680679
val query = MongoDBObject("spaceId" -> spaceId.stringify)
681-
ExtractorsForSpaceDAO.update(query, $addToSet("extractors" -> extractor), true, false, WriteConcern.Safe)
680+
ExtractorsForSpaceDAO.update(query, $addToSet("enabled" -> extractor), true, false, WriteConcern.Safe)
681+
}
682+
683+
def disableExtractor(spaceId: UUID, extractor: String) {
684+
val query = MongoDBObject("spaceId" -> spaceId.stringify)
685+
ExtractorsForSpaceDAO.update(query, $addToSet("disabled" -> extractor), true, false, WriteConcern.Safe)
686+
}
687+
688+
def setDefaultExtractor(spaceId: UUID, extractor: String) {
689+
val query = MongoDBObject("spaceId" -> spaceId.stringify)
690+
ExtractorsForSpaceDAO.update(query, $pull("disabled" -> extractor,"enabled" -> extractor), true, false, WriteConcern.Safe)
682691
}
683692

684693
/**
685694
* Returns a list of extractors associated with this spaceId.
686695
*/
687-
def getAllExtractors(spaceId: UUID): List[String] = {
688-
//Note: in models.ExtractorsForSpace, spaceId must be a String
696+
def getAllExtractors(spaceId: UUID): Option[ExtractorsForSpace] = {
697+
// Note: in models.ExtractorsForSpace, spaceId must be a String
689698
// if space Id is UUID, will compile but throws Box run-time error
690699
val query = MongoDBObject("spaceId" -> spaceId.stringify)
691-
692-
val list = (for (extr <- ExtractorsForSpaceDAO.find(query)) yield extr).toList
693-
//get extractors' names for given space id
694-
val extractorList: List[String] = list.flatMap(_.extractors)
695-
extractorList
700+
ExtractorsForSpaceDAO.findOne(query)
696701
}
697702

698703

app/services/mongodb/MongoSalatPlugin.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,9 @@ class MongoSalatPlugin(app: Application) extends Plugin {
445445

446446
// Updates permissions for the admin Role
447447
updateMongo("update-admin-role", updateAdminRole)
448+
449+
// Updates extractors enabled and disabled in a space
450+
updateMongo("update-space-extractors-selection", updateSpaceExtractorsSelection)
448451
}
449452

450453
private def updateMongo(updateKey: String, block: () => Unit): Unit = {
@@ -1668,4 +1671,19 @@ class MongoSalatPlugin(app: Application) extends Plugin {
16681671
}
16691672
}
16701673

1674+
private def updateSpaceExtractorsSelection(): Unit = {
1675+
collection("spaces.extractors").foreach { space =>
1676+
val enabled = space.getAsOrElse[MongoDBList]("extractors", MongoDBList.empty)
1677+
1678+
collection("spaces.extractors").update(
1679+
MongoDBObject("_id" -> space.get("_id")),
1680+
MongoDBObject("$set" -> MongoDBObject("enabled" -> enabled)), upsert = false, multi = false)
1681+
1682+
collection("spaces.extractors").update(
1683+
MongoDBObject("_id" -> space.get("_id")),
1684+
$unset("extractors"))
1685+
}
1686+
print("DONE")
1687+
}
1688+
16711689
}

app/views/spaces/updateExtractors.scala.html

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
@(runningExtractors: List[ExtractorInfo], selectedExtractors: List[String], globalSelections: List[String], space: UUID, spaceName: String)(implicit user: Option[models.User])
1+
@(runningExtractors: List[ExtractorInfo], enabledInSpace: List[String], disabledInSpace: List[String], globalSelections: List[String], space: UUID, spaceName: String)(implicit user: Option[models.User])
22
@import _root_.util.Formatters._
33
@import helper._
44

5-
@main("Space") {
5+
@main("Space Extractors") {
66
<div>
77
<ol class="breadcrumb">
88
<li><span class="glyphicon glyphicon-hdd"></span> <a href="@routes.Spaces.getSpace(space)" title="@spaceName"> @Html(ellipsize(spaceName,18))</a></li>
@@ -16,7 +16,7 @@ <h1> No extractors running</h1>
1616
</div>
1717
} else {
1818
<div class="page-header">
19-
<h1>Extractors</h1>
19+
<h1>Space Extractors</h1>
2020
</div>
2121
<div class="row top-padding">
2222
<div class="col-xs-12">
@@ -32,20 +32,19 @@ <h1>Extractors</h1>
3232
<table class="table">
3333
<thead>
3434
<tr>
35-
<th>Enabled</th>
3635
<th>Name</th>
3736
<th>Description</th>
3837
<th>Process Triggers</th>
38+
<th class="text-center" title="Extractors marked for execution in all spaces">Enabled By SuperAdmin</th>
39+
<th class="text-center" title="Follow selection in 'Enabled By SuperAdmin' column">Use Default</th>
40+
<th class="text-center" title="Always trigger execution in this space">Enable in Space</th>
41+
<th class="text-center" title="Never trigger execution in this space">Disable in Space</th>
3942
</tr>
4043
</thead>
4144
<tbody>
4245
<!-- Display all running extractors. If extractor already in this space, check the box. -->
4346
@runningExtractors.map { extractor =>
4447
<tr>
45-
<td><input type="checkbox" name="extractors" value="@extractor.name"
46-
@if(globalSelections.contains(extractor.name)) {disabled="disabled"}
47-
@if(globalSelections.contains(extractor.name) ||
48-
selectedExtractors.contains(extractor.name)) {checked} else {unchecked}></td>
4948
<td><a href="@routes.Extractors.showExtractorInfo(extractor.name)">@extractor.name</a></td>
5049
<td>@extractor.description</td>
5150
<td>
@@ -83,6 +82,23 @@ <h1>Extractors</h1>
8382
None
8483
</p>
8584
</td>
85+
<td class="text-center">
86+
@if(globalSelections.contains(extractor.name)) {
87+
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
88+
}
89+
</td>
90+
<td class="text-center">
91+
<input type="radio" name="[email protected]" value="default"
92+
@if(!enabledInSpace.contains(extractor.name) && !disabledInSpace.contains(extractor.name)) {checked}>
93+
</td>
94+
<td class="text-center">
95+
<input type="radio" name="[email protected]" value="enabled"
96+
@if(enabledInSpace.contains(extractor.name)) {checked}>
97+
</td>
98+
<td class="text-center">
99+
<input type="radio" name="[email protected]" value="disabled"
100+
@if(disabledInSpace.contains(extractor.name)) {checked}>
101+
</td>
86102
</tr>
87103
}
88104
</tbody>

0 commit comments

Comments
 (0)