]> source.dussan.org Git - archiva.git/commitdiff
[MRM-1583] ui rewrite: rewrite reports page
authorOlivier Lamy <olamy@apache.org>
Thu, 5 Jul 2012 20:43:37 +0000 (20:43 +0000)
committerOlivier Lamy <olamy@apache.org>
Thu, 5 Jul 2012 20:43:37 +0000 (20:43 +0000)
Submitted by Adrien Lecharpentier.

git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@1357889 13f79535-47bb-0310-9956-ffa450edef68

archiva-modules/archiva-web/archiva-rest/archiva-rest-api/pom.xml
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml
archiva-modules/archiva-web/archiva-web-common/src/main/resources/org/apache/archiva/i18n/default.properties
archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/archiva/general-admin.js
archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/templates/archiva/general-admin.html
archiva-modules/archiva-web/archiva-webapp-js/src/main/webapp/js/templates/archiva/menu.html
archiva-modules/plugins/repository-statistics/src/main/java/org/apache/archiva/metadata/repository/stats/DefaultRepositoryStatisticsManager.java
archiva-modules/plugins/repository-statistics/src/main/java/org/apache/archiva/metadata/repository/stats/RepositoryStatistics.java

index 0277de251b19fbc641c7731ddb7b1f1751456a92..1dcae612cf92d845b21dc7b152d464d109e1a31a 100644 (file)
       <artifactId>archiva-repository-scanner</artifactId>
     </dependency>
 
+    <dependency>
+      <groupId>org.apache.archiva</groupId>
+      <artifactId>repository-statistics</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>org.apache.archiva.redback</groupId>
       <artifactId>redback-authorization-api</artifactId>
index a497537a15d0620ba209559159c81ab5292f82a7..b9900104880d9972f9830e03cfd1665b7d7b25ec 100644 (file)
@@ -69,6 +69,7 @@
       <ref bean="commonServices#rest"/>
       <ref bean="browseService#rest"/>
       <ref bean="systemStatusService#rest"/>
+      <ref bean="reportRepositoriesService#rest" />
     </jaxrs:serviceBeans>
 
     <jaxrs:outInterceptors>
index 9e44174456d072fb24cedece69072bec7da550e7..7ba34a498c5e51f1b60689d45e4e10de888c673e 100644 (file)
@@ -141,6 +141,7 @@ menu.repository-scanning=Repository Scanning
 menu.system-status=System Status
 menu.appearance-configuration=Appearance
 menu.ui-configuration=UI Configuration
+menu.reports=Reports
 
 #user
 user.change.password.required=Change password required
@@ -416,4 +417,32 @@ fileupload.save=Save Files
 fileupload.upload.required=You must upload your files first.
 fileupload.artifacts.saved=Artifacts uploaded and saved on Server side.
 
-
+#reports
+report.title = Reports
+report.statistics.title = Repository statistics
+report.statistics.selected-repositories.label = Selected repositories
+repository.groups.available-repositories.label = Available repositories
+report.statistics.row-count.label = Row count
+report.statistics.start-date.label = Start date
+report.statistics.end-date.label = End date
+report.statistics.btn-view = View Statistics
+report.statistics.endDate.explanations-title=Info
+report.statistics.rowCount.explanations-title=Info
+report.statistics.rowCount.explanations=set the number of report you want to read. If you have selected two or more \
+  repositories, only the latest report of those repositories will be displayed.
+report.statistics.repositories.required=You must select at least one repository
+report.health.title = Repository Health
+report.select.all-repositories = All repositories
+report.health.groupId.label = GroupId
+report.health.repositoryId.label = RepositoryId
+report.health.btn-view = Show Report
+report.repository.illegal-access = You have no access to the repository {0}
+report.message.no-report=There is no report available.
+report.health.result.id=ID
+report.health.result.namespace=Namespace
+report.health.result.project=Project
+report.health.result.version=Version
+report.health.result.name=Name
+report.health.result.problem=Problem
+report.health.result.message=Message
+report.result.title=Result
index 055f7195b01ccaa7934e9efdd1ba764ae28b2489..40b2fe63e2d4e0618301e826514912026e1ed5de 100644 (file)
@@ -17,7 +17,7 @@
  * under the License.
  */
 define("archiva.general-admin",["jquery","i18n","order!utils","order!jquery.tmpl","order!knockout","order!knockout.simpleGrid",
-  "jquery.validate","bootstrap"]
+  "knockout.sortable","jquery.validate","bootstrap"]
     , function() {
 
   //-------------------------
@@ -875,4 +875,245 @@ define("archiva.general-admin",["jquery","i18n","order!utils","order!jquery.tmpl
     });
   }
 
+  //---------------------------
+  // report configuration page
+  //---------------------------
+  StatisticsReportRequest=function() {
+    this.repositories = ko.observableArray( [] );
+    this.rowCount = ko.observable(100);
+    this.startDate = ko.observable();
+    this.endDate = ko.observable();
+  }
+
+  reportStatisticsFormValidator=function(){
+    var validate = $("#report-statistics-form-id").validate({
+      rules: {
+        rowCountStatistics: {
+          required:true,
+          number: true,
+          min: 10
+        },
+        startDate: {
+          date: true
+        },
+        endDate: {
+          date: true
+        }
+      },
+      showErrors: function(validator, errorMap, errorList) {
+        customShowError("#report-statistics-form-id", validator, errorMap, errorMap);
+      }
+    })
+  }
+  ReportStatisticsViewModel=function(repositoriesAvailable){
+    reportStatisticsFormValidator();
+
+    var self=this;
+    this.availableRepositories = ko.observableArray( repositoriesAvailable );
+    this.statisticsReport = ko.observable( new StatisticsReportRequest() );
+
+    $("#startDate" ).datepicker();
+    $("#endDate" ).datepicker();
+    $("#rowCount-info-button" ).popover();
+
+    this.showStatistics=function() {
+      if (!$("#report-statistics-form-id").valid()) {
+        return;
+      }
+      if(this.statisticsReport().repositories().length==0){
+        displayErrorMessage( $.i18n.prop('report.statistics.repositories.required'), "repositoriesErrorMessage" );
+        return;
+      }
+      clearUserMessages( "repositoriesErrorMessage" );
+      var resultTabContent = $("#report-result");
+
+      url = "restServices/archivaServices/reportServices/getStatisticsReport/?rowCount="
+        + this.statisticsReport().rowCount();
+
+      for(var i=0;i<this.statisticsReport().repositories().length;i++){
+        url += "&repository=" + this.statisticsReport().repositories()[i];
+      }
+
+      if(this.statisticsReport().startDate()!=null){
+        url += "&startDate=" + this.statisticsReport().startDate();
+      }
+      if(this.statisticsReport().endDate()!=null){
+        url += "&endDate=" + this.statisticsReport().endDate();
+      }
+
+      $.ajax(url, {
+        type: "GET",
+        contentType: 'application/json',
+        dataType: 'json',
+        success: function(data){
+          resultTabContent.html( $( "#report-statistics" ).tmpl() );
+          var reportStatistics = new ReportStatisticsResultViewModel( data );
+          ko.applyBindings( reportStatistics, resultTabContent.get( 0 ) );
+          $( "#report-result-tab-li" ).removeClass( "hide" );
+          $( "#report-result-tab-li" ).addClass( "active" );
+          $( "#report-stat-tab-li" ).removeClass( "active" );
+          $( "#report-stat-tab-content" ).removeClass( "active" );
+          resultTabContent.addClass( "active" );
+        },
+        error: function(data){
+          var res = $.parseJSON(data.responseText);
+          displayErrorMessage($.i18n.prop(res.errorMessage));
+        }
+      });
+    }
+  }
+  ReportStatisticsResultViewModel=function(report){
+    this.reports = ko.observableArray( report );
+    var self = this;
+
+    this.tableReportViewModel = new ko.simpleGrid.viewModel({
+      data: this.reports,
+      viewModel: this,
+      columns: [
+        { headerText: "Repository ID", rowText: "repositoryId" },
+        { headerText: "Start Date", rowText: function(item){return new Date(item.scanStartTime);}},
+        { headerText: "Total File Count", rowText: "totalFileCount" },
+        { headerText: "Total Size", rowText: "totalArtifactFileSize" },
+        { headerText: "Artifact Count", rowText: "totalArtifactCount" },
+        { headerText: "Group Count", rowText: "totalGroupCount" },
+        { headerText: "Project Count", rowText: "totalProjectCount" },
+        { headerText: "Archetypes", rowText: function (item) { return item.totalCountForType.pom === "" ? item.totalCountForType.pom : "0"} },
+        { headerText: "Jars", rowText: function (item) { return item.totalCountForType.jar === "" ? item.totalCountForType.jar : "0" } },
+        { headerText: "Wars", rowText: function (item) { return item.totalCountForType.war === "" ? item.totalCountForType.war : "0" } },
+        { headerText: "Ears", rowText: function (item) { return item.totalCountForType.ear === "" ? item.totalCountForType.ear : "0" } },
+        { headerText: "Exes", rowText: function (item) { return item.totalCountForType.exe === "" ? item.totalCountForType.exe : "0" } },
+        { headerText: "Dlls", rowText: function (item) { return item.totalCountForType.dll === "" ? item.totalCountForType.dll : "0" } },
+        { headerText: "Zips", rowText: function (item) { return item.totalCountForType.zip === "" ? item.totalCountForType.zip : "0" } }
+      ],
+      pageSize: 10
+    });
+  }
+
+  HealthReportRequest=function(){
+    this.repositoryId = ko.observable();
+    this.rowCount = ko.observable(100);
+    this.groupId = ko.observable();
+  }
+  HealthReportResult=function(repositoryId,namespace,project,version,id,message,problem,name,facetId){
+    this.repositoryId = repositoryId;
+    this.namespace = namespace;
+    this.project = project;
+    this.version = version;
+    this.id = id;
+    this.message = message;
+    this.problem = problem;
+    this.name = name;
+    this.facetId = facetId;
+  }
+  mapHealthReportResult=function(data){
+    if(data==null) return;
+    return new HealthReportResult( data.repositoryId, data.namespace, data.project, data.version, data.id, data.message,
+                                   data.problem, data.name, data.facetId );
+  }
+  mapHealthReportResults=function(data){
+    if (data != null)
+    {
+      return $.isArray(data)? $.map(data, function(item){
+        return mapHealthReportResult(item);
+      }):[mapHealthReportResult(data)];
+    }
+    return [];
+  }
+  ReportHealthResultViewModel=function(report){
+    this.reports = ko.observableArray( report );
+    var self = this;
+    this.tableReportViewModel = new ko.simpleGrid.viewModel({
+      data: this.reports,
+      viewModel: this,
+      columns: [
+        { headerText: "ID", rowText: "id" },
+        { headerText: "Namespace", rowText: "namespace" },
+        { headerText: "Project", rowText: "project" },
+        { headerText: "Version", rowText: "version" },
+        { headerText: "Name", rowText: "name" },
+        { headerText: "Problem", rowText: "problem" },
+        { headerText: "Message", rowText: "message" }
+        ],
+      pageSize: 10
+    });
+  }
+
+  reportHealthFormValidator=function(){
+    var validate = $("#main-content #report-health-form-id").validate({
+      rules: {
+        rowCountHealth: {
+          required: true,
+          number: true,
+          min: 10
+        },
+        repositoryId: {
+          required: true
+        }
+      },
+      showErrors: function(validator, errorMap, errorList) {
+        customShowError("#main-content #report-health-form-id", validator, errorMap, errorMap);
+      }
+    })
+  }
+  ReportHealthViewModel=function(){
+    reportHealthFormValidator();
+    this.healthReport = ko.observable(new HealthReportRequest());
+
+    this.showHealth=function() {
+      if (!$("#main-content #report-health-form-id").valid()) {
+        return;
+      }
+
+      var resultTabContent = $("#report-result");
+
+      var url =
+        "restServices/archivaServices/reportServices/getHealthReports/" + this.healthReport().repositoryId() + "/"
+          + this.healthReport().rowCount();
+
+      if (this.healthReport().groupId())
+      {
+        url += "?groupId=" + this.healthReport().groupId();
+      }
+
+      $.ajax(url, {
+        type: "GET",
+        contentType: 'application/json',
+        dataType: 'json',
+        success: function(data){
+          var reports = new ReportHealthResultViewModel( mapHealthReportResults( data ) );
+          resultTabContent.html( $( "#report-health" ).tmpl() );
+          ko.applyBindings( reports, resultTabContent.get( 0 ) );
+          $( "#report-result-tab-li" ).removeClass( "hide" );
+          $( "#report-result-tab-li" ).addClass( "active" );
+          $( "#report-health-tab-li" ).removeClass( "active" );
+          $( "#report-health-tab-content" ).removeClass( "active" );
+          resultTabContent.addClass( "active" );
+        },
+        error: function(data){
+            var res = $.parseJSON(data.responseText);
+            displayRestError(res);
+          }
+      });
+    }
+  }
+
+  displayReportsPage=function(){
+    screenChange();
+    clearUserMessages();
+    var mainContent = $("#main-content");
+    mainContent.html(mediumSpinnerImg());
+    $.ajax("restServices/archivaServices/searchService/observableRepoIds", {
+      type: "GET",
+      dataType: 'json',
+      success: function(data) {
+        var repos = mapStringList( data );
+        mainContent.html( $( "#report-base" ).tmpl( {repositoriesList:repos} ) );
+        var statisticsReportViewModel = ReportStatisticsViewModel( repos );
+        var healthReportViewModel = ReportHealthViewModel( );
+        ko.applyBindings( statisticsReportViewModel, mainContent.get( 0 ) );
+        ko.applyBindings( healthReportViewModel, mainContent.get( 0 ) );
+      }
+    })
+  }
+
 });
\ No newline at end of file
index 6a2d17aa7705803dd9365b3abf9ec6350a836850..23d6ae01ef3187a63838595eb446aa94f510d39e 100644 (file)
     <p>
         ${$.i18n.prop('apperance-configuration.details-description')}
     </p>
-    
+
     <form id="appearance-configuration-form-id" class="well form-horizontal">
         <fieldset id="appearance-configuration-fielset-id">
             <div class="control-group">
     </tr>
 {% } %}
 </script>
+
+<script id="report-base" type="text/html">
+  <div class="page-header">
+    <h2>${$.i18n.prop('report.title')}</h2>
+  </div>
+
+    <ul class="nav nav-tabs">
+      <li class="active" id="report-stat-tab-li"><a href="#report-stat-tab-content" data-toggle="tab">${$.i18n.prop('report.statistics.title')}</a></li>
+      <li id="report-health-tab-li"><a href="#report-health-tab-content" data-toggle="tab">${$.i18n.prop('report.health.title')}</a></li>
+      <li id="report-result-tab-li" class="hide"><a href="#report-result" data-toggle="tab">${$.i18n.prop('report.result.title')}</a></li>
+    </ul>
+
+    <div class="tab-content">
+      <div class="tab-pane active" id="report-stat-tab-content">
+        <form class="form-horizontal" id="report-statistics-form-id">
+          <fieldset id="form-statistics-report">
+            <div class="row-fluid">
+              <div class="span6 row-fluid">
+                <div class="row-fluid" id="repositoriesErrorMessage"></div>
+                <div class="row-fluid">
+                  <div class="span6 dotted">
+                    <h5>${$.i18n.prop('report.statistics.selected-repositories.label')}</h5>
+                    <hr/>
+                    <div style="min-height: 40px"
+                         data-bind="sortable: { template: 'statistics-repositories-order-tmpl', data:statisticsReport().repositories}">
+                    </div>
+                  </div>
+                  <div class="span6 dotted">
+                    <h5>${$.i18n.prop('repository.groups.available-repositories.label')}</h5>
+                    <hr/>
+                    <div style="min-height: 40px"
+                         data-bind="sortable: {template: 'statistics-repositories-order-tmpl',data:availableRepositories}">
+                    </div>
+                </div>
+                </div>
+              </div>
+              <div class="span6 well">
+                  <div class="control-group">
+                      <label for="rowCountStatistics" class="control-label">
+                          ${$.i18n.prop('report.statistics.row-count.label')}
+                      </label>
+                      <div class="controls">
+                          <input type="text" id="rowCountStatistics" name="rowCountStatistics" class="input-small"
+                                 data-bind="value: statisticsReport().rowCount"/>
+
+                          <button class="btn btn-warning btn-mini" id="rowCount-info-button"
+                                  data-original-title="${$.i18n.prop('report.statistics.rowCount.explanations-title')}"
+                                  data-content="${$.i18n.prop('report.statistics.rowCount.explanations')}">
+                              <i class="icon-question-sign icon-white"></i>
+                          </button>
+                      </div>
+                  </div>
+                  <div class="control-group">
+                      <label for="startDate" class="control-label">
+                          ${$.i18n.prop('report.statistics.start-date.label')}
+                      </label>
+                      <div class="controls">
+                          <input type="text" id="startDate" name="startDate" class="input-small"
+                                 data-bind="value: statisticsReport().startDate"/>
+                      </div>
+                  </div>
+                  <div class="control-group">
+                      <label for="endDate" class="control-label">
+                          ${$.i18n.prop('report.statistics.end-date.label')}
+                      </label>
+                      <div class="controls">
+                          <input type="text" id="endDate" name="endDate" class="input-small"
+                                 data-bind="value: statisticsReport().endDate"/>
+                      </div>
+                  </div>
+              </div>
+            </div>
+              <div class="form-actions">
+                  <button class="btn btn-primary" data-bind="click: showStatistics">
+                      ${$.i18n.prop('report.statistics.btn-view')}
+                  </button>
+              </div>
+          </fieldset>
+        </form>
+      </div>
+
+      <div class="tab-pane" id="report-health-tab-content">
+        <form class="form-horizontal" id="report-health-form-id">
+          <fieldset id="form-health-report">
+            <div class="control-group">
+              <label for="rowCountHealth" class="control-label">
+                ${$.i18n.prop('report.statistics.row-count.label')}
+              </label>
+              <div class="controls">
+                <input type="text" id="rowCountHealth" name="rowCountHealth" class="input-small required"
+                        data-bind="value: healthReport().rowCount"/>
+              </div>
+            </div>
+            <div class="control-group">
+              <label for="groupId" class="control-label">
+                ${$.i18n.prop('report.health.groupId.label')}
+              </label>
+              <div class="controls">
+                <input type="text" id="groupId" name="groupId" data-bind="value: healthReport().groupId"/>
+              </div>
+            </div>
+            <div class="control-group">
+              <label for="repositoryId" class="control-label">
+                ${$.i18n.prop('report.health.repositoryId.label')}
+              </label>
+              <div class="controls">
+                <select id="repositoryId" name="repositoryId" data-bind="value: healthReport().repositoryId"
+                        class="required">
+                    <option value="all">${$.i18n.prop('report.select.all-repositories')}</option>
+                    {{each(i, repoId) repositoriesList}}
+                      <option value="${repoId}">${repoId}</option>
+                    {{/each}}
+                </select>
+              </div>
+            </div>
+            <div class="form-actions">
+              <a href="#" class="btn btn-primary" data-bind="click: showHealth">
+                ${$.i18n.prop('report.health.btn-view')}
+              </a>
+            </div>
+          </fieldset>
+        </form>
+      </div>
+
+      <div class="tab-pane" id="report-result">
+    </div>
+</script>
+
+<script id="statistics-repositories-order-tmpl" type="text/html">
+    <div class="well draggable-item">
+        ${$data}
+    </div>
+</script>
+
+<script id="report-health" type="text/html">
+    <div class="page-header">
+        <h3>${$.i18n.prop('report.health.title')}</h3>
+    </div>
+    <table class="table table-bordered table-striped"
+           data-bind="simpleGrid: tableReportViewModel,simpleGridTemplate:'table-report-tmpl',pageLinksId:'reportHealthPageLinkId'">
+    </table>
+    <div id="reportHealthPageLinkId"></div>
+</script>
+
+<script id="report-statistics" type="text/html">
+    <div class="page-header">
+        <h3>${$.i18n.prop('report.statistics.title')}</h3>
+    </div>
+    <table class="table table-bordered table-striped"
+           data-bind="simpleGrid: tableReportViewModel,simpleGridTemplate:'table-report-tmpl',pageLinksId:'reportStatisticsPageLinkId'">
+    </table>
+    <div id="reportStatisticsPageLinkId"></div>
+</script>
+<script id="table-report-tmpl" type="text/html">
+    <thead>
+    {{each(i, columnDefinition) columns}}
+      <th>${ columnDefinition.headerText }</th>
+    {{/each}}
+    </thead>
+    <tbody>
+    {{each(i, row) itemsOnCurrentPage()}}
+      <tr>
+        {{each(i, columnDefinition) columns}}
+          {{var val = (typeof columnDefinition.rowText == 'function' ? columnDefinition.rowText(row) : row[columnDefinition.rowText])}}
+          <td>
+              ${val}
+          </td>
+        {{/each}}
+      </tr>
+    {{/each}}
+    </tbody>
+</script>
index 527887795041be1506907ac128e8c48fea1749d5..dcd6c7f709c2ebf1aaed665050908f13a2f4c94e 100644 (file)
       <li style="display:none" redback-permissions="{permissions: ['archiva-manage-configuration']}">
         <a href="#" id="menu-ui-configuration-list-a" onclick="displayUiConfiguration()">${$.i18n.prop('menu.ui-configuration')}</a>
       </li>
+      <li style="display:none" redback-permissions="{permissions: ['archiva-manage-configuration']}">
+        <a href="#" id="menu-report-list-a" onclick="displayReportsPage()">${$.i18n.prop('menu.reports')}</a>
+      </li>
+
 
     </ul>
 
index 4bad96e5f9533b6588c2da06fc7c47a2ee774502..d363543ad86d5ccbf629dbb781ec48d9a9d7236e 100644 (file)
@@ -123,6 +123,7 @@ public class DefaultRepositoryStatisticsManager
         throws MetadataRepositoryException
     {
         RepositoryStatistics repositoryStatistics = new RepositoryStatistics();
+        repositoryStatistics.setRepositoryId( repositoryId );
         repositoryStatistics.setScanStartTime( startTime );
         repositoryStatistics.setScanEndTime( endTime );
         repositoryStatistics.setTotalFileCount( totalFiles );
index 8e0863478f7f75298d8b068c4e2566fefdf6f8a2..f32c6992f75992d134dda209303fa6d863c87fae 100644 (file)
@@ -54,6 +54,8 @@ public class RepositoryStatistics
 
     private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );
 
+    private String repositoryId;
+
     public Date getScanEndTime()
     {
         return scanEndTime;
@@ -139,6 +141,16 @@ public class RepositoryStatistics
         return scanEndTime.getTime() - scanStartTime.getTime();
     }
 
+    public String getRepositoryId()
+    {
+        return repositoryId;
+    }
+
+    public void setRepositoryId( String repositoryId )
+    {
+        this.repositoryId = repositoryId;
+    }
+
     public String getFacetId()
     {
         return FACET_ID;
@@ -167,6 +179,7 @@ public class RepositoryStatistics
         properties.put( "totalGroupCount", String.valueOf( totalGroupCount ) );
         properties.put( "totalProjectCount", String.valueOf( totalProjectCount ) );
         properties.put( "newFileCount", String.valueOf( newFileCount ) );
+        properties.put( "repositoryId", repositoryId );
         for ( Map.Entry<String, Long> entry : totalCountForType.entrySet() )
         {
             properties.put( "count-" + entry.getKey(), String.valueOf( entry.getValue() ) );
@@ -184,6 +197,7 @@ public class RepositoryStatistics
         totalGroupCount = Long.parseLong( properties.get( "totalGroupCount" ) );
         totalProjectCount = Long.parseLong( properties.get( "totalProjectCount" ) );
         newFileCount = Long.parseLong( properties.get( "newFileCount" ) );
+        repositoryId = properties.get( "repositoryId" );
         totalCountForType.clear();
         for ( Map.Entry<String, String> entry : properties.entrySet() )
         {
@@ -244,6 +258,10 @@ public class RepositoryStatistics
         {
             return false;
         }
+        if ( !repositoryId.equals( that.repositoryId ) )
+        {
+            return false;
+        }
 
         return true;
     }
@@ -260,6 +278,7 @@ public class RepositoryStatistics
         result = 31 * result + (int) ( totalProjectCount ^ ( totalProjectCount >>> 32 ) );
         result = 31 * result + (int) ( newFileCount ^ ( newFileCount >>> 32 ) );
         result = 31 * result + totalCountForType.hashCode();
+        result = 31 * result + repositoryId.hashCode();
         return result;
     }
 
@@ -269,7 +288,8 @@ public class RepositoryStatistics
         return "RepositoryStatistics{" + "scanEndTime=" + scanEndTime + ", scanStartTime=" + scanStartTime +
             ", totalArtifactCount=" + totalArtifactCount + ", totalArtifactFileSize=" + totalArtifactFileSize +
             ", totalFileCount=" + totalFileCount + ", totalGroupCount=" + totalGroupCount + ", totalProjectCount=" +
-            totalProjectCount + ", newFileCount=" + newFileCount + ", totalCountForType=" + totalCountForType + '}';
+            totalProjectCount + ", newFileCount=" + newFileCount + ", totalCountForType=" + totalCountForType + ", " +
+            "repositoryId=" + repositoryId + '}';
     }
 
     public Map<String, Long> getTotalCountForType()
@@ -284,7 +304,7 @@ public class RepositoryStatistics
 
     public void setTotalCountForType( String type, long count )
     {
-        totalCountForType.put( type, count );
+        totalCountForType.put( type.replaceAll( "-", "_" ).replaceAll( "\\.", "_" ), count );
     }
     
     private static final class ZeroForNullHashMap<K, V extends Long> extends HashMap<K, V>