Merge branch 'stable-2.16' into stable-3.0
* stable-2.16:
Promote aggregation key to case class
Add ability to ignore specific files suffixes
Change-Id: Ifceb12d03188813db37816fe84ac38ff72f79fb6
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 6c08a06..e9d9718 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -34,4 +34,24 @@
```ini
[contributors]
extract-issues = true
+ ```
+
+- `contributors.ignore-file-suffix`
+
+ List of file suffixes to be ignored from the analytics.
+ Files matching any of the specified suffixes will not be accounted for in
+ `num_files`, `num_distinct_files`, `added_lines` and `deleted_lines` fields
+ nor will they be listed in the `commits.files` array field.
+ This can be used to explicitly ignore binary files for which, file-based
+ statistics makes little or no sense.
+
+ Default: empty
+
+ Example:
+ ```ini
+ [contributors]
+ ignore-file-suffix = .dmg
+ ignore-file-suffix = .ko
+ ignore-file-suffix = .png
+ ignore-file-suffix = .exe
```
\ No newline at end of file
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/AnalyticsConfig.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/AnalyticsConfig.scala
index fd31626..10eb8ed 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/AnalyticsConfig.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/AnalyticsConfig.scala
@@ -23,15 +23,18 @@
trait AnalyticsConfig {
def botlikeFilenameRegexps: List[String]
def isExtractIssues: Boolean
+ def ignoreFileSuffixes: List[String]
}
class AnalyticsConfigImpl @Inject() (val pluginConfigFactory: PluginConfigFactory, @PluginName val pluginName: String) extends AnalyticsConfig{
lazy val botlikeFilenameRegexps: List[String] = pluginConfigBotLikeFilenameRegexp
lazy val isExtractIssues: Boolean = pluginConfig.getBoolean(Contributors, null, ExtractIssues, false)
+ lazy val ignoreFileSuffixes: List[String] = pluginConfig.getStringList(Contributors, null, IgnoreFileSuffix).toList
private lazy val pluginConfig: Config = pluginConfigFactory.getGlobalPluginConfig(pluginName)
private val Contributors = "contributors"
private val BotlikeFilenameRegexp = "botlike-filename-regexp"
private val ExtractIssues = "extract-issues"
+ private val IgnoreFileSuffix = "ignore-file-suffix"
private lazy val pluginConfigBotLikeFilenameRegexp = pluginConfig.getStringList(Contributors, null, BotlikeFilenameRegexp).toList
}
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala
index f634725..98e4e06 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/Contributors.scala
@@ -24,8 +24,6 @@
import com.googlesource.gerrit.plugins.analytics.common._
import org.kohsuke.args4j.{Option => ArgOption}
-import scala.util.Try
-
@CommandMetaData(name = "contributors", description = "Extracts the list of contributors to a project")
class ContributorsCommand @Inject()(val executor: ContributorsService,
val projects: ProjectsCollection,
@@ -130,10 +128,11 @@
}
class ContributorsService @Inject()(repoManager: GitRepositoryManager,
- projectCache:ProjectCache,
+ projectCache: ProjectCache,
histogram: UserActivityHistogram,
gsonFmt: GsonFormatter,
commitsStatisticsCache: CommitsStatisticsCache) {
+
import RichBoolean._
def get(projectRes: ProjectResource, startDate: Option[Long], stopDate: Option[Long],
@@ -141,7 +140,7 @@
: TraversableOnce[UserActivitySummary] = {
ManagedResource.use(repoManager.openRepository(projectRes.getNameKey)) { repo =>
- val stats = new Statistics(projectRes.getNameKey, commitsStatisticsCache)
+ val stats = new Statistics(projectRes.getNameKey, commitsStatisticsCache)
val branchesExtractor = extractBranches.option(new BranchesExtractor(repo))
histogram.get(repo, new AggregatedHistogramFilterByDates(startDate, stopDate, branchesExtractor, aggregationStrategy))
@@ -155,10 +154,10 @@
case class IssueInfo(code: String, link: String)
-case class UserActivitySummary(year: Integer,
- month: Integer,
- day: Integer,
- hour: Integer,
+case class UserActivitySummary(year: Option[Int],
+ month: Option[Int],
+ day: Option[Int],
+ hour: Option[Int],
name: String,
email: String,
numCommits: Integer,
@@ -179,24 +178,30 @@
def apply(statisticsHandler: Statistics)(uca: AggregatedUserCommitActivity)
: Iterable[UserActivitySummary] = {
- def stringToIntOrNull(x: String): Integer = Try(new Integer(x)).getOrElse(null)
+ statisticsHandler.forCommits(uca.getIds: _*).map { stat =>
+ val maybeBranches =
+ uca.key.branch.fold(Array.empty[String])(Array(_))
- uca.key.split("/", AggregationStrategy.MAX_MAPPING_TOKENS) match {
- case Array(email, year, month, day, hour, branch) =>
- statisticsHandler.forCommits(uca.getIds: _*).map { stat =>
- val maybeBranches =
- Option(branch).filter(_.nonEmpty).map(b => Array(b)).getOrElse(Array.empty)
-
- UserActivitySummary(
- stringToIntOrNull(year), stringToIntOrNull(month), stringToIntOrNull(day), stringToIntOrNull(hour),
- uca.getName, email, stat.commits.size,
- stat.numFiles, stat.numDistinctFiles, stat.addedLines, stat.deletedLines,
- stat.commits.toArray, maybeBranches, stat.issues.map(_.code)
- .toArray, stat.issues.map(_.link).toArray, uca.getLatest, stat
- .isForMergeCommits,stat.isForBotLike
- )
- }
- case _ => throw new Exception(s"invalid key format found ${uca.key}")
+ UserActivitySummary(
+ uca.key.year,
+ uca.key.month,
+ uca.key.day,
+ uca.key.hour,
+ uca.getName,
+ uca.key.email,
+ stat.commits.size,
+ stat.numFiles,
+ stat.numDistinctFiles,
+ stat.addedLines,
+ stat.deletedLines,
+ stat.commits.toArray,
+ maybeBranches,
+ stat.issues.map(_.code).toArray,
+ stat.issues.map(_.link).toArray,
+ uca.getLatest,
+ stat.isForMergeCommits,
+ stat.isForBotLike
+ )
}
}
}
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregatedCommitHistogram.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregatedCommitHistogram.scala
index 74abf08..f39fc73 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregatedCommitHistogram.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregatedCommitHistogram.scala
@@ -16,19 +16,19 @@
import java.util.Date
-import com.googlesource.gerrit.plugins.analytics.common.AggregationStrategy.BY_BRANCH
+import com.googlesource.gerrit.plugins.analytics.common.AggregationStrategy.{AggregationKey, BY_BRANCH}
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.revwalk.RevCommit
import org.gitective.core.stat.{CommitHistogram, CommitHistogramFilter, UserCommitActivity}
-class AggregatedUserCommitActivity(val key: String, val name: String, val email: String)
+class AggregatedUserCommitActivity(val key: AggregationKey, val name: String, val email: String)
extends UserCommitActivity(name, email)
class AggregatedCommitHistogram(var aggregationStrategy: AggregationStrategy)
extends CommitHistogram {
def includeWithBranches(commit: RevCommit, user: PersonIdent, branches: Set[String]): Unit = {
- for ( branch <- branches ) {
+ for (branch <- branches) {
val originalStrategy = aggregationStrategy
this.aggregationStrategy = BY_BRANCH(branch, aggregationStrategy)
this.include(commit, user)
@@ -38,11 +38,13 @@
override def include(commit: RevCommit, user: PersonIdent): AggregatedCommitHistogram = {
val key = aggregationStrategy.mapping(user, commit.getAuthorIdent.getWhen)
- val activity = Option(users.get(key)) match {
+ val keyString = key.toString
+
+ val activity = Option(users.get(keyString)) match {
case None =>
val newActivity = new AggregatedUserCommitActivity(key,
user.getName, user.getEmailAddress)
- users.put(key, newActivity)
+ users.put(keyString, newActivity)
newActivity
case Some(foundActivity) => foundActivity
}
@@ -56,7 +58,7 @@
}
object AggregatedCommitHistogram {
- type AggregationStrategyMapping = (PersonIdent, Date) => String
+ type AggregationStrategyMapping = (PersonIdent, Date) => AggregationKey
}
abstract class AbstractCommitHistogramFilter(aggregationStrategy: AggregationStrategy)
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregationStrategy.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregationStrategy.scala
index 079c307..3292fc9 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregationStrategy.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/AggregationStrategy.scala
@@ -41,33 +41,57 @@
def utc: LocalDateTime = d.toInstant.atZone(ZoneOffset.UTC).toLocalDateTime
}
+ case class AggregationKey(email: String,
+ year: Option[Int] = None,
+ month: Option[Int] = None,
+ day: Option[Int] = None,
+ hour: Option[Int] = None,
+ branch: Option[String] = None)
+
object EMAIL extends AggregationStrategy {
val name: String = "EMAIL"
- val mapping: (PersonIdent, Date) => String = (p, _) => s"${p.getEmailAddress}/////"
+ val mapping: (PersonIdent, Date) => AggregationKey = (p, _) =>
+ AggregationKey(email = p.getEmailAddress)
}
object EMAIL_YEAR extends AggregationStrategy {
val name: String = "EMAIL_YEAR"
- val mapping: (PersonIdent, Date) => String = (p, d) => s"${p.getEmailAddress}/${d.utc.getYear}////"
+ val mapping: (PersonIdent, Date) => AggregationKey = (p, d) =>
+ AggregationKey(email = p.getEmailAddress, year = Some(d.utc.getYear))
}
object EMAIL_MONTH extends AggregationStrategy {
val name: String = "EMAIL_MONTH"
- val mapping: (PersonIdent, Date) => String = (p, d) => s"${p.getEmailAddress}/${d.utc.getYear}/${d.utc.getMonthValue}///"
+ val mapping: (PersonIdent, Date) => AggregationKey = (p, d) =>
+ AggregationKey(email = p.getEmailAddress,
+ year = Some(d.utc.getYear),
+ month = Some(d.utc.getMonthValue))
}
object EMAIL_DAY extends AggregationStrategy {
val name: String = "EMAIL_DAY"
- val mapping: (PersonIdent, Date) => String = (p, d) => s"${p.getEmailAddress}/${d.utc.getYear}/${d.utc.getMonthValue}/${d.utc.getDayOfMonth}//"
+ val mapping: (PersonIdent, Date) => AggregationKey = (p, d) =>
+ AggregationKey(email = p.getEmailAddress,
+ year = Some(d.utc.getYear),
+ month = Some(d.utc.getMonthValue),
+ day = Some(d.utc.getDayOfMonth))
}
object EMAIL_HOUR extends AggregationStrategy {
val name: String = "EMAIL_HOUR"
- val mapping: (PersonIdent, Date) => String = (p, d) => s"${p.getEmailAddress}/${d.utc.getYear}/${d.utc.getMonthValue}/${d.utc.getDayOfMonth}/${d.utc.getHour}/"
+ val mapping: (PersonIdent, Date) => AggregationKey = (p, d) =>
+ AggregationKey(email = p.getEmailAddress,
+ year = Some(d.utc.getYear),
+ month = Some(d.utc.getMonthValue),
+ day = Some(d.utc.getDayOfMonth),
+ hour = Some(d.utc.getHour))
}
- case class BY_BRANCH(branch: String, baseAggregationStrategy: AggregationStrategy) extends AggregationStrategy {
+ case class BY_BRANCH(branch: String,
+ baseAggregationStrategy: AggregationStrategy)
+ extends AggregationStrategy {
val name: String = s"BY_BRANCH($branch)"
- val mapping: (PersonIdent, Date) => String = (p, d) => s"${baseAggregationStrategy.mapping(p, d)}$branch"
+ val mapping: (PersonIdent, Date) => AggregationKey = (p, d) =>
+ baseAggregationStrategy.mapping(p, d).copy(branch = Some(branch))
}
}
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCache.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCache.scala
index 8d573f0..c684c57 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCache.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCache.scala
@@ -20,7 +20,6 @@
import com.googlesource.gerrit.plugins.analytics.common.CommitsStatisticsCache.COMMITS_STATISTICS_CACHE
import org.eclipse.jgit.lib.ObjectId
-@ImplementedBy(classOf[CommitsStatisticsCacheImpl])
trait CommitsStatisticsCache {
def get(project: String, objectId: ObjectId): CommitsStatistics
}
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCacheModule.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCacheModule.scala
index 9b2e358..5e4fe2f 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCacheModule.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsCacheModule.scala
@@ -21,6 +21,7 @@
class CommitsStatisticsCacheModule extends CacheModule() {
override protected def configure(): Unit = {
+ bind(classOf[CommitsStatisticsCache]).to(classOf[CommitsStatisticsCacheImpl])
persist(CommitsStatisticsCache.COMMITS_STATISTICS_CACHE, classOf[CommitsStatisticsCacheKey], classOf[CommitsStatistics])
.version(1)
.diskLimit(-1)
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsLoader.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsLoader.scala
index 1144901..8025922 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsLoader.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/CommitsStatisticsLoader.scala
@@ -34,7 +34,8 @@
gitRepositoryManager: GitRepositoryManager,
projectCache: ProjectCache,
botLikeExtractor: BotLikeExtractor,
- config: AnalyticsConfig
+ config: AnalyticsConfig,
+ ignoreFileSuffixFilter: IgnoreFileSuffixFilter
) extends CacheLoader[CommitsStatisticsCacheKey, CommitsStatistics] {
override def load(cacheKey: CommitsStatisticsCacheKey): CommitsStatistics = {
@@ -70,6 +71,7 @@
val df = new DiffFormatter(DisabledOutputStream.INSTANCE)
df.setRepository(repo)
+ df.setPathFilter(ignoreFileSuffixFilter)
df.setDiffComparator(RawTextComparator.DEFAULT)
df.setDetectRenames(true)
val diffs = df.scan(oldTree, newTree).asScala
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/GsonFormatter.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/GsonFormatter.scala
index d36cfef..b054554 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/GsonFormatter.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/GsonFormatter.scala
@@ -15,19 +15,18 @@
package com.googlesource.gerrit.plugins.analytics.common
import java.io.PrintWriter
-
-import com.google.gerrit.json.OutputFormat
-import com.google.gson.{Gson, GsonBuilder, JsonSerializer}
-import com.google.inject.Singleton
import java.lang.reflect.Type
-import com.google.gson._
+import com.google.gerrit.json.OutputFormat
+import com.google.gson.{Gson, GsonBuilder, JsonSerializer, _}
+import com.google.inject.Singleton
@Singleton
class GsonFormatter {
val gsonBuilder: GsonBuilder =
OutputFormat.JSON_COMPACT.newGsonBuilder
.registerTypeHierarchyAdapter(classOf[Iterable[Any]], new IterableSerializer)
+ .registerTypeHierarchyAdapter(classOf[Option[Any]], new OptionSerializer())
def format[T](values: TraversableOnce[T], out: PrintWriter) {
val gson: Gson = gsonBuilder.create
@@ -45,4 +44,12 @@
}
}
+ class OptionSerializer extends JsonSerializer[Option[Any]] {
+ def serialize(src: Option[Any], typeOfSrc: Type, context: JsonSerializationContext): JsonElement = {
+ src match {
+ case None => JsonNull.INSTANCE
+ case Some(v) => context.serialize(v)
+ }
+ }
+ }
}
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilter.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilter.scala
new file mode 100644
index 0000000..d875eb0
--- /dev/null
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilter.scala
@@ -0,0 +1,35 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.analytics.common
+
+import com.google.inject.{Inject, Singleton}
+import com.googlesource.gerrit.plugins.analytics.AnalyticsConfig
+import org.eclipse.jgit.treewalk.TreeWalk
+import org.eclipse.jgit.treewalk.filter.TreeFilter
+import org.gitective.core.PathFilterUtils
+
+@Singleton
+case class IgnoreFileSuffixFilter @Inject() (config: AnalyticsConfig) extends TreeFilter {
+
+ private lazy val suffixFilter =
+ if (config.ignoreFileSuffixes.nonEmpty)
+ PathFilterUtils.orSuffix(config.ignoreFileSuffixes:_*).negate()
+ else
+ TreeFilter.ALL
+
+ override def include(treeWalk: TreeWalk): Boolean = treeWalk.isSubtree || suffixFilter.include(treeWalk)
+ override def shouldBeRecursive(): Boolean = suffixFilter.shouldBeRecursive()
+ override def clone(): TreeFilter = this
+}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/BotLikeExtractorImplSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/BotLikeExtractorImplSpec.scala
index 5bb73c5..42b0d1c 100644
--- a/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/BotLikeExtractorImplSpec.scala
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/BotLikeExtractorImplSpec.scala
@@ -49,5 +49,6 @@
private def newBotLikeExtractorImpl(botLikeRegexps: List[String]) = new BotLikeExtractorImpl(new AnalyticsConfig {
override lazy val botlikeFilenameRegexps = botLikeRegexps
override lazy val isExtractIssues: Boolean = false
+ override def ignoreFileSuffixes: List[String] = List.empty
})
}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilterSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilterSpec.scala
new file mode 100644
index 0000000..4ed373c
--- /dev/null
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/common/IgnoreFileSuffixFilterSpec.scala
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.analytics.common
+
+import com.google.gerrit.acceptance.UseLocalDisk
+import com.googlesource.gerrit.plugins.analytics.AnalyticsConfig
+import com.googlesource.gerrit.plugins.analytics.test.GerritTestDaemon
+import org.eclipse.jgit.treewalk.TreeWalk
+import org.scalatest.{FlatSpec, Matchers}
+
+@UseLocalDisk
+class IgnoreFileSuffixFilterSpec extends FlatSpec with Matchers with GerritTestDaemon {
+
+ behavior of "IgnoreFileSuffixFilter"
+
+ it should "include a file with suffix not listed in configuration" in {
+ val ignoreSuffix = ".dmg"
+ val fileSuffix = ".txt"
+ val aFile = s"aFile$fileSuffix"
+ val commit = testFileRepository.commitFile(aFile, "some content")
+
+ val walk = TreeWalk.forPath(testFileRepository.getRepository, aFile, commit.getTree)
+
+ newIgnoreFileSuffix(ignoreSuffix).include(walk) shouldBe true
+ }
+
+ it should "not include a file with suffix listed in configuration" in {
+ val ignoreSuffix = ".dmg"
+ val aFile = s"aFile$ignoreSuffix"
+ val commit = testFileRepository.commitFile(aFile, "some content")
+
+ val walk = TreeWalk.forPath(testFileRepository.getRepository, aFile, commit.getTree)
+
+ newIgnoreFileSuffix(ignoreSuffix).include(walk) shouldBe false
+ }
+
+ private def newIgnoreFileSuffix(suffixes: String*) = IgnoreFileSuffixFilter(new AnalyticsConfig {
+ override lazy val botlikeFilenameRegexps = List.empty
+ override lazy val isExtractIssues: Boolean = false
+ override def ignoreFileSuffixes: List[String] = suffixes.toList
+ })
+}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/CommitStatisticsSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/CommitStatisticsSpec.scala
index b2ae9e7..8c5eb60 100644
--- a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/CommitStatisticsSpec.scala
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/CommitStatisticsSpec.scala
@@ -16,7 +16,7 @@
import com.google.gerrit.acceptance.UseLocalDisk
import com.googlesource.gerrit.plugins.analytics.CommitInfo
-import com.googlesource.gerrit.plugins.analytics.common.{CommitsStatistics, CommitsStatisticsLoader, Statistics}
+import com.googlesource.gerrit.plugins.analytics.common.{CommitsStatistics, Statistics}
import org.scalatest.{FlatSpec, Inside, Matchers}
@UseLocalDisk
@@ -131,5 +131,4 @@
case wrongContent => fail(s"Expected two results instead got $wrongContent")
}
}
-
}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/ContributorsServiceSpec.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/ContributorsServiceSpec.scala
new file mode 100644
index 0000000..acefed1
--- /dev/null
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/ContributorsServiceSpec.scala
@@ -0,0 +1,83 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.analytics.test
+
+import java.lang.reflect.Type
+
+import com.google.gerrit.acceptance.UseLocalDisk
+import com.google.gson._
+import com.googlesource.gerrit.plugins.analytics.UserActivitySummary
+import com.googlesource.gerrit.plugins.analytics.common.AggregationStrategy.EMAIL_HOUR
+import com.googlesource.gerrit.plugins.analytics.common.GsonFormatter
+import com.googlesource.gerrit.plugins.analytics.test.TestAnalyticsConfig.IGNORED_FILE_SUFFIX
+import org.scalatest.{FlatSpec, Inside, Matchers}
+
+import scala.collection.JavaConverters._
+
+@UseLocalDisk
+class ContributorsServiceSpec extends FlatSpec with Matchers with GerritTestDaemon with Inside {
+
+ "ContributorsService" should "get commit statistics" in {
+ val aContributorName = "Contributor Name"
+ val aContributorEmail = "contributor@test.com"
+ val aFileName = "file.txt"
+ val anIgnoredFileName = s"file$IGNORED_FILE_SUFFIX"
+
+ val commit = testFileRepository.commitFiles(
+ List(anIgnoredFileName -> "1\n2\n", aFileName -> "1\n2\n"),
+ newPersonIdent(aContributorName, aContributorEmail)
+ )
+
+ val statsJson = daemonTest.restSession.get(s"/projects/${fileRepositoryName.get()}/analytics~contributors?aggregate=${EMAIL_HOUR.name}")
+
+ statsJson.assertOK()
+
+ val stats = TestGson().fromJson(statsJson.getEntityContent, classOf[UserActivitySummary])
+
+ inside(stats) {
+ case UserActivitySummary(_, _, _, _, theAuthorName, theAuthorEmail, numCommits, numFiles, numDistinctFiles, addedLines, deletedLines, commits, _, _, _, _, _, _) =>
+ theAuthorName shouldBe aContributorName
+ theAuthorEmail shouldBe aContributorEmail
+ numCommits shouldBe 1
+ numFiles shouldBe 1
+ numDistinctFiles shouldBe 1
+ addedLines shouldBe 2
+ deletedLines shouldBe 0
+ commits.head.files should contain only aFileName
+ commits.head.sha1 shouldBe commit.name
+ }
+ }
+}
+
+object TestGson {
+
+ class SetStringDeserializer extends JsonDeserializer[Set[String]] {
+ override def deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Set[String] =
+ json.getAsJsonArray.asScala.map(_.getAsString).toSet
+ }
+
+ class OptionDeserializer extends JsonDeserializer[Option[Any]] {
+ override def deserialize(jsonElement: JsonElement, `type`: Type, jsonDeserializationContext: JsonDeserializationContext): Option[Any] = {
+ Some(jsonElement)
+ }
+ }
+
+ def apply(): Gson =
+ new GsonFormatter()
+ .gsonBuilder
+ .registerTypeHierarchyAdapter(classOf[Iterable[String]], new SetStringDeserializer)
+ .registerTypeHierarchyAdapter(classOf[Option[Any]], new OptionDeserializer())
+ .create()
+}
\ No newline at end of file
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GerritTestDaemon.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GerritTestDaemon.scala
index 0024e1e..1b6b674 100644
--- a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GerritTestDaemon.scala
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/GerritTestDaemon.scala
@@ -20,9 +20,13 @@
import com.google.gerrit.acceptance.{AbstractDaemonTest, GitUtil}
import com.google.gerrit.extensions.annotations.PluginName
import com.google.gerrit.extensions.client.SubmitType
+import com.google.gerrit.acceptance._
+import com.google.gerrit.extensions.restapi.RestApiModule
import com.google.gerrit.reviewdb.client.Project
-import com.google.inject.{AbstractModule, Module}
-import com.googlesource.gerrit.plugins.analytics.AnalyticsConfig
+import com.google.gerrit.server.project.ProjectResource.PROJECT_KIND
+import com.google.inject.AbstractModule
+import com.googlesource.gerrit.plugins.analytics.{AnalyticsConfig, ContributorsResource}
+import com.googlesource.gerrit.plugins.analytics.common.CommitsStatisticsCache
import org.eclipse.jgit.api.MergeCommand.FastForwardMode
import org.eclipse.jgit.api.{Git, MergeResult}
import org.eclipse.jgit.internal.storage.file.FileRepository
@@ -69,6 +73,7 @@
new PersonIdent(new PersonIdent(name, email), ts)
override def beforeEach {
+ daemonTest.setUpTestPlugin()
fileRepositoryName = daemonTest.newProject(testSpecificRepositoryName)
fileRepository = daemonTest.getRepository(fileRepositoryName)
testFileRepository = GitUtil.newTestRepository(fileRepository)
@@ -136,9 +141,14 @@
}
}
-object GerritTestDaemon extends AbstractDaemonTest {
+@TestPlugin(
+ name = "analytics",
+ sysModule = "com.googlesource.gerrit.plugins.analytics.test.GerritTestDaemon$TestModule"
+)
+object GerritTestDaemon extends LightweightPluginDaemonTest {
baseConfig = new Config()
AbstractDaemonTest.temporaryFolder.create()
+ tempDataDir.create()
def newProject(nameSuffix: String) = {
resourcePrefix = ""
@@ -151,15 +161,21 @@
def adminAuthor = admin.newIdent
def getInstance[T](clazz: Class[T]): T =
- server.getTestInjector.getInstance(clazz)
+ plugin.getSysInjector.getInstance(clazz)
- override def createModule(): Module = new AbstractModule {
+ def getCanonicalWebUrl: String = canonicalWebUrl.get()
+
+ def restSession: RestSession = adminRestSession
+
+ class TestModule extends AbstractModule {
override def configure(): Unit = {
- bind(classOf[AnalyticsConfig]).toInstance(new AnalyticsConfig {
- override def botlikeFilenameRegexps: List[String] = List.empty
- override def isExtractIssues: Boolean = true
+ bind(classOf[CommitsStatisticsCache]).to(classOf[CommitsStatisticsNoCache])
+ bind(classOf[AnalyticsConfig]).toInstance(TestAnalyticsConfig)
+ install(new RestApiModule() {
+ override protected def configure() = {
+ get(PROJECT_KIND, "contributors").to(classOf[ContributorsResource])
+ }
})
- bind(classOf[String]).annotatedWith(classOf[PluginName]).toInstance("analytics")
}
}
}
\ No newline at end of file
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestAnalyticsConfig.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestAnalyticsConfig.scala
new file mode 100644
index 0000000..598df31
--- /dev/null
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestAnalyticsConfig.scala
@@ -0,0 +1,24 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.analytics.test
+
+import com.googlesource.gerrit.plugins.analytics.AnalyticsConfig
+
+object TestAnalyticsConfig extends AnalyticsConfig {
+ val IGNORED_FILE_SUFFIX = ".bin"
+ val botlikeFilenameRegexps: List[String] = List.empty
+ val isExtractIssues: Boolean = true
+ val ignoreFileSuffixes: List[String] = List(IGNORED_FILE_SUFFIX)
+}
diff --git a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestCommitStatisticsNoCache.scala b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestCommitStatisticsNoCache.scala
index 8834418..b0b962f 100644
--- a/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestCommitStatisticsNoCache.scala
+++ b/src/test/scala/com/googlesource/gerrit/plugins/analytics/test/TestCommitStatisticsNoCache.scala
@@ -14,6 +14,7 @@
package com.googlesource.gerrit.plugins.analytics.test
+import com.google.inject.Inject
import com.googlesource.gerrit.plugins.analytics.common._
import org.eclipse.jgit.lib.ObjectId
@@ -23,7 +24,7 @@
lazy val commitsStatisticsNoCache = CommitsStatisticsNoCache(daemonTest.getInstance(classOf[CommitsStatisticsLoader]))
}
-case class CommitsStatisticsNoCache(commitsStatisticsLoader: CommitsStatisticsLoader) extends CommitsStatisticsCache {
+case class CommitsStatisticsNoCache @Inject() (commitsStatisticsLoader: CommitsStatisticsLoader) extends CommitsStatisticsCache {
override def get(project: String, objectId: ObjectId): CommitsStatistics =
commitsStatisticsLoader.load(CommitsStatisticsCacheKey(project, objectId))
}