mirror of
https://github.com/ysoftdevs/odc-analyzer.git
synced 2026-01-18 17:47:23 +01:00
More user-friendly project watching
This commit is contained in:
@@ -115,6 +115,35 @@ h3.library-identification{
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.projects-watching li.with-buttons{
|
||||
list-style-type: none;
|
||||
}
|
||||
.projects-watching .watched{
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.projects-watching .collapsed ul{
|
||||
display: none;
|
||||
}
|
||||
.projects-watching .watching-btn-expand {
|
||||
display: none;
|
||||
}
|
||||
.projects-watching .collapsed .watching-btn-expand{
|
||||
display: inline;
|
||||
}
|
||||
.projects-watching .collapsed .watching-btn-collapse{
|
||||
display: none;
|
||||
}
|
||||
.projects-watching li{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.projects-watching .toggle-buttons button{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.projects-watching .toggle-buttons{
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: -19px;
|
||||
}
|
||||
|
||||
40
app/com/ysoft/concurrent/FutureLock.scala
Normal file
40
app/com/ysoft/concurrent/FutureLock.scala
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.ysoft.concurrent
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
trait FutureLock[T] {
|
||||
def whenLocked(cannotLock: => Future[T])(implicit executionContext: ExecutionContext): Future[T]
|
||||
}
|
||||
|
||||
object FutureLock {
|
||||
|
||||
def futureLock[T](lock: AtomicBoolean)(f: => Future[T]): FutureLock[T] = new FutureLock[T]() {
|
||||
override def whenLocked(cannotLock: => Future[T])(implicit executionContext: ExecutionContext): Future[T] = {
|
||||
if (lock.compareAndSet(/*expect = */ false, /*update = */ true)) {
|
||||
try {
|
||||
f.andThen { case _ =>
|
||||
val wasLocked = lock.getAndSet(false)
|
||||
if (!wasLocked) {
|
||||
throw new RuntimeException("The lock was not being held when trying to unlock")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
// So, the Exception was raised before creation of the Future. As a result, the Future will not relase the lock.
|
||||
// In other words, its our responsibility to release the lock:
|
||||
val wasLocked = lock.getAndSet(false)
|
||||
if (!wasLocked) {
|
||||
throw new RuntimeException("The lock was not being held when throwing the following exception", e)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
cannotLock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -22,7 +22,7 @@ sealed trait Filter{
|
||||
private final case class ProjectFilter(project: ReportInfo) extends Filter{
|
||||
override def filters: Boolean = true
|
||||
override def descriptionHtml: Html = views.html.filters.project(project)
|
||||
override def descriptionText: String = s"project ${friendlyProjectName(project)}"
|
||||
override def descriptionText: String = s"project ${friendlyProjectNameString(project)}"
|
||||
override def subReports(r: Result): Option[Result] = {
|
||||
@inline def reportInfo = project
|
||||
def f[T](m: Map[ReportInfo, T]): Map[String, T] = (
|
||||
@@ -39,7 +39,7 @@ private final case class TeamFilter(team: Team) extends Filter{
|
||||
override def filters: Boolean = true
|
||||
override def subReports(r: Result): Option[Result] = {
|
||||
val Wildcard = """^(.*): \*$""".r
|
||||
val reportInfoByFriendlyProjectNameMap = r.projectsReportInfo.ungroupedReportsInfo.map(ri => friendlyProjectName(ri) -> ri).toSeq.groupBy(_._1).mapValues{
|
||||
val reportInfoByFriendlyProjectNameMap = r.projectsReportInfo.ungroupedReportsInfo.map(ri => friendlyProjectNameString(ri) -> ri).toSeq.groupBy(_._1).mapValues{
|
||||
case Seq((_, ri)) => ri
|
||||
case other => sys.error("some duplicate value: "+other)
|
||||
}.map(identity)
|
||||
|
||||
@@ -36,7 +36,7 @@ final class DependencyCheckReportsProcessor @Inject() (
|
||||
|
||||
@deprecated("use HTML output instead", "SNAPSHOT") private val showDependencies: (Seq[GroupedDependency]) => Seq[String] = {
|
||||
_.map { s =>
|
||||
s.dependencies.map { case (dep, projects) => s"${dep.fileName} @ ${projects.toSeq.sorted.map(friendlyProjectName).mkString(", ")}" }.mkString(", ") + " " + s.hashes
|
||||
s.dependencies.map { case (dep, projects) => s"${dep.fileName} @ ${projects.toSeq.sorted.map(friendlyProjectNameString).mkString(", ")}" }.mkString(", ") + " " + s.hashes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ final case class ReportInfo(
|
||||
|
||||
def bare = copy(subprojectNameOption = None, fullId = fullId.takeWhile(_ != '/'))
|
||||
|
||||
def isBare = subprojectNameOption.isEmpty
|
||||
def isNotBare = !isBare
|
||||
|
||||
}
|
||||
|
||||
object ProjectsWithReports{
|
||||
|
||||
@@ -32,6 +32,6 @@ package object controllers {
|
||||
val subProjectOption = Some(removeMess(theRest)).filter(_ != "")
|
||||
subProjectOption.fold(baseName)(baseName+"/"+_)
|
||||
}*/
|
||||
def friendlyProjectName(reportInfo: ReportInfo) = reportInfo.subprojectNameOption.fold(reportInfo.projectName)(reportInfo.projectName+": "+_)
|
||||
def friendlyProjectNameString(reportInfo: ReportInfo) = reportInfo.subprojectNameOption.fold(reportInfo.projectName)(reportInfo.projectName+": "+_)
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class EmailExportService(from: String, nobodyInterestedContact: String, mailerCl
|
||||
def mailForVulnerabilityProjectsChange(vuln: Vulnerability, emailMessageId: EmailMessageId, diff: SetDiff[String], projects: ProjectsWithReports) = {
|
||||
def showProjects(s: Set[String]) = s.map(p =>
|
||||
"* " + (try{
|
||||
friendlyProjectName(projects.parseUnfriendlyName(p))
|
||||
friendlyProjectNameString(projects.parseUnfriendlyName(p))
|
||||
}catch{ // It might fail on project that has been removed
|
||||
case e: NoSuchElementException => s"unknown project $p"
|
||||
})
|
||||
|
||||
5
app/views/friendlyProjectName.scala.html
Normal file
5
app/views/friendlyProjectName.scala.html
Normal file
@@ -0,0 +1,5 @@
|
||||
@(reportInfo: ReportInfo)
|
||||
@reportInfo.projectName@reportInfo.subprojectNameOption match {
|
||||
case None => { <i>(with all subprojects)</i>}
|
||||
case Some(subproject) => {: @subproject}
|
||||
}
|
||||
@@ -3,30 +3,79 @@
|
||||
@button(action: Call)(label: String) = {
|
||||
@form(action, 'style -> "display: inline-block"){
|
||||
@CSRF.formField
|
||||
<button type="submit" class="btn">@label</button>
|
||||
<button type="submit" class="btn btn-link">@label</button>
|
||||
}
|
||||
}
|
||||
@main("Watch projects"){
|
||||
<ul class="projects-watching">
|
||||
@for(
|
||||
project <- projects;
|
||||
fullId = project.fullId;
|
||||
isWatchedThroughParent = project.subprojectNameOption.isDefined && (watchedProjects contains project.projectId);
|
||||
isWatchedDirectly = watchedProjects contains fullId;
|
||||
isWatched = isWatchedDirectly || isWatchedThroughParent
|
||||
){
|
||||
<li @if(isWatched){class="watched"}>
|
||||
@friendlyProjectName(project)
|
||||
@if(isWatchedThroughParent){
|
||||
<button disabled class="btn">unwatch</button>
|
||||
<span class="badge">watched through parent</span>
|
||||
@toggleButton(id: String, buttonClass: String, labelClass: String) = {
|
||||
<button type="button" onclick="$($(this).attr('data-target')).toggleClass('collapsed');" data-target="#@id" class="btn btn-link @buttonClass"><span class="glyphicon @labelClass"></span></button>
|
||||
}
|
||||
@projectListItem(project: ReportInfo, subprojects: Seq[ReportInfo])(children: Html) = {
|
||||
@for(
|
||||
isWatchedDirectly <- Some(watchedProjects contains project.fullId); // hack allowing one to define a variable
|
||||
isWatchedByParent = project.isNotBare && (watchedProjects contains project.bare.fullId);
|
||||
watchedChildCount = subprojects.count(p => watchedProjects contains p.fullId);
|
||||
hasWatchedChild = watchedChildCount > 0;
|
||||
hasButtons = !subprojects.isEmpty;
|
||||
classes = Seq(
|
||||
if(isWatchedDirectly) Some("watched") else None,
|
||||
if(hasWatchedChild && !isWatchedDirectly) None else Some("collapsed"),
|
||||
if(hasButtons) Some("with-buttons") else None
|
||||
).flatten;
|
||||
id = s"project-${urlEncode(project.fullId)}"
|
||||
){
|
||||
<li id="@id" @if(!classes.isEmpty){class="@classes.mkString(" ")}">
|
||||
@if(hasButtons) {
|
||||
<span class="toggle-buttons">
|
||||
@toggleButton(id, buttonClass = "watching-btn-expand", labelClass = "glyphicon-plus-sign")
|
||||
@toggleButton(id, buttonClass = "watching-btn-collapse", labelClass = "glyphicon-minus-sign")
|
||||
</span>
|
||||
}
|
||||
@friendlyProjectName(project)
|
||||
@if(project.isBare){
|
||||
@if(isWatchedDirectly){
|
||||
<span class="badge">You watch this project with all subprojects.</span>
|
||||
}else{
|
||||
@if(isWatchedDirectly){
|
||||
@button(routes.Notifications.unwatch(fullId))("unwatch")
|
||||
}else{
|
||||
@button(routes.Notifications.watch(fullId))("watch")
|
||||
@if(hasWatchedChild){
|
||||
<span class="badge">You watch @watchedChildCount @if(watchedChildCount==1){subproject}else{subprojects}.</span>
|
||||
}
|
||||
}
|
||||
}else{@* non-bare *@
|
||||
@if(isWatchedDirectly && !isWatchedByParent){
|
||||
<span class="badge">You explicitly watch this project.</span>
|
||||
}
|
||||
}
|
||||
|
||||
@if(isWatchedByParent) {
|
||||
<span class="badge">You watch the parent project.</span>
|
||||
<button disabled class="btn btn-link">unwatch</button>
|
||||
}else{
|
||||
@if(isWatchedDirectly){
|
||||
@button(routes.Notifications.unwatch(project.fullId))("unwatch")
|
||||
}else{
|
||||
@button(routes.Notifications.watch(project.fullId))("watch")
|
||||
}
|
||||
}
|
||||
@children
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@headExtension = {
|
||||
}
|
||||
@main("Watch projects", headExtension = headExtension){
|
||||
<ul class="projects-watching">
|
||||
@for(
|
||||
(projectGroup, projectsInGroup) <- projects.groupBy(_.bare)
|
||||
){
|
||||
@projectListItem(projectGroup, projectsInGroup) {
|
||||
<ul>
|
||||
@for(
|
||||
project <- projectsInGroup;
|
||||
if project.isNotBare
|
||||
) {
|
||||
@projectListItem(project, Seq()){ }
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
Reference in New Issue
Block a user