Skip to content

Commit 05ae2fe

Browse files
committed
Merge branch 'develop' into release/1.12.0
2 parents ccb0405 + 1f01887 commit 05ae2fe

20 files changed

+283
-66
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7-
## Unreleased
7+
## [Unreleased]
8+
9+
### Added
10+
- Track user_id with every extraction event. [#94](https://github.com/clowder-framework/clowder/issues/94)
11+
- Added a new storage report at `GET api/reports/storage/spaces/:id` for auditing user storage usage on a space basis.
12+
- The file and dataset metrics reports also have support for since and until ISO8601 date parameters.
813

914
### Fixed
1015
- Fixed permissions checks on search results for search interfaces that would cause misleading counts. [#60](https://github.com/clowder-framework/clowder/issues/60)

app/api/Datasets.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2751,7 +2751,7 @@ class Datasets @Inject()(
27512751
// Setup userList, add all users of all spaces associated with the dataset
27522752
dataset.spaces.foreach { spaceId =>
27532753
spaces.get(spaceId) match {
2754-
case Some(spc) => userList = spaces.getUsersInSpace(spaceId) ::: userList
2754+
case Some(spc) => userList = spaces.getUsersInSpace(spaceId, None) ::: userList
27552755
case None => NotFound(s"Error: No $spaceTitle found for $id.")
27562756
}
27572757
}

app/api/Files.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1832,7 +1832,7 @@ class Files @Inject()(
18321832
datasets.findByFileIdDirectlyContain(id).foreach(dataset => {
18331833
dataset.spaces.foreach { spaceId =>
18341834
spaces.get(spaceId) match {
1835-
case Some(spc) => userList = spaces.getUsersInSpace(spaceId) ::: userList
1835+
case Some(spc) => userList = spaces.getUsersInSpace(spaceId, None) ::: userList
18361836
case None => NotFound(s"Error: No $spaceTitle found for $id.")
18371837
}
18381838
}

app/api/Reporting.scala

Lines changed: 122 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ package api
22

33
import api.Permission._
44
import play.api.libs.iteratee.Enumerator
5+
56
import scala.concurrent.{ExecutionContext, Future}
67
import play.api.libs.concurrent.Execution.Implicits._
78
import play.api.mvc.Controller
89
import play.api.Logger
910
import javax.inject.Inject
10-
import java.util.{TimeZone, Date}
11+
import java.util.{Date, TimeZone}
12+
1113
import services._
12-
import models.{File, Dataset, Collection, ProjectSpace, User, UserStatus}
14+
import models.{Collection, Dataset, File, ProjectSpace, UUID, User, UserStatus}
15+
import util.Parsers
16+
17+
import scala.collection.mutable.ListBuffer
1318

1419

1520
/**
@@ -25,10 +30,10 @@ class Reporting @Inject()(selections: SelectionService,
2530
val dateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
2631
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"))
2732

28-
def fileMetrics() = ServerAdminAction { implicit request =>
33+
def fileMetrics(since: Option[String], until: Option[String]) = ServerAdminAction { implicit request =>
2934
Logger.debug("Generating file metrics report")
3035

31-
val results = files.getMetrics()
36+
val results = files.getIterator(None, since, until)
3237
var headerRow = true
3338
val enum = Enumerator.generateM({
3439
val chunk = if (headerRow) {
@@ -58,10 +63,10 @@ class Reporting @Inject()(selections: SelectionService,
5863
)
5964
}
6065

61-
def datasetMetrics() = ServerAdminAction { implicit request =>
66+
def datasetMetrics(since: Option[String], until: Option[String]) = ServerAdminAction { implicit request =>
6267
Logger.debug("Generating dataset metrics report")
6368

64-
val results = datasets.getMetrics()
69+
val results = datasets.getIterator(None, since, until)
6570
var headerRow = true
6671
val enum = Enumerator.generateM({
6772
val chunk = if (headerRow) {
@@ -135,10 +140,10 @@ class Reporting @Inject()(selections: SelectionService,
135140
collections.getMetrics().foreach(coll => {
136141
contents += _buildCollectionRow(coll, true)
137142
})
138-
datasets.getMetrics().foreach(ds => {
143+
datasets.getIterator(None, None, None).foreach(ds => {
139144
contents += _buildDatasetRow(ds, true)
140145
})
141-
files.getMetrics().foreach(f => {
146+
files.getIterator(None, None, None).foreach(f => {
142147
contents += _buildFileRow(f)
143148
})
144149

@@ -223,7 +228,7 @@ class Reporting @Inject()(selections: SelectionService,
223228
)
224229
}
225230

226-
def _buildFileRow(f: File): String = {
231+
private def _buildFileRow(f: File): String = {
227232
var contents = ""
228233

229234
// Parent datasets, collections & spaces are sublists within the columns
@@ -287,7 +292,7 @@ class Reporting @Inject()(selections: SelectionService,
287292
return contents
288293
}
289294

290-
def _buildDatasetRow(ds: Dataset, returnAllColums: Boolean = false): String = {
295+
private def _buildDatasetRow(ds: Dataset, returnAllColums: Boolean = false): String = {
291296
"""returnAllColumns will include empty columns to align with file rows on report"""
292297
var contents = ""
293298

@@ -341,7 +346,7 @@ class Reporting @Inject()(selections: SelectionService,
341346
return contents
342347
}
343348

344-
def _buildCollectionRow(coll: Collection, returnAllColums: Boolean = false): String = {
349+
private def _buildCollectionRow(coll: Collection, returnAllColums: Boolean = false): String = {
345350
"""returnAllColumns will include empty columns to align with file rows on report"""
346351

347352
var contents = ""
@@ -389,4 +394,110 @@ class Reporting @Inject()(selections: SelectionService,
389394
return contents
390395
}
391396

397+
def spaceStorage(id: UUID, since: Option[String], until: Option[String]) = ServerAdminAction { implicit request =>
398+
// Iterate over the files of every dataset in the space
399+
val results = datasets.getIterator(Some(id), None, None) // TODO: Can't use time filters here if user intends files
400+
401+
var headerRow = true
402+
val enum = Enumerator.generateM({
403+
val chunk = if (headerRow) {
404+
val header = "file_type,id,name,owner,owner_email,owner_id,size_kb,uploaded,location,parent_datasets,parent_collections,parent_spaces,space_owners,space_admins\n"
405+
headerRow = false
406+
Some(header.getBytes("UTF-8"))
407+
} else {
408+
scala.concurrent.blocking {
409+
if (results.hasNext) {
410+
try {
411+
val ds = results.next
412+
413+
// Each file in the dataset inherits same parent info from dataset
414+
val ds_list = ds.id.stringify
415+
var coll_list = ""
416+
var space_list = ""
417+
val space_ids: ListBuffer[UUID] = ListBuffer.empty
418+
var j = 1
419+
var k = 1
420+
ds.collections.foreach(coll => {
421+
if (!coll_list.contains(coll.uuid)) {
422+
coll_list += (if (j>1) ", " else "") + coll.uuid
423+
j += 1
424+
}
425+
})
426+
ds.spaces.foreach(sp => {
427+
if (!space_list.contains(sp.uuid)) {
428+
space_list += (if (k>1) ", " else "") + sp.uuid
429+
space_ids += sp
430+
k += 1
431+
}
432+
})
433+
434+
// Get admin and owner of space(s)
435+
// TODO: Should we include email and/or name too?
436+
var space_owner_list = ""
437+
var space_admin_list = ""
438+
var l = 1
439+
var m = 1
440+
spaces.get(space_ids.toList).found.foreach(sp => {
441+
space_owner_list += (if (l>1) ", " else "") + sp.creator.uuid
442+
l += 1
443+
spaces.getUsersInSpace(sp.id, Some("Admin")).foreach(spadmin => {
444+
space_admin_list += (if (m>1) ", " else "") + spadmin.id.uuid
445+
m += 1
446+
})
447+
})
448+
449+
var contents = ""
450+
files.get(ds.files).found.foreach(f => {
451+
// TODO: Need to redesign File model because this is gonna be so slow...
452+
val sinceOK = {
453+
since match {
454+
case None => true
455+
case Some(t) => (Parsers.fromISO8601(t).before(f.uploadDate))
456+
}
457+
}
458+
val untilOK = {
459+
until match {
460+
case None => true
461+
case Some(t) => (Parsers.fromISO8601(t).after(f.uploadDate))
462+
}
463+
}
464+
465+
if (sinceOK && untilOK) {
466+
// build next row of storage report
467+
contents += "\""+f.contentType+"\","
468+
contents += "\""+f.id.toString+"\","
469+
contents += "\""+f.filename+"\","
470+
contents += "\""+f.author.fullName+"\","
471+
contents += "\""+f.author.email.getOrElse("")+"\","
472+
contents += "\""+f.author.id+"\","
473+
contents += (f.length/1000).toInt.toString+","
474+
contents += dateFormat.format(f.uploadDate)+","
475+
contents += "\""+f.loader_id+"\","
476+
contents += "\""+ds_list+"\","
477+
contents += "\""+coll_list+"\","
478+
contents += "\""+space_list+"\","
479+
contents += "\""+space_owner_list+"\","
480+
contents += "\""+space_admin_list+"\""
481+
contents += "\n"
482+
}
483+
})
484+
// Submit all file rows for this dataset at once
485+
Some(contents.getBytes("UTF-8"))
486+
}
487+
catch {
488+
case _ => Some("".getBytes("UTF-8"))
489+
}
490+
}
491+
else None
492+
}
493+
}
494+
495+
Future(chunk)
496+
})
497+
498+
Ok.chunked(enum.andThen(Enumerator.eof)).withHeaders(
499+
"Content-Type" -> "text/csv",
500+
"Content-Disposition" -> ("attachment; filename=SpaceStorage"+id.stringify+".csv")
501+
)
502+
}
392503
}

app/api/Spaces.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ class Spaces @Inject()(spaces: SpaceService,
451451
aResult match {
452452
case aMap: JsSuccess[Map[String, String]] => {
453453
//Set up a map of existing users to check against
454-
val existingUsers = spaces.getUsersInSpace(spaceId)
454+
val existingUsers = spaces.getUsersInSpace(spaceId, None)
455455
var existUserRole: Map[String, String] = Map.empty
456456
for (aUser <- existingUsers) {
457457
spaces.getRoleForUserInSpace(spaceId, aUser.id) match {
@@ -527,8 +527,8 @@ class Spaces @Inject()(spaces: SpaceService,
527527
case None => Logger.debug("A role was sent up that doesn't exist. It is " + k)
528528
}
529529
}
530-
if(space.userCount != spaces.getUsersInSpace(space.id).length){
531-
spaces.updateUserCount(space.id, spaces.getUsersInSpace(space.id).length)
530+
if(space.userCount != spaces.getUsersInSpace(space.id, None).length){
531+
spaces.updateUserCount(space.id, spaces.getUsersInSpace(space.id, None).length)
532532
}
533533

534534
Ok(Json.obj("status" -> "success"))
@@ -699,7 +699,7 @@ class Spaces @Inject()(spaces: SpaceService,
699699
}
700700
}
701701

702-
userService.listUsersInSpace(s.id).map { member =>
702+
userService.listUsersInSpace(s.id, None).map { member =>
703703
val theHtml = views.html.spaces.verifySpaceEmail(s.id.stringify, s.name, member.getMiniUser.fullName)
704704
Mail.sendEmail("Space Status update", request.user, member, theHtml)
705705
}

app/controllers/Collections.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ class Collections @Inject() (datasets: DatasetService, collections: CollectionSe
642642
// Setup userList, add all users of all spaces associated with the collection
643643
collection.spaces.foreach { spaceId =>
644644
spaceService.get(spaceId) match {
645-
case Some(spc) => userList = spaceService.getUsersInSpace(spaceId) ::: userList
645+
case Some(spc) => userList = spaceService.getUsersInSpace(spaceId, None) ::: userList
646646
case None => Redirect (routes.Collections.collection(id)).flashing ("error" -> s"Error: No $spaceTitle found for collection $id.");
647647
}
648648
}
@@ -653,7 +653,7 @@ class Collections @Inject() (datasets: DatasetService, collections: CollectionSe
653653
collection.spaces.foreach { spaceId =>
654654
spaceService.get(spaceId) match {
655655
case Some(spc) => {
656-
val usersInCurrSpace: List[User] = spaceService.getUsersInSpace(spaceId)
656+
val usersInCurrSpace: List[User] = spaceService.getUsersInSpace(spaceId, None)
657657
if (usersInCurrSpace.nonEmpty) {
658658

659659
usersInCurrSpace.foreach { usr =>

app/controllers/Datasets.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ class Datasets @Inject() (
786786
// Setup userList, add all users of all spaces associated with the dataset
787787
dataset.spaces.foreach { spaceId =>
788788
spaceService.get(spaceId) match {
789-
case Some(spc) => userList = spaceService.getUsersInSpace(spaceId) ::: userList
789+
case Some(spc) => userList = spaceService.getUsersInSpace(spaceId, None) ::: userList
790790
case None => Redirect(routes.Datasets.dataset(id)).flashing("error" -> s"Error: No $spaceTitle found for $Messages('dataset.title') $id.")
791791
}
792792
}
@@ -797,7 +797,7 @@ class Datasets @Inject() (
797797
dataset.spaces.foreach { spaceId =>
798798
spaceService.get(spaceId) match {
799799
case Some(spc) => {
800-
val usersInCurrSpace: List[User] = spaceService.getUsersInSpace(spaceId)
800+
val usersInCurrSpace: List[User] = spaceService.getUsersInSpace(spaceId, None)
801801
if (usersInCurrSpace.nonEmpty) {
802802

803803
usersInCurrSpace.foreach { usr =>

app/controllers/Spaces.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class Spaces @Inject() (spaces: SpaceService, users: UserService, events: EventS
177177
val collectionsInSpace = spaces.getCollectionsInSpace(Some(id.stringify), Some(size))
178178
val datasetsInSpace = datasets.listSpace(size, id.toString(), user)
179179
val publicDatasetsInSpace = datasets.listSpaceStatus(size, id.toString(), "publicAll", user)
180-
val usersInSpace = spaces.getUsersInSpace(id)
180+
val usersInSpace = spaces.getUsersInSpace(id, None)
181181
var curationObjectsInSpace: List[CurationObject] = List()
182182
var inSpaceBuffer = usersInSpace.to[ArrayBuffer]
183183
creator match {
@@ -250,7 +250,7 @@ class Spaces @Inject() (spaces: SpaceService, users: UserService, events: EventS
250250
case Some(s) => {
251251
val creator = users.findById(s.creator)
252252
var creatorActual: User = null
253-
val usersInSpace = spaces.getUsersInSpace(id)
253+
val usersInSpace = spaces.getUsersInSpace(id, None)
254254
var inSpaceBuffer = usersInSpace.to[ArrayBuffer]
255255
creator match {
256256
case Some(theCreator) => {
@@ -376,7 +376,7 @@ class Spaces @Inject() (spaces: SpaceService, users: UserService, events: EventS
376376
val subject: String = "Request for access from " + AppConfiguration.getDisplayName
377377
val body = views.html.spaces.requestemail(user, id.toString, s.name)
378378

379-
for (requestReceiver <- spaces.getUsersInSpace(s.id)) {
379+
for (requestReceiver <- spaces.getUsersInSpace(s.id, None)) {
380380
spaces.getRoleForUserInSpace(s.id, requestReceiver.id) match {
381381
case Some(aRole) => {
382382
if (aRole.permissions.contains(Permission.EditSpace.toString)) {

app/models/Extraction.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ case class Extraction(
1919
extractor_id: String,
2020
status: String = "N/A",
2121
start: Date,
22-
end: Option[Date])
22+
end: Option[Date],
23+
user_id: UUID = User.anonymous.id
24+
)
2325

2426
/**
2527
* Currently running extractor name

app/services/DatasetService.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,6 @@ trait DatasetService {
383383

384384
def incrementDownloads(id: UUID, user: Option[User])
385385

386-
def getMetrics(): Iterator[Dataset]
386+
def getIterator(space: Option[UUID], since: Option[String], until: Option[String]): Iterator[Dataset]
387387

388388
}

0 commit comments

Comments
 (0)