Interactive Web Analytics Reporting Dashboard for Enterprise Businesses. Integrating Google’s Web tools for a one-stop reporting interface
Apostolos Gouvalas
SID: 3306160002
SCHOOL OF SCIENCE & TECHNOLOGY A thesis submitted for the degree of Master of Science (MSc) in Mobile and Web Computing
DECEMBER 2017 THESSALONIKI – GREECE
Interactive Web Analytics Reporting Dashboard for Enterprise Businesses. Integrating Google’s Web tools for a one-stop reporting interface
Apostolos Gouvalas
SID: 3306160002
Supervisor: Dr. Christos Berberidis
SCHOOL OF SCIENCE & TECHNOLOGY A thesis submitted for the degree of Master of Science (MSc) in Mobile and Web Computing
DECEMBER 2017 THESSALONIKI – GREECE
Abstract
The aim of this dissertation was to develop, using cutting-edge technologies, a visual analytics dashboard for enterprise businesses, utilizing the Analytics and Real Time Re- porting APIs from Google APIs. Moreover, we had to present the data from all web prop- erties of a user’s Google Analytics account in order to give them a better overview across all their tracking properties, while maintaining a user-friendly environment.
Apostolos Gouvalas 18 Dec 2017
-i- Acknowledgements
I would like first to thank my dissertation supervisor Dr. Christos Berberidis of School of Science at International Hellenic University for providing guidance when needed. I would also like to thank the collaborative company, iTrust, for providing the thesis subject and specially their lead developer Mpampis Sykovaridis for guiding me regard- ing the technologies I utilized in the development process and for giving me constructive feedback when I present them the final product. Next, I would like to thank my parents for supporting me both financially and emo- tionally with my decision to study at this master and throughout my life in general. Last but not least, I would not have achieved any of this without my girlfriend Chrysa, who was supporting and encouraging me from the very beginning and throughout the year in order to overcome any obstacles.
-ii- Contents
ABSTRACT ...... I
ACKNOWLEDGEMENTS ...... II
CONTENTS ...... III
1 INTRODUCTION ...... 1
1.1 THE PROBLEM ...... 2
1.2 SOLUTION ...... 2
1.3 OVERVIEW OF NEXT CHAPTERS ...... 3
2 LITERATURE REVIEW ...... 5
3 UTILIZED TECHNOLOGIES ...... 9
3.1 BACK-END ...... 10 3.1.1 Node.js ...... 10 3.1.2 npm & Yarn ...... 11 3.1.3 Express.js ...... 12 3.1.4 Passport.js ...... 12 3.1.5 Mongoose.js ...... 12 3.1.6 Socket.io ...... 13 3.1.7 Googleapis ...... 13 3.1.8 Request ...... 13 3.1.9 Express-session ...... 13 3.1.10 Lodash ...... 13 3.1.11 Morgan ...... 13 3.1.12 Concurrently ...... 14
3.2 FRONT-END ...... 14 3.2.1 React ...... 14 3.2.2 React Router ...... 15 3.2.3 Redux ...... 15 3.2.4 React-sparklines ...... 15 3.2.5 Redux-form ...... 15 3.2.6 Moment.js ...... 15 3.2.7 React-widgets ...... 15
-iii- 3.2.8 MaterializeCSS ...... 16 3.2.9 Font Awesome ...... 16 3.2.10 Particle.js – React Component ...... 16
4 IMPLEMENTATION ...... 17
4.1 ARCHITECTURE HIGH-LEVEL OVERVIEW ...... 17
4.2 ITRALYTICS BACK-END ...... 18 4.2.1 Authentication Related Route Handlers ...... 18 4.2.2 Google Analytics Related Route Handlers ...... 22 4.2.2.1 requireLogin Middleware ...... 23 4.2.2.2 validateAccessToken Middleware ...... 23 4.2.2.3 refreshAccessToken Middleware ...... 24 4.2.2.4 GET /api/analytics/accountSummary ...... 25 4.2.2.5 GET /api/analytics/reportsBatchGet ...... 27 4.2.3 Socket.io & Google Analytics Real Time Reporting API ...... 28
4.3 ITRALYTICS FRONT-END ...... 30 4.3.1 Initial crate-react-app setup ...... 31 4.3.2 Single Page Application ...... 34 4.3.3 Connection to our API & displays ...... 36 4.3.3.1 Log-in ...... 36 4.3.3.2 Fetching User’s Google Analytics Web Properties ...... 37 4.3.3.3 Initialize ChartsList Component ...... 40 4.3.3.4 Display Analytics for All User’s Web Properties ...... 40 4.3.3.5 Get Real-time Analytics data ...... 41
5 FUTURE WORK ...... 45
6 CONCLUSIONS ...... 47
BIBLIOGRAPHY ...... 49
APPENDICES ...... 55
Appendix 1: List of Web Analytics tools [54] ...... 55 Appendix 2: Back-end & font-end technologies list...... 56 Appendix 3: iTrAlytics Back-end ...... 57 Appendix 4: iTrAlytics Front-end Screens ...... 61
-iv- List of Figures
Figure 1: Google Analytics – Hierarchy of accounts ...... 5 Figure 2: 2016 Stack Overflow developer survey ...... 9 Figure 3: 2017 Stack Overflow developer survey ...... 9 Figure 4: Application technologies overview ...... 18 Figure 5: Enable Google APIs and get credentials ...... 19 Figure 6: Google OAuth Credentials ...... 20 Figure 7: Running two servers on development process...... 32 Figure 8: Forward requests to our Express server...... 33 Figure 9: iTrAlytics React components overview...... 35 Figure 10: How Redux works ...... 38 Figure 11: Access to store with React-Redux Provider component ...... 39 Figure 12: Traditional features of web analytics tools analyzed by Ivan Bekavac and Daniela Garbin Praničević ...... 55 Figure 13: Passport.js Google strategy. Retrieve or Save a user to the database...... 57 Figure 14: Route handler for starting the OAuth process ...... 57 Figure 15: Route handler after user give permission to access his/her Google data...... 58 Figure 16: Route handler for logging out users ...... 58 Figure 17: Route handler for fetching users web properties information ...... 59 Figure 18: Route handler for fetching Google Analytics data ...... 60 Figure 19: iTrAlytics landing page ...... 61 Figure 20: iTrAlytics Google OAuth ...... 62 Figure 21: iTrAlytics after log-in ...... 62 Figure 22: iTrAlytics dashboard initial view ...... 63 Figure 23: iTrAlytics dashboard Pageviews/Week analytics ...... 64 Figure 24: iTrAlytics dashboard Real-time analytics ...... 65
-v- List of Tables
Table 1: List of technologies used in back-end & front-end ...... 56
-vi- 1 Introduction
There are more than 3.78 billion internet users [1] [2], as of this writing, and this mandates to all kind of businesses, regardless of their size, kind of operations or place of origin, to have an internet presence, in order for them to grow in our ever-evolving digital era. Putting your business on the web not only makes it possible for new clients/customers to get to know your business but also, for already customers to keep up with your updates and offers which can help your business grow loyal customers. It does not matter if your main business operations are not online, as only the fact that you have a corporate website with some valuable information for your services, offers and location are enough to en- gage new customers and help grow your business. In the other case, that your main busi- ness operations are online, it comes without saying that a strong online presence is essen- tial. However, since there are so many internet users from all around the world, it comes to the business owner to somehow identify critical information about these users. Infor- mation such as where the users are coming from, in order for instance to have multilingual support to the website, or for e-shop owners to support multiple currencies. Other infor- mation could be related to the browser or the device the users are using to access the corporate website, which can dictate the development of the website to support specific browsers or make it responsive to different device widths and heights. Such useful information about your users led to the development of web analytics tools, such as the Google Analytics. According to the Digital Analytics Association, “Web Analytics is the measurement, collection, analysis and reporting of Internet data for the purposes of understanding and optimizing Web usage” [3]. While, nowadays there are numerous web analytics tools (see Figure 12 in Appendix 1) with different functionalities, measures, capabilities, requirements and of course pric- ing, we will make use of the data we can get from the Google Analytics API and develop our own web analytics dashboard to present them. Google Analytics is the major platform for getting and viewing web analytics data and is used by all kind of businesses.
-1- 1.1 The Problem Google, offers two variations of Google Analytics: Google Analytics, which is free, and Google Analytics 360, which is more suitable for enterprises and comes with more capa- bilities and functionality than the free version, but also comes with a monthly fee. The free version is widely used both by small to medium companies and large corporations, as well as individual site owners, not only because it is a free product, powered by a giant tech firm, but also because it is a powerful tool, with many capabilities and insights for your users. However, many newcomers or not so tech savvy people may find the Google Analytics platform somehow overwhelming. There is too much information in the default Google Analytics dashboard and this led to the demand of developing software that can simplify the presentation of the information [4]. Another need that the free Google Analytics does not cover is when we have more than one websites and we would like to have an overview of all our websites in one dash- board. Google Analytics offers insights for a specific website at a time and it only offers aggregated overview of all your websites in the Google Analytics 360 suite, by letting you set up a so called “Roll Up account” [5].
1.2 Solution To tackle the two aforementioned problems, we developed our own web application, us- ing modern web development technologies and utilizing the Google APIs. Thus, iTraLyt- ics was born. The approach we took in iTraLytics to solve the abovementioned problems was to query the user’s Google Analytics account and ask for all their declared websites. Then, we make queries for all retrieved websites in order to get the data we want, and we present them to the user in simple line charts, also known as sparklines [6]. With this approach, the user can have a quick overview for key metrics, about their online visitors across all their websites. Furthermore, following a similar methodology, we created a second dashboard which presents real time data about active users in any of the user’s websites, which we also categorize them according to the device they use to access the website whether it be from desktop, tablet or mobile device.
-2- 1.3 Overview of Next Chapters In the following chapter, we will review the google analytics account structure and how the presentation of the data lead to our problem regarding the view of multiple websites and also, we will make a quick review to existing software about visual analytic dash- boards. Next, we will present the technologies we used for our web application and in the fourth chapter we will have a look at the implementation level, meaning that we are going to see what architecture we used for our development process and core concepts and fea- tures of the app. In the end, we will propose some additions for the application, as future work and we will conclude the dissertation with a sum up of all chapters.
-3-
2 Literature Review
To better understand the needs of our application a review was made both on our web analytics provider, which in our case is Google Analytics (free edition), and on existing software, that are used by enterprise companies for visualizing their web analytics data. To begin with, we mentioned that one of the problems we will try to solve is the way we will present the data from Google Analytics when a user has many properties in their account. A Google Analytics account is structured like the following figure:
Figure 1: Google Analytics – Hierarchy of accounts
First, a user must have an account so as to get access to Analytics. Then, on this account, the user can add properties. A property, in the Google Analytics is a website, a device or a mobile application that the user wishes to get analytics data for. How a user handles the relationship between his/her account and the properties is up to him/her. The user can create one account with only one property or can have one account with many properties. Finally, the views are his/her access point to a report. The user defines specific views for a specific property [7].
-5- The problem we will try to solve rises from the fact that, when a user is having more than one properties in his/her account, he/she can view only one “view”, which as we said is tied to one property. In our implementation we will change the view part. In iTrAlytics, our view will be comprised of the views of all our declared properties, giving us a greater overview across all our sites. The idea to develop our own web application for presenting web analytics data in a dashboard is not something new. In fact, there is a plethora of software available in the market that trying to offer enterprise businesses an easy way to overview their data in beautiful dashboards. While we cannot do an exhaustive review of all such software, we will however mention some of the most used and well-funded one’s. Before we begin our review, it is worth mentioning that our application cannot com- pete with such software as they are made by big development teams and they also get big sums of funding from corporate businesses. By developing our own application, we do not try to make a market ready product, but rather we focus on solving the aforementioned problems and use a modern technology stack for our development process. According to a Tracxn1 2016 report about BI and analytics, over $1060M were in- vested in visualization tools from which the $726M was for visual analytics tools [8]. As we can see, the visualization of analytics data is a multi-million industry and corporate companies, that are interested in using such tools, are willing to make big investments. Some of the tools in this category that are quite popular are the Klipfolio, Cyfe and the Domo which, according to the same abovementioned report, received the most funds, reaching $458.6M from various investors [8]. The software we mentioned are used by companies of various sizes and industries. They all have some things in common, including the ease of connection with various data sources ranging from Google Analytics and Marketo, to databases and local files. After the data connection, they provide customizable dashboards to display valuable infor- mation to the end-user. Specifically, Klipfolio is a cloud-hosted real-time dashboard that is accessible via web and mobile. It offers 100s of data services connectors that are built for ease of use by their clients. After a data connection, Klipfolio will automatically retrieve the data and will
1 Tracxn provides information for startups in venture capital, private equity and corporate development
-6- provide a customizable dashboard to the end-user which will lead them to critical decision making by monitoring their data [9, 10]. Cyfe, provides a cloud-based service enabling its users to monitor and share business data from a single location in real-time. It offers similar ease in terms of data connectivity and thus, allows its clients to monitor everything from sales and web analytics, to mar- keting campaigns and custom business data [11, 12]. Finally, Domo is developed with similar, to both aforementioned software, purpose in mind which is to help businesses grow and support them in valuable and critical deci- sion making by monitoring their data. From the previous software, Domo defers in that it offers even more options for data connectivity out of the box with over 450 connectors. Furthermore, as we already saw, it is a platform which received a huge amount of funding by companies such as eBay, Google and Facebook [13, 14]. While we did not get into much detail in the aforesaid software, as it is not needed for our purpose, we got the main idea, which is to offer an easy way to their clients to connect their data to the individual platform and then the platform will structure the data for a visual feedback to the client. This whole process is critical for businesses, since in our digital world the amount of data we produce changes every second and it is easy to lose valuable information.
-7-
3 Utilized Technologies iTralytics behind the scenes utilizes plenty of JavaScript technologies so as to offer its end-users the right data about the users of their websites via Google Analytics and, at the same time, a great user experience. A lot of factors led us to consider JavaScript as the main programming language of the iTrAlytics web application. According to Stack Overflow 2016 & 2017 developer surveys, JavaScript is the num- ber one programming language among developers [15] [16].
Figure 2: 2016 Stack Overflow developer sur- Figure 3: 2017 Stack Overflow developer sur- vey vey
Moreover, JavaScript was undertaken a major update at June of 2015 with the 6th edition gone public. This version gave the language new and powerful features, like destructur- ing, classes, arrow functions and more [17] [18]. Furthermore, Node.js, which it will be discussed a bit later, made it possible for us, to run JavaScript on the server. In addition, we are now at 2017 and front-end JavaScript frameworks like Angular, React, Vue.js, just to name a few, are getting more and more attention from the developers and companies who are looking at modern web development processes [19].
-9- More or less, all the aforementioned reasons, led us to pick up JavaScript as the main development language, as with the approach we took, by having a Node.js back-end and rendering the front-end with React, we ended up using JavaScript across all our applica- tion, either in the front-end or the back-end. Having the same language for the whole app, just made the whole process a lot easier in terms of debugging, development and main- tainability. Followingly, we are going to see every technology and JavaScript package we made use of in iTrAlytics development process, by separating them in back-end and front-end. Only the utility library Lodash, which was used both in the front-end and the back-end, it will be described once in the back-end section. For a full list of the technologies used, with URLs to the official sites, see Table 1: List of technologies used in back-end & front- end at appendix 2.
3.1 Back-end
3.1.1 Node.js Node.js is the base of our back-end environment and is the framework which allows us to run JavaScript outside of the browser. Node.js feature set allows for system file ma- nipulation, create and remove folders, query databases directly or even create web servers using Node.js. In general, it gives capabilities of more “popular” languages, like Java, PHP or python. From the official nodejs.org, Node.js is “a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.” [20]. To better understand this, we are going to explain this definition, about what Node.js is. The first part of the definition refers to the fact that both JavaScript code written either on Node.js or Chrome browser are using the same V8 JavaScript engine. Just for refer- ence, the V8 engine is used inside Node.js and Google’s Chrome browser and is an open source JavaScript runtime engine which is written in C++ and takes JavaScript code and compiles it to machine code, making our applications really fast [21] [22]. The second part of the definition conveys much more information, so we will break it into smaller parts. To begin with, I/O refers to the communication from Node.js appli- cation to other things inside of internet of things, like a read or write request to a database,
-10- a file manipulation on our file system or when making an HTTP request to a separate web server like the Google Analytics API for fetching user activity on our website. The non- blocking I/O means that, for instance, while one user is requesting a URL from google, other users can be requesting to read a file from database. They can be requesting all sort of things without preventing anyone else from getting some work done. Finally, the event- driven part refers to the event-driven programming in which events, such as a key press from the keyboard, a mouse click, a message from a function or a response from an API, determine the flow of the program. Node.js behind the scenes utilizes an event loop to handle events, but we are not going to see details into that, as is out of the scope of this dissertation [21] [22].
3.1.2 npm & Yarn Node.js makes it possible to run JavaScript code outside of browser and this led to the development of new node-based tools or also known as packages, which we, developers, make use of, to help us in the web development process. Tools such as grunt and gulp with which we can automate time consuming tasks, like auto-compiling sass code to css, minify JavaScript and many more are accessible to us because of Node.js. However, so as to make use of these tools, we need another tool which can facilitate the installation process, the updating process to newer versions and also keep track of currently installed packages in our projects. This tool is the npm, which stands for node package manager, and is a package manager for JavaScript and the default package man- ager of Node.js. From the official documentation of npm “npm is a way to reuse code from other developers, and also a way to share your code with them, and it makes it easy to manage the different versions of code.” [23] [24]. Currently, npm registry holds 475,000 packages of free and reusable code [24]. Yarn is a newer package manager for JavaScript made by engineers on Facebook and in collaboration with Exponent, Google and Tilde [25]. Yarn was developed after engi- neers at these companies tried to solve problems with consistency, security and perfor- mance they faced upon using npm in large codebases and teams. We made use of Yarn to install all our packages for the development of iTrAlytics.
-11- 3.1.3 Express.js The official website describes Express.js as “a minimal and flexible Node.js web appli- cation framework that provides a robust set of features for web and mobile applications” [26]. The minimal aspect of Express is what makes it appealing to a lot of developers. Min- imal does not mean that it is not useful or lucking features, but only that Express offers the minimum layer between us and the server [27]. Express is also flexible and lets us use its functionality as needed, as we can replace whatever does not fit our needs. Other frameworks take the opposite approach by having strict rules that we have to follow and making it difficult to remove unwanted functional- ity or even alter it [27]. Finally, the web application framework part refers to the functionality the Express offers, which among other it offers simple routing handling, session management and ease of templating with mustache, EJS, etc. in order to build single-page or multi-page and hybrid web applications [27]. Express makes use of special functions, which are called middlewares and have ac- cess to the request (req) and response (res) object as well as the next middleware function (usually denoted with a variable named next) in the application’s request-response cycle [28]. We can also write our own middlewares or install packages that offer middlewares for specific purposes, like the Passport package that we will see next.
3.1.4 Passport.js Passport does one thing and it does it right. It authenticates requests. Passport is an au- thentication middleware that has general helpers for handling authentication in Express apps. Due to the varying methods of authentication one app can have, Passport has au- thentication mechanisms for specific methods, like email/password, Google OAuth, Fa- cebook OAuth, etc. as individual packages which are known as strategies [29]. In iTrA- lytics we make use of the passport-google-oauth20 strategy for handling OAuth 2.0 au- thentication with Google.
3.1.5 Mongoose.js MongoDB is quite often the database of choice with Node.js due to their shared use of JavaScript and the popularity of JSON as a data format for web APIs. Mongoose is an
-12- object data modeling (ODM) library which offers built-in ways to define, maintain and validate data structures and models and use them to interact with the DB [30].
3.1.6 Socket.io Socket.io is a powerful JavaScript framework for real-time bidirectional event-based communication. Socket.io provides a server and a client library for making real-time up- dates between a browser client and a web server [31, 32]. We make use of socket.io to automatically make real-time queries in the Google Real-time Analytics API.
3.1.7 Googleapis Googleapis or google-api-nodejs-client is the official Node.js client library for accessing Google APIs. This package also offers authorization and authentication with OAuth 2.0, API keys and JWT [33]. We make use of this package to make calls to the Google Real- time Analytics API.
3.1.8 Request This package simplifies the way we make HTTP requests by providing easy to use and remember methods for common HTTP methods, like put, get, post, etc. [34]. We make use of request package to make requests to Google APIs.
3.1.9 Express-session Express-session is a middleware for handling session in Express apps. We use it in com- bination to the connect-mongo package which handles the session store to a MongoDB [35, 36].
3.1.10 Lodash Lodash is a popular JavaScript utility library which offers helpful functions when working with arrays, numbers, objects, etc. [37]. We make use of Lodash library both in the back- end and the front-end and mainly when iterating through arrays.
3.1.11 Morgan Morgan is an Express middleware that logs HTTP requests and we use it on the develop- ment environment of our application to know when we get a request from the front-end client or when we make a request to the Google APIs [38].
-13- 3.1.12 Concurrently Concurrently package let us run commands from our package.json file and we use it in the development environment to run simultaneously both the back-end express server and the front-end webpack server, which is part of the create-react-app we will see shortly [39].
3.2 Front-end
3.2.1 React React is not a full featured framework like Angular or ember, but rather it is a library for building composable user interfaces. React encourages the creation of highly reusable UI components, such as comment boxes, pop up modals, sortable tables, etc. Each compo- nent has each own functionality, and we can nest components to build complex UIs. Even- tually, we end up with a web page which is comprised of multiple components. This makes our code more readable and maintainable, as each React component encloses the relevant, to it, HTML and JavaScript functionality. Apart from that, React is really good when data change over time. When a component is first initialized, its render method is called, which generates the view. When our data change, the render method is called again, which will make the specific component that handles this data to re-render its view [40]. However, getting started development with React can be time consuming and some- how troublesome, as for the moment we need some extra configuration to build React applications. Namely, we need a compiler, like Babel, which will translate our modern JavaScript code into code that will also work on older browsers. Furthermore, we need a bundler as well, like webpack or Browserify which will handle the task of bundling our modular code into one minified package so as to optimize load times [41]. For us developers, in order to speed up our development process we can make use of create-react-app. Create-react-app is a package which is backed up by the developers of React at Facebook and let us create React applications without caring about the build configuration as create-react-app takes care of that. In addition, it gives us some other useful functionalities, like meaningful error messages, test ready environment and more [42].
-14- 3.2.2 React Router React router is a navigational component and helps us declare the routing of our applica- tion. Routing takes place as our application is rendering and then we declare our routes in a composable way throughout our app like any other component [43].
3.2.3 Redux Redux is “predictable state container for JavaScript apps” [44]. State container means a collection of all the data that describes the app. With Redux, we have a centralized state object which holds all our application data. Redux is a standalone, lightweight library which can be used with Ember, Angular, React, jQuery or vanilla JavaScript.
3.2.4 React-sparklines This package is a React component for making beautiful and expressive Sparkline charts [6] [45].
3.2.5 Redux-form Redux-form makes a lot of work behind the scenes for us, which intends to have form data flowing into our redux application state. Practically, this mean we can do things like instant validation, enable/disable, hide/show component on form input change, or submit. Moreover, redux-form keeps track for the values of each field, if a field is focused or not, and even if the user has interacted with a field or not [46].
3.2.6 Moment.js Moment.js is a JavaScript library that makes working with Date object a lot easier. Addi- tionally, this library provides a very useful set of functions to work with Date objects, like date localization, different display formats, date validators and more [47].
3.2.7 React-widgets React widgets offers a set of form input react components that are extensible and easy to use [48]. In our application, we make use of react-widgets datepicker and dropdown com- ponents.
-15- 3.2.8 MaterializeCSS MaterializeCSS is a CSS framework based on Material Design and we used it throughout our app to style and handle the layout changes [49].
3.2.9 Font Awesome Font Awesome is an iconic font and CSS toolkit. By using it, we can display in our ap- plication scalable vector icons which we can customize with the power of CSS [50].
3.2.10 Particle.js – React Component Finally, for the spectacular particle interaction at the landing page of the app, we made use of the Particle.js – React Component, which is a wrapper for the original Particle.js library made by Vincent Garreau. This library permits us to create customizable particles interactions [51, 52].
-16- 4 Implementation
Now that we have an overview of the technologies we used in iTrAlytics, we are going to present how we fit them together in order to produce the final application. At this point it is worth mentioning that we took the extra step to also deploy our application to the Heroku platform. Thus, we went through all steps of the development lifecycle of a modern web application. We setup a local development environment and made the necessary steps aiming to deploy our application to a production environment like Heroku. Although we will not get into details how Heroku works, it is enough to know that it is a cloud Platform as a Service, meaning that we do not have to worry about the infra- structure and thus we can focus on our app. Heroku offers a free plan which we used to deploy iTrAlytics. Furthermore, Heroku provides a command line interface so as to in- teract with the platform and makes it very easy to deploy our application by pushing our Git code.
4.1 Architecture High-Level overview In the following figure (Figure 4) we observe an overview of how our application works. When a user enters the iTrAlytics URL into their browser, we are going to respond to them by sending an HTML document with a bundle JavaScript file that contains our React app application. While a user interacts with the front-end React app, we want to show them some information. This information is handled from the back-end of our application. The React app will communicate with our own Express API, which will handle all the communication with Google APIs and the Mongo database. The React app will never directly communicate with either the Google APIs or the Mongo database, where we store our user information and Google Analytics profile information. For this purpose, an API has been set up to deal with this. The communication between our front-end and the Ex- press API is done via HTTP request which return some amount of data in JSON format.
-17-
Figure 4: Application technologies overview
Next, we are going to see how things work in the back-end and afterwards we will move on to the front-end.
4.2 iTrAlytics Back-end Our back-end is powered by Node.js and Express. We run a node server on a specific port of our system (e.g: http://localhost:5000 in our development environment), which can receive requests and send back responses. Then, Express looks at the requests and if we have set up a specific Route Handler that matches the request, Express will handle it and send back a response to whomever made the initial request.
4.2.1 Authentication Related Route Handlers The first Route Handler we wrote was for signing in users with Google OAuth. app.get( '/auth/google', passport.authenticate('google', { scope: [ 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/analytics', 'https://www.googleapis.com/auth/analytics.edit' ], accessType: 'offline', approvalPrompt: 'force'
-18- }) );
The above sample code will handle any request made to our server with this specific route: http:localhost:5000/auth/google. We mentioned in the previous chapter that we are using the Passport.js library to han- dle authentication, alongside the google strategy of Passport.js which will instruct Pass- port.js how to handle Google OAuth. In order to be able to use Google OAuth, we need first to get a client ID and a client secret from the Google OAuth service.
Figure 5: Enable Google APIs and get credentials
By visiting the https://console.developers.google.com page, we can enable the google APIs that our app will make use of and also get the essential API credentials for the OAuth process. In Figure 5, we first (see No.1) ensure that we have selected the appropriate project, or we create a new one, if we don’t have already one. Then, (see No.2) we need to enable some APIs. iTrAlytics makes use of the (see No.3) Analytics API, Google An- alytics Reporting API and Google+ API (this is for the OAuth process). Finally, we visit the Credentials link (see No.4).
-19-
Figure 6: Google OAuth Credentials
From the Credentials page (see Figure 6) first we need the (see #1) Client ID and the Client secret. We will use those in our google strategy for Passport.js (see Appendix 3, Figure 13). Then, (see #2 of Figure 6) we declare the origin URL. This is the URL where the request is coming from and is the root URL of our back-end Express app. Finally, we declare (see #3 of Figure 6) authorized URIs. Those are the URIs where the user will be redirected after he/she grants permission to our app to access his/her Google data. We declare what data exactly we will have access in our application, in the above route han- dler for “/auth/google”, in the scope2 object. This is a custom route which we are han- dling in our Express app with a Route Handler (see Appendix 3, Figure 15). At this point, we have access to four variables, that are coming back to us after the OAuth process with the Google, from which we are interesting only at three. We get an Access Token value, a Refresh Token value and the profile variable which contains the Google ID of the user, his/her email and display name. We store these information to our Mongo database, for future user information retrieval as well as to authenticate user requests to Google APIs. The instance of our MongoDB is on the cloud, managed by the mLab. mLab is a cloud database service (or a Database-as-a-Service) which hosts MongoDB databases [53]. They offer a free plan with some storage limitations, but it is enough for our storage needs.
2 Scope URLs are predefined by Google and there is a full list of them alongside with what access you get, here: https://developers.google.com/identity/protocols/googlescopes
-20- We use Mongoose.js to connect to our MongoDB, which is a straight forward process. We only need to provide the URL to our MongoDB, which is provided to us by the mLab service. const keys = require('./config/keys'); const mongoose = require('mongoose'); // Connecting to MongoDB using mongoose to our application // Use native promises mongoose.Promise = global.Promise; mongoose .connect(keys.mongo.URI, { useMongoClient: true }) .then(() => console.log('Connection to MongoDB successful.')) .catch(err => console.error('Could not connect to the database: ', err));
We store our sensitive data, like the Google related data we discussed above, our mLab MongoDB URL etc. to a separate file we call “keys.js” and we require it when we need any data from there. Inside our keys.js file we conditionally load a separate file which actually contains the keys we need, depending to which environment we are; development or production.
/** * File : keys.js.js * Project : iTrAlytics * Author : Apostolos Gouvalas */
// keys.js - figure out what set of credentials to return if (process.env.NODE_ENV === 'production') { // we are in production - return the prod set of keys module.exports = require('./prod'); } else { // we are in development - return the den keys module.exports = require('./dev'); }
The actual MongoDB URL in our file it is in the following form:
The obfuscated code is our username and password for the user we have set up in mLab service and who is able to connect to our MonogDB called “itralytics-dev”. We have also setup two more route handlers regarding authentication. The first one is the /api/currentUser:
/** * GET /api/currentUser * req: incoming Request * res: outgoing Response */ app.get('/api/currentUser', (req, res) => { // allow only a subset of User model to be accessible by a user let forUser;
-21- if (req.user) { forUser = _.pick(req.user, ['email', 'name']); } else { forUser = false; } res.send(forUser || req.user); });
This route handler will return a subset of the User model, containing only the email and the name of the user, as long as it finds a logged-in user. We have some sensitive infor- mation in our User model and thus we use the pick() function of the Lodash library which permits us get a subset of an object properties. The second route handler is used for the logout process, which is handled by: /api/log- out:
/** * GET /api/logout */ app.get('/api/logout', (req, res) => { req.logout(); req.session = null; res.redirect('/'); });
This route handler will logout a user, destroy its session and redirect him/her to our root path “/”. Now that we have covered in some detail our authentication related route handlers, we are going to analyze our Google Analytics related route handlers. This part of the project is really interesting, as these route handlers are used as the intermediate routes from our front-end to the Google APIs. It will be here that we perform some logic in order to return the correct data to our front-end.
4.2.2 Google Analytics Related Route Handlers To make requests to Google APIs we have some prerequisites. First, we need to confirm that whomever made the request is an authenticated user, and then and only then, we need to check their AccessToken (the one we got from Google, during the OAuth process). Access tokens are used alongside our requests to Google APIs in order for Google to authenticate us and are only valid for 3600 seconds (which is one hour). Thus, we had to perform some logic, in order to check if the Access token is still valid or to Refresh it otherwise. For making our requests to Google APIs we made use of the request library with promises.
-22- Since we want to run the above logic in our requests, Express provides a functionality called middlewares. Middlewares are pieces of code that we or others write. Subsequently we can run this code in our route handlers and perform checks, fetch data or whatever logic we need to perform. Finally, we can decide if the request will continue or need to be stopped.
4.2.2.1 requireLogin Middleware In this middleware we check if the user is logged-in. If the user it is not logged-in we return an error message alongside a 401 status code, indicating an Unauthorized request. If, however, the user is indeed logged-in we call the next() function which will run the next middleware, if we have chain one, or continue to our actual route handling code.
// route middleware to make sure a user is logged in module.exports = (req, res, next) => { console.log('*** requireLogin is Running...'); if (!req.user || !req.isAuthenticated()) { return res.status(401).send({ error: 'You must log in!' }); }
// there is a logged in user next(); };
4.2.2.2 validateAccessToken Middleware This middleware will fetch the AccessToken, which we store to our User model during the OAuth process and make a request to the https://www.googleapis.com/oauth2/v1/to- keninfo?access_token=OurAccessTokenHere. It will return an object with some infor- mation, if the Access token is still valid, or an error, if it is not. In the first case that the user still has a valid Access token we make a call to next() in order to continue to our route handling code. Otherwise, we call our refreshAccessToken middleware which will try to refresh the Access token. const rp = require('request-promise-native'); const refreshToken = require('./refreshAccessToken');
/** * get Token Info * GET https://www.googleapis.com/oauth2/v1/tokeninfo?access_to- ken=${access_token} */ module.exports = async (req, res, next) => { console.log('*** validateAccessToken is Running...'); if (!req.user || !req.isAuthenticated()) { return res.status(401).send({ error: 'You must log in!' }); }
-23- try { const tokenInfo = await rp( `https://www.googleapis.com/oauth2/v3/tokeninfo?access_to- ken=${req.user .googleAccessToken}` );
console.log('/!\\ Your AccessToken is still valid.'); next(); } catch (error) {
console.log('/!\\ Your AccessToken has expired, we will try to re- fresh it.'); // call refresh token refreshToken(req, res, next); } };
4.2.2.3 refreshAccessToken Middleware This is our last middleware, which is called only if the user’s Access token is invalid and tries to refresh it. Here we have a bit more complicated logic, as we need to make a POST request to Google, including our Google Client ID, Client secret and the user’s Refresh token (which we get the first time a user OAuth’s with our app and we store it in our database). If we successfully get a response from Google, we have also to update the user’s data in the database, so that we can store the newly refreshed Access token. Only if we successfully do that we continue to our rest route handler. const axios = require('axios'); const mongoose = require('mongoose'); const passport = require('passport'); const keys = require('../config/keys'); mongoose.Promise = global.Promise; const User = mongoose.model('users'); /** * Refresh Token * POST url: https://www.googleapis.com/oauth2/v4/token?cli- ent_id=${c_id}&client_secret=${c_scrt}&refresh_token=${r_to- ken}&grant_type=refresh_token * @param req * @param res * @param next * @returns {Promise.
const tokenRefresh = await axios({ method: 'post', url:
-24- `https://www.googleapis.com/oauth2/v4/token?client_id=${c_id}&cli- ent_secret=${c_scrt}&refresh_token=${r_token}&grant_type=refresh_to- ken` });
if (tokenRefresh.data.access_token) { // Token successfully refreshed console.log( '/!\\ Your AccessToken has been refreshed. We will try to save it in the db now.' ); try { // call the findByIdAndUpdate() const user = await User.findOneAndUpdate( { googleId: req.user.googleId }, { $set: { googleAccessToken: tokenRefresh.data.access_token, googleParams : tokenRefresh.data } }, { new: true } );
// fail to update the user if (!user) { throw new Error('Error while trying to save the new Ac- cessToken to the db.'); }
req.login(user, function(err) { if (err) return next(err); //res.redirect('/api/analytics/accountSummary'); next(); });
} finally {}
} // End if (tokenRefresh.data.access_token)... } catch (err) { console.log('/!\\ An error occurred while trying to refresh the token:'); console.log('Status Code: ', err.statusCode); console.log('Message: ', err.message); return next(err); } };
4.2.2.4 GET /api/analytics/accountSummary This is the first route handler in our Google Analytics related route handlers and is making a request to the Google Analytics Management API, so we can retrieve all of user’s de- clared web properties in their Google Analytics account. Since this information is some- what static, and we use each web properties profile id in our next route handler, we store this information to the session for faster retrieval.
/** * GET /api/analytics/accountSummary
-25- */ app.get( '/api/analytics/accountSummary', requireLogin, validateAccessToken, async (req, res) => { //do we have a cached version? if (req.session['gaProfiles']) { console.log('Google Profile from cache'); console.log(JSON.stringify(req.session['gaProfiles'], null, 2)); res.status(200).send(JSON.parse(req.session['gaProfiles'])); return; } try { const analyticsAccounts = await rp({ url: 'https://www.googleapis.com/analytics/v3/management/ac- counts', auth: { bearer: req.user.googleAccessToken }, json: true }); // get the account id let accountId = analyticsAccounts.items[0].id; let profiles = []; const analyticsWebProperties = await rp({ url: `https://www.googleapis.com/analytics/v3/management/ac- counts/${accountId}/webproperties`, auth: { bearer: req.user.googleAccessToken }, json: true }); if (analyticsWebProperties.items.length < 1){ throw new Error('Your Account does not have any Web Proper- ties. Try add some or try with another account.'); }
analyticsWebProperties.items.forEach(function(webProp) { if (webProp.defaultProfileId) { profiles.push({ id: 'ga:' + webProp.defaultProfileId, name: webProp.name, site: webProp.websiteUrl }); } });
req.session['gaProfiles'] = JSON.stringify(profiles);
res.status(200).send(profiles); } catch (error) { console.error('/!\\ Error while trying to get Analytics Pro- files:'); console.log('Status Code: ', error.statusCode); console.log('Message: ', error.message); res.status(400).send(error); } } );
-26- 4.2.2.5 GET /api/analytics/reportsBatchGet In this route handler we are making the actual request to Google so that we can get the Google Analytics Report data that we want to present in the front-end. This route handler will check if we passed along some query parameters to specify the web property from which we want to fetch the data, the date range (start/end date), the metrics (pageviews or unique pageviews) and the dimensions (year, month, week or day). For all properties, except the web property profile id, we are making a check if they are passed as query arguments to our route handler. In case they are not, we give them a default value. If we succeed with the request, we respond by sending our Analytics data alongside a 200 status code to indicate that everything went as intended.
/** * GET /api/analytics/reportsBatchGet */ app.get( '/api/analytics/reportsBatchGet', requireLogin, validateAccessToken, async (req, res) => { try { if (!req.query.viewId) { console.error('/!\\ Error while trying to get Analytics Re- ports Data: NO profile ID provided'); return res.status(400).send(); } // set the params from the query OR to the defaults let viewId = req.query.viewId;
let startDate = (req.query.startDate === 'undefined' || !req.query.startDate) ? '2017-07-01' : req.query.startDate; let endDate = (req.query.endDate === 'undefined' || !req.query.endDate) ? '2017-10-29' : req.query.endDate;
let metricsExpression = (req.query.metricsExpression === 'undefined' || !req.query.metricsExpression) ? 'ga:pageviews' : req.query.metricsExpression;
let dimensionsName = (req.query.dimensionsName === 'undefined' || !req.query.dimen- sionsName) ? 'ga:month' : req.query.dimensionsName;
const analyticsReportsBatchGet = await rp({ method: 'POST', url: 'https://analyticsreporting.googleapis.com/v4/re- ports:batchGet', auth: { bearer: req.user.googleAccessToken
-27- }, body: { reportRequests: [ { viewId: viewId, dateRanges: [ { startDate: startDate, endDate: endDate } ], metrics: [ { expression: metricsExpression } ], dimensions: [ { name: dimensionsName } ] } ] }, json: true });
let analyticsData = analyticsReportsBatchGet.reports[0].data;
res.status(200).send(analyticsData); } catch (error) { console.error('/!\\ Error while trying to get Analytics Reports Data:'); console.log('Status Code: ', error.statusCode); console.log('Message: ', error.message); res.status(400).send(); } } );
4.2.3 Socket.io & Google Analytics Real Time Reporting API In chapter 4.2.1 we mention that we have to enable an API from the Google APIs web page in order to make use of it in our application. Google Analytics Real Time Reporting API is somewhat different as it is available in limited beta version and we had to first sign up to access the API. Here we will discuss how we made use of the Google Analytics Real Time Reporting API in combination to socket.io library so we can deliver real-time analytics to our users. This time, we took another approach than the route handling, that we saw previously, so we could experiment with the different possibilities we had. We made use of “rooms” with socket.io, like the ones we can find in a chat application but instead of exchanging communication messages, we sent the real-time analytics data
-28- to the user. A room is an arbitrary channel that we define and in which sockets can join and leave. Since we are on the back-end of our application, we will see how we handle things here and in the next chapter we will see the front-end of the socket.io setup. First, we configured our express server to allow for incoming web socket connections. This means that our server will be able to accept connections and we will set up the client to make the connection. Tis way, we can establish a persistent connection between the client (front-end) and our server.
/** * setup the Socket.io server */ const io = require('socket.io').listen(server3);
Now we are ready to emit or listen for events. We, then, listen for clients connecting to our server.
/** * Listen when a client connects to our server */ io.use(function (socket, next){ // wrap and use the express session middleware sessionMiddleware(socket.request, {}, next); }) .on('connection', socket => { console.log(` <~~> Server: New user connected to Server.`);
Here is the first tricky part. Since we will start the whole “fetch and send the real-time” process automatically, when a user enters a specific part of our front-end application and we do not use the route handler approach like before, we need somehow to get the user data, so we can authenticate with Google and make the requests to the Real Time Report- ing API. For that reason, we wrap the session middleware (as we can see in the above code snippet) to our socket.io. With that in place we can get the logged-in user id which we can use it to fetch the user data from the database. As we will see in the next chapter, in our front-end, we create an event when a user enters a specific part of our front-end application. Thus, in our back-end we listen for that event:
// listen for 'getReal' events socket.on('getReal', async function(data) { console.log(`<~~ Server: Received a 'getReal' event.`);
3 Where server, is our express app server
-29- Once, we receive such an event, we then get the user id from the session and make a request to our database to fetch the whole user data:
// get the connected user id let clientUserId = socket.request.session.passport.user; // convert string Id to ObjectId let clientUserObjId = mongoose.Types.ObjectId(clientUserId); user = await findAndReturnClientUser(clientUserObjId);
Alongside the “getReal” event that the front-end send us, it also sends us a room name. We use that to establish a room specific connection for our data exchange.
// join the 'Get Real' room, to establish a room-specific channel lay- ered over our socket connection. socket.join(data.room); console.log(`~~> Server joined the room '${data.room}'`);
As soon as our server socket.io joins this room it starts making requests to Real Time Reporting API every fifteen seconds4 and we send the data to our front-end.
/** call API and emit every 15 sec */ setInterval( () => getRealTimeDataApiAndEmit(socket), 15000 );
Finally, we listen for another event from the client side, which denotes that the user left the Real-time part of our application and thus we should leave the room and stop making requests to the Google’s API.
// listen for 'UnReal' events socket.on('UnReal', data => { console.log(`~~> Server: Client left the room, so end the subscrip- tion to that room.`); // end the client subscription to that room socket.leave(data.room); });
Having analyzed the back-end application structure we are now ready to see how things work in the front-end with the create-react-app.
4.3 iTrAlytics Front-end We mentioned at the chapter 3.2.1 that we made use of create-react-app so we can use React in our front-end part of the application. By doing so, we speed up the development process a lot. Running a React application requires a certain amount of pre-configuration like, configuring webpack loaders to bundle our application files, add webpack plugins
4 Due to quote limitations of the Real Time Reporting API, and because we make a lot of request, we have to set a time interval, in order to not exceed the limits.
-30- for extra functionality and write babel configuration so it can translate our modern JavaS- cript (ES6) to JavaScript that most browsers can understand. Create-react-app comes pre- configured with all aforementioned configurations and since it is made by the original React developers we can rest assured that we get the optimal configuration.
4.3.1 Initial crate-react-app setup First of all, create-react-app provides a simple cli which we run like:
> create-rect-app client
The above command will create a folder named “client” and a predefined scaffolding for our application, as well as install all initial dependencies. When the installation is finished we can navigate to this folder and run: yarn start
This will start a server, from webpack, running at: http:localhost:3000
By visiting the above URL, we have access to the front-end of our application running React. The above server at port 3000 is only running locally during the development stage and we do not use it in the production. Create-react-app provides a script, which we run before deploying our application to production, like we did with Heroku, and is bundling up our application to produce an optimized version of our application. This optimized version, consists of an index.html file and a static folder which contains our CSS, JavaS- cript and media that we wrote and used in our application. In other words, we only need to run this server in the development environment. However, since we already have a server running at port 5000, for our back-end part of the application, we needed a way to also run the front-end server at the same time. While this can be done easily, by running each server from their respective folder, we found out that this was a time-consuming process, as during development we had to re- start our servers a lot, and thus, running them from their respective folders was not the best approach for us. For that, we used a module called “concurrently” which let us chain up commands to our package.json file. Then we ended up running one command to start both our servers. To get a better understanding of the architecture that our application has at the mo- ment we will have a look at the diagram in Figure 7.
-31-
Figure 7: Running two servers on development process.
From the image above, we see an overview of our application architecture. From top to bottom, we have our browser when someone visits our application and two servers. From left to right we have our React server (the one running by webpack) and our Node.js/Express server. As we have seen, the Express server pulls some information from the MongoDB and sends us json data in response to various requests we make. The React server will take a couple of different components (Header.js, App.js etc.) and serve a bundle of JavaScript, so we can view our application. In other words, we got one server which serves us with the font-end application assets and another one which serves all our data of the application. This setup right now it may seem confusing or unnecessary, but in production we only have our Express server and nothing else. As we already men- tioned, the create-react-app has a build script which leaves us only with the necessary JavaScript, CSS and media files, that we need to run our React app. The last piece of configuration we need to make, in order our front-end React appli- cation to communicate with our Express backend server, is to setup a proxy. Create-react-
-32- app is already bundled with a proxy functionality and thus, we only need to configure it in our package.json file5, like this:
"proxy": { "/auth/google": { "target": "http://localhost:5000" }, "/api/*": { "target": "http://localhost:5000" } },
Basically, here we say to our React application to forward any request to /auth/google or /api/* paths to our Express server. Figure 8 shows how our requests are handled by the proxy.
Figure 8: Forward requests to our Express server.
With that in place we are ready to start developing our front-end application.
5 This package.json file resides in our client folder and is the configuration file of our create-react-app.
-33- 4.3.2 Single Page Application We developed our React application as a Single Page Application (SPA). This approach defers from the traditional approach where we have multi-page applications, for example the development of a website. The main difference in SPA is in navigation, where when we navigate through our application we do not load an entirely new page. Our pages, also known as views in the context of SPAs, are loaded inline within the same initial page. This means, that our views are comprised from different parts or components and each component is in charge for a specific view in our application. When a user navigates from the menu to a specific page of our app, we simply load the appropriate component for the view that the user requested. However, when we use a web application we have certain expectations regarding the navigation. When we click on a menu link we expect not only to see a change in the content of the page and in the URL displayed in the location bar of our browser, but also that we can use the browser’s back and forward buttons. Such functionality, for multi- page applications, works out of the box without us requiring doing anything. In SPAs however, because we do not navigate to a new page, we have to provide such functional- ity. In order to successfully do so to our SPA, we need to provide routing to our applica- tion. Routing is when we map URLs to views, inside our single-page app. Essentially, we have only one index.html with a plain HTML5 document and some css links and only a single div inside it, like:
Then, the create-react-app will add/load a bundle file containing all our JavaScript files. With React, we write JavaScript code which renders the HTML code we want to
-34- display in our application. We separate each view of our application in components which contain logic and HTML for that specific view. In the end we have something like the following diagram in Figure 9.
Figure 9: iTrAlytics React components overview.
React offers a collection of components called React Router for handling routing to React applications and we made use of that so as to map certain components to specific routes. import { BrowserRouter, Route } from 'react-router-dom'; ... render() { return (
-35-
Dashboard
4.3.3 Connection to our API & displays Now that we have an understanding of the architecture of our application, we are going to see how we use our Express API to make calls for the various functionalities we pro- vide through it; OAouth, Google Analytics querying, Real Time Analytics querying. In Appendix 4: iTrAlytics Front-end Screens we provide the screens of the application front-end.
4.3.3.1 Log-in The log-in process is quite straight forward, since we make the heavy lifting in the Ex- press server of our app. In the front-end all we have to do is to include a link to our Express API at route /auth/google, like this:
Then the Passport.js and the Google OAuth will take place, as we have discussed in chap- ter 4.2.1 and if the user gives us permission to access his/her Google data, then we store his/her information to our MongoDB for later retrieval and the user is logged-in. As a
-36- note, here we do not have to use a Link component for our hyperlink, since the OAuth process will take us outside of our application anyway.
4.3.3.2 Fetching User’s Google Analytics Web Properties At this stage our user is logged-in to our application and he/she can visit our dashboard route. Here, behind the scenes we take advantage of React’s component life cycle meth- ods and we make an automatic call to our back-end API at route /api/analytics/ac- countSummary, when the dashboard component mount to the view, by calling the com- ponentDidMount() method of our Dashboard class component. This call, in turn, as we already mentioned in chapter 4.2.2.4, will make a call to the user’s Google Analytics account in order to fetch his/her declared web properties. At this point, during development stage, we thought that we will need these infor- mation throughout our application in ReportsParams, ChartsList and RealTimeDisplay components to use each web properties profile id for our queries in the Google APIs. React State In React, when we need to keep track of information between view renderings, we store this information in the component’s state. State is an object within each of our component and holds information about the component. A great think React does, is that whenever the state is changed, it makes the component that handles this state to re-render, making it ideal for information that change over time, like our real-time data display component. We can initialize and update the state of a component, but state is stored locally in each component. When we need to use the information stored in the state of a component to another component we have to pass down this state in another component through its props. Props is sort for properties and we can also store information to the props object, but we cannot change them during the component life cycle (props are immutable). Props working similar to how an argument is passed to a function. For example, supposedly we have a simple component like this: class Greetings extends React.Component { render() { return
Greetings, {this.props.name}
; } } const element =The last line will create a property called “name” with value “Chrysa”.
-37- Now that we saw what props are, we can get back to the state. We said that we need to pass down the state as props to other components that need the information stored in this state. This means that we need to initialize our state to our most-top component and then pass this state to its children components. However, we must also pass a callback to our state handler, as any of the children components can change the state and thus we need to also update it in the top-most component. All this sounds like a lot of work and it is. For that, we made use of the Redux library. Redux to the rescue Redux, which we briefly described in chapter 3.2.3, is not a React specific library but it plays really well with it. By using Redux in our application, state is stored globally in the Redux store. By doing so, we centralize all our data, but it makes it a lot easier for a component to get the state it needs.
Figure 10: How Redux works
Figure 10 describes the flow of how Redux handles the state of our application. Start- ing from the top, a React component calls an Action Creator which in turn produces an
-38- action (fetch data, change data or whatever we want to do). The action is then dispatched (or send) to all our different reducers, which will then update the states of our Redux Store. After the update, the states that resides in the store will be sent back to our react components which if changed will make them re-render and thus display the new data. In our application to glue React and Redux we make use of the React-Redux library. This library’s sole purpose is to make sure that React and Redux play nicely together. With React-Redux library we wrap our application in a Provider component which es- sentially informs our application that we have a Store for our state:
// redux store - createStore( reducer, initial state, middleware ) const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
ReactDom.render(
We can visualize how this works by looking at the following figure.
Figure 11: Access to store with React-Redux Provider component
Back to our application, as we already mentioned, we need to have access to the user’s Google Analytics Web Properties, and that is why we store them in the global state of our application when our Dashboard component mounts. All we have to do, is call an action, in particular the fetchProfiles()action, which will make a call to our API route
-39- /api/analytics/accountSummary in order to fetch the user’s web properties infor- mation.
4.3.3.3 Initialize ChartsList Component Due to Redux, we fetch and store at our global state the web properties information at the moment the user visits the /dash route of our React app, which loads the Dashboard com- ponent, which in turn, initially displays the SideBar, ReportsParams and ChartsList com- ponents. We use this information to initialize our ChartsList component to display the title of the web property and also provide a link to it (see Figure 22 of Appendix 4).
4.3.3.4 Display Analytics for All User’s Web Properties At this point, the user can make choices from a form which we render in the Reports- Params component. In order to connect our form with the Redux of our application we made use of the redux-form library. Redux-form, can hook up any form to the redux part of the application giving us access to all choices the user makes in the form in the global state. Although we could check for changes to any of the form field the instance it changes and make a call to our back-end API, we rather do this when user submits the form via the “GET REPORT” button in our interface. We run the following, when the user submits the form: onFormParamsSubmit(values) { let startDate, endDate; if (typeof values.startDate !== 'string') { startDate = moment(values.startDate).format('YYYY-MM-DD'); }
if (typeof values.endDate !== 'string') { endDate = moment(values.endDate).format('YYYY-MM-DD'); }
let dateRange = { startDate: startDate, endDate: endDate };
let metricsExpression = { text: values.metrics.metric, value: values.metrics.value };
let dimensionsName = { text: values.dimensions.dimension, value: values.dimensions.value };
// make a call to the analytics report api _.map(this.props.profiles, profile => {
-40- this.props.dispatchFetchReportsData( profile, dateRange, metricsExpression, dimensionsName ); });
}
Here we make some data checks and modeling which will help us to make the query to our back-end API. At the end, for each of the user’s web properties profiles we make a call to our action dispatchFetchReportsData which triggers a series of events. First it will make a call to our Express API at route /api/analytics/reportsBatch-
Get?${params} passing our form data as query parameters. Then, as we saw in chapter 4.2.2.5, we make a request to the Google Analytics API providing the parameters the user has chosen in the form. After we get back the results, we dispatch an action to our reduc- ers. There the appropriate reducer will handle this action and will change the state of the analytics data we keep in our global state. As we mentioned previously, React will re- render a component when it’s state change. Since we use the analytics data state for ren- dering our charts in the ChartsList component, when they change from the above action, our charts will also change and display the data we received from the Google Analytics API (see Figure 23: iTrAlytics dashboard Pageviews/Week analyticsFigure 23 of Appen- dix 4).
4.3.3.5 Get Real-time Analytics data Finally, the user can visit through the SideBar’s component navigation link, the “Real Time” dashboard. We already mentioned in chapter 4.2.3 that we make use of the socket.io library for real time communication between our back-end and front-end and also exchanging data via sockets. To do so, in the front-end we made use of the socket.io- client library to get access to the socket.io client’s methods. Here we also take advantage of Reacts components life cycle methods. In particular, again we make use of the componentDidMount() method to do three things, the instance the component mounts on user’s screen. Firstly, we connect to the server socket: // connect to server socket if (process.env.NODE_ENV === 'production') { console.log(`https://itralytics.herokuapp.com/`); socket = io.connect(`https://itralytics.herokuapp.com/`); } else { console.log('http://localhost:5000/'); socket = io.connect('http://localhost:5000/'); }
-41-
We also check to see if we are in development or production environment and we connect accordingly. Afterwards, we send, or emit in socket.io jargon, an event to the server and also a room name. // emit a 'getReal' event to the server console.log(`--> Client: Emitting 'getReal' event.`); socket.emit('getReal', { profilesData: this.props.profiles, room: 'Get Real' });
We saw that, in our back-end we wait for this event and also that we connect to a specific room that the client sends. This is happening here. In addition, in back-end as soon as we enter the room the client sends us, we start making queries to the Real Time Analytics API. When we receive a response from Google API, we emit an event and pass along with it the response data. Thus, now in client side, we listen for that event and we update our display data with the data we receive from the event: // listen for the 'TheRealDeal' event and call the updateRealData() to update the state socket.on('TheRealDeal', payload => { console.log(` --> Client: received 'TheRealDeal' event. `);
this.updateRealData(payload); });
This will update the data, which in this component we store at its state (and not the global state) as we do not need them in any other component and thus React will re-render the component each time we change this data, showing us the correct numbers. Similarly, to the Analytics approach, we also monitor all of user’s web properties at once and display real-time user activity for each web property. Moreover, we categorize the user activity by the device they used to enter a web property we monitor, and we display that infor- mation to our dashboard. Finally, we make use of another component life cycle method: componentWillUnmount() { // emit an 'UnReal' event to designate that we are leaving the page and thus the room console.log(`--> Client: We are leaving, so Emitting 'UnReal' event.`); socket.emit('UnReal', { room: 'Get Real' }); }
-42- This code will run when the user of our application navigates away from this component and will emit the “Unreal” event, for which we listen at the back-end and as soon as we receive it we stop making requests to the Google Real Time Analytics API. At this point, we also conclude our front-end part of our application.
-43-
5 Future Work
Although we met all the requirements for the application, there is room for improvement and we can also build upon our existing Express API and front-end React views. One thing that could be improved or altered is the way we get the Real Time Analytics data. In our application we made use of the socket.io library only to experiment with this technology. A more proper way could be to also make an API route for handling Real Time Analytics calls, like the way we did for the Google Analytics API calls. This will add to our API and our application would be more consistent in the way it handles external API calls. Apart from that, a great addition could be the integration of other Google APIs like for instance the Google AdWords API, so a user could get reports about their advertise- ment campaigns across all his/her web properties. Finally, another addition which was out of the scope of this dissertation but nonethe- less it would be a good overall addition for the application, would be the integration of Facebook and Twitter analytics as these social platforms can provide valuable infor- mation about the web properties a user tracks.
-45-
6 Conclusions
In this thesis we developed from the ground up our own modern web application in MERN6 stack in order to provide a dashboard for presenting Google Analytics and Real- time Analytics data. The main advantage of our application is that we provide an overview of all web properties a user is tracking in their Google Analytics account, something which is not allowed in the free version of Google Analytics. Moreover, we had to overcome some difficulties during the development stage regarding how to figure out the connection be- tween out technologies. Due to the cutting-edge technologies we used for our application, it was hard or impossible to find example implementations either written or online, to help us in the development process. However, despite the difficulties we faced, the whole process of building an applica- tion from scratch and using only one programming language, namely the JavaScript pro- gramming language, for both our back-end and the front-end of the application it was a great experience. Having one development language made the whole process a lot easier, in terms of debugging, data exchange between our back-end and the front-end and prob- ably will make the future maintenance a lot easier too. To conclude, we have managed to build a solid application which we can build upon it more functionality regarding the analytics by also integrating other analytic data sources like from Facebook and Twitter analytics. We can also build more views for our existing dashboard, by configuring the query parameters we use to fetch data from the Google Analytics API.
6 MERN stack is from MongoDB, Express, React and Node.js
-47-
Bibliography
[1] InternetLiveStats.com, "Number of Internet Users," 02 November 2017. [Online]. Available: http://www.internetlivestats.com/internet-users/.
[2] Miniwatts Marketing Group, "World Internet Users Statistics and 2017 World Population Stats," 02 November 2017. [Online]. Available: http://www.internetworldstats.com/stats.htm.
[3] Web Analytics Association, "Web Analytics Definitions," Web Analytics Association, Wakefield, USA, 2008.
[4] Marketing Land, "Enterprise Web Analytics Platforms 2015: A Marketer's Guide," Marketing Land, 2015.
[5] Google Analytics Help, "About Roll-Up Reporting," 2017. [Online]. Available: https://support.google.com/analytics/answer/6033415. [Accessed 14 October 2017].
[6] E. R. Tufte, "Sparklines: Intense, Simple, Word-Sized Graphics," in Beautiful Evidence, Graphics Press, 2006, pp. 46-63.
[7] Google, "Hierarchy of accounts, users, properties, and views - Analytics Help," 2017. [Online]. Available: https://support.google.com/analytics/answer/1009618?hl=en. [Accessed 12 October 2017].
[8] Tracxn, "Tracxn Report: BI & Analytics," Tracxn, 2016.
[9] Crunchbase, "Klipfolio | Crunchbase," 2017. [Online]. Available: https://www.crunchbase.com/organization/klipfolio. [Accessed 23 October 2017].
[10] Klipfolio, "Business Dashboard Software for Everyone - Klipfoio," 2017. [Online]. Available: https://www.klipfolio.com/. [Accessed 23 October 2017].
-49- [11] Crunchbase, "Cyfe | Crunchbase," 2017. [Online]. Available: https://www.crunchbase.com/organization/cyfe. [Accessed 23 October 2017].
[12] Cyfe, "All-In-One Business Dashboard - Cyfe," 2017. [Online]. Available: https://www.cyfe.com/. [Accessed 23 October 2017].
[13] Crunchbase, "Domo | Crunchbase," 2017. [Online]. Available: https://www.crunchbase.com/organization/domo. [Accessed 23 October 2017].
[14] Domo, "Data Management - Data Analytics - Data Reporting | Domo," 2017. [Online]. Available: https://www.domo.com/. [Accessed 23 October 2017].
[15] Stack Overflow, "Developer Survey Results 2016," Stack Overflow, 2016. [Online]. Available: https://insights.stackoverflow.com/survey/2016#technology. [Accessed 19 August 2017].
[16] Stack Overflow, "Developer Survey Results 2017," Stack Overflow, 2017. [Online]. Available: https://insights.stackoverflow.com/survey/2017#technology. [Accessed 19 August 2017].
[17] MDN web docs by Mozilla, "A re-introduction to JavaScript," Mozilla, 27 November 2017. [Online]. Available: https://developer.mozilla.org/en- US/docs/Web/JavaScript/A_re-introduction_to_JavaScript. [Accessed 29 November 2017].
[18] Ecma International, "ECMAScript® 2015 Language Specification," Ecma International, Geneva, 2015.
[19] Stack Overflow, "Developer Survey Results 2017," Stack Overflow, 2017. [Online]. Available: https://insights.stackoverflow.com/survey/2017#technology- most-loved-dreaded-and-wanted-frameworks-libraries-and-other-technologies. [Accessed 19 August 2017].
[20] Node.js Foundation, "Node.js," Node.js Foundation, 2017. [Online]. Available: https://nodejs.org/en/. [Accessed 15 August 2017].
[21] A. Mardan, JavaScript and Node FUNdamentals, Learnpub, 2014.
[22] J. Delaney and C. Gu, Node.js at a Glance, Mountain View: Whale Path, 2014.
[23] "01 - What id npm? npm Documentation," npm, Inc., 2017. [Online]. Available: https://docs.npmjs.com/getting-started/what-is-npm. [Accessed 22 August 2017].
-50- [24] "npm," npm, Inc., 2017. [Online]. Available: https://www.npmjs.com/. [Accessed 22 August 2017].
[25] S. McKenzie, C. Nakazawa and J. Kyle, "Yarn: A new package manager for JavaScript," Facebook, 11 October 2016. [Online]. Available: https://code.facebook.com/posts/1840075619545360. [Accessed 22 August 2017].
[26] StrongLoop, IBM, and other expressjs.com contributors, "Express - Node.js web application framework," Node.js Foundation, 2017. [Online]. Available: http://expressjs.com/. [Accessed 22 August 2017].
[27] E. Brown, Web Development with Node & Express, Sebastopol: O’Reilly Media, 2014.
[28] "using Express middleware," Espress, 2017. [Online]. Available: http://expressjs.com/en/guide/using-middleware.html. [Accessed 22 August 2017].
[29] J. Hanson, "Passport.js - Documentation," Passport.js, 2017. [Online]. Available: http://www.passportjs.org/docs/. [Accessed 24 August 2017].
[30] "Mongoose ODM," Automattic, 2017. [Online]. Available: http://mongoosejs.com/. [Accessed 25 August 2017].
[31] "Socket.IO," socket.io, 2017. [Online]. Available: https://socket.io/. [Accessed 28 August 2017].
[32] R. Rai, Socket.IO Real-time Web Application Development, Birmingham: Packt Publiching, 2013.
[33] "Google's officially supported Node.js client library for accessing Google APIs.," Google, 2017. [Online]. Available: https://github.com/google/google-api-nodejs- client. [Accessed 29 August 2017].
[34] "Request - Simplified HTTP client," Request, 2017. [Online]. Available: https://github.com/request/request. [Accessed 30 August 2017].
[35] "Simple session middleware for Express," ExpressJS, 2017. [Online]. Available: https://github.com/expressjs/session. [Accessed 28 August 2017].
[36] J. Desboeufs, "MongoDB session store for Express and Connect," 2017. [Online]. Available: https://github.com/jdesboeufs/connect-mongo. [Accessed 28 August 2017].
-51- [37] "Lodash," Lodash, 2017. [Online]. Available: https://lodash.com/. [Accessed 22 August 2017].
[38] D. Wilson, "HTTP request logger middleware for node.js," 2017. [Online]. Available: https://github.com/expressjs/morgan. [Accessed 29 August 2017].
[39] G. Henke, "Run commands concurrently.," 2017. [Online]. Available: https://github.com/kimmobrunfeldt/concurrently. [Accessed 15 September 2017].
[40] P. Hunt, "Why did we build React? - React Blog," reactjs.org, 05 06 2013. [Online]. Available: https://reactjs.org/blog/2013/06/05/why-react.html. [Accessed 15 September 2017].
[41] "Installation - React," reactjs.org, [Online]. Available: https://reactjs.org/docs/installation.html. [Accessed 15 September 2017].
[42] "Create React apps with no build configuration.," Facebook Incubator, [Online]. Available: https://github.com/facebookincubator/create-react-app. [Accessed 16 September 2017].
[43] "React Router: Declarative Routing for React.js," reactjs.org, 2017. [Online]. Available: https://reacttraining.com/react-router/. [Accessed 22 September 2017].
[44] D. Abramov, "Read Me - Redux," redux.js.org, 2015. [Online]. Available: https://redux.js.org/. [Accessed 25 September 2017].
[45] Yankov and B. Yankov, "Beautiful and expressive Sparklines React component," 2017. [Online]. Available: https://github.com/borisyankov/react-sparklines. [Accessed 26 September 2017].
[46] E. Rasmussen, "Redux Form," 2017. [Online]. Available: https://redux- form.com/7.2.0/. [Accessed 27 September 2017].
[47] T. Wood and I. I. Chernev, "Moment.js," 2017. [Online]. Available: https://momentjs.com/. [Accessed 26 September 2017].
[48] J. Quense, "React Widgets," 2017. [Online]. Available: https://github.com/jquense/react-widgets. [Accessed 27 September 2017].
[49] A. Wang, "MaterializeCSS," 2017. [Online]. Available: http://materializecss.com/. [Accessed 28 September 2017].
-52- [50] D. Gandy, "Font Awesome," 2017. [Online]. Available: http://fontawesome.io/. [Accessed 27 September 2017].
[51] S. Bembi, "Particle,js - React Component," 2017. [Online]. Available: https://github.com/Wufe/react-particles-js. [Accessed 25 October 2017].
[52] V. Garreau, "A lightweight JavaScript library for creating particles," 2017. [Online]. Available: https://github.com/VincentGarreau/particles.js/. [Accessed 23 October 2017].
[53] "MongoDB Hosting: Database-as-a-Service by mLab," mLab, 2017. [Online]. Available: https://mlab.com/. [Accessed 19 September 2017].
[54] I. Bekavac and D. G. Praničević, "Web analytics tools and web metrics tools: An overview and comparative analysis," Croatian Operational Research Review (CRORR), vol. 6, no. 2, pp. 373-386, October 2015.
-53-
Appendices
Here you can find additional information, graphs, tables and the application screens.
Appendix 1: List of Web Analytics tools [54]
Figure 12: Traditional features of web analytics tools analyzed by Ivan Bekavac and Daniela Garbin Praničević
-55- Appendix 2: Back-end & font-end technologies list Table 1: List of technologies used in back-end & front-end
Back-end Front-end
Node.js React
Express.js React Router
Passport.js Redux
Mongoose.js react-spasklines
socket.io axios
googleapis Redux Form
Request Moment.js
express-session react-widgets
connect-mongo socket.io-client
Lodash MaterializeCSS
morgan Font Awesome
Concurrently jQuery
Lodash
Particle.js - React Component
-56- Appendix 3: iTrAlytics Back-end
Figure 13: Passport.js Google strategy. Retrieve or Save a user to the database.
Figure 14: Route handler for starting the OAuth process
-57-
Figure 15: Route handler after user give permission to access his/her Google data.
Figure 16: Route handler for logging out users
-58-
Figure 17: Route handler for fetching users web properties information
-59-
Figure 18: Route handler for fetching Google Analytics data
-60- Appendix 4: iTrAlytics Front-end Screens
Figure 19: iTrAlytics landing page
-61-
Figure 20: iTrAlytics Google OAuth
Figure 21: iTrAlytics after log-in
-62-
Figure 22: iTrAlytics dashboard initial view
-63-
Figure 23: iTrAlytics dashboard Pageviews/Week analytics
-64-
Figure 24: iTrAlytics dashboard Real-time analytics
-65-