├── .gitignore
├── LICENSE
├── README.md
├── examples
└── example_01.js
├── index.js
├── package-lock.json
├── package.json
├── src
├── ApiHelper
│ └── index.js
├── Client
│ └── index.js
├── DimensionFilter
│ └── index.js
├── MetricFilter
│ └── index.js
├── ObjectBuilder
│ └── index.js
├── Request
│ └── index.js
└── ResultParser
│ └── index.js
└── test
├── 01_ApiHelper.js
├── 02_Client.js
└── 03_DimensionFilter.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | usage/
3 | .npm
4 | .env
5 | .nyc_output
6 | key.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 ringoldslescinskis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Simple Google Analytics client for NodeJs
4 |
5 | It should be much easier to retrieve data from the Google Analytics API and this package helps you achieve that. Focus on analyzing the data let it handle the rest.
6 |
7 | This is still very much work in progress so please check back.
8 |
9 | **Note:** Recent v0.3.0 update removed the need to manually create filter objects. Please see the demo.
10 |
11 | ## Down to business
12 |
13 |
14 | Getting the top 10 links is as simple as this:
15 |
16 | ```JavaScript
17 | var analytics = new SimpleGA("./key.json");
18 |
19 | var request = Request()
20 | .select("pagepath","pageviews")
21 | .from(12345678)
22 | .orderDesc("pageviews")
23 | .results(10);
24 |
25 | var data = await analytics.run(request);
26 | ```
27 |
28 | By default, data will be returned as an array of objects in the format below. For the top 10 list the result would look similar to this:
29 |
30 | ```JavaScript
31 | [
32 | {
33 | pagePath: "/",
34 | pageviews: 123
35 | },
36 | ...
37 | ]
38 | ```
39 |
40 | ## What it really is
41 | **node-simple-ga** helps you create and make [Reporting API v4 compliant](https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet) JSON requests in a function-oriented manner, parse the response, and paginate additional requests if requested by the user. Further improvements will be focused on creating requests in a more robust and efficient way.
42 | ## What it won't be
43 | This package is not and will not be a data processing package. Data processing is left up to you - the developer.
44 | ## Installation
45 | To use the package, run:
46 | ```JavaScript
47 | npm i node-simple-ga
48 | ```
49 | Before using the package, you must create and set up a [Service Account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount). You can also watch a video tutorial on [how to set up a Service account](https://www.youtube.com/watch?v=r6cWB0xnOwE). While the title says it's a PHP tutorial, it doesn't really matter because you won't be using PHP anyway. Focus on the account creation and granting read access to the service account.
50 | ## Usage
51 | Typical usage for the script follows the following script:
52 | 1) Require node-simple-ga
53 | 2) Initialize the analytics package by providing a valid service account key (see Installation)
54 | 3) Create a request object
55 | 4) Add metrics and dimensions
56 | 6) Add filters
57 | 7) Specify the sort order
58 | 8) Run the request
59 |
60 | Optionally:
61 | 1) Clone the request object
62 | 2) Make changes to the request object
63 | 3) Run the request
64 |
65 | ## Example
66 | * Return all pages of two views XXXXXXXX and YYYYYYYY who don't have at least 100 pageviews,
67 | * Show page urls, pageviews and users,
68 | * Make sure page url doesn't contain /archive/,
69 | * Load only those entries that came from the US,
70 | * Sort results by the amount of users in a descending order
71 | * Get only the top 100 results from XXXXXXXX
72 | * For view YYYYYYYY also get the amount of sessions
73 |
74 | ```JavaScript
75 | const {
76 | SimpleGA,
77 | Request
78 | } = require("node-simple-ga");
79 |
80 | (async function() {
81 | var analytics = new SimpleGA("./key.json");
82 |
83 | var request1 = Request()
84 | .select("pagepath","pageviews","users")
85 | .from(XXXXXXXX)
86 | .where("pagepath").not().contains("/archive/")
87 | .where("country").is("US")
88 | .where("pageviews").lessThan(101)
89 | .results(100)
90 | .orderDesc("users");
91 |
92 | var request2 = request1.clone()
93 | .select("sessions")
94 | .from(YYYYYYYY)
95 | .everything();
96 |
97 | try {
98 | var [data1, data2] = await Promise.all([
99 | analytics.run(request1),
100 | analytics.run(request2)
101 | ]);
102 | } catch (err) {
103 | console.error(err);
104 | }
105 | })();
106 | ```
107 | Since it's not possible to use negative lookups in Google Analytics (e.g. urls that don't contain something), you must first look up all urls with the thing you want to avoid and then negate the operation (in this case with .not())
108 |
109 | Please note that if you don't specify a date, only the last 7 days, excluding today, will be processed.
110 |
111 | > If a date range is not provided, the default date range is (startDate: current date - 7 days, endDate: current date - 1 day).
112 | > [Source](https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet#ReportRequest.FIELDS)
113 |
114 | ## Reference
115 | When specifying a dimension or a metric, you don't have to add ga: at the beginning and you may enter it in a case insensitive manner; the system will fix it for you i.e. pagepath becomes ga:pagePath when querying Google Analytics.
116 |
117 | Before processing data, you should know the difference between dimensions and metrics. [Read more about it here.](https://support.google.com/analytics/answer/1033861?hl=en)
118 |
119 | ### Request functions
120 |
121 | #### select(*keys*), select([*keys*]), fetch(*keys*), fetch([*keys*])
122 |
123 | Specify in a case-insensitive manner which dimensions and metrics you're going to need. You can pass both, an array or a list of metrics or dimensions. It's useful if you generate metrics dynamically. However, if you pass a custom key, such as a computed metric, it's up to you to ensure it's written correctly.
124 |
125 | ```Javascript
126 | select("pagepath","sessions","users")
127 | ```
128 | is the same as
129 | ```Javascript
130 | select(["pageviews","sessions","users"])
131 | ```
132 |
133 |
134 | #### from(*view*)
135 |
136 | Used to set the [View](https://support.google.com/analytics/answer/2649553?hl=en) you're going to process. You can only specify one view per request.
137 |
138 |
139 | #### where(*keys*), where([*keys*])
140 |
141 | Which key or keys will be filtered. You can specify multiple keys so that the following filter will be applied to all keys. Note that if you specify multiple keys at the same time, an **OR** operation will be applied, meaning one of the filters must be true.
142 |
143 | where() is followed by either not() and/or one of the operator functions. All operator functions accept multiple values they are applied to all selected keys. For example, if you have specified 2 keys and 2 values in the operator, you'll end up having an OR filter with 4 conditions.
144 |
145 | The only exception of the operator functions is the inList() function. All values that are passed in will be passed as an array of strings, since the goal of this function is to determine whether dimension value is in the list. It means that if you specify 2 keys and 3 values in the inList() operator, you'll end up with 2 inList() filters.
146 |
147 | All operator functions are found in the section called **Operator functions**.
148 |
149 |
150 | #### whereDimension(*key*), whereDimensions(*keys*), whereDimensions(*[keys]*)
151 | Which dimension(-s) will be targeted by the filter. If you're using default dimensions, you can use the where() function. Use these functions if you want to target custom dimensions.
152 |
153 |
154 | #### whereMetric(*key*), whereMetrics(*keys*), whereMetrics(*[keys]*)
155 | Which metrics(-s) will be targeted by the filter. If you're using default metrics, you can use the where() function. Use these functions if you want to target a custom metrics.
156 |
157 |
158 | #### orderDesc(*key*)
159 |
160 | Which metric or dimension will be sorted in a descending order.
161 |
162 |
163 | #### orderAsc(*name*)
164 |
165 | Which metric or dimension will be sorted in an ascending order.
166 |
167 |
168 | #### during(*dateFrom, dateTo*), period(*dateFrom, dateTo*)
169 |
170 | What time frame should results be selected from.
171 |
172 | *dateFrom and dateTo must be written in the YYYY-MM-DD format, meaning April 24, 2016 must be written as 2018-04-24.*
173 |
174 |
175 | #### limit(*count*), results(*count*)
176 |
177 | How many results per page should be returned. If you don't specify the amount of results, everything will be returned.
178 |
179 |
180 | #### everything()
181 |
182 | Returns all results. Use on a cloned request that has an amount of results specified.
183 |
184 |
185 | #### clearFilters()
186 |
187 | Removes all filters.
188 |
189 |
190 | #### unselect(*keys*), unselect([*keys*])
191 |
192 | Which keys will be removed from the selection.
193 |
194 |
195 | #### clone()
196 |
197 | Returns an identical copy of the request object.
198 |
199 | ## Operator functions
200 |
201 | #### not()
202 |
203 | Negates current filter
204 | ```Javascript
205 | where("pagetitle","dimension10").not()...
206 | ```
207 |
208 |
209 | #### is(*values*), is([*values*]), equals(*values*), equals([*values*])
210 |
211 | What must the key value be equal to.
212 |
213 |
214 | #### contains(*values*), contains([*values*])
215 |
216 | What must the dimension value contain.
217 |
218 |
219 | #### beginsWith(*values*), beginsWith([*values*])
220 |
221 | What must the dimension value begin with.
222 |
223 |
224 | #### endsWith(*values*), endsWith([*values*])
225 |
226 | What must the dimension value end with.
227 |
228 |
229 | #### inList(*values*), inList([*values*])
230 |
231 | What must the dimension be equal to.
232 | ```Javascript
233 | where("pagetitle","dimension10").inList("apple","orange")
234 | ```
235 | *It means that pageTitle or dimension10 must be equal to either apple or orange.*
236 |
237 |
238 | #### greaterThan(*values*), greaterThan([*values*])
239 |
240 | What must the key value be greater than.
241 |
242 |
243 | #### lessThan(*values*), lessThan([*values*])
244 |
245 | What must the key value be less than.
246 |
247 |
248 | #### matchesRegex(*expressions*), matchesRegex([*expressions*])
249 |
250 | What regular expression must the dimension values match.
251 |
252 | *Please note that Google Analytics allows the use of a limited amount of regular expressions. For example, it doesn't allow negative lookaheads, which is why the not() function must be used.*
253 |
254 | ## Pagination
255 | Google Analytics API returns up to 10'000 results per request, which means there will be cases when you'll have to make additional requests to get more data. However, this also means you might not need all the data and it can be achieved in two ways: filtering or limiting the amount of pages.
256 |
257 | To limit the amount of pages, for example 2 pages (up to 20'000 results, if the result count is not specified), the script will look something like this:
258 |
259 | ```Javascript
260 | var analytics = new SimpleGA("keyLocation");
261 |
262 | // Build the request object
263 | var request = Request()
264 | ...
265 |
266 | var data = await analytics.run(request,{pages: 2});
267 | ```
268 |
269 | Please note that while it is possible to specify the amount of results and the amount of pages, it is not advised to do so, because you might end up hitting the API limits. For example, if you have specified that you need 10 results and that you need 3 pages, you will end up making 3 requests requesting 10 results per request, which means you will get only 30 results. It's not only significantly slower but it's also wasteful.
270 |
271 | It's better to use either results() or specifying the amount of pages.
272 |
273 | ## Transforming results
274 | Version 0.5.0 has the ability to transform the result. For example, if you want to rename the pagePath to url, all you have to do is specify the transformation in the run command.
275 |
276 | ```Javascript
277 | var data = await analytics.run(request,{
278 | rename: {
279 | pagePath: "url"
280 | }
281 | });
282 | ```
283 |
284 | ## Author
285 | Ringolds Leščinskis
286 |
287 | Website: [www.lescinskis.com](https://www.lescinskis.com)
288 |
289 | E-mail: ringolds@lescinskis.com
--------------------------------------------------------------------------------
/examples/example_01.js:
--------------------------------------------------------------------------------
1 | require("dotenv").load();
2 |
3 | const path = require("path");
4 | const { SimpleGA, Request } = require("../index.js");
5 |
6 | (async function() {
7 | var analytics = new SimpleGA(path.join(__dirname, "../key.json"));
8 |
9 | var request1 = Request()
10 | .select("dimension12","pageviews","users")
11 | .from(process.env.GA_VIEW_ID)
12 | .during("2018-09-20","2018-09-25")
13 | .orderDesc("pageviews")
14 | .limit(20);
15 |
16 | var request2 = Request()
17 | .select("dimension12","entrances")
18 | .from(process.env.GA_VIEW_ID)
19 | .during("2018-09-20","2018-09-25")
20 | .orderDesc("entrances")
21 | .limit(20);
22 |
23 | try {
24 |
25 | var r1 = analytics.run(request1,{
26 | rename: {
27 | dimension12: "title"
28 | }
29 | });
30 | var r2 = analytics.run(request2,{
31 | rename: {
32 | dimension12: "title"
33 | }
34 | });
35 |
36 | var [res1,res2] = await Promise.all([
37 | r1,r2
38 | ]);
39 |
40 | console.log({res1,res2});
41 | } catch (err) {
42 | console.error(err);
43 | }
44 | })();
45 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // https://developers.google.com/adsense/management/reporting/relative_dates
2 |
3 | const Client = require("./src/Client");
4 | const Request = require("./src/Request");
5 | const DimensionFilter = require("./src/DimensionFilter");
6 | const MetricFilter = require("./src/MetricFilter");
7 | const ResultParser = require("./src/ResultParser");
8 |
9 | const { google } = require("googleapis");
10 | const cloneDeep = require("clone-deep");
11 |
12 | const SimpleGA = function(param) {
13 | this.client = null;
14 |
15 | if (typeof param === "string") {
16 | this.client = Client.createFromKeyFile(param);
17 | } else {
18 | if (param.key) {
19 | this.client = Client.createFromKey(param.key);
20 | }
21 | if (param.keyFile) {
22 | this.client = Client.createFromKeyFile(param.keyFile);
23 | }
24 | }
25 |
26 | if (!this.client) {
27 | throw Error("Google Analytics client wasn't created!");
28 | }
29 |
30 | this.client.authorize(function(err, tokens) {
31 | if (err) {
32 | throw new Error(err);
33 | }
34 | });
35 |
36 | this.analytics = google.analyticsreporting("v4");
37 | };
38 |
39 | SimpleGA.prototype.runRaw = function(request, params = {}, currentPage = 1) {
40 |
41 | var that = this;
42 | return new Promise(function(resolve, reject) {
43 | var entries = [];
44 | var headers = null;
45 |
46 | that.analytics.reports.batchGet(
47 | {
48 | auth: that.client,
49 | resource: {
50 | reportRequests: [request.make()]
51 | }
52 | },
53 | async function(err, response) {
54 | if (err) {
55 | return reject(err);
56 | }
57 |
58 |
59 | var report = response.data.reports[0];
60 |
61 | if (currentPage == 1) {
62 | var metricColumnHeader = report.columnHeader.metricHeader.metricHeaderEntries.map(function(entry) {
63 | return entry.name;
64 | });
65 |
66 | headers = {
67 | dimensions: ResultParser.cleanKeys(report.columnHeader.dimensions),
68 | metrics: ResultParser.cleanKeys(metricColumnHeader)
69 | };
70 | }
71 |
72 | if(!("rows" in report.data)) {
73 | return resolve({ headers, entries });
74 | }
75 |
76 | report.data.rows.forEach(function(entry) {
77 | entries.push({
78 | dimensions: entry.dimensions,
79 | metrics: entry.metrics[0].values
80 | });
81 | });
82 |
83 | // If we're at the last page, stop
84 | if (params.pages && currentPage === params.pages) {
85 | return resolve({ headers, entries });
86 | }
87 |
88 | // If there are more pages, get the results
89 | if (report.nextPageToken) {
90 | request.pageToken(report.nextPageToken);
91 | var requestedData = await that.runRaw(
92 | request,
93 | {
94 | pages: params.pages
95 | },
96 | currentPage + 1
97 | );
98 | entries = entries.concat(requestedData.entries);
99 | }
100 |
101 | return resolve({ headers, entries });
102 | }
103 | );
104 | });
105 | };
106 |
107 | SimpleGA.prototype.run = function(request, params = {}) {
108 |
109 | var that = this;
110 |
111 | return new Promise(async function(resolve, reject){
112 | if(request.get("pageSize") && !params.pages) {
113 | params.pages = 1;
114 | }
115 |
116 | var result = await that.runRaw(request, params);
117 |
118 | if (params.rawResults) {
119 | return result;
120 | }
121 |
122 | var processedResult = [];
123 |
124 | result.entries.forEach(function(entry) {
125 | let dimensions = ResultParser.mergeKeyValueArrays(result.headers.dimensions, entry.dimensions);
126 | let metrics = ResultParser.mergeKeyValueArrays(result.headers.metrics, entry.metrics);
127 | processedResult.push(Object.assign({},dimensions,metrics));
128 | });
129 |
130 | if(params.rename && Object.keys(params.rename).length > 0 && processedResult.length > 0) {
131 |
132 | let oldKeys = Object.keys(params.rename);
133 | let entry = processedResult[0];
134 |
135 | oldKeys = oldKeys.filter(function(oldKey){
136 | return oldKey in entry;
137 | });
138 |
139 | processedResult = processedResult.map(function(entry){
140 | oldKeys.forEach(function(oldKey){
141 | let newKey = params.rename[oldKey];
142 | entry[newKey] = entry[oldKey];
143 | delete entry[oldKey];
144 | });
145 | return entry;
146 | });
147 | }
148 |
149 | resolve(processedResult);
150 |
151 | });
152 |
153 | };
154 |
155 | module.exports = {
156 | SimpleGA,
157 | Request,
158 | DimensionFilter,
159 | MetricFilter
160 | };
161 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-simple-ga",
3 | "version": "0.5.1",
4 | "description": "A simple to use NodeJs package for the Google Analytics Reporting API",
5 | "keywords": [
6 | "nodejs",
7 | "google analytics",
8 | "ga",
9 | "analytics api",
10 | "google analytics api",
11 | "analytics reporting api v4",
12 | "universal analytics",
13 | "analytics v4",
14 | "reporting api v4",
15 | "google analytics client"
16 | ],
17 | "main": "index.js",
18 | "scripts": {
19 | "test": "node node_modules/nyc/bin/nyc node_modules/mocha/bin/mocha",
20 | "prettier": "prettier --no-config --print-width 120 --parser flow --use-tabs --write *.js **/*.js"
21 | },
22 | "homepage": "https://github.com/lescinskiscom/node-simple-ga#readme",
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/lescinskiscom/node-simple-ga"
26 | },
27 | "author": {
28 | "name": "Ringolds Lescinskis",
29 | "email": "ringolds@lescinskis.com",
30 | "url": "https://www.lescinskis.com"
31 | },
32 | "license": "MIT",
33 | "dependencies": {
34 | "clone-deep": "^4.0.0",
35 | "googleapis": "^66.0.0",
36 | "moment": "^2.22.2"
37 | },
38 | "devDependencies": {
39 | "chai": "^4.1.2",
40 | "dotenv": "^6.0.0",
41 | "mocha": "^8.2.1",
42 | "nyc": "^15.1.0",
43 | "prettier": "^1.13.7"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/ApiHelper/index.js:
--------------------------------------------------------------------------------
1 | var metrics = ["metric1","metric2","metric3","metric4","metric5","metric6","metric7","metric8","metric9","metric10","metric11","metric12","metric13","metric14","metric15","metric16","metric17","metric18","metric19","metric20","users","newUsers","percentNewSessions","1dayUsers","7dayUsers","14dayUsers","28dayUsers","30dayUsers","sessionsPerUser","visitors","newVisits","percentNewVisits","sessions","bounces","bounceRate","sessionDuration","avgSessionDuration","uniqueDimensionCombinations","hits","visits","visitBounceRate","organicSearches","impressions","adClicks","adCost","CPM","CPC","CTR","costPerTransaction","costPerGoalConversion","costPerConversion","RPC","ROAS","ROI","margin","goalXXStarts","goalStartsAll","goalXXCompletions","goalCompletionsAll","goalXXValue","goalValueAll","goalValuePerSession","goalXXConversionRate","goalConversionRateAll","goalXXAbandons","goalAbandonsAll","goalXXAbandonRate","goalAbandonRateAll","goalValuePerVisit","pageValue","entrances","entranceRate","pageviews","pageviewsPerSession","uniquePageviews","timeOnPage","avgTimeOnPage","exits","exitRate","pageviewsPerVisit","contentGroupUniqueViewsXX","searchResultViews","searchUniques","avgSearchResultViews","searchSessions","percentSessionsWithSearch","searchDepth","avgSearchDepth","searchRefinements","percentSearchRefinements","searchDuration","avgSearchDuration","searchExits","searchExitRate","searchGoalXXConversionRate","searchGoalConversionRateAll","goalValueAllPerSearch","searchVisits","percentVisitsWithSearch","pageLoadTime","pageLoadSample","avgPageLoadTime","domainLookupTime","avgDomainLookupTime","pageDownloadTime","avgPageDownloadTime","redirectionTime","avgRedirectionTime","serverConnectionTime","avgServerConnectionTime","serverResponseTime","avgServerResponseTime","speedMetricsSample","domInteractiveTime","avgDomInteractiveTime","domContentLoadedTime","avgDomContentLoadedTime","domLatencyMetricsSample","screenviews","uniqueScreenviews","screenviewsPerSession","timeOnScreen","avgScreenviewDuration","uniqueAppviews","totalEvents","uniqueEvents","eventValue","avgEventValue","sessionsWithEvent","eventsPerSessionWithEvent","visitsWithEvent","eventsPerVisitWithEvent","transactions","transactionsPerSession","transactionRevenue","revenuePerTransaction","transactionRevenuePerSession","transactionShipping","transactionTax","totalValue","itemQuantity","uniquePurchases","revenuePerItem","itemRevenue","itemsPerPurchase","localTransactionRevenue","localTransactionShipping","localTransactionTax","localItemRevenue","buyToDetailRate","cartToDetailRate","internalPromotionCTR","internalPromotionClicks","internalPromotionViews","localProductRefundAmount","localRefundAmount","productAddsToCart","productCheckouts","productDetailViews","productListCTR","productListClicks","productListViews","productRefundAmount","productRefunds","productRemovesFromCart","productRevenuePerPurchase","quantityAddedToCart","quantityCheckedOut","quantityRefunded","quantityRemovedFromCart","refundAmount","revenuePerUser","totalRefunds","transactionsPerUser","transactionsPerVisit","transactionRevenuePerVisit","socialInteractions","uniqueSocialInteractions","socialInteractionsPerSession","socialInteractionsPerVisit","userTimingValue","userTimingSample","avgUserTimingValue","exceptions","exceptionsPerScreenview","fatalExceptions","fatalExceptionsPerScreenview","metricXX","dcmFloodlightQuantity","dcmFloodlightRevenue","dcmCPC","dcmCTR","dcmClicks","dcmCost","dcmImpressions","dcmROAS","dcmRPC","dcmMargin","adsenseRevenue","adsenseAdUnitsViewed","adsenseAdsViewed","adsenseAdsClicks","adsensePageImpressions","adsenseCTR","adsenseECPM","adsenseExits","adsenseViewableImpressionPercent","adsenseCoverage","adxImpressions","adxCoverage","adxMonetizedPageviews","adxImpressionsPerSession","adxViewableImpressionsPercent","adxClicks","adxCTR","adxRevenue","adxRevenuePer1000Sessions","adxECPM","dfpImpressions","dfpCoverage","dfpMonetizedPageviews","dfpImpressionsPerSession","dfpViewableImpressionsPercent","dfpClicks","dfpCTR","dfpRevenue","dfpRevenuePer1000Sessions","dfpECPM","backfillImpressions","backfillCoverage","backfillMonetizedPageviews","backfillImpressionsPerSession","backfillViewableImpressionsPercent","backfillClicks","backfillCTR","backfillRevenue","backfillRevenuePer1000Sessions","backfillECPM","cohortActiveUsers","cohortAppviewsPerUser","cohortAppviewsPerUserWithLifetimeCriteria","cohortGoalCompletionsPerUser","cohortGoalCompletionsPerUserWithLifetimeCriteria","cohortPageviewsPerUser","cohortPageviewsPerUserWithLifetimeCriteria","cohortRetentionRate","cohortRevenuePerUser","cohortRevenuePerUserWithLifetimeCriteria","cohortSessionDurationPerUser","cohortSessionDurationPerUserWithLifetimeCriteria","cohortSessionsPerUser","cohortSessionsPerUserWithLifetimeCriteria","cohortTotalUsers","cohortTotalUsersWithLifetimeCriteria","correlationScore","queryProductQuantity","relatedProductQuantity","dbmCPA","dbmCPC","dbmCPM","dbmCTR","dbmClicks","dbmConversions","dbmCost","dbmImpressions","dbmROAS","dsCPC","dsCTR","dsClicks","dsCost","dsImpressions","dsProfit","dsReturnOnAdSpend","dsRevenuePerClick"];
2 | var dimensions = ["dimension1","dimension2","dimension3","dimension4","dimension5","dimension6","dimension7","dimension8","dimension9","dimension10","dimension11","dimension12","dimension13","dimension14","dimension15","dimension16","dimension17","dimension18","dimension19","dimension20","userType","sessionCount","daysSinceLastSession","userDefinedValue","userBucket","visitorType","visitCount","sessionDurationBucket","visitLength","referralPath","fullReferrer","campaign","source","medium","sourceMedium","keyword","adContent","socialNetwork","hasSocialSourceReferral","campaignCode","adGroup","adSlot","adDistributionNetwork","adMatchType","adKeywordMatchType","adMatchedQuery","adPlacementDomain","adPlacementUrl","adFormat","adTargetingType","adTargetingOption","adDisplayUrl","adDestinationUrl","adwordsCustomerID","adwordsCampaignID","adwordsAdGroupID","adwordsCreativeID","adwordsCriteriaID","adQueryWordCount","isTrueViewVideoAd","goalCompletionLocation","goalPreviousStep1","goalPreviousStep2","goalPreviousStep3","browser","browserVersion","operatingSystem","operatingSystemVersion","mobileDeviceBranding","mobileDeviceModel","mobileInputSelector","mobileDeviceInfo","mobileDeviceMarketingName","deviceCategory","browserSize","dataSource","continent","subContinent","country","region","metro","city","latitude","longitude","networkDomain","networkLocation","cityId","continentId","countryIsoCode","metroId","regionId","regionIsoCode","subContinentCode","flashVersion","javaEnabled","language","screenColors","sourcePropertyDisplayName","sourcePropertyTrackingId","screenResolution","socialActivityContentUrl","hostname","pagePath","pagePathLevel1","pagePathLevel2","pagePathLevel3","pagePathLevel4","pageTitle","landingPagePath","secondPagePath","exitPagePath","previousPagePath","pageDepth","landingContentGroupXX","previousContentGroupXX","contentGroupXX","searchUsed","searchKeyword","searchKeywordRefinement","searchCategory","searchStartPage","searchDestinationPage","searchAfterDestinationPage","appInstallerId","appVersion","appName","appId","screenName","screenDepth","landingScreenName","exitScreenName","eventCategory","eventAction","eventLabel","transactionId","affiliation","sessionsToTransaction","daysToTransaction","productSku","productName","productCategory","currencyCode","checkoutOptions","internalPromotionCreative","internalPromotionId","internalPromotionName","internalPromotionPosition","orderCouponCode","productBrand","productCategoryHierarchy","productCategoryLevelXX","productCouponCode","productListName","productListPosition","productVariant","shoppingStage","visitsToTransaction","socialInteractionNetwork","socialInteractionAction","socialInteractionNetworkAction","socialInteractionTarget","socialEngagementType","userTimingCategory","userTimingLabel","userTimingVariable","exceptionDescription","experimentId","experimentVariant","dimensionXX","customVarNameXX","customVarValueXX","date","year","month","week","day","hour","minute","nthMonth","nthWeek","nthDay","nthMinute","dayOfWeek","dayOfWeekName","dateHour","dateHourMinute","yearMonth","yearWeek","isoWeek","isoYear","isoYearIsoWeek","nthHour","dcmClickAd","dcmClickAdId","dcmClickAdType","dcmClickAdTypeId","dcmClickAdvertiser","dcmClickAdvertiserId","dcmClickCampaign","dcmClickCampaignId","dcmClickCreativeId","dcmClickCreative","dcmClickRenderingId","dcmClickCreativeType","dcmClickCreativeTypeId","dcmClickCreativeVersion","dcmClickSite","dcmClickSiteId","dcmClickSitePlacement","dcmClickSitePlacementId","dcmClickSpotId","dcmFloodlightActivity","dcmFloodlightActivityAndGroup","dcmFloodlightActivityGroup","dcmFloodlightActivityGroupId","dcmFloodlightActivityId","dcmFloodlightAdvertiserId","dcmFloodlightSpotId","dcmLastEventAd","dcmLastEventAdId","dcmLastEventAdType","dcmLastEventAdTypeId","dcmLastEventAdvertiser","dcmLastEventAdvertiserId","dcmLastEventAttributionType","dcmLastEventCampaign","dcmLastEventCampaignId","dcmLastEventCreativeId","dcmLastEventCreative","dcmLastEventRenderingId","dcmLastEventCreativeType","dcmLastEventCreativeTypeId","dcmLastEventCreativeVersion","dcmLastEventSite","dcmLastEventSiteId","dcmLastEventSitePlacement","dcmLastEventSitePlacementId","dcmLastEventSpotId","userAgeBracket","userGender","interestOtherCategory","interestAffinityCategory","interestInMarketCategory","visitorAgeBracket","visitorGender","acquisitionCampaign","acquisitionMedium","acquisitionSource","acquisitionSourceMedium","acquisitionTrafficChannel","cohort","cohortNthDay","cohortNthMonth","cohortNthWeek","channelGrouping","correlationModelId","queryProductId","queryProductName","queryProductVariation","relatedProductId","relatedProductName","relatedProductVariation","dbmClickAdvertiser","dbmClickAdvertiserId","dbmClickCreativeId","dbmClickExchange","dbmClickExchangeId","dbmClickInsertionOrder","dbmClickInsertionOrderId","dbmClickLineItem","dbmClickLineItemId","dbmClickSite","dbmClickSiteId","dbmLastEventAdvertiser","dbmLastEventAdvertiserId","dbmLastEventCreativeId","dbmLastEventExchange","dbmLastEventExchangeId","dbmLastEventInsertionOrder","dbmLastEventInsertionOrderId","dbmLastEventLineItem","dbmLastEventLineItemId","dbmLastEventSite","dbmLastEventSiteId","dsAdGroup","dsAdGroupId","dsAdvertiser","dsAdvertiserId","dsAgency","dsAgencyId","dsCampaign","dsCampaignId","dsEngineAccount","dsEngineAccountId","dsKeyword","dsKeywordId"];
3 |
4 |
5 | module.exports = (function() {
6 | var metricDimensionNames = {};
7 | var metricDimensionData = {};
8 |
9 | var helper = {
10 | generateApiName: function(name) {
11 | return `ga:${name}`;
12 | },
13 | sortMetricsDimensions: function(values) {
14 | var that = this;
15 |
16 | var result = {
17 | metrics: [],
18 | dimensions: []
19 | }
20 |
21 | values.forEach(function(value){
22 | value = that.fixName(value);
23 | result[metricDimensionData[value]].push(value);
24 | });
25 |
26 | return result;
27 | },
28 | fixName: function(name) {
29 | name = name.toLowerCase();
30 | if(!(name in metricDimensionNames)) {
31 | return name;
32 | }
33 | return metricDimensionNames[name];
34 | },
35 | register: function(id, name, type) {
36 | metricDimensionNames[id] = name;
37 | metricDimensionData[name] = type;
38 | },
39 | registerMetric: function(name) {
40 | this.register(name.toLowerCase(),name,"metrics");
41 | },
42 | registerDimension: function(name) {
43 | this.register(name.toLowerCase(),name,"dimensions");
44 | }
45 | };
46 |
47 | for(metric of metrics) {
48 | helper.registerMetric(metric);
49 | }
50 |
51 | for(dimension of dimensions) {
52 | helper.registerDimension(dimension);
53 | }
54 |
55 | return helper;
56 | })()
--------------------------------------------------------------------------------
/src/Client/index.js:
--------------------------------------------------------------------------------
1 | var { google } = require("googleapis");
2 | var fs = require("fs");
3 |
4 | module.exports = {
5 | createFromKeyFile: function(path) {
6 | return this.createFromKey(JSON.parse(fs.readFileSync(path, "utf8")));
7 | },
8 | createFromKey: function(key) {
9 | var params = {
10 | email: key.client_email,
11 | privateKey: key.private_key,
12 | permissions: ["https://www.googleapis.com/auth/analytics.readonly"]
13 | };
14 | return this.createFromParams(params);
15 | },
16 | createFromParams: function(params) {
17 | return new google.auth.JWT(params.email, null, params.privateKey, params.permissions, null);
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/src/DimensionFilter/index.js:
--------------------------------------------------------------------------------
1 | // https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet
2 |
3 | var ObjectBuilder = require("../ObjectBuilder");
4 | var ApiHelper = require("../ApiHelper");
5 |
6 | var DimensionFilter = function() {};
7 |
8 | DimensionFilter.prototype = Object.create(new ObjectBuilder());
9 |
10 | DimensionFilter.prototype.condition = function(operator = "EXACT", value) {
11 | this.set("operator", operator);
12 | return this.set("expressions", value);
13 | };
14 |
15 | DimensionFilter.prototype.dimension = function(name) {
16 | name = ApiHelper.generateApiName(name);
17 | return this.set("dimensionName", name);
18 | };
19 |
20 | DimensionFilter.prototype.not = function() {
21 | return this.set("not", true);
22 | };
23 |
24 | module.exports = DimensionFilter;
--------------------------------------------------------------------------------
/src/MetricFilter/index.js:
--------------------------------------------------------------------------------
1 | var ObjectBuilder = require("../ObjectBuilder");
2 | var ApiHelper = require("../ApiHelper");
3 |
4 | var MetricFilter = function() {};
5 |
6 | MetricFilter.prototype = Object.create(new ObjectBuilder());
7 |
8 | MetricFilter.prototype.metric = function(name) {
9 | name = ApiHelper.generateApiName(name);
10 | return this.set("metricName", name);
11 | };
12 |
13 | MetricFilter.prototype.condition = function(operator = "EQUAL", value) {
14 | this.set("operator", operator);
15 | return this.set("comparisonValue", value.toString());
16 | };
17 |
18 | MetricFilter.prototype.not = function() {
19 | return this.set("not", true);
20 | };
21 |
22 | module.exports = MetricFilter;
--------------------------------------------------------------------------------
/src/ObjectBuilder/index.js:
--------------------------------------------------------------------------------
1 | const cloneDeep = require("clone-deep");
2 |
3 | var ObjectBuilder = function() {
4 | this.data = {};
5 | };
6 |
7 | ObjectBuilder.prototype.reset = function() {
8 | this.data = {};
9 | return this;
10 | };
11 |
12 | ObjectBuilder.prototype.set = function(key, value) {
13 | if (value == null) {
14 | return this.clear(key);
15 | }
16 |
17 | var obj = {};
18 | var selectedObj = obj;
19 |
20 | key.split(".").forEach(function(fragment, i, array) {
21 | selectedObj[fragment] = {};
22 | if (i === array.length - 1) {
23 | selectedObj[fragment] = value;
24 | }
25 | selectedObj = selectedObj[fragment];
26 | });
27 |
28 | this.data = { ...this.data, ...obj };
29 | return this;
30 | };
31 |
32 | ObjectBuilder.prototype.get = function(key) {
33 | return this.data[key] ? this.data[key] : null;
34 | };
35 |
36 | ObjectBuilder.prototype.append = function(key, ...values) {
37 | if (!this.data[key]) {
38 | this.data[key] = [];
39 | }
40 | values = this.getValues(values);
41 |
42 | values.forEach(
43 | function(value) {
44 | this.data[key].push(value);
45 | }.bind(this)
46 | );
47 |
48 | return this;
49 | };
50 |
51 | ObjectBuilder.prototype.getValues = function(values) {
52 | if (!values) {
53 | return [];
54 | }
55 | if (values.length === 0) {
56 | return [];
57 | }
58 |
59 | if (values.length === 1 && Array.isArray(values[0])) {
60 | values = values[0];
61 | }
62 |
63 | return values;
64 | };
65 |
66 | ObjectBuilder.prototype.remove = function(key, param, ...values) {
67 | if (!this.data[key]) {
68 | return this;
69 | }
70 |
71 | this.getValues(values).forEach(
72 | function(value) {
73 | this.data[key] = this.data[key].filter(function(entry) {
74 | return entry[param] !== value;
75 | });
76 | }.bind(this)
77 | );
78 |
79 | if (this.data.length == 0) {
80 | this.clear(key);
81 | }
82 |
83 | return this;
84 | };
85 |
86 | ObjectBuilder.prototype.clear = function(key) {
87 | if (!key) {
88 | return this.reset();
89 | }
90 | delete this.data[key];
91 | return this;
92 | };
93 |
94 | ObjectBuilder.prototype.make = function() {
95 | return JSON.parse(JSON.stringify(this.data));
96 | };
97 |
98 | ObjectBuilder.prototype.clone = function(type, filter) {
99 | return cloneDeep(this);
100 | };
101 |
102 | module.exports = ObjectBuilder;
103 |
--------------------------------------------------------------------------------
/src/Request/index.js:
--------------------------------------------------------------------------------
1 | // https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet
2 | // https://developers.google.com/analytics/devguides/reporting/core/dimsmets
3 |
4 | var ObjectBuilder = require("../ObjectBuilder");
5 | var ApiHelper = require("../ApiHelper");
6 |
7 | var DimensionFilter = require("../DimensionFilter");
8 | var MetricFilter = require("../MetricFilter");
9 | var moment = require("moment");
10 |
11 | var metricFilterList = [];
12 | var dimensionFilterList = [];
13 | var notFilter = false;
14 |
15 | var Request = function() {};
16 |
17 | Request.prototype = Object.create(new ObjectBuilder());
18 |
19 | const makeDimensionObject = function(name) {
20 | return { name };
21 | };
22 |
23 | const makeHistogramObject = function(name, histogramBuckets = null) {
24 | var obj = makeDimensionObject(name);
25 | if (histogramBuckets) {
26 | obj.histogramBuckets = histogramBuckets;
27 | }
28 | return obj;
29 | };
30 |
31 | const makeMetricObject = function(name, type = "INTEGER") {
32 | return {
33 | expression: name,
34 | formattingType: type ? type : "INTEGER"
35 | };
36 | };
37 |
38 | const makeFiltersObject = function(filters, operator = "OR") {
39 | return {
40 | operator,
41 | filters: filters.map(function(filter) {
42 | return filter.make();
43 | })
44 | };
45 | };
46 |
47 | Request.prototype.from = function(viewId) {
48 | return this.view(viewId);
49 | }
50 |
51 | Request.prototype.view = function(viewId) {
52 | viewId = ApiHelper.generateApiName(viewId);
53 | return this.set("viewId", viewId);
54 | };
55 |
56 | Request.prototype.pageSize = function(size) {
57 | return this.set("pageSize", size);
58 | };
59 |
60 | Request.prototype.limit = function(count) {
61 | return this.pageSize(count);
62 | };
63 |
64 | Request.prototype.results = function(count) {
65 | return this.limit(count);
66 | };
67 |
68 |
69 | Request.prototype.everything = function() {
70 | return this.pageSize(null);
71 | };
72 |
73 | Request.prototype.pageToken = function(token) {
74 | return this.set("pageToken", token);
75 | };
76 |
77 | Request.prototype.removePageToken = function(token) {
78 | return this.pageToken(null);
79 | };
80 |
81 | Request.prototype.offset = function(value) {
82 | return this.pageToken(value);
83 | };
84 |
85 | Request.prototype.removeOffset = function(value) {
86 | return this.pageToken(null);
87 | };
88 |
89 | Request.prototype.sample = function(size) {
90 | return this.set("samplingLevel", size.toUpperCase());
91 | };
92 |
93 | Request.prototype.fast = function() {
94 | return this.sample("SMALL");
95 | };
96 |
97 | Request.prototype.precise = function() {
98 | return this.sample("LARGE");
99 | };
100 |
101 | // Date functions
102 |
103 | Request.prototype.today = function() {
104 | var now = moment().format("YYYY-MM-DD");
105 | return this.set("dateRanges", [{
106 | startDate: now,
107 | endDate: now
108 | }]);
109 | }
110 |
111 | Request.prototype.yesterday = function() {
112 | var now = moment().format("YYYY-MM-DD").subtract(1,"day");
113 | return this.set("dateRanges", [{
114 | startDate: now,
115 | endDate: now
116 | }]);
117 | }
118 |
119 | Request.prototype.thisWeek = function() {
120 | var startDate = moment().startOf('isoWeek').format("YYYY-MM-DD");
121 | var endDate = moment().format("YYYY-MM-DD");
122 | return this.set("dateRanges", [{
123 | startDate,
124 | endDate
125 | }]);
126 | }
127 |
128 | Request.prototype.lastWeek = function() {
129 | var weekStart = moment().startOf('isoWeek');
130 | var startDate = moment(weekStart).subtract(7,"days").format("YYYY-MM-DD");
131 | var endDate = moment(weekStart).subtract(1,"day").format("YYYY-MM-DD");
132 | return this.set("dateRanges", [{
133 | startDate,
134 | endDate
135 | }]);
136 | }
137 |
138 | Request.prototype.period = function(from, to) {
139 | return this.set("dateRanges", [{
140 | startDate: from,
141 | endDate: to
142 | }]);
143 | };
144 |
145 | Request.prototype.during = function(from, to) {
146 | return this.period(from,to);
147 | }
148 |
149 | Request.prototype.periods = function(...params) {
150 | // clear previous dates
151 | this.set("dateRanges",[]);
152 | params.forEach(function(param){
153 | if(Array.isArray(param) && param.length == 2) {
154 | this.period(param[0],param[1]);
155 | return;
156 | }
157 | this.period(param);
158 | });
159 | return this;
160 | };
161 |
162 | Request.prototype.dimension = function(dimension) {
163 | dimension = ApiHelper.generateApiName(dimension);
164 | dimension = makeDimensionObject(dimension);
165 | return this.append("dimensions", dimension);
166 | };
167 |
168 | Request.prototype.histogram = function(dimension, histogramBuckets = null) {
169 | histogramBuckets = histogramBuckets.forEach(function(bucket) {
170 | return bucket.toString();
171 | });
172 | dimension = ApiHelper.generateApiName(dimension);
173 | dimension = makeHistogramObject(dimension, histogramBuckets);
174 | return this.set("dimensions", [dimension]);
175 | };
176 |
177 | Request.prototype.dimensions = function(...values) {
178 | values = this.getValues(values);
179 | values = values.map(ApiHelper.generateApiName);
180 | values = values.map(makeDimensionObject);
181 | return this.append("dimensions", values);
182 | };
183 |
184 | Request.prototype.fetch = function(...values) {
185 | values = this.getValues(values);
186 |
187 | values = ApiHelper.sortMetricsDimensions(values);
188 |
189 | if(values.metrics.length > 0) {
190 | this.metrics(values.metrics);
191 | }
192 |
193 | if(values.dimensions.length > 0) {
194 | this.dimensions(values.dimensions);
195 | }
196 |
197 | return this;
198 | };
199 |
200 | Request.prototype.select = function(...values) {
201 | return this.fetch(values);
202 | }
203 |
204 | Request.prototype.clearDimensions = function() {
205 | return this.clear("dimensions");
206 | };
207 |
208 | Request.prototype.removeDimension = function(name) {
209 | name = ApiHelper.generateApiName(name);
210 | return this.remove("dimensions", "name", name);
211 | };
212 |
213 | Request.prototype.removeDimensions = function(...values) {
214 | var that = this;
215 | values = this.getValues(values);
216 | values.forEach(function(value){
217 | that.removeDimension(value)
218 | })
219 | return this;
220 | };
221 |
222 | Request.prototype.unselect = function(...keys) {
223 | keys = this.getValues(keys);
224 |
225 | keys = ApiHelper.sortMetricsDimensions(keys);
226 |
227 | if(keys.metrics.length > 0) {
228 | this.removeMetrics(keys.metrics);
229 | }
230 |
231 | if(keys.dimensions.length > 0) {
232 | this.removeDimensions(keys.dimensions);
233 | }
234 |
235 | return this;
236 | }
237 |
238 | Request.prototype.metric = function(name) {
239 | name = ApiHelper.generateApiName(name);
240 | return this.append("metrics", makeMetricObject(name));
241 | };
242 |
243 | Request.prototype.metricDesc = function(name) {
244 | return this.metric(name).orderDesc(name);
245 | };
246 |
247 | Request.prototype.metricAsc = function(name) {
248 | return this.metric(name).orderAsc(name);
249 | };
250 |
251 | Request.prototype.dimensionDesc = function(name) {
252 | return this.dimension(name).orderDesc(name);
253 | };
254 |
255 | Request.prototype.dimensionAsc = function(name) {
256 | return this.dimension(name).orderAsc(name);
257 | };
258 |
259 | Request.prototype.metrics = function(...values) {
260 | values = this.getValues(values);
261 | values = values.map(ApiHelper.generateApiName);
262 | values = values.map(function(value){
263 | return makeMetricObject(value);
264 | });
265 | return this.append("metrics", values);
266 | };
267 |
268 | Request.prototype.metricInt = function(name) {
269 | return this.metric(name, "INTEGER");
270 | };
271 |
272 | Request.prototype.nameFloat = function(name) {
273 | return this.metric(name, "FLOAT");
274 | };
275 |
276 | Request.prototype.nameCurrency = function(name) {
277 | return this.metric(name, "CURRENCY");
278 | };
279 |
280 | Request.prototype.namePercent = function(name) {
281 | return this.metric(name, "PERCENT");
282 | };
283 |
284 | Request.prototype.nameTime = function(name) {
285 | return this.metric(name, "TIME");
286 | };
287 |
288 | Request.prototype.clearMetrics = function() {
289 | return this.clear("metrics");
290 | };
291 |
292 | Request.prototype.removeMetric = function(name) {
293 | name = ApiHelper.generateApiName(name);
294 | return this.remove("metrics", "expression", name);
295 | };
296 |
297 | Request.prototype.removeMetrics = function(...values) {
298 | var that = this;
299 | values = this.getValues(values);
300 | values.forEach(function(value){
301 | that.removeMetric(value)
302 | })
303 | return this;
304 | };
305 |
306 | Request.prototype.filtersExpression = function(expression) {
307 | return this.set("filtersExpression", expression);
308 | };
309 |
310 | Request.prototype.orderBy = function(params) {
311 | return this.append("orderBys", {
312 | fieldName: ApiHelper.generateApiName(params.name),
313 | orderType: params.type ? params.type : "VALUE",
314 | sortOrder: params.order ? params.order : "DESCENDING"
315 | });
316 | };
317 |
318 | Request.prototype.orderAsc = function(name) {
319 | return this.orderBy({
320 | name: name,
321 | order: "ASCENDING"
322 | });
323 | };
324 |
325 | Request.prototype.orderDesc = function(name) {
326 | return this.orderBy({
327 | name: name
328 | });
329 | };
330 |
331 | Request.prototype.orderHistogram = function(name) {
332 | return this.orderBy({
333 | name: name,
334 | type: "HISTOGRAM_BUCKET"
335 | });
336 | };
337 |
338 | Request.prototype.removeOrder = function(name) {
339 | name = ApiHelper.generateApiName(name);
340 | return this.remove("orderBys", "fieldName", name);
341 | };
342 |
343 | Request.prototype.removeOrders = function(...values) {
344 | values = this.getValues(values);
345 | values = values.map(ApiHelper.generateApiName);
346 | return this.remove("orderBys", "fieldName", values);
347 | };
348 |
349 | Request.prototype.filter = function(type, filter) {
350 | return this.filters(type, [filter]);
351 | };
352 |
353 | Request.prototype.filters = function(type, filters) {
354 | filters = makeFiltersObject(filters,"AND");
355 | return this.append(type, filters);
356 | };
357 |
358 | Request.prototype.orFilters = function(type, filters) {
359 | filters = makeFiltersObject(filters);
360 | return this.append(type, filters);
361 | };
362 |
363 |
364 | Request.prototype.metricFilter = function(filter) {
365 | return this.filter("metricFilterClauses", filter);
366 | };
367 |
368 | Request.prototype.metricFilters = function(filters) {
369 | return this.filters("metricFilterClauses", filters);
370 | };
371 |
372 | Request.prototype.metricOrFilters = function(filters) {
373 | return this.orFilters("metricFilterClauses", filters);
374 | };
375 |
376 | Request.prototype.dimensionFilter = function(filter) {
377 | return this.filter("dimensionFilterClauses", filter);
378 | };
379 |
380 | Request.prototype.dimensionFilters = function(filters) {
381 | return this.filters("dimensionFilterClauses", filters);
382 | };
383 |
384 | Request.prototype.dimensionOrFilters = function(filters) {
385 | return this.orFilters("dimensionFilterClauses", filters);
386 | };
387 |
388 | Request.prototype.clearDimensionFilters = function() {
389 | return this.clear("dimensionFilterClauses");
390 | };
391 |
392 | Request.prototype.clearMetricFilters = function() {
393 | return this.clear("metricFilterClauses");
394 | };
395 |
396 | Request.prototype.clearFilters = function() {
397 | this.clearDimensionFilters();
398 | this.clearMetricFilters();
399 | return this;
400 | };
401 |
402 | Request.prototype.where = function(...values) {
403 | values = this.getValues(values);
404 | values = ApiHelper.sortMetricsDimensions(values);
405 | metricFilterList = values.metrics;
406 | dimensionFilterList = values.dimensions;
407 | notFilter = false;
408 | return this;
409 | }
410 |
411 | Request.prototype.whereDimensions = function(...values) {
412 | values = this.getValues(values);
413 | dimensionFilterList = values;
414 | notFilter = false;
415 | return this;
416 | }
417 |
418 | Request.prototype.whereDimension = function(value) {
419 | dimensionFilterList = [value];
420 | notFilter = false;
421 | return this;
422 | }
423 |
424 | Request.prototype.whereMetrics = function(...values) {
425 | values = this.getValues(values);
426 | metricFilterList = values;
427 | notFilter = false;
428 | return this;
429 | }
430 |
431 | Request.prototype.whereMetric = function(value) {
432 | metricFilterList = [values];
433 | notFilter = false;
434 | return this;
435 | }
436 |
437 | Request.prototype.not = function() {
438 | notFilter = true;
439 | return this;
440 | }
441 |
442 | Request.prototype.filterConditions = function(values, dimensionCondition=null, metricCondition=null) {
443 |
444 | if(dimensionCondition && dimensionFilterList.length > 0) {
445 | var dimensionValues = values;
446 | if(dimensionCondition === "IN_LIST") {
447 | dimensionValues = [dimensionValues];
448 | }
449 | var filters = [];
450 | dimensionFilterList.forEach(function(name){
451 | dimensionValues.forEach(function(value){
452 | var f = new DimensionFilter()
453 | .dimension(name)
454 | // All values must be converted to a string
455 | .condition(dimensionCondition, Array.isArray(value) ? value : [value.toString()])
456 | if(notFilter) {
457 | f.not();
458 | }
459 | filters.push(f);
460 | });
461 | });
462 | if(filters.length == 1) {
463 | this.dimensionFilter(filters[0]);
464 | } else {
465 | this.dimensionOrFilters(filters);
466 | }
467 | dimensionFilterList = [];
468 | }
469 |
470 | if(metricCondition && metricFilterList.length > 0) {
471 | var metricValues = values;
472 | var filters = [];
473 | metricFilterList.forEach(function(name){
474 | metricValues.forEach(function(value){
475 | var f = new MetricFilter()
476 | .metric(name)
477 | // All values must be converted to a string
478 | .condition(metricCondition,value.toString())
479 | if(notFilter) {
480 | f.not();
481 | }
482 | filters.push(f);
483 | });
484 | });
485 | if(filters.length == 1) {
486 | this.metricFilter(filters[0]);
487 | } else {
488 | this.metricOrFilters(filters);
489 | }
490 | metricFilterList = [];
491 | }
492 |
493 | return this;
494 | }
495 |
496 | Request.prototype.equals = function(...values) {
497 | return this.filterConditions(values, "EXACT", "EQUAL");
498 | }
499 |
500 | Request.prototype.is = function(...values) {
501 | return this.equals(values);
502 | }
503 |
504 | Request.prototype.matchesRegex = function(...values) {
505 | return this.filterConditions(values, "REGEXP");
506 | }
507 |
508 | Request.prototype.beginsWith = function(...values) {
509 | return this.filterConditions(values, "BEGINS_WITH");
510 | }
511 |
512 | Request.prototype.endsWith = function(...values) {
513 | return this.filterConditions(values, "ENDS_WITH");
514 | }
515 |
516 | Request.prototype.contains = function(...values) {
517 | return this.filterConditions(values, "PARTIAL");
518 | }
519 |
520 | Request.prototype.greaterThan = function(...values) {
521 | return this.filterConditions(values, "NUMERIC_GREATER_THAN", "GREATER_THAN");
522 | }
523 |
524 | Request.prototype.lessThan = function(...values) {
525 | return this.filterConditions(values, "NUMERIC_LESS_THAN", "LESS_THAN");
526 | }
527 |
528 | Request.prototype.inList = function(...values) {
529 | return this.filterConditions(values, "IN_LIST");
530 | }
531 |
532 | Request.prototype.registerMetric = function(name) {
533 | ApiHelper.registerMetric(name);
534 | return this;
535 | }
536 |
537 | Request.prototype.registerDimension = function(name) {
538 | ApiHelper.registerDimension(name);
539 | return this;
540 | }
541 |
542 | module.exports = function() {
543 | return new Request().clone().results(10000);
544 | }
--------------------------------------------------------------------------------
/src/ResultParser/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | cleanKeys: function(columns) {
3 | return columns.map(function(column) {
4 | return column.replace(/^ga:/, "");
5 | });
6 | },
7 | castValue: function(value) {
8 | var newValue = Number(value);
9 |
10 | if (isNaN(newValue)) {
11 | return value;
12 | }
13 |
14 | return newValue;
15 | },
16 | mergeKeyValueArrays: function(keys, values) {
17 | var that = this;
18 | return Object.assign.apply(
19 | {},
20 | keys.map((v, i) => ({
21 | [v]: that.castValue(values[i])
22 | }))
23 | );
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/test/01_ApiHelper.js:
--------------------------------------------------------------------------------
1 | const should = require("chai").should();
2 |
3 | const ApiHelper = require("../src/ApiHelper");
4 |
5 | function randomString() {
6 | return Math.random()
7 | .toString(36)
8 | .substr(2, 5);
9 | }
10 |
11 | describe("ApiHelper", function() {
12 | var str = randomString();
13 | it("should prepend '" + str + "' with ga:", function(done) {
14 | ApiHelper.generateApiName(str).should.equal("ga:" + str);
15 | done();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/test/02_Client.js:
--------------------------------------------------------------------------------
1 | const should = require("chai").should();
2 | const fs = require("fs");
3 |
4 | const Client = require("../src/Client");
5 |
6 | describe("Client", function() {
7 | it("should create a client from key file", function(done) {
8 | var cl = Client.createFromKeyFile("./key.json");
9 | cl.constructor.name.should.equal("JWT");
10 | done();
11 | });
12 | var key = JSON.parse(fs.readFileSync("./key.json", "utf8"));
13 | it("should create a client from key", function(done) {
14 | var cl = Client.createFromKey(key);
15 | cl.constructor.name.should.equal("JWT");
16 | done();
17 | });
18 | it("should create a client from params", function(done) {
19 | var cl = Client.createFromParams({
20 | email: key.client_email,
21 | privateKey: key.private_key,
22 | permissions: ["https://www.googleapis.com/auth/analytics.readonly"]
23 | });
24 | cl.constructor.name.should.equal("JWT");
25 | done();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/03_DimensionFilter.js:
--------------------------------------------------------------------------------
1 | const should = require("chai").should();
2 | const fs = require("fs");
3 |
4 | const DimensionFilter = require("../src/DimensionFilter");
5 |
6 | function randomString() {
7 | return Math.random()
8 | .toString(36)
9 | .substr(2, 5);
10 | }
11 |
12 | function randomNumber() {
13 | return Math.floor(Math.random() * 10000);
14 | }
15 |
16 | describe("DimensionFilter", function() {
17 | var str = randomString();
18 | it("should have operator equal to " + str, function(done) {
19 | var filter = new DimensionFilter().condition("", str).make();
20 | filter.operator.should.equal(str);
21 | done();
22 | });
23 | var str = randomString();
24 | it("should have expressions equal to ['" + str + "']", function(done) {
25 | var filter = new DimensionFilter().condition(str, "").make();
26 | filter.expressions.should.eql([str]);
27 | done();
28 | });
29 | var str = randomString();
30 | it("should have dimensionName equal to ga:" + str, function(done) {
31 | var filter = new DimensionFilter().dimension(str).make();
32 | filter.dimensionName.should.equal("ga:" + str);
33 | done();
34 | });
35 | it("should have not equal to true", function(done) {
36 | var filter = new DimensionFilter().not().make();
37 | filter.not.should.equal(true);
38 | done();
39 | });
40 | it("should have not equal to true", function(done) {
41 | var filter = new DimensionFilter().inverse().make();
42 | filter.not.should.equal(true);
43 | done();
44 | });
45 | describe("operator", function() {
46 | it("should have operator equal to REGEXP", function(done) {
47 | var filter = new DimensionFilter().matchRegex("").make();
48 | filter.operator.should.equal("REGEXP");
49 | done();
50 | });
51 | it("should have operator equal to BEGINS_WITH", function(done) {
52 | var filter = new DimensionFilter().beginsWith("").make();
53 | filter.operator.should.equal("BEGINS_WITH");
54 | done();
55 | });
56 | it("should have operator equal to ENDS_WITH", function(done) {
57 | var filter = new DimensionFilter().endsWith("").make();
58 | filter.operator.should.equal("ENDS_WITH");
59 | done();
60 | });
61 | it("should have operator equal to PARTIAL", function(done) {
62 | var filter = new DimensionFilter().contains("").make();
63 | filter.operator.should.equal("PARTIAL");
64 | done();
65 | });
66 | it("should have operator equal to EXACT", function(done) {
67 | var filter = new DimensionFilter().is("").make();
68 | filter.operator.should.equal("EXACT");
69 | done();
70 | });
71 | it("should have operator equal to EXACT", function(done) {
72 | var filter = new DimensionFilter().matches("").make();
73 | filter.operator.should.equal("EXACT");
74 | done();
75 | });
76 | it("should have operator equal to NUMERIC_EQUAL", function(done) {
77 | var filter = new DimensionFilter().equalsTo("").make();
78 | filter.operator.should.equal("NUMERIC_EQUAL");
79 | done();
80 | });
81 | it("should have operator equal to NUMERIC_EQUAL", function(done) {
82 | var filter = new DimensionFilter().eq("").make();
83 | filter.operator.should.equal("NUMERIC_EQUAL");
84 | done();
85 | });
86 | it("should have operator equal to NUMERIC_GREATER_THAN", function(done) {
87 | var filter = new DimensionFilter().greaterThan("").make();
88 | filter.operator.should.equal("NUMERIC_GREATER_THAN");
89 | done();
90 | });
91 | it("should have operator equal to NUMERIC_GREATER_THAN", function(done) {
92 | var filter = new DimensionFilter().gt("").make();
93 | filter.operator.should.equal("NUMERIC_GREATER_THAN");
94 | done();
95 | });
96 | it("should have operator equal to NUMERIC_LESS_THAN", function(done) {
97 | var filter = new DimensionFilter().lessThan("").make();
98 | filter.operator.should.equal("NUMERIC_LESS_THAN");
99 | done();
100 | });
101 | it("should have operator equal to NUMERIC_LESS_THAN", function(done) {
102 | var filter = new DimensionFilter().lt("").make();
103 | filter.operator.should.equal("NUMERIC_LESS_THAN");
104 | done();
105 | });
106 | it("should have operator equal to IN_LIST", function(done) {
107 | var filter = new DimensionFilter().inList("").make();
108 | filter.operator.should.equal("IN_LIST");
109 | done();
110 | });
111 | });
112 |
113 | describe("expressions", function() {
114 | var str = randomString();
115 | it("should equal to ['" + str + "']", function(done) {
116 | var filter = new DimensionFilter().matchRegex(str).make();
117 | filter.expressions.should.eql([str]);
118 | done();
119 | });
120 | it("should equal to ['" + str + "']", function(done) {
121 | var filter = new DimensionFilter().beginsWith(str).make();
122 | filter.expressions.should.eql([str]);
123 | done();
124 | });
125 | it("should equal to ['" + str + "']", function(done) {
126 | var filter = new DimensionFilter().endsWith(str).make();
127 | filter.expressions.should.eql([str]);
128 | done();
129 | });
130 | it("should equal to ['" + str + "']", function(done) {
131 | var filter = new DimensionFilter().contains(str).make();
132 | filter.expressions.should.eql([str]);
133 | done();
134 | });
135 | it("should equal to ['" + str + "']", function(done) {
136 | var filter = new DimensionFilter().is(str).make();
137 | filter.expressions.should.eql([str]);
138 | done();
139 | });
140 | it("should equal to ['" + str + "']", function(done) {
141 | var filter = new DimensionFilter().matches(str).make();
142 | filter.expressions.should.eql([str]);
143 | done();
144 | });
145 | var num = randomNumber();
146 | it("should equal to ['" + num + "']", function(done) {
147 | var filter = new DimensionFilter().equalsTo(num).make();
148 | filter.expressions.should.eql([num.toString()]);
149 | done();
150 | });
151 | it("should equal to ['" + num + "']", function(done) {
152 | var filter = new DimensionFilter().eq(num).make();
153 | filter.expressions.should.eql([num.toString()]);
154 | done();
155 | });
156 | it("should equal to ['" + num + "']", function(done) {
157 | var filter = new DimensionFilter().greaterThan(num).make();
158 | filter.expressions.should.eql([num.toString()]);
159 | done();
160 | });
161 | it("should equal to ['" + num + "']", function(done) {
162 | var filter = new DimensionFilter().gt(num).make();
163 | filter.expressions.should.eql([num.toString()]);
164 | done();
165 | });
166 | it("should equal to ['" + (num - 1) + "']", function(done) {
167 | var filter = new DimensionFilter().greaterThanEqualTo(num).make();
168 | filter.expressions.should.eql([(num - 1).toString()]);
169 | done();
170 | });
171 | it("should equal to ['" + (num - 1) + "']", function(done) {
172 | var filter = new DimensionFilter().gte(num).make();
173 | filter.expressions.should.eql([(num - 1).toString()]);
174 | done();
175 | });
176 | it("should equal to ['" + num + "']", function(done) {
177 | var filter = new DimensionFilter().lessThan(num).make();
178 | filter.expressions.should.eql([num.toString()]);
179 | done();
180 | });
181 | it("should equal to ['" + num + "']", function(done) {
182 | var filter = new DimensionFilter().lt(num).make();
183 | filter.expressions.should.eql([num.toString()]);
184 | done();
185 | });
186 | it("should equal to ['" + (num + 1) + "']", function(done) {
187 | var filter = new DimensionFilter().lessThanEqualTo(num).make();
188 | filter.expressions.should.eql([(num + 1).toString()]);
189 | done();
190 | });
191 | it("should equal to ['" + (num + 1) + "']", function(done) {
192 | var filter = new DimensionFilter().lte(num).make();
193 | filter.expressions.should.eql([(num + 1).toString()]);
194 | done();
195 | });
196 | it("should equal to ['1','2','3']", function(done) {
197 | var filter = new DimensionFilter().inList([1, 2, 3]).make();
198 | filter.expressions.should.eql(["1", "2", "3"]);
199 | done();
200 | });
201 | it("should equal to ['1','2','3']", function(done) {
202 | var filter = new DimensionFilter().inList(1, 2, 3).make();
203 | filter.expressions.should.eql(["1", "2", "3"]);
204 | done();
205 | });
206 | });
207 |
208 | it("should have caseSensitive equal to true", function(done) {
209 | var filter = new DimensionFilter().caseSensitive().make();
210 | filter.caseSensitive.should.equal(true);
211 | done();
212 | });
213 | });
214 |
--------------------------------------------------------------------------------