Joal has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/384590 )

Change subject: Upgrade restbase-modules to latest
......................................................................


Upgrade restbase-modules to latest

It makes month we've not done that, we are many versions
behind for core modules hyperswtich, service-runner, and
restbase-mod-table-[cassandra|sqlite].
This patch also adds linting and corrects code accordingly.

Bug: T178312

Change-Id: Ie7847c1de76cb7e6cc868a1d3b94af68785f86bc
---
A .eslintrc.yml
M .jscs.json
M .jshintrc
M config.example.wikimedia.yaml
M config.test.yaml
M lib/aqsUtil.js
M lib/druidUtil.js
M package.json
M projects/aqs_default.yaml
M sys/legacy/pagecounts.js
M sys/mediawiki-history-metrics.js
M sys/pageviews.js
A sys/table.js
M sys/unique-devices.js
M test/aqs_test_module.yaml
M test/features/mediawiki-history-metrics/mediawiki-history-metrics.js
M test/features/pageviews/pageviews.js
M test/index.js
M test/utils/run_tests.sh
M test/utils/server.js
20 files changed, 546 insertions(+), 539 deletions(-)

Approvals:
  Ppchelko: Looks good to me, but someone else must approve
  Joal: Verified; Looks good to me, approved



diff --git a/.eslintrc.yml b/.eslintrc.yml
new file mode 100644
index 0000000..9e2c225
--- /dev/null
+++ b/.eslintrc.yml
@@ -0,0 +1 @@
+extends: 'eslint-config-node-services'
\ No newline at end of file
diff --git a/.jscs.json b/.jscs.json
index bf87d75..8a4d757 100644
--- a/.jscs.json
+++ b/.jscs.json
@@ -10,7 +10,9 @@
     "catch"
   ],
   "validateIndentation": 4,
-  "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
+  "requireCamelCaseOrUpperCaseIdentifiers": {
+    "ignoreProperties": true
+  },
   "requireCapitalizedComments": null,
   "maximumLineLength": 100,
   "validateQuoteMarks": null,
@@ -23,6 +25,7 @@
     "node_modules/**",
     "test/**",
     "coverage/**",
-    "test.db.**"
+    "test.db.**",
+    "maintenance/**"
   ]
-}
\ No newline at end of file
+}
diff --git a/.jshintrc b/.jshintrc
index 5288038..8acdd9d 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,38 +1,37 @@
 {
-       "predef": [
-               "ve",
+  "predef": [
+    "ve",
 
-               "setImmediate",
+    "setImmediate",
 
-               "QUnit",
+    "QUnit",
 
-               "Map",
-               "Set"
-       ],
+    "Map",
+    "Set"
+  ],
 
-       "bitwise": true,
-       "laxbreak": true,
-       "curly": true,
-       "eqeqeq": true,
-       "immed": true,
-       "latedef": "nofunc",
-       "maxlen":false,
-       "newcap": true,
-       "noarg": true,
-       "noempty": true,
-       "nonew": true,
-       "regexp": false,
-       "undef": true,
-       "strict": true,
-       "trailing": true,
+  "bitwise": true,
+  "laxbreak": true,
+  "curly": true,
+  "eqeqeq": true,
+  "immed": true,
+  "latedef": "nofunc",
+  "newcap": true,
+  "noarg": true,
+  "noempty": true,
+  "nonew": true,
+  "regexp": false,
+  "undef": true,
+  "strict": true,
+  "trailing": true,
 
-       "smarttabs": true,
-       "multistr": true,
+  "smarttabs": true,
+  "multistr": true,
 
-       "node": true,
+  "node": true,
 
-       "nomen": false,
-       "loopfunc": true,
-       "esnext": true
-       //"onevar": true
+  "nomen": false,
+  "loopfunc": true,
+        "esnext": true
+  //"onevar": true
 }
diff --git a/config.example.wikimedia.yaml b/config.example.wikimedia.yaml
index b61aad9..39f7d33 100644
--- a/config.example.wikimedia.yaml
+++ b/config.example.wikimedia.yaml
@@ -1,23 +1,23 @@
 # Analytics Query Service config
 aqs_project: &aqs_project
   x-modules:
-    /:
-      - path: projects/aqs_default.yaml
-        options: &default_options
-          table:
-            hosts: [localhost]
-            keyspace: system
-            username: cassandra
-            password: cassandra
-            defaultConsistency: one # or 'localQuorum' for production
-            storage_groups:
-              - name: default.group.local
-                domains: /./
-          druid:
-            scheme: http
-            host: druid1004.eqiad.wmnet
-            port: 8082
-            query_path: /druid/v2/
+    - path: projects/aqs_default.yaml
+      options: &default_options
+        table:
+          backend: cassandra
+          hosts: [localhost]
+          keyspace: system
+          username: cassandra
+          password: cassandra
+          defaultConsistency: one # or 'localQuorum' for production
+          storage_groups:
+            - name: default.group.local
+              domains: /./
+        druid:
+          scheme: http
+          host: druid1004.eqiad.wmnet
+          port: 8082
+          query_path: /druid/v2/
 
 # Swagger spec root.
 spec: &spec_root
diff --git a/config.test.yaml b/config.test.yaml
index 965ddc0..dc49ed6 100644
--- a/config.test.yaml
+++ b/config.test.yaml
@@ -1,23 +1,23 @@
 # A synthetic domain to test aqs modules data
 aqs.wikimedia.org: &analytics.wikimedia.org
   x-modules:
-    /:
-      - path: test/aqs_test_module.yaml
-      - path: projects/aqs_default.yaml
-        options: &default_options
-          table:
-            hosts: [localhost]
-            keyspace: system
-            username: cassandra
-            password: cassandra
-            defaultConsistency: one # or 'localQuorum' for production
-            storage_groups:
-              - name: test.group.local
-                domains: /./
-            dbname: test.db.sqlite3 # ignored in cassandra, but useful in 
SQLite testing
-          druid:
-            # Use a fake internal endpoint in restbase to test
-            query_path: /analytics.wikimedia.org/sys/fake-druid/druid/v2
+    - path: test/aqs_test_module.yaml
+    - path: projects/aqs_default.yaml
+      options: &default_options
+        table:
+          backend: cassandra
+          hosts: [ localhost ]
+          keyspace: system
+          username: cassandra
+          password: cassandra
+          defaultConsistency: one # or 'localQuorum' for production
+          storage_groups:
+            - name: test.group.local
+              domains: /./
+          dbname: test.db.sqlite3 # ignored in cassandra, but useful in SQLite 
testing
+        druid:
+          # Use a fake internal endpoint in restbase to test
+          query_path: /analytics.wikimedia.org/sys/fake-druid/druid/v2
 
 spec_root: &spec_root
   title: "The Analytics Query Service root"
diff --git a/lib/aqsUtil.js b/lib/aqsUtil.js
index b07e369..4eac56b 100644
--- a/lib/aqsUtil.js
+++ b/lib/aqsUtil.js
@@ -1,10 +1,9 @@
 'use strict';
 
-var HyperSwitch = require('hyperswitch');
-var HTTPError = HyperSwitch.HTTPError;
-var druidUtil = require('./druidUtil');
+const HyperSwitch = require('hyperswitch');
+const HTTPError = HyperSwitch.HTTPError;
 
-var aqsUtil = {};
+const aqsUtil = {};
 
 aqsUtil.notFoundCatcher = function(e) {
 
@@ -39,7 +38,7 @@
  * Parameter validators
  * Only needed internally, not exposed
  */
-var throwIfNeeded = function(errors) {
+function throwIfNeeded(errors) {
     if (errors && errors.length) {
         throw new HTTPError({
             status: 400,
@@ -49,7 +48,7 @@
             }
         });
     }
-};
+}
 
 /**
  * Normalizes the project parameter to "en.wikipedia"
@@ -78,19 +77,18 @@
     }
 
     try {
-        var year = timestamp.substring(0, 4);
-        var month = timestamp.substring(4, 6);
-        var day = timestamp.substring(6, 8);
-        var hour = opts.fakeHour ? '00' : timestamp.substring(8, 10);
+        const year = timestamp.substring(0, 4);
+        const month = timestamp.substring(4, 6);
+        const day = timestamp.substring(6, 8);
+        const hour = opts.fakeHour ? '00' : timestamp.substring(8, 10);
 
-        var dt = new Date([year, month, day].join('-') + ' '
-            + hour + ':00:00 UTC');
+        const dt =  new Date(`${year}-${month}-${day} ${hour}:00:00 UTC`);
 
         return dt.toString() !== 'Invalid Date'
             && dt.getUTCFullYear() === parseInt(year, 10)
             && dt.getUTCMonth() === (parseInt(month, 10) - 1)
             && dt.getUTCDate() === parseInt(day, 10)
-            && dt.getUTCHours() === parseInt(hour);
+            && dt.getUTCHours() === parseInt(hour, 10);
 
     } catch (e) {
         return false;
@@ -115,18 +113,17 @@
 aqsUtil.validateStartAndEnd = function(rp, opts) {
     opts = opts || {};
 
-    var errors = [];
-    var invalidMessage = opts.fakeHour ?
-        'invalid, must be a valid date in YYYYMMDD format' :
-        'invalid, must be a valid timestamp in YYYYMMDDHH format';
+    const errors = [];
+    const messageFormat = opts.fakeHour ? 'YYYYMMDD' : 'YYYYMMDDHH';
+    const invalidMessage = `invalid, must be a valid date in ${messageFormat} 
format`;
 
     aqsUtil.normalizeProject(rp);
 
     if (!aqsUtil.validateTimestamp(rp.start, opts)) {
-        errors.push('start timestamp is ' + invalidMessage);
+        errors.push(`start timestamp is ${invalidMessage}`);
     }
     if (!aqsUtil.validateTimestamp(rp.end, opts)) {
-        errors.push('end timestamp is ' + invalidMessage);
+        errors.push(`end timestamp is ${invalidMessage}`);
     }
 
     if (rp.start > rp.end) {
@@ -173,16 +170,13 @@
 };
 
 aqsUtil.validateYearMonthDay = function(rp) {
-    var errors = [];
+    const errors = [];
 
     aqsUtil.normalizeProject(rp);
 
     // fake a timestamp in the YYYYMMDDHH format so we can reuse the validator
-    var validDate = aqsUtil.validateTimestamp(
-        rp.year + rp.month +
-        ((rp.day === 'all-days') ? '01' : rp.day) +
-        '00'
-    );
+    const day = ((rp.day === 'all-days') ? '01' : rp.day);
+    const validDate = 
aqsUtil.validateTimestamp(`${rp.year}${rp.month}${day}00`);
 
     if (!validDate) {
         errors.push('Given year/month/day is invalid date');
@@ -192,20 +186,21 @@
 };
 
 aqsUtil.convertTimestampToDate = function(timestamp) {
-    var year = parseInt(timestamp.substring(0, 4), 10);
-    var month = parseInt(timestamp.substring(4, 6), 10) - 1;
-    var day = parseInt(timestamp.substring(6, 8), 10);
+    const year = parseInt(timestamp.substring(0, 4), 10);
+    const month = parseInt(timestamp.substring(4, 6), 10) - 1;
+    const day = parseInt(timestamp.substring(6, 8), 10);
     return new Date(Date.UTC(year, month, day));
 };
 
 aqsUtil.convertDateToTimestamp = function(date) {
-    return date.getUTCFullYear().toString() +
-            ('0' + (date.getUTCMonth() + 1)).slice(-2) +
-            ('0' + date.getUTCDate()).slice(-2);
+    const year = date.getUTCFullYear().toString();
+    const month = (`0${(date.getUTCMonth() + 1)}`).slice(-2);
+    const day = (`0${date.getUTCDate()}`).slice(-2);
+    return `${year}${month}${day}`;
 };
 
 aqsUtil.getFirstFullMonthFirstDay = function(startDate) {
-    var dt = aqsUtil.convertTimestampToDate(startDate);
+    const dt = aqsUtil.convertTimestampToDate(startDate);
 
     if (dt.getUTCDate() === 1) {
         return startDate;
@@ -218,8 +213,8 @@
 };
 
 aqsUtil.getLastFullMonthLastDay = function(endDate) {
-    var dt = aqsUtil.convertTimestampToDate(endDate);
-    var lastDayOfCurrentMonth = new Date(Date.UTC(dt.getUTCFullYear(), 
dt.getUTCMonth() + 1, 0));
+    const dt = aqsUtil.convertTimestampToDate(endDate);
+    const lastDayOfCurrentMonth = new Date(Date.UTC(dt.getUTCFullYear(), 
dt.getUTCMonth() + 1, 0));
 
     if (dt.getUTCDate() === lastDayOfCurrentMonth.getUTCDate()) {
         return endDate;
@@ -231,7 +226,7 @@
 };
 
 aqsUtil.getDayAfterLastFullMonth = function(endDate) {
-    var dt = aqsUtil.convertTimestampToDate(endDate);
+    const dt = aqsUtil.convertTimestampToDate(endDate);
     dt.setUTCDate(1);
     return aqsUtil.convertDateToTimestamp(dt);
 };
diff --git a/lib/druidUtil.js b/lib/druidUtil.js
index cb5579d..fd5a492 100644
--- a/lib/druidUtil.js
+++ b/lib/druidUtil.js
@@ -1,39 +1,31 @@
 'use strict';
 
-var druidUtil = {};
+const druidUtil = {};
 
 /*
- * Intervals building functions
+ * Intervals building function
  */
-var makeInterval = function(start, end) {
-    return start + '/' + end ;
-};
-
 druidUtil.makeInterval = function(start, end) {
-    return [ makeInterval(start, end) ];
+    return [ `${start}/${end}` ];
 };
 
 /*
  * Filters building functions
  */
 druidUtil.makeSelectorFilter = function(dimension, value) {
-    return { type: 'selector', dimension: dimension, value: value };
+    return { type: 'selector', dimension, value };
 };
-
 druidUtil.makeRegexFilter = function(dimension, pattern) {
-    return { type: 'regex', dimension: dimension, pattern: pattern };
+    return { type: 'regex', dimension, pattern };
 };
-
 druidUtil.makeAndFilter = function(fields) {
-    return { type: 'and', fields: fields };
+    return { type: 'and', fields };
 };
-
 druidUtil.makeOrFilter = function(fields) {
-    return { type: 'or', fields: fields };
+    return { type: 'or', fields };
 };
-
 druidUtil.makeNotFilter = function(field) {
-    return { type: 'not', field: field };
+    return { type: 'not', field };
 };
 
 
@@ -41,12 +33,11 @@
  * Aggregations building functions
  */
 druidUtil.makeCount = function(name) {
-    return { type: 'count', name: name };
+    return { type: 'count', name };
 };
-
-var makeSimpleTypeAggregation = function(type, name, fieldName) {
-    return { type: type, name: name, fieldName: fieldName };
-};
+function makeSimpleTypeAggregation(type, name, fieldName) {
+    return { type, name, fieldName };
+}
 // Sum
 druidUtil.makeLongSum = function(name, fieldName) {
     return makeSimpleTypeAggregation('longSum', name, fieldName);
@@ -80,13 +71,11 @@
 druidUtil.makeDoubleLast = function(name, fieldName) {
     return makeSimpleTypeAggregation('doubleLast', name, fieldName);
 };
-
 druidUtil.makeCardinalityAggregation = function(name, fields) {
-    return { type: 'cardinality', name: name, fields: fields };
+    return { type: 'cardinality', name, fields };
 };
-
 druidUtil.makeFilteredAggregation = function(filter, aggregator) {
-    return { type: 'filtered', filter: filter, aggregator: aggregator };
+    return { type: 'filtered', filter, aggregator };
 };
 
 
@@ -94,29 +83,23 @@
  * PostAggregations building functions
  */
 druidUtil.makeFieldAccessor = function(fieldName) {
-    return { type: 'fieldAccess', fieldName: fieldName };
+    return { type: 'fieldAccess', fieldName };
 };
-
-var makeArithmeticPostAggregation = function(fn, name, fields) {
-    return { type: 'arithmetic', name: name, fn: fn, fields: fields };
-};
-
+function makeArithmeticPostAggregation(fn, name, fields) {
+    return { type: 'arithmetic', name, fn, fields };
+}
 druidUtil.makePlusPostAggregation = function(name, fields) {
     return makeArithmeticPostAggregation('+', name, fields);
 };
-
 druidUtil.makeMinusPostAggregation = function(name, fields) {
     return makeArithmeticPostAggregation('-', name, fields);
 };
-
 druidUtil.makeTimesPostAggregation = function(name, fields) {
     return makeArithmeticPostAggregation('*', name, fields);
 };
-
 druidUtil.makeDividePostAggregation = function(name, fields) {
     return makeArithmeticPostAggregation('/', name, fields);
 };
-
 druidUtil.makeQuotientPostAggregation = function(name, fields) {
     return makeArithmeticPostAggregation('quotient', name, fields);
 };
@@ -128,42 +111,38 @@
 
 const JSON_HEADER = { 'content-type': 'application/json' };
 
-var makeQuery = function(uri, body) {
-    return { uri: uri, headers: JSON_HEADER, body: body };
+function makeQuery(uri, body) {
+    return { uri, headers: JSON_HEADER, body };
+}
+
+druidUtil.makeTimeseriesQuery = function(uri, dataSource, granularity,
+                                         filter, aggregations, 
postAggregations, intervals) {
+    return makeQuery(uri, {
+        queryType: 'timeseries',
+        dataSource,
+        granularity,
+        filter,
+        aggregations,
+        postAggregations,
+        intervals
+    });
 };
 
-druidUtil.makeTimeseriesQuery = function(uri, datasource, granularity,
-                                         filters, aggregations, 
postAggregations, intervals) {
-    return makeQuery(uri,
-        {
-            queryType: 'timeseries',
-            dataSource: datasource,
-            granularity: granularity,
-            filter: filters,
-            aggregations: aggregations,
-            postAggregations: postAggregations,
-            intervals: intervals
-        }
-    );
-};
-
-druidUtil.makeTopN = function(uri, datasource, granularity,
+druidUtil.makeTopN = function(uri, dataSource, granularity,
                                    dimension, threshold, metric,
-                                   filters, aggregations, postAggregations, 
intervals) {
-    return makeQuery(uri,
-        {
-            queryType: 'topN',
-            dataSource: datasource,
-            granularity: granularity,
-            dimension: dimension,
-            threshold: threshold,
-            metric: metric,
-            filter: filters,
-            aggregations: aggregations,
-            postAggregations: postAggregations,
-            intervals: intervals
-        }
-    );
+                                   filter, aggregations, postAggregations, 
intervals) {
+    return makeQuery(uri, {
+        queryType: 'topN',
+        dataSource,
+        granularity,
+        dimension,
+        threshold,
+        metric,
+        filter,
+        aggregations,
+        postAggregations,
+        intervals
+    });
 };
 
 module.exports = druidUtil;
diff --git a/package.json b/package.json
index 20f3f50..06bf72c 100644
--- a/package.json
+++ b/package.json
@@ -24,26 +24,35 @@
     "url": "https://phabricator.wikimedia.org/tag/analytics/";
   },
   "dependencies": {
-    "bluebird": "^3.1.1",
-    "restbase-mod-table-cassandra": "^0.8.15",
-    "service-runner": "^1.1.0",
-    "hyperswitch": "^0.1.1"
+    "bluebird": "^3.5.0",
+    "restbase-mod-table-cassandra": "^0.11.2",
+    "service-runner": "^2.3.0",
+    "hyperswitch": "^0.9.1"
   },
   "devDependencies": {
-    "js-yaml": "^3.5.2",
-    "preq": "^0.4.8",
-    "bunyan": "^1.5.1",
-    "coveralls": "^2.11.6",
-    "istanbul": "^0.4.2",
-    "mocha": "^2.3.4",
-    "mocha-jscs": "^4.0.0",
-    "mocha-jshint": "^2.2.6",
-    "mocha-lcov-reporter": "^1.0.0",
-    "restbase-mod-table-sqlite": "^0.1.14",
-    "temp": "^0.8.3"
+    "ajv": "^5.1.5",
+    "bunyan": "^1.8.10",
+    "coveralls": "^2.13.1",
+    "eslint-config-node-services": "^2.2.2",
+    "eslint-config-wikimedia": "^0.4.0",
+    "eslint-plugin-jsdoc": "^3.1.0",
+    "eslint-plugin-json": "^1.2.0",
+    "istanbul": "^0.4.5",
+    "js-yaml": "^3.8.4",
+    "mocha": "^3.4.2",
+    "mocha-eslint": "^3.0.1",
+    "mocha-jscs": "^5.0.1",
+    "mocha-jshint": "^2.3.1",
+    "mocha-lcov-reporter": "^1.3.0",
+    "mocha.parallel": "^0.15.2",
+    "preq": "^0.5.2",
+    "restbase-mod-table-sqlite": "^0.1.20"
+  },
+  "engines": {
+    "node": ">=4"
   },
   "deploy": {
-    "node": "4.4.6",
+    "node": "6.11.1",
     "target": "debian",
     "dependencies": {
       "_all": []
diff --git a/projects/aqs_default.yaml b/projects/aqs_default.yaml
index d7fb8bf..78d2454 100644
--- a/projects/aqs_default.yaml
+++ b/projects/aqs_default.yaml
@@ -1,52 +1,64 @@
-swagger: '2.0'
 paths:
   /{api:v1}: &default_project_paths_v1
-    swagger: '2.0'
-    # swagger options, overriding the shared ones from the merged specs (?)
-    info:
-      version: 1.0.0-beta
-      title: Analytics REST API
-      description: >
-          Analytics Query Service setup
-      x-is-api-root: true
-
-    x-host-basePath: /api/rest_v1
-
     x-modules:
-      /pageviews:
-        - path: v1/pageviews.yaml
-      /legacy/pagecounts:
-        - path: v1/legacy/pagecounts.yaml
-      /unique-devices:
-        - path: v1/unique-devices.yaml
-      /edited-pages:
-        - path: v1/edited-pages.yaml
-      /editors:
-        - path: v1/editors.yaml
-      /registered-users:
-        - path: v1/registered-users.yaml
-      /edits:
-        - path: v1/edits.yaml
-      /bytes-difference:
-        - path: v1/bytes-difference.yaml
+      - spec:
+          # Careful - 2 indentations here !
+          info:
+            version: 1.0.0-beta
+            title: Analytics REST API
+            description: Analytics Query Service setup
+
+          x-is-api-root: true
+          x-host-basePath: /api/rest_v1
+
+          paths:
+            /pageviews:
+              x-modules:
+                - path: v1/pageviews.yaml
+            /legacy/pagecounts:
+              x-modules:
+                - path: v1/legacy/pagecounts.yaml
+            /unique-devices:
+              x-modules:
+                - path: v1/unique-devices.yaml
+            /edited-pages:
+              x-modules:
+                - path: v1/edited-pages.yaml
+            /editors:
+              x-modules:
+                - path: v1/editors.yaml
+            /registered-users:
+              x-modules:
+                - path: v1/registered-users.yaml
+            /edits:
+              x-modules:
+                - path: v1/edits.yaml
+            /bytes-difference:
+              x-modules:
+                - path: v1/bytes-difference.yaml
 
   /{api:sys}:
-    swagger: 2.0
-    info:
-      x-is-api-root: true
     x-modules:
-      /table:
-        - type: npm
-          name: restbase-mod-table-cassandra
-          options:
-            conf: '{{options.table}}'
-      /pageviews:
-        - path: sys/pageviews.js
-      /legacy/pagecounts:
-        - path: sys/legacy/pagecounts.js
-      /unique-devices:
-        - path: sys/unique-devices.js
-      /mediawiki-history-metrics:
-        - path: sys/mediawiki-history-metrics.js
-          options:
-            druid: '{{options.druid}}'
+      - spec:
+          # Careful - 2 indentations here !
+          paths:
+            /table:
+              x-modules:
+                - path: sys/table.js
+                  options:
+                    conf: '{{options.table}}'
+            /pageviews:
+              x-modules:
+                - path: sys/pageviews.js
+            /legacy/pagecounts:
+              x-modules:
+                - path: sys/legacy/pagecounts.js
+            /unique-devices:
+              x-modules:
+                - path: sys/unique-devices.js
+            /mediawiki-history-metrics:
+              x-modules:
+                - path: sys/mediawiki-history-metrics.js
+                  options:
+                    druid: '{{options.druid}}'
+        options: '{{options}}'
diff --git a/sys/legacy/pagecounts.js b/sys/legacy/pagecounts.js
index d7aa458..b8a6983 100644
--- a/sys/legacy/pagecounts.js
+++ b/sys/legacy/pagecounts.js
@@ -8,26 +8,27 @@
  * https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-raw
  */
 
-var HyperSwitch = require('hyperswitch');
-var path = require('path');
-var URI = HyperSwitch.URI;
+const HyperSwitch = require('hyperswitch');
+const path = require('path');
+const URI = HyperSwitch.URI;
 
-var aqsUtil = require('../../lib/aqsUtil');
+const aqsUtil = require('../../lib/aqsUtil');
 
-var spec = HyperSwitch.utils.loadSpec(path.join(__dirname, 'pagecounts.yaml'));
+const spec = HyperSwitch.utils.loadSpec(path.join(__dirname, 
'pagecounts.yaml'));
 
 // Pagecounts service
 function LPCS(options) {
     this.options = options;
 }
 
-var tables = {
+function tableURI(domain, tableName) {
+    return new URI([domain, 'sys', 'table', tableName, '']);
+}
+
+const tables = {
     project: 'lgc.pagecounts.per.project'
 };
-var tableURI = function(domain, tableName) {
-    return new URI([domain, 'sys', 'table', tableName, '']);
-};
-var tableSchemas = {
+const tableSchemas = {
     project: {
         table: tables.project,
         version: 1,
@@ -48,14 +49,14 @@
 };
 
 LPCS.prototype.pagecountsPerProject = function(hyper, req) {
-    var rp = req.params;
+    const rp = req.params;
 
     aqsUtil.validateStartAndEnd(rp, {
         zeroHour: rp.granularity !== 'hourly',
         fullMonths: rp.granularity === 'monthly'
     });
 
-    var dataRequest = hyper.get({
+    const dataRequest = hyper.get({
         uri: tableURI(rp.domain, tables.project),
         body: {
             table: tables.project,
@@ -69,9 +70,9 @@
     }).catch(aqsUtil.notFoundCatcher);
 
     // Parse long from string to int
-    return dataRequest.then(aqsUtil.normalizeResponse).then(function(res) {
+    return dataRequest.then(aqsUtil.normalizeResponse).then((res) => {
         if (res.body.items) {
-            res.body.items.forEach(function(item) {
+            res.body.items.forEach((item) => {
                 if (item.count !== null) {
                     try {
                         item.count = parseInt(item.count, 10);
@@ -87,16 +88,16 @@
 };
 
 module.exports = function(options) {
-    var lpcs = new LPCS(options);
+    const lpcs = new LPCS(options);
 
     return {
-        spec: spec,
+        spec,
         operations: {
             pagecountsPerProject: lpcs.pagecountsPerProject.bind(lpcs)
         },
         resources: [
             {
-                uri: '/{domain}/sys/table/' + tables.project,
+                uri: `/{domain}/sys/table/${tables.project}`,
                 body: tableSchemas.project
             }
         ]
diff --git a/sys/mediawiki-history-metrics.js b/sys/mediawiki-history-metrics.js
index 54e9fec..4d6068e 100644
--- a/sys/mediawiki-history-metrics.js
+++ b/sys/mediawiki-history-metrics.js
@@ -10,20 +10,18 @@
  *
  */
 
-var HyperSwitch = require('hyperswitch');
-var HTTPError = HyperSwitch.HTTPError;
-var path = require('path');
-var URI = HyperSwitch.URI;
+const HyperSwitch = require('hyperswitch');
+const HTTPError = HyperSwitch.HTTPError;
+const path = require('path');
 
-var aqsUtil = require('../lib/aqsUtil');
-var druidUtil = require('../lib/druidUtil');
+const aqsUtil = require('../lib/aqsUtil');
+const druidUtil = require('../lib/druidUtil');
 
-var spec = HyperSwitch.utils.loadSpec(path.join(__dirname, 
'mediawiki-history-metrics.yaml'));
-var schemas = HyperSwitch.utils.loadSpec(path.join(__dirname, 
'mediawiki-history-schemas.yaml'));
-var D = schemas.druid;
-var A2D = schemas.aqs2druid;
+const spec = HyperSwitch.utils.loadSpec(path.join(__dirname, 
'mediawiki-history-metrics.yaml'));
+const schemas = HyperSwitch.utils.loadSpec(path.join(__dirname, 
'mediawiki-history-schemas.yaml'));
+const D = schemas.druid;
+const A2D = schemas.aqs2druid;
 
-const ALL = 'all';
 // How many results to return in topN queries
 const TOP_THRESHOLD = 100;
 
@@ -38,15 +36,14 @@
     this.druid = options.druid;
 }
 
-var requestURI = function(druid) {
+function requestURI(druid) {
     if (druid) {
-        var uri = '';
-        uri += (druid.scheme) ? druid.scheme + '://' : '';
-        uri += druid.host || '';
-        uri += (druid.port) ? ':' + druid.port : '';
-        uri += druid.query_path || '';
+        const scheme = (druid.scheme) ? `${druid.scheme}://` : '';
+        const host = druid.host || '';
+        const port = (druid.port) ? `:${druid.port}` : '';
+        const path = druid.query_path || '';
 
-        return uri;
+        return `${scheme}${host}${port}${path}`;
     } else { // Fail with 500 if druid conf is not set
         throw new HTTPError({
             status: 500,
@@ -56,7 +53,7 @@
             }
         });
     }
-};
+}
 
 
 const druidQueriesBlocks = {
@@ -81,7 +78,7 @@
 };
 
 
-var validateRequestParams = function(requestParams, opts) {
+function validateRequestParams(requestParams, opts) {
     opts = opts || {};
 
     aqsUtil.normalizeProject(requestParams, opts);
@@ -96,7 +93,7 @@
         // Druid uses ISO date format, so convert to YYYY-MM-DD
         isoDateFormat: true
     }));
-};
+}
 
 
 /*
@@ -105,22 +102,23 @@
  * With those events, metrics are additive and therefore 'all'
  * parameter values always mean 'no filtering'
  */
-var eventsFiltersFromRequestParams = function(requestParams) {
-    return AQS_PARAMS.filter(paramName => {
+function eventsFiltersFromRequestParams(requestParams) {
+    return AQS_PARAMS.filter((paramName) => {
         return requestParams[paramName] && // Parameter is defined in request
                 A2D.filter[paramName] && // Parameter has a related filter 
function
-                !A2D.all.hasOwnProperty(requestParams[paramName]); // 
Parameter value is not ALL
-    }).map(paramName => {
+                // Parameter value is not ALL
+                !Object.prototype.hasOwnProperty.call(A2D.all, 
requestParams[paramName]);
+    }).map((paramName) => {
         // Get filter function by name from schemas
-        var makeFilter = druidUtil[A2D.filter[paramName]];
-        var filterDim = A2D.dimension[paramName];
-        var filterVal = requestParams[paramName];
-        if (A2D.hasOwnProperty(paramName)) { // Convert or keep same
+        const makeFilter = druidUtil[A2D.filter[paramName]];
+        const filterDim = A2D.dimension[paramName];
+        let filterVal = requestParams[paramName];
+        if (Object.prototype.hasOwnProperty.call(A2D, paramName)) { // Convert 
or keep same
             filterVal = A2D[paramName][requestParams[paramName]];
         }
         return makeFilter(filterDim, filterVal);
     });
-};
+}
 
 
 /*
@@ -132,81 +130,66 @@
  * Since the digests are currently not fully optimized, some special
  * cases also apply (see below)
  */
-var digestsFiltersFromRequestParams = function(requestParams) {
-    return AQS_PARAMS.filter(paramName => {
+function digestsFiltersFromRequestParams(requestParams) {
+    return AQS_PARAMS.filter((paramName) => {
         // Special case:
         // request[project] = all-projects --> No filter
         // request[activity-level] = all-activity-levels --> No filter
         // request[editor-type] = all-editor-types --> Filter
         // request[page-type] = all-page-types --> Filter
-        if ({ project: true, 'activity-level': true 
}.hasOwnProperty(paramName)) {
+        if (paramName === 'project' || paramName === 'activity-level') {
             return requestParams[paramName] && // Parameter is defined in 
request
                 A2D.filter[paramName] && // Parameter has a related filter 
function
-                !A2D.all.hasOwnProperty(requestParams[paramName]); // 
Parameter value is not ALL
+                // Parameter value is not ALL
+                !Object.prototype.hasOwnProperty.call(A2D.all, 
requestParams[paramName]);
         } else {
             return requestParams[paramName] && // Parameter is defined in 
request
                 A2D.dimension[paramName] && // Parameter has a related druid 
dimension
                 A2D.filter[paramName]; // Parameter has a related filter 
function
         }
-    }).map(paramName => {
+    }).map((paramName) => {
         // Get filter function by name from schemas
-        var makeFilter = druidUtil[A2D.filter[paramName]];
-        var filterDim = A2D.dimension[paramName];
-        var filterVal = requestParams[paramName];
-        if (A2D.hasOwnProperty(paramName)) { // Convert or keep same
+        const makeFilter = druidUtil[A2D.filter[paramName]];
+        const filterDim = A2D.dimension[paramName];
+        let filterVal = requestParams[paramName];
+        if (Object.prototype.hasOwnProperty.call(A2D, paramName)) { // Convert 
or keep same
             filterVal = A2D[paramName][requestParams[paramName]];
         }
         return makeFilter(filterDim, filterVal);
     });
-};
+}
 
-var digestGranularityFilter = function(granularity) {
+function digestGranularityFilter(granularity) {
     return druidUtil.makeSelectorFilter(
         D.dimension.eventType,
         A2D['granularity-digest'][granularity]
     );
-};
+}
 
-
-/*
-
-Currently not used - We have decided to go for metrics without
-deletion drift at first, in order to be able to explain it and
-show it correctly in the future. Keeping this function for later
-
-var deletedCurrentFilters = function(granularity) {
-    var deletedCurrents = A2D['granularity-deleted_currents'][granularity];
-    return deletedCurrents.map(tag => {
-        return druidUtil.makeNotFilter(
-            druidUtil.makeSelectorFilter(D.dimension.otherTags, tag));
-    });
-};
-*/
-
-var eventsCountingAggregation = function(outputMetricName) {
+function eventsCountingAggregation(outputMetricName) {
     return druidUtil.makeLongSum(outputMetricName, D.metric.events);
-};
+}
 
-var convertDruidResultToAqsResult = function(druidResult, requestParams, 
keyFilters, isTop) {
+function convertDruidResultToAqsResult(druidResult, requestParams, keyFilters, 
isTop) {
     if (druidResult.status === 200) {
         // Overwrite body: Druid result is an array of results,
         // we send a single item with an array of results
-        var coreItem = {};
+        const coreItem = {};
 
-        AQS_PARAMS.forEach(paramName => {
+        AQS_PARAMS.forEach((paramName) => {
             if (requestParams[paramName]) {
                 coreItem[paramName] = requestParams[paramName];
             }
         });
 
-        coreItem.results = druidResult.body.map(druidRes => {
-            var aqsRes = { timestamp: druidRes.timestamp };
+        coreItem.results = druidResult.body.map((druidRes) => {
+            const aqsRes = { timestamp: druidRes.timestamp };
             if (isTop) {
                 // Just copy the result array to top field
                 aqsRes.top = druidRes.result;
             } else {
                 // Iterate over result keys and keep/convert only needed ones
-                Object.keys(druidRes.result).forEach(k => {
+                Object.keys(druidRes.result).forEach((k) => {
                     if ((keyFilters === undefined) || (keyFilters[k] !== 
undefined)) {
                         // Results from Druid can be floats in case of
                         // count-distinct approximations. We convert them to 
int
@@ -220,13 +203,15 @@
         // Return a single item
         druidResult.body = { items: [ coreItem ] };
     } else {
-        druidResult.body = 'Druid server error.\nIt would be great if you 
could send us ' +
-            'an email ([email protected]) with a copy of this message.\n 
' +
-            'Thanks a lot !\n' + druidResult.body;
+        druidResult.body = ```Druid server error.
+It would be great if you could send us an email ([email protected])
+with a copy of this message.
+Thanks a lot!
+${druidResult.body}```;
     }
 
     return druidResult;
-};
+}
 
 
 /*
@@ -235,10 +220,10 @@
 MHMS.prototype.newPagesTimeseries = function(hyper, req) {
 
     // Validate request parameters in place
-    var rp = req.params;
+    const rp = req.params;
     validateRequestParams(rp);
 
-    var druidRequest = druidUtil.makeTimeseriesQuery(
+    const druidRequest = druidUtil.makeTimeseriesQuery(
         requestURI(this.druid),
         D.datasource,
         A2D.granularity[rp.granularity],
@@ -270,13 +255,13 @@
         druidUtil.makeInterval(rp.start, rp.end)
     );
 
-    var keyFilters = {};
+    const keyFilters = {};
     keyFilters[D.outputMetric.newPages] = true;
 
     return hyper
         .post(druidRequest)
         .catch(aqsUtil.notFoundCatcher)
-        .then(aqsUtil.normalizeResponse).then(res => {
+        .then(aqsUtil.normalizeResponse).then((res) => {
             // Need to filter out some druid results fields
             // we only want the post-aggregation one
             return convertDruidResultToAqsResult(res, rp, keyFilters);
@@ -289,10 +274,10 @@
 MHMS.prototype.newlyRegisteredUsersTimeseries = function(hyper, req) {
 
     // Validate request parameters in place
-    var rp = req.params;
+    const rp = req.params;
     validateRequestParams(rp);
 
-    var druidRequest = druidUtil.makeTimeseriesQuery(
+    const druidRequest = druidUtil.makeTimeseriesQuery(
         requestURI(this.druid),
         D.datasource,
         A2D.granularity[rp.granularity],
@@ -312,7 +297,7 @@
     return hyper
         .post(druidRequest)
         .catch(aqsUtil.notFoundCatcher)
-        .then(aqsUtil.normalizeResponse).then(res => {
+        .then(aqsUtil.normalizeResponse).then((res) => {
             return convertDruidResultToAqsResult(res, rp);
         });
 };
@@ -326,14 +311,14 @@
 MHMS.prototype.digestsTimeseries = function(hyper, req) {
 
     // Validate request parameters in place
-    var rp = req.params;
+    const rp = req.params;
     validateRequestParams(rp, {
         noAllProjects: true // Don't accept all-projects aggregation
     });
 
     // editors or edited-pages specific parts
-    var eventEntityFilter;
-    var outputMetric;
+    let eventEntityFilter;
+    let outputMetric;
     if (rp['digest-type'] === 'editors') {
         eventEntityFilter = druidQueriesBlocks.filter.users;
         outputMetric = D.outputMetric.editors;
@@ -344,7 +329,7 @@
         throw new Error('Internal error - Invalid digest-type parameter for 
digestsTimeseries');
     }
 
-    var druidRequest = druidUtil.makeTimeseriesQuery(
+    const druidRequest = druidUtil.makeTimeseriesQuery(
         requestURI(this.druid),
         D.datasource,
         A2D.granularity[rp.granularity],
@@ -359,7 +344,7 @@
     return hyper
         .post(druidRequest)
         .catch(aqsUtil.notFoundCatcher)
-        .then(aqsUtil.normalizeResponse).then(res => {
+        .then(aqsUtil.normalizeResponse).then((res) => {
             return convertDruidResultToAqsResult(res, rp);
         });
 };
@@ -373,7 +358,7 @@
 MHMS.prototype.revisionsTimeseries = function(hyper, req) {
 
     // Validate request parameters in place
-    var rp = req.params;
+    const rp = req.params;
     validateRequestParams(rp,
       // Accept all-projects aggregation if not grouping by page-id or 
editor-id
       (rp['page-id'] || rp['editor-id']) ? { noAllProjects: true } : {}
@@ -381,7 +366,7 @@
 
 
     // edits, net-bytes-diff or abs-bytes-diff specific parts
-    var aggregation;
+    let aggregation;
     if (rp.metric === 'edits') {
         aggregation = eventsCountingAggregation(D.outputMetric.edits);
     } else if (rp.metric === 'net-bytes-diff') {
@@ -394,7 +379,7 @@
         throw new Error('Internal error - Invalid metric parameter for 
revisionsTimeseries');
     }
 
-    var druidRequest = druidUtil.makeTimeseriesQuery(
+    const druidRequest = druidUtil.makeTimeseriesQuery(
         requestURI(this.druid),
         D.datasource,
         A2D.granularity[rp.granularity],
@@ -409,7 +394,7 @@
     return hyper
         .post(druidRequest)
         .catch(aqsUtil.notFoundCatcher)
-        .then(aqsUtil.normalizeResponse).then(res => {
+        .then(aqsUtil.normalizeResponse).then((res) => {
             return convertDruidResultToAqsResult(res, rp);
         });
 };
@@ -423,13 +408,13 @@
 MHMS.prototype.revisionsTop = function(hyper, req) {
 
     // Validate request parameters in place
-    var rp = req.params;
+    const rp = req.params;
     validateRequestParams(rp, {
         noAllProjects: true // Don't accept all-projects aggregation
     });
 
     // editors or edited-pages specific parts
-    var topDimension;
+    let topDimension;
     if (rp['top-type'] === 'editors') {
         topDimension = D.dimension.userId;
     } else if (rp['top-type'] === 'edited-pages') {
@@ -439,8 +424,8 @@
     }
 
     // edits, net-bytes-diff or abs-bytes-diff specific parts
-    var aggregation;
-    var outputMetric;
+    let aggregation;
+    let outputMetric;
     if (rp.metric === 'edits') {
         outputMetric = D.outputMetric.edits;
         aggregation = eventsCountingAggregation(outputMetric);
@@ -454,7 +439,7 @@
         throw new Error('Internal error - Invalid metric parameter for 
revisionsTop');
     }
 
-    var druidRequest = druidUtil.makeTopN(
+    const druidRequest = druidUtil.makeTopN(
         requestURI(this.druid),
         D.datasource,
         A2D.granularity[rp.granularity],
@@ -472,17 +457,17 @@
     return hyper
         .post(druidRequest)
         .catch(aqsUtil.notFoundCatcher)
-        .then(aqsUtil.normalizeResponse).then(res => {
+        .then(aqsUtil.normalizeResponse).then((res) => {
             return convertDruidResultToAqsResult(res, rp, undefined, true);
         });
 };
 
 
 module.exports = function(options) {
-    var mhms = new MHMS(options);
+    const mhms = new MHMS(options);
 
     return {
-        spec: spec,
+        spec,
         operations: {
             newPagesTimeseries: mhms.newPagesTimeseries.bind(mhms),
             newlyRegisteredUsersTimeseries: 
mhms.newlyRegisteredUsersTimeseries.bind(mhms),
diff --git a/sys/pageviews.js b/sys/pageviews.js
index 0f8b151..a9ef7cd 100644
--- a/sys/pageviews.js
+++ b/sys/pageviews.js
@@ -6,14 +6,14 @@
  * This API serves pre-aggregated pageview statistics from Cassandra
  */
 
-var HyperSwitch = require('hyperswitch');
-var path = require('path');
-var HTTPError = HyperSwitch.HTTPError;
-var URI = HyperSwitch.URI;
+const HyperSwitch = require('hyperswitch');
+const path = require('path');
+const HTTPError = HyperSwitch.HTTPError;
+const URI = HyperSwitch.URI;
 
-var aqsUtil = require('../lib/aqsUtil');
+const aqsUtil = require('../lib/aqsUtil');
 
-var spec = HyperSwitch.utils.loadSpec(path.join(__dirname, 'pageviews.yaml'));
+const spec = HyperSwitch.utils.loadSpec(path.join(__dirname, 
'pageviews.yaml'));
 
 const MONTHLY = 'monthly';
 const DAILY = 'daily';
@@ -23,16 +23,17 @@
     this.options = options;
 }
 
+function tableURI(domain, tableName) {
+    return new URI([domain, 'sys', 'table', tableName, '']);
+}
 
-var tables = {
+const tables = {
     articleFlat: 'pageviews.per.article.flat',
     project_v2: 'pageviews.per.project.v2',
     tops: 'top.pageviews',
 };
-var tableURI = function(domain, tableName) {
-    return new URI([domain, 'sys', 'table', tableName, '']);
-};
-var tableSchemas = {
+
+const tableSchemas = {
     articleFlat: {
         table: tables.articleFlat,
         version: 3,
@@ -99,7 +100,7 @@
 };
 
 
-var viewCountColumnsForArticleFlat = {
+const viewCountColumnsForArticleFlat = {
     views_all_access_all_agents: 'aa', // views for all-access, all-agents
     views_all_access_bot: 'ab',        // views for all-access, bot
     views_all_access_spider: 'as',     // views for all-access, spider
@@ -125,13 +126,13 @@
 // view count column in the dictionary above, using its short name.
 // The short name saves space because cassandra stores the column name with
 // each record.
-Object.keys(viewCountColumnsForArticleFlat).forEach(function(k) {
+Object.keys(viewCountColumnsForArticleFlat).forEach((k) => {
     tableSchemas.articleFlat.attributes[viewCountColumnsForArticleFlat[k]] = 
'int';
 });
 
 
 PJVS.prototype.pageviewsForArticleFlat = function(hyper, req) {
-    var rp = req.params;
+    const rp = req.params;
 
     // dates are passed in as YYYYMMDD but we need the HH to match the loaded 
data
     // which was originally planned at hourly resolution, so we pass "fakeHour"
@@ -142,7 +143,7 @@
         fullMonths: rp.granularity === MONTHLY
     });
 
-    var dataRequest = hyper.get({
+    const dataRequest = hyper.get({
         uri: tableURI(rp.domain, tables.articleFlat),
         body: {
             table: tables.articleFlat,
@@ -157,23 +158,23 @@
     }).catch(aqsUtil.notFoundCatcher);
 
     function viewKey(access, agent) {
-        var ret = ['views', access, agent].join('_');
+        const ret = ['views', access, agent].join('_');
         return viewCountColumnsForArticleFlat[ret.replace(/-/g, '_')];
     }
 
     function removeDenormalizedColumns(item) {
-        Object.keys(viewCountColumnsForArticleFlat).forEach(function(k) {
+        Object.keys(viewCountColumnsForArticleFlat).forEach((k) => {
             delete item[viewCountColumnsForArticleFlat[k]];
         });
     }
 
-    return dataRequest.then(aqsUtil.normalizeResponse).then(function(res) {
+    return dataRequest.then(aqsUtil.normalizeResponse).then((res) => {
         if (res.body.items) {
-            var monthViews = {};
+            const monthViews = {};
             const aggregateMonthly = rp.granularity === MONTHLY;
 
-            res.body.items.forEach(function(item) {
-                var yearAndMonth = item.timestamp.substring(0, 6);
+            res.body.items.forEach((item) => {
+                const yearAndMonth = item.timestamp.substring(0, 6);
 
                 item.access = rp.access;
                 item.agent = rp.agent;
@@ -185,12 +186,12 @@
                 removeDenormalizedColumns(item);
 
                 if (aggregateMonthly) {
-                    if (!monthViews.hasOwnProperty(yearAndMonth)) {
-                        var newMonth = {
+                    if (!Object.prototype.hasOwnProperty.call(monthViews, 
yearAndMonth)) {
+                        const newMonth = {
                             project: item.project,
                             article: item.article,
                             granularity: MONTHLY,
-                            timestamp: yearAndMonth + '0100',
+                            timestamp: `${yearAndMonth}0100`,
                             access: rp.access,
                             agent: rp.agent,
                             views: 0
@@ -204,9 +205,9 @@
             });
 
             if (aggregateMonthly) {
-                var sortedMonths = Object.keys(monthViews);
+                const sortedMonths = Object.keys(monthViews);
                 sortedMonths.sort();
-                res.body.items = sortedMonths.map(function(month) {
+                res.body.items = sortedMonths.map((month) => {
                     return monthViews[month];
                 });
             }
@@ -217,7 +218,7 @@
 };
 
 PJVS.prototype.pageviewsForProjects = function(hyper, req) {
-    var rp = req.params;
+    const rp = req.params;
 
     aqsUtil.validateStartAndEnd(rp, {
         fakeHour: (rp.granularity === MONTHLY || rp.granularity === DAILY),
@@ -225,7 +226,7 @@
         fullMonths: rp.granularity === MONTHLY
     });
 
-    var dataRequest = hyper.get({
+    const dataRequest = hyper.get({
         uri: tableURI(rp.domain, tables.project_v2),
         body: {
             table: tables.project_v2,
@@ -240,9 +241,9 @@
 
     }).catch(aqsUtil.notFoundCatcher);
 
-    return dataRequest.then(aqsUtil.normalizeResponse).then(function(res) {
+    return dataRequest.then(aqsUtil.normalizeResponse).then((res) => {
         if (res.body.items) {
-            res.body.items.forEach(function(item) {
+            res.body.items.forEach((item) => {
                 // prefer the v column if it's loaded
                 if (item.v !== null) {
                     try {
@@ -260,11 +261,11 @@
 };
 
 PJVS.prototype.pageviewsForTops = function(hyper, req) {
-    var rp = req.params;
+    const rp = req.params;
 
     aqsUtil.validateYearMonthDay(rp);
 
-    var dataRequest = hyper.get({
+    const dataRequest = hyper.get({
         uri: tableURI(rp.domain, tables.tops),
         body: {
             table: tables.tops,
@@ -279,9 +280,9 @@
 
     }).catch(aqsUtil.notFoundCatcher);
 
-    return dataRequest.then(aqsUtil.normalizeResponse).then(function(res) {
+    return dataRequest.then(aqsUtil.normalizeResponse).then((res) => {
         if (res.body.items) {
-            res.body.items.forEach(function(item) {
+            res.body.items.forEach((item) => {
                 // prefer the articlesJSON column if it's loaded
                 if (item.articlesJSON !== null) {
                     item.articles = item.articlesJSON;
@@ -310,10 +311,10 @@
 
 
 module.exports = function(options) {
-    var pjvs = new PJVS(options);
+    const pjvs = new PJVS(options);
 
     return {
-        spec: spec,
+        spec,
         operations: {
             pageviewsForArticle: pjvs.pageviewsForArticleFlat.bind(pjvs),
             pageviewsForProjects: pjvs.pageviewsForProjects.bind(pjvs),
@@ -321,15 +322,15 @@
         },
         resources: [
             {
-                uri: '/{domain}/sys/table/' + tables.articleFlat,
+                uri: `/{domain}/sys/table/${tables.articleFlat}`,
                 body: tableSchemas.articleFlat,
             }, {
                 // table where per-project data will be stored with fixed 
timestamps (T156312)
-                uri: '/{domain}/sys/table/' + tables.project_v2,
+                uri: `/{domain}/sys/table/${tables.project_v2}`,
                 body: tableSchemas.project_v2,
             }, {
                 // top pageviews table
-                uri: '/{domain}/sys/table/' + tables.tops,
+                uri: `/{domain}/sys/table/${tables.tops}`,
                 body: tableSchemas.tops,
             }
         ]
diff --git a/sys/table.js b/sys/table.js
new file mode 100644
index 0000000..3bbd576
--- /dev/null
+++ b/sys/table.js
@@ -0,0 +1,17 @@
+"use strict";
+
+/*
+ * A simple wrapper module over storage modules which allows to switch between 
storage
+ * implementation using a config option.
+ */
+
+module.exports = (options) => {
+    options.conf.backend = options.conf.backend || 'cassandra';
+
+    if (options.conf.backend !== 'cassandra'
+            && options.conf.backend !== 'sqlite') {
+        throw new Error(`Unsupported backend version specified: 
${options.conf.backend}`);
+    }
+
+    return require(`restbase-mod-table-${options.conf.backend}`)(options);
+};
diff --git a/sys/unique-devices.js b/sys/unique-devices.js
index 2b33727..6536c78 100644
--- a/sys/unique-devices.js
+++ b/sys/unique-devices.js
@@ -6,27 +6,28 @@
  * This API serves pre-aggregated unique devices statistics from Cassandra
  */
 
-var HyperSwitch = require('hyperswitch');
-var path = require('path');
-var URI = HyperSwitch.URI;
+const HyperSwitch = require('hyperswitch');
+const path = require('path');
+const URI = HyperSwitch.URI;
 
-var aqsUtil = require('../lib/aqsUtil');
+const aqsUtil = require('../lib/aqsUtil');
 
-var spec = HyperSwitch.utils.loadSpec(path.join(__dirname, 
'unique-devices.yaml'));
+const spec = HyperSwitch.utils.loadSpec(path.join(__dirname, 
'unique-devices.yaml'));
 
 // Unique devices Service
 function UDVS(options) {
     this.options = options;
 }
 
+function tableURI(domain, tableName) {
+    return new URI([domain, 'sys', 'table', tableName, '']);
+}
 
-var tables = {
+const tables = {
     project: 'unique.devices',
 };
-var tableURI = function(domain, tableName) {
-    return new URI([domain, 'sys', 'table', tableName, '']);
-};
-var tableSchemas = {
+
+const tableSchemas = {
     project: {
         table: tables.project,
         version: 1,
@@ -49,7 +50,7 @@
 
 
 UDVS.prototype.uniqueDevices = function(hyper, req) {
-    var rp = req.params;
+    const rp = req.params;
 
     aqsUtil.validateStartAndEnd(rp, {
         // YYYYMMDD dates are allowed, but need an hour to pass validation
@@ -59,7 +60,7 @@
         stripHour: true,
     });
 
-    var dataRequest = hyper.get({
+    const dataRequest = hyper.get({
         uri: tableURI(rp.domain, tables.project),
         body: {
             table: tables.project,
@@ -74,9 +75,9 @@
     }).catch(aqsUtil.notFoundCatcher);
 
     // Parse long from string to int
-    return dataRequest.then(aqsUtil.normalizeResponse).then(function(res) {
+    return dataRequest.then(aqsUtil.normalizeResponse).then((res) => {
         if (res.body.items) {
-            res.body.items.forEach(function(item) {
+            res.body.items.forEach((item) => {
                 if (item.devices !== null) {
                     try {
                         item.devices = parseInt(item.devices, 10);
@@ -92,17 +93,17 @@
 };
 
 module.exports = function(options) {
-    var udvs = new UDVS(options);
+    const udvs = new UDVS(options);
 
     return {
-        spec: spec,
+        spec,
         operations: {
             uniqueDevices: udvs.uniqueDevices.bind(udvs)
         },
         resources: [
             {
                 // unique devices per project table
-                uri: '/{domain}/sys/table/' + tables.project,
+                uri: `/{domain}/sys/table/${tables.project}`,
                 body: tableSchemas.project,
             }
         ]
diff --git a/test/aqs_test_module.yaml b/test/aqs_test_module.yaml
index 5ed46a2..6d3f0bf 100644
--- a/test/aqs_test_module.yaml
+++ b/test/aqs_test_module.yaml
@@ -1,139 +1,141 @@
 # Simple test spec
 
-swagger: 2.0
 paths:
   /{api:v1}:
-    swagger: '2.0'
-    info:
-      version: 1.0.0-beta
-      title: AQS testing APIs
-      x-is-api-root: true
-    paths:
-      
/pageviews/insert-per-article-flat/{project}/{article}/{granularity}/{timestamp}/{views}:
-        post:
-          x-request-handler:
-            - put_to_storage:
-                request:
-                  method: 'put'
-                  uri: '/{domain}/sys/table/pageviews.per.article.flat/'
-                  body:
-                    table: 'pageviews.per.article.flat'
-                    attributes:
-                      project: '{{request.params.project}}'
-                      article: '{{request.params.article}}'
-                      granularity: '{{request.params.granularity}}'
-                      timestamp: '{{request.params.timestamp}}'
-                      aa:  '{{request.params.views}}1'
-                      ab:  '{{request.params.views}}2'
-                      as:  '{{request.params.views}}3'
-                      au:  '{{request.params.views}}4'
-                      da:  '{{request.params.views}}5'
-                      db:  '{{request.params.views}}6'
-                      ds:  '{{request.params.views}}7'
-                      du:  null
-                      maa: '{{request.params.views}}9'
-                      mab: '{{request.params.views}}10'
-                      mas: '{{request.params.views}}11'
-                      mau: '{{request.params.views}}12'
-                      mwa: '{{request.params.views}}13'
-                      mwb: '{{request.params.views}}14'
-                      mws: '{{request.params.views}}15'
-                      mwu: '{{request.params.views}}16'
-          x-monitor: false
+    x-modules:
+      - spec:
+            # Careful - 2 indentations here !
+            info:
+              version: 1.0.0-beta
+              title: AQS testing APIs
+              x-is-api-root: true
+            paths:
+              
/pageviews/insert-per-article-flat/{project}/{article}/{granularity}/{timestamp}/{views}:
+                post:
+                  x-request-handler:
+                    - put_to_storage:
+                        request:
+                          method: 'put'
+                          uri: 
'/{domain}/sys/table/pageviews.per.article.flat/'
+                          body:
+                            table: 'pageviews.per.article.flat'
+                            attributes:
+                              project: '{{request.params.project}}'
+                              article: '{{request.params.article}}'
+                              granularity: '{{request.params.granularity}}'
+                              timestamp: '{{request.params.timestamp}}'
+                              aa:  '{{request.params.views}}1'
+                              ab:  '{{request.params.views}}2'
+                              as:  '{{request.params.views}}3'
+                              au:  '{{request.params.views}}4'
+                              da:  '{{request.params.views}}5'
+                              db:  '{{request.params.views}}6'
+                              ds:  '{{request.params.views}}7'
+                              du:  null
+                              maa: '{{request.params.views}}9'
+                              mab: '{{request.params.views}}10'
+                              mas: '{{request.params.views}}11'
+                              mau: '{{request.params.views}}12'
+                              mwa: '{{request.params.views}}13'
+                              mwb: '{{request.params.views}}14'
+                              mws: '{{request.params.views}}15'
+                              mwu: '{{request.params.views}}16'
+                  x-monitor: false
 
-      
/pageviews/insert-aggregate/{project}/{access}/{agent}/{granularity}/{timestamp}/{views}:
-        post:
-          x-request-handler:
-            - put_to_storage:
-                request:
-                  method: 'put'
-                  uri: '/{domain}/sys/table/pageviews.per.project.v2/'
-                  body:
-                    table: 'pageviews.per.project.v2'
-                    attributes:
-                      project: '{{request.params.project}}'
-                      access: '{{request.params.access}}'
-                      agent: '{{request.params.agent}}'
-                      granularity: '{{request.params.granularity}}'
-                      timestamp: '{{request.params.timestamp}}'
-                      views: '{{request.params.views}}'
-          x-monitor: false
+              
/pageviews/insert-aggregate/{project}/{access}/{agent}/{granularity}/{timestamp}/{views}:
+                post:
+                  x-request-handler:
+                    - put_to_storage:
+                        request:
+                          method: 'put'
+                          uri: '/{domain}/sys/table/pageviews.per.project.v2/'
+                          body:
+                            table: 'pageviews.per.project.v2'
+                            attributes:
+                              project: '{{request.params.project}}'
+                              access: '{{request.params.access}}'
+                              agent: '{{request.params.agent}}'
+                              granularity: '{{request.params.granularity}}'
+                              timestamp: '{{request.params.timestamp}}'
+                              views: '{{request.params.views}}'
+                  x-monitor: false
 
-      
/pageviews/insert-aggregate-long/{project}/{access}/{agent}/{granularity}/{timestamp}/{v}:
-        post:
-          x-request-handler:
-            - put_to_storage:
-                request:
-                  method: 'put'
-                  uri: '/{domain}/sys/table/pageviews.per.project.v2/'
-                  body:
-                    table: 'pageviews.per.project.v2'
-                    attributes:
-                      project: '{{request.params.project}}'
-                      access: '{{request.params.access}}'
-                      agent: '{{request.params.agent}}'
-                      granularity: '{{request.params.granularity}}'
-                      timestamp: '{{request.params.timestamp}}'
-                      v: '{{request.params.v}}'
-          x-monitor: false
+              
/pageviews/insert-aggregate-long/{project}/{access}/{agent}/{granularity}/{timestamp}/{v}:
+                post:
+                  x-request-handler:
+                    - put_to_storage:
+                        request:
+                          method: 'put'
+                          uri: '/{domain}/sys/table/pageviews.per.project.v2/'
+                          body:
+                            table: 'pageviews.per.project.v2'
+                            attributes:
+                              project: '{{request.params.project}}'
+                              access: '{{request.params.access}}'
+                              agent: '{{request.params.agent}}'
+                              granularity: '{{request.params.granularity}}'
+                              timestamp: '{{request.params.timestamp}}'
+                              v: '{{request.params.v}}'
+                  x-monitor: false
 
-      /pageviews/insert-top/{project}/{access}/{year}/{month}/{day}:
-        post:
-          x-request-handler:
-            - put_to_storage:
-                request:
-                  method: 'put'
-                  uri: '/{domain}/sys/table/top.pageviews/'
-                  body:
-                    table: 'top.pageviews'
-                    attributes:
-                      project: '{{request.params.project}}'
-                      access: '{{request.params.access}}'
-                      year: '{{request.params.year}}'
-                      month: '{{request.params.month}}'
-                      day: '{{request.params.day}}'
-                      articlesJSON: '{{request.body.articles}}'
-          x-monitor: false
+              /pageviews/insert-top/{project}/{access}/{year}/{month}/{day}:
+                post:
+                  x-request-handler:
+                    - put_to_storage:
+                        request:
+                          method: 'put'
+                          uri: '/{domain}/sys/table/top.pageviews/'
+                          body:
+                            table: 'top.pageviews'
+                            attributes:
+                              project: '{{request.params.project}}'
+                              access: '{{request.params.access}}'
+                              year: '{{request.params.year}}'
+                              month: '{{request.params.month}}'
+                              day: '{{request.params.day}}'
+                              articlesJSON: '{{request.body.articles}}'
+                  x-monitor: false
 
-      
/legacy/pagecounts/insert-aggregate/{project}/{access-site}/{granularity}/{timestamp}/{count}:
-        post:
-          x-request-handler:
-            - put_to_storage:
-                request:
-                  method: 'put'
-                  uri: '/{domain}/sys/table/lgc.pagecounts.per.project/'
-                  body:
-                    table: 'lgc.pagecounts.per.project'
-                    attributes:
-                      project: '{{request.params.project}}'
-                      'access-site': '{{request.params.access-site}}'
-                      granularity: '{{request.params.granularity}}'
-                      timestamp: '{{request.params.timestamp}}'
-                      count: '{{request.params.count}}'
-          x-monitor: false
+              
/legacy/pagecounts/insert-aggregate/{project}/{access-site}/{granularity}/{timestamp}/{count}:
+                post:
+                  x-request-handler:
+                    - put_to_storage:
+                        request:
+                          method: 'put'
+                          uri: 
'/{domain}/sys/table/lgc.pagecounts.per.project/'
+                          body:
+                            table: 'lgc.pagecounts.per.project'
+                            attributes:
+                              project: '{{request.params.project}}'
+                              'access-site': '{{request.params.access-site}}'
+                              granularity: '{{request.params.granularity}}'
+                              timestamp: '{{request.params.timestamp}}'
+                              count: '{{request.params.count}}'
+                  x-monitor: false
 
-      
/unique-devices/insert/{project}/{access-site}/{granularity}/{timestamp}/{devices}:
-        post:
-          x-request-handler:
-            - put_to_storage:
-                request:
-                  method: 'put'
-                  uri: '/{domain}/sys/table/unique.devices/'
-                  body:
-                    table: 'unique.devices'
-                    attributes:
-                      project: '{{request.params.project}}'
-                      'access-site': '{{request.params.access-site}}'
-                      granularity: '{{request.params.granularity}}'
-                      timestamp: '{{request.params.timestamp}}'
-                      devices: '{{request.params.devices}}'
-          x-monitor: false
+              
/unique-devices/insert/{project}/{access-site}/{granularity}/{timestamp}/{devices}:
+                post:
+                  x-request-handler:
+                    - put_to_storage:
+                        request:
+                          method: 'put'
+                          uri: '/{domain}/sys/table/unique.devices/'
+                          body:
+                            table: 'unique.devices'
+                            attributes:
+                              project: '{{request.params.project}}'
+                              'access-site': '{{request.params.access-site}}'
+                              granularity: '{{request.params.granularity}}'
+                              timestamp: '{{request.params.timestamp}}'
+                              devices: '{{request.params.devices}}'
+                  x-monitor: false
 
   # Fake internal endpoint simulating a druid cluster for testing
   /{api:sys}:
-    swagger: 2.0
-    info:
-      x-is-api-root: true
     x-modules:
-      /fake-druid:
-        - path: test/features/mediawiki-history-metrics/fake-druid.js
+      - spec:
+          # Careful - 2 indentations here !
+          paths:
+            /fake-druid:
+              x-modules:
+                - path: test/features/mediawiki-history-metrics/fake-druid.js
diff --git 
a/test/features/mediawiki-history-metrics/mediawiki-history-metrics.js 
b/test/features/mediawiki-history-metrics/mediawiki-history-metrics.js
index e166b15..a91c1d7 100644
--- a/test/features/mediawiki-history-metrics/mediawiki-history-metrics.js
+++ b/test/features/mediawiki-history-metrics/mediawiki-history-metrics.js
@@ -23,10 +23,8 @@
     this.timeout(20000);
 
     // Start server before running tests
-    before('before-suite', setupDone => {
-        return server.start().then(() => {
-            setupDone();
-        });
+    before('before-suite', () => {
+        return server.start();
     });
 
     var makeTest = function(fixture) {
diff --git a/test/features/pageviews/pageviews.js 
b/test/features/pageviews/pageviews.js
index 95f78b7..f8aca78 100644
--- a/test/features/pageviews/pageviews.js
+++ b/test/features/pageviews/pageviews.js
@@ -40,7 +40,7 @@
     // Start server before running tests
     // insert here data that tests assume exists on db to start working
     before('before-suite', function(setupDone) {
-        return server.start().then(function() {
+        server.start().then(function() {
             const dataToInsert = {
                 2015070200: 100,
                 2015072100: 200,
diff --git a/test/index.js b/test/index.js
index b21a583..c126552 100644
--- a/test/index.js
+++ b/test/index.js
@@ -5,3 +5,8 @@
 require('mocha-jshint')();
 // Run jscs as part of normal testing
 require('mocha-jscs')();
+require('mocha-eslint')([
+    'lib',
+    'sys',
+    'v1'
+]);
\ No newline at end of file
diff --git a/test/utils/run_tests.sh b/test/utils/run_tests.sh
index d6807c1..a471729 100644
--- a/test/utils/run_tests.sh
+++ b/test/utils/run_tests.sh
@@ -11,6 +11,13 @@
         rm -f test.db.sqlite3
     else
         echo "Running with Cassandra backend"
+        if [ `nc -z localhost 9042 < /dev/null; echo $?` != 0 ]; then
+          echo "Waiting for Cassandra to start..."
+          while [ `nc -z localhost 9042; echo $?` != 0 ]; do
+            sleep 1
+          done
+          echo "Cassandra is ready."
+        fi
         export RB_TEST_BACKEND=cassandra
         sh ./test/utils/cleandb.sh
     fi
@@ -28,7 +35,7 @@
     if [ "$?" -eq 0 ]; then
         runTest "cassandra" $1
     else
-        echo "Cassandra not available. Using SQLite backed for tests"
+        echo "Cassandra not available. Using SQLite backend for tests"
         runTest "sqlite" $1
     fi
 elif [ "$2" = "sqlite" ]; then
diff --git a/test/utils/server.js b/test/utils/server.js
index 3ae3240..076902e 100644
--- a/test/utils/server.js
+++ b/test/utils/server.js
@@ -8,7 +8,6 @@
 var fs        = require('fs');
 var assert    = require('./assert');
 var yaml      = require('js-yaml');
-var temp      = require('temp').track();
 
 var hostPort  = 'http://localhost:7231';
 var aqsURL    = hostPort + '/analytics.wikimedia.org/v1';
@@ -21,14 +20,7 @@
             throw new Error('Invalid RB_TEST_BACKEND env variable value. 
Allowed values: "cassandra", "sqlite"');
         }
         if (backendImpl === 'sqlite') {
-            // First, replace the module in all projects and move them to the 
temp directory
-            var tempDir = temp.mkdirSync('tempProjects');
-            fs.readdirSync(__dirname + 
'/../../projects').forEach(function(fileName) {
-                var fileStr = fs.readFileSync(__dirname + '/../../projects/' + 
fileName).toString()
-                        .replace(/restbase\-mod\-table\-cassandra/g, 
'restbase-mod-table-sqlite');
-                fs.writeFileSync(tempDir + '/' + fileName, fileStr);
-            });
-            confString = confString.replace(/projects\//g, tempDir + '/');
+            confString = confString.replace(/backend: cassandra/, "backend: 
sqlite");
         }
     }
     return yaml.safeLoad(confString);

-- 
To view, visit https://gerrit.wikimedia.org/r/384590
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ie7847c1de76cb7e6cc868a1d3b94af68785f86bc
Gerrit-PatchSet: 3
Gerrit-Project: analytics/aqs
Gerrit-Branch: master
Gerrit-Owner: Joal <[email protected]>
Gerrit-Reviewer: Joal <[email protected]>
Gerrit-Reviewer: Milimetric <[email protected]>
Gerrit-Reviewer: Mobrovac <[email protected]>
Gerrit-Reviewer: Ppchelko <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to