mirror of
https://github.com/ysoftdevs/odc-analyzer.git
synced 2026-03-20 00:04:59 +01:00
Refactored LibDepStatistics to include FailedProjects
This commit is contained in:
23
app/com/ysoft/odc/statistics/FailedProjects.scala
Normal file
23
app/com/ysoft/odc/statistics/FailedProjects.scala
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ysoft.odc.statistics
|
||||||
|
|
||||||
|
import controllers.ReportInfo
|
||||||
|
|
||||||
|
final class FailedProjects(val failedProjectsSet: Set[String]){
|
||||||
|
def isFailed(projectFullId: String): Boolean = {
|
||||||
|
val projectBareId = projectFullId.takeWhile(_ != '/')
|
||||||
|
failedProjectsSet contains projectBareId
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object FailedProjects {
|
||||||
|
private[statistics] def combineFails(failedReportDownloads: Map[String, Throwable], parsingFailures: Map[ReportInfo, Throwable]): FailedProjects = {
|
||||||
|
/*
|
||||||
|
Fail can happen at multiple places:
|
||||||
|
1. Build cannot be downloaded (auth error, connection error, …) or is failed (failedReportDownloads)
|
||||||
|
2. Build is successful and can be downloaded, but it cannot be parsed (parsingFailures)
|
||||||
|
*/
|
||||||
|
val failedProjectsSet = failedReportDownloads.keySet ++ parsingFailures.keySet.map(_.projectId)
|
||||||
|
new FailedProjects(failedProjectsSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/com/ysoft/odc/statistics/LibDepStatistics.scala
Normal file
34
app/com/ysoft/odc/statistics/LibDepStatistics.scala
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package com.ysoft.odc.statistics
|
||||||
|
|
||||||
|
import controllers.DependencyCheckReportsParser.Result
|
||||||
|
import controllers._
|
||||||
|
import models.Library
|
||||||
|
|
||||||
|
case class LibDepStatistics(libraries: Set[(Int, Library)], dependencies: Set[GroupedDependency], failedProjects: FailedProjects){
|
||||||
|
def vulnerableRatio = vulnerableDependencies.size.toDouble / dependencies.size.toDouble
|
||||||
|
lazy val vulnerabilities: Set[Vulnerability] = dependencies.flatMap(_.vulnerabilities)
|
||||||
|
lazy val vulnerabilitiesByName = vulnerabilities.map(v => v.name -> v).toMap
|
||||||
|
lazy val vulnerabilityNames = vulnerabilities.map(_.name)
|
||||||
|
lazy val vulnerabilitiesToDependencies: Map[Vulnerability, Set[GroupedDependency]] = vulnerableDependencies.flatMap(dep =>
|
||||||
|
dep.vulnerabilities.map(vuln => (vuln, dep))
|
||||||
|
).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
|
||||||
|
vulnerableDependencies.flatMap(dep => dep.vulnerabilities.map(_ -> dep)).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
|
||||||
|
vulnerableDependencies.flatMap(dep => dep.vulnerabilities.map(_ -> dep)).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
|
||||||
|
lazy val vulnerableDependencies = dependencies.filter(_.isVulnerable)
|
||||||
|
lazy val (dependenciesWithCpe, dependenciesWithoutCpe) = dependencies.partition(_.hasCpe)
|
||||||
|
lazy val cpeRatio = dependenciesWithCpe.size.toDouble / dependencies.size.toDouble
|
||||||
|
lazy val weaknesses = vulnerabilities.flatMap(_.cweOption)
|
||||||
|
lazy val weaknessesFrequency = LibDepStatistics.computeWeaknessesFrequency(vulnerabilities)
|
||||||
|
}
|
||||||
|
|
||||||
|
object LibDepStatistics{
|
||||||
|
private def computeWeaknessesFrequency(vulnerabilities: Set[Vulnerability]) = vulnerabilities.toSeq.map(_.cweOption).groupBy(identity).mapValues(_.size).map(identity).withDefaultValue(0)
|
||||||
|
def apply(libraries: Set[(Int, Library)], dependencies: Set[GroupedDependency], failedReportDownloads: Map[String, Throwable], parsedReports: Result): LibDepStatistics = LibDepStatistics(
|
||||||
|
libraries = libraries,
|
||||||
|
dependencies = dependencies,
|
||||||
|
failedProjects = FailedProjects.combineFails(
|
||||||
|
failedReportDownloads = failedReportDownloads,
|
||||||
|
parsingFailures = parsedReports.failedAnalysises
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
8
app/com/ysoft/odc/statistics/TagStatistics.scala
Normal file
8
app/com/ysoft/odc/statistics/TagStatistics.scala
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package com.ysoft.odc.statistics
|
||||||
|
|
||||||
|
import models.LibraryTag
|
||||||
|
|
||||||
|
case class TagStatistics(tagRecord: (Int, LibraryTag), stats: LibDepStatistics){
|
||||||
|
def tag: LibraryTag = tagRecord._2
|
||||||
|
def tagId: Int = tagRecord._1
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
import com.ysoft.concurrent.FutureLock._
|
import com.ysoft.concurrent.FutureLock._
|
||||||
|
import com.ysoft.odc.statistics.{FailedProjects, LibDepStatistics}
|
||||||
import com.ysoft.odc.{Absolutizer, ArtifactFile, ArtifactItem, SetDiff}
|
import com.ysoft.odc.{Absolutizer, ArtifactFile, ArtifactItem, SetDiff}
|
||||||
import controllers.Statistics.LibDepStatistics
|
|
||||||
import models.{EmailMessageId, ExportedVulnerability}
|
import models.{EmailMessageId, ExportedVulnerability}
|
||||||
import play.api.i18n.MessagesApi
|
import play.api.i18n.MessagesApi
|
||||||
import play.api.libs.Crypto
|
import play.api.libs.Crypto
|
||||||
@@ -91,29 +91,6 @@ class Notifications @Inject()(
|
|||||||
} yield (missingTickets, newTicketIds, projectUpdates.toSet: Set[Any])
|
} yield (missingTickets, newTicketIds, projectUpdates.toSet: Set[Any])
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class FailedProjects(val failedProjectsSet: Set[String]){
|
|
||||||
def isFailed(projectFullId: String): Boolean = {
|
|
||||||
val projectBareId = projectFullId.takeWhile(_ != '/')
|
|
||||||
failedProjectsSet contains projectBareId
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private object FailedProjects {
|
|
||||||
// TODO: Move elsewhere
|
|
||||||
def combineFails(failedReportDownloads: Map[String, Throwable], parsingFailures: Map[ReportInfo, Throwable]): FailedProjects = {
|
|
||||||
/*
|
|
||||||
Fail can happen at multiple places:
|
|
||||||
1. Build cannot be downloaded (auth error, connection error, …) or is failed (failedReportDownloads)
|
|
||||||
2. Build is successful and can be downloaded, but it cannot be parsed (parsingFailures)
|
|
||||||
*/
|
|
||||||
val failedProjectsSet = failedReportDownloads.keySet ++ parsingFailures.keySet.map(_.projectId)
|
|
||||||
new FailedProjects(failedProjectsSet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import FailedProjects.combineFails
|
|
||||||
|
|
||||||
private def exportFailedReports(lds: LibDepStatistics, failed: FailedProjects): Future[Unit] = {
|
private def exportFailedReports(lds: LibDepStatistics, failed: FailedProjects): Future[Unit] = {
|
||||||
if(failed.failedProjectsSet.nonEmpty){
|
if(failed.failedProjectsSet.nonEmpty){
|
||||||
???
|
???
|
||||||
@@ -134,8 +111,8 @@ class Notifications @Inject()(
|
|||||||
(successfulReports, failedReports) <- resultsFuture
|
(successfulReports, failedReports) <- resultsFuture
|
||||||
libraries <- librariesService.all
|
libraries <- librariesService.all
|
||||||
parsedReports = dependencyCheckReportsParser.parseReports(successfulReports)
|
parsedReports = dependencyCheckReportsParser.parseReports(successfulReports)
|
||||||
lds = LibDepStatistics(dependencies = parsedReports.groupedDependencies.toSet, libraries = libraries.toSet)
|
lds = LibDepStatistics(dependencies = parsedReports.groupedDependencies.toSet, libraries = libraries.toSet, failedReportDownloads = failedReports, parsedReports = parsedReports)
|
||||||
failed = combineFails(failedReports, parsedReports.failedAnalysises)
|
failed = lds.failedProjects
|
||||||
failedReportsExportFuture = Fut(()) // TODO: exportFailedReports(lds, failed)
|
failedReportsExportFuture = Fut(()) // TODO: exportFailedReports(lds, failed)
|
||||||
issuesExportResultFuture = exportToIssueTracker(lds, failed, parsedReports.projectsReportInfo)
|
issuesExportResultFuture = exportToIssueTracker(lds, failed, parsedReports.projectsReportInfo)
|
||||||
diffDbExportResultFuture = exportToDiffDb(lds, failed, parsedReports.projectsReportInfo)
|
diffDbExportResultFuture = exportToDiffDb(lds, failed, parsedReports.projectsReportInfo)
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package controllers
|
|||||||
import com.github.nscala_time.time.Imports._
|
import com.github.nscala_time.time.Imports._
|
||||||
import com.google.inject.Inject
|
import com.google.inject.Inject
|
||||||
import com.google.inject.name.Named
|
import com.google.inject.name.Named
|
||||||
|
import com.ysoft.odc.statistics.{LibDepStatistics, TagStatistics}
|
||||||
import com.ysoft.odc.{ArtifactFile, ArtifactItem}
|
import com.ysoft.odc.{ArtifactFile, ArtifactItem}
|
||||||
import models.{Library, LibraryTag}
|
import models.LibraryTag
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import play.api.i18n.MessagesApi
|
import play.api.i18n.MessagesApi
|
||||||
import play.twirl.api.Txt
|
import play.twirl.api.Txt
|
||||||
@@ -13,34 +14,6 @@ import views.html.DefaultRequest
|
|||||||
|
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
object Statistics{
|
|
||||||
case class LibDepStatistics(libraries: Set[(Int, Library)], dependencies: Set[GroupedDependency]){
|
|
||||||
def vulnerableRatio = vulnerableDependencies.size.toDouble / dependencies.size.toDouble
|
|
||||||
lazy val vulnerabilities: Set[Vulnerability] = dependencies.flatMap(_.vulnerabilities)
|
|
||||||
lazy val vulnerabilitiesByName = vulnerabilities.map(v => v.name -> v).toMap
|
|
||||||
lazy val vulnerabilityNames = vulnerabilities.map(_.name)
|
|
||||||
lazy val vulnerabilitiesToDependencies: Map[Vulnerability, Set[GroupedDependency]] = vulnerableDependencies.flatMap(dep =>
|
|
||||||
dep.vulnerabilities.map(vuln => (vuln, dep))
|
|
||||||
).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
|
|
||||||
vulnerableDependencies.flatMap(dep => dep.vulnerabilities.map(_ -> dep)).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
|
|
||||||
vulnerableDependencies.flatMap(dep => dep.vulnerabilities.map(_ -> dep)).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
|
|
||||||
lazy val vulnerableDependencies = dependencies.filter(_.isVulnerable)
|
|
||||||
lazy val (dependenciesWithCpe, dependenciesWithoutCpe) = dependencies.partition(_.hasCpe)
|
|
||||||
lazy val cpeRatio = dependenciesWithCpe.size.toDouble / dependencies.size.toDouble
|
|
||||||
lazy val weaknesses = vulnerabilities.flatMap(_.cweOption)
|
|
||||||
lazy val weaknessesFrequency = computeWeaknessesFrequency(vulnerabilities)
|
|
||||||
}
|
|
||||||
case class TagStatistics(tagRecord: (Int, LibraryTag), stats: LibDepStatistics){
|
|
||||||
def tag: LibraryTag = tagRecord._2
|
|
||||||
def tagId: Int = tagRecord._1
|
|
||||||
}
|
|
||||||
|
|
||||||
def computeWeaknessesFrequency(vulnerabilities: Set[Vulnerability]) = vulnerabilities.toSeq.map(_.cweOption).groupBy(identity).mapValues(_.size).map(identity).withDefaultValue(0)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
import controllers.Statistics._
|
|
||||||
|
|
||||||
class Statistics @Inject() (
|
class Statistics @Inject() (
|
||||||
reportsParser: DependencyCheckReportsParser,
|
reportsParser: DependencyCheckReportsParser,
|
||||||
reportsProcessor: DependencyCheckReportsProcessor,
|
reportsProcessor: DependencyCheckReportsProcessor,
|
||||||
@@ -105,17 +78,19 @@ class Statistics @Inject() (
|
|||||||
val tagsFuture = tagsService.all
|
val tagsFuture = tagsService.all
|
||||||
val parsedReports = selection.result
|
val parsedReports = selection.result
|
||||||
for{
|
for{
|
||||||
tagStatistics <- statisticsForTags(parsedReports, tagsFuture)
|
tagStatistics <- statisticsForTags(parsedReports, failedResults, tagsFuture)
|
||||||
|
libraries <- librariesService.all
|
||||||
} yield Ok(views.html.statistics.basic(
|
} yield Ok(views.html.statistics.basic(
|
||||||
tagStatistics = tagStatistics,
|
tagStatistics = tagStatistics,
|
||||||
projectsWithSelection = selection.projectsWithSelection,
|
projectsWithSelection = selection.projectsWithSelection,
|
||||||
parsedReports = parsedReports
|
parsedReports = parsedReports,
|
||||||
|
lds = LibDepStatistics(libraries.toSet, parsedReports.groupedDependencies.toSet, failedResults, parsedReports)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def statisticsForTags(parsedReports: DependencyCheckReportsParser.Result, tagsFuture: Future[Seq[(Int, LibraryTag)]]): Future[Seq[Statistics.TagStatistics]] = {
|
def statisticsForTags(parsedReports: DependencyCheckReportsParser.Result, failedReports: Map[String, Throwable], tagsFuture: Future[Seq[(Int, LibraryTag)]]): Future[Seq[TagStatistics]] = {
|
||||||
val librariesFuture = librariesService.byPlainLibraryIdentifiers(parsedReports.allDependencies.flatMap(_._1.plainLibraryIdentifiers).toSet)
|
val librariesFuture = librariesService.byPlainLibraryIdentifiers(parsedReports.allDependencies.flatMap(_._1.plainLibraryIdentifiers).toSet)
|
||||||
val libraryTagAssignmentsFuture = librariesFuture.flatMap{libraries => libraryTagAssignmentsService.forLibraries(libraries.values.map(_._1).toSet)}
|
val libraryTagAssignmentsFuture = librariesFuture.flatMap{libraries => libraryTagAssignmentsService.forLibraries(libraries.values.map(_._1).toSet)}
|
||||||
val tagsToLibrariesFuture = libraryTagAssignmentsService.tagsToLibraries(libraryTagAssignmentsFuture)
|
val tagsToLibrariesFuture = libraryTagAssignmentsService.tagsToLibraries(libraryTagAssignmentsFuture)
|
||||||
@@ -130,7 +105,15 @@ class Statistics @Inject() (
|
|||||||
val tagDependencies: Set[GroupedDependency] = tagLibraries.flatMap{case (_, lib) => librariesToDependencies(lib.plainLibraryIdentifier)}
|
val tagDependencies: Set[GroupedDependency] = tagLibraries.flatMap{case (_, lib) => librariesToDependencies(lib.plainLibraryIdentifier)}
|
||||||
// TODO: vulnerabilities in the past
|
// TODO: vulnerabilities in the past
|
||||||
if(tagLibraries.isEmpty) None
|
if(tagLibraries.isEmpty) None
|
||||||
else Some(TagStatistics(tagRecord = tagRecord, stats = LibDepStatistics(libraries = tagLibraries, dependencies = tagDependencies)))
|
else Some(TagStatistics(
|
||||||
|
tagRecord = tagRecord,
|
||||||
|
stats = LibDepStatistics(
|
||||||
|
libraries = tagLibraries,
|
||||||
|
dependencies = tagDependencies,
|
||||||
|
failedReportDownloads = failedReports,
|
||||||
|
parsedReports = parsedReports
|
||||||
|
)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,10 +125,15 @@ class Statistics @Inject() (
|
|||||||
for{
|
for{
|
||||||
libraries <- librariesService.byPlainLibraryIdentifiers(parsedReports.allDependencies.flatMap(_._1.plainLibraryIdentifiers).toSet)
|
libraries <- librariesService.byPlainLibraryIdentifiers(parsedReports.allDependencies.flatMap(_._1.plainLibraryIdentifiers).toSet)
|
||||||
tagOption <- tagIdOption.fold[Future[Option[(Int, LibraryTag)]]](Future.successful(None))(tagId => tagsService.getById(tagId).map(Some(_)))
|
tagOption <- tagIdOption.fold[Future[Option[(Int, LibraryTag)]]](Future.successful(None))(tagId => tagsService.getById(tagId).map(Some(_)))
|
||||||
statistics <- tagOption.fold(Future.successful(LibDepStatistics(dependencies = parsedReports.groupedDependencies.toSet, libraries = libraries.values.toSet))){ tag =>
|
statistics <- tagOption.fold(Future.successful(LibDepStatistics(
|
||||||
statisticsForTags(parsedReports, Future.successful(Seq(tag))).map{
|
dependencies = parsedReports.groupedDependencies.toSet,
|
||||||
|
libraries = libraries.values.toSet,
|
||||||
|
failedReportDownloads = failedResults,
|
||||||
|
parsedReports = parsedReports
|
||||||
|
))){ tag =>
|
||||||
|
statisticsForTags(parsedReports, failedResults, Future.successful(Seq(tag))).map{
|
||||||
case Seq(TagStatistics(_, stats)) => stats // statisticsForTags is designed for multiple tags, but we have just one…
|
case Seq(TagStatistics(_, stats)) => stats // statisticsForTags is designed for multiple tags, but we have just one…
|
||||||
case Seq() => LibDepStatistics(libraries = Set(), dependencies = Set()) // We don't want to crash when no dependencies are there…
|
case Seq() => LibDepStatistics(libraries = Set(), dependencies = Set(), failedReportDownloads = failedResults, parsedReports) // We don't want to crash when no dependencies are there…
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} yield Ok(views.html.statistics.vulnerabilities(
|
} yield Ok(views.html.statistics.vulnerabilities(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import com.mohiva.play.silhouette.api.Environment
|
import com.mohiva.play.silhouette.api.Environment
|
||||||
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
|
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
|
||||||
import models.{User, SnoozeInfo}
|
import models.{SnoozeInfo, User}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by user on 7/15/15.
|
* Created by user on 7/15/15.
|
||||||
@@ -16,6 +16,7 @@ package object controllers {
|
|||||||
type DateTime = org.joda.time.DateTime
|
type DateTime = org.joda.time.DateTime
|
||||||
type SnoozesInfo = Map[String, SnoozeInfo]
|
type SnoozesInfo = Map[String, SnoozeInfo]
|
||||||
type AuthEnv = Environment[User, CookieAuthenticator]
|
type AuthEnv = Environment[User, CookieAuthenticator]
|
||||||
|
type LibDepStatistics = com.ysoft.odc.statistics.LibDepStatistics
|
||||||
|
|
||||||
|
|
||||||
val NormalUrlPattern = """^(http(s?)|ftp(s?))://.*""".r
|
val NormalUrlPattern = """^(http(s?)|ftp(s?))://.*""".r
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
@import com.ysoft.odc.statistics.TagStatistics
|
||||||
@(
|
@(
|
||||||
projectsWithSelection: ProjectsWithSelection,
|
projectsWithSelection: ProjectsWithSelection,
|
||||||
tagStatistics: Seq[Statistics.TagStatistics],
|
lds: LibDepStatistics,
|
||||||
|
tagStatistics: Seq[TagStatistics],
|
||||||
parsedReports: DependencyCheckReportsParser.Result
|
parsedReports: DependencyCheckReportsParser.Result
|
||||||
)(implicit messagesApi: MessagesApi, requestHeader: DefaultRequest)
|
)(implicit messagesApi: MessagesApi, requestHeader: DefaultRequest)
|
||||||
@import com.ysoft.odc.CWE
|
@import com.ysoft.odc.CWE
|
||||||
@import controllers.Statistics.TagStatistics
|
|
||||||
@import play.api.libs.json.{JsNull, JsString}
|
@import play.api.libs.json.{JsNull, JsString}
|
||||||
@import scala.language.implicitConversions
|
@import scala.language.implicitConversions
|
||||||
@implicitTagStatistics(ts: TagStatistics) = @{ts.stats}
|
@implicitTagStatistics(ts: TagStatistics) = @{ts.stats}
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
<div id="weakness" data-data='@{plotData(Statistics.computeWeaknessesFrequency(parsedReports.groupedDependencies.flatMap(_.vulnerabilities).toSet))}'></div>
|
<div id="weakness" data-data='@{plotData(lds.weaknessesFrequency)}'></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
var WeaknessIdentifier = function(brief, verbose){
|
var WeaknessIdentifier = function(brief, verbose){
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@(
|
@(
|
||||||
projectsWithSelection: ProjectsWithSelection,
|
projectsWithSelection: ProjectsWithSelection,
|
||||||
tagOption: Option[(Int, LibraryTag)],
|
tagOption: Option[(Int, LibraryTag)],
|
||||||
statistics: Statistics.LibDepStatistics
|
statistics: LibDepStatistics
|
||||||
)(implicit messagesApi: MessagesApi, requestHeader: DefaultRequest)
|
)(implicit messagesApi: MessagesApi, requestHeader: DefaultRequest)
|
||||||
|
|
||||||
@main(
|
@main(
|
||||||
|
|||||||
Reference in New Issue
Block a user