Get Google analytics data every hour (by crone) - google-api

I used the following API code to get data from Google analytics.
but now I need to split it because I need to get user authorization and then create a crone job every hour (without having to ask another permission) from a different server to get GA data.
What changes should be made?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="google-signin-client_id" content="<REPLACE_WITH_CLIENT_ID>">
<meta name="google-signin-scope" content="https://www.googleapis.com/auth/analytics.readonly">
</head>
<body>
<!-- The Sign-in button. This will run `queryReports()` on success. -->
<p class="g-signin2" data-onsuccess="queryReports"></p>
<script>
// Replace with your view ID.
var VIEW_ID = 'ga:104831427';
// Query the API and print the results to the page.
function queryReports() {
gapi.client.request({
path: '/v4/reports:batchGet',
root: 'https://analyticsreporting.googleapis.com/',
method: 'POST',
body: {
reportRequests: [
{
viewId: VIEW_ID,
dateRanges: [
{
startDate: '7daysAgo',
endDate: 'today'
}
],
"metrics": [
{
"expression": "ga:sessions"
}
],
"dimensions": [
{ "name": "ga:date" }
]
}
]
}
}).then(displayResults, console.error.bind(console));
}
function displayResults(response) {
var formattedJson = JSON.stringify(response.result, null, 2);
window.parent.postMessage({formattedJson:(formattedJson)}, "my-domain.com");
}
</script>
<!-- Load the JavaScript API client and Sign-in library. -->
<script src="https://apis.google.com/js/client:platform.js"></script>
</body>
</html>

You can use the Google SuperProxy to fetch the results for you https://developers.google.com/analytics/solutions/google-analytics-super-proxy
Furthermore, you can use the extend the token using the below:
https://accounts.google.com/o/oauth2/token?client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
&refresh_token=REFRESH_TOKEN
&grant_type=refresh_token
More details on how to do it is available here: http://thisistony.com/blog/googleanalytics/google-analytics-api-oauth-ever-wondered-how-to-get-the-access_token/

Related

How to deal when llogged user refresh page of vuejs/vuex app?

in my laravel 5.8 / vuejs 2.5 / "vuex 3.1 user login into the system and some data are stored in user's store, like in auth/Login.vue:
<script>
export default {
...
mounted() {
this.setAppTitle("", 'Login', bus);
}, // mounted() {
computed: {
authError() {
return this.$store.getters.authError;
}
}, // computed: {
methods: {
authenticate() {
this.$store.dispatch('login'); // calling action
login(this.$data.form)
.then((res) => {
this.$store.commit("loginSuccess", res); // calling mutation
this.$store.dispatch('retrieveUserLists', res.user.id );
this.$router.push({path: '/websites-blogs'}); // For debugging!
})
.catch((error) => {
this.$store.commit("loginFailed", {error}); // calling mutation
});
}
}, // methods: {
and store where user's account and his data are kept resources/js/store.js :
export default {
state : {
currentLoggedUser: user,
// personal data only for logged user
userLists: [],
},
getters : {
...
userLists(state) {
return state.userLists;
},
It works ok, until logged user refresh page (F5 or CTRL+R) and user is still logged in my page, but some data, say (userLists)
are empty and some listing is empty.
I have MainApp.vue :
<template>
<body class="account-body">
<v-dialog/>
<MainHeader></MainHeader>
<div class="content p-0 m-0" style="width: 100% !important; margin: auto !important;">
<notifications group="wiznext_notification"/>
<router-view></router-view>
</div>
</body>
</template>
<script>
...
export default {
name: 'main-app',
components: {MainHeader},
mixins: [appMixin],
created() {
},
mounted() {
...
},
methods: {
...
}, // methods: {
}
</script>
and resources/views/index.blade.php :
<?php $current_dashboard_template = 'Horizontal' ?>
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title id="app_title">{{ config('app.name', 'Laravel') }}</title>
<link href="{{ asset(('css/Horizontal/bootstrap.min.css')) }}" rel="stylesheet" type="text/css">
<link href="{{ asset(('css/Horizontal/icons.css')) }}" rel="stylesheet" type="text/css">
<link href="{{ asset(('css/Horizontal/style.css')) }}" rel="stylesheet" type="text/css">
...
{{--<link href="css/Horizontal/ion.rangeSlider.css" rel="stylesheet" type="text/css"/>--}}
{{--<link href="css/Horizontal/ion.rangeSlider.skinModern.css" rel="stylesheet" type="text/css"/>--}}
{{--<link href="css/Horizontal/powerange.css" rel="stylesheet" type="text/css"/>--}}
<link href="{{ asset(('css/'.$current_dashboard_template.'/app.css')) }}" rel="stylesheet" type="text/css">
...
</head>
<body>
<div class="wrapper" id="app">
<main>
<div id="main_content">
<mainapp></mainapp>
</div>
</main>
</div>
</body>
#include('footer')
<script src="{{ asset('js/jquery.min.js') }}"></script>
<script src="{{ asset('js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ asset('js/metisMenu.min.js') }}"></script>
<script src="{{ asset('js/waves.min.js') }}"></script>
<script src="{{ asset('js/jquery.slimscroll.min.js') }}"></script>
<script src="{{ asset('js/app.js') }}{{ "?dt=".time() }}"></script>
{{--<script type="text/javascript" src="{{ asset('js/vuecsv.min.js') }}"></script>--}}
</html>
Could you please to advice a proper way of such situations ? What could I do?
MODIFIED :
I tried to remake my storage and seems it works ok :
const user = getLocalUser();
export default {
state : {
currentLoggedUser: user,
isLoggedIn: !!user,
loading: false,
auth_error: null,
api_url: '/api',
// personal data only for logged user
userLists: [], // I need to save data on page refresh
},
getters : {
...
userLists(state) {
// that works ok after user logged into the system and userLists is read on all page without refresh
if ( state.userLists.length > 0 ) {
return state.userLists;
}
// if userLists is empty check data in localStorage which were saved in refreshUserLists mutation
let localStorageUserLists = JSON.parse(localStorage.getItem("userLists"));
if ( localStorageUserLists.length > 0 ) {
console.log("localStorageUserLists RETURNED::")
return localStorageUserLists;
}
return [];
},
},
mutations : {
// that works ok after user logged into the system and his personal data(in my case 5 rows) are read from db into the vuex store
refreshUserLists(state, payload) {
state.userLists = payload;
localStorage.setItem("userLists", JSON.stringify(payload) );
},
},
What else have I to pay attention at ?
So the thing is, Vuex control your data status across your entire application and components, but once your user press F5 or any other refresh option, the Vuex is reset, there is nothing you can do about it, it's just like it works.
One work around solution i can think right now is to save the data on Vuex and maybe on localstorage, then, on vuex create lifecycle you can just populate the data with the localstorage data if it's empty.
I disagree with the proposed solutions. A browser refresh usually indicates that the user wants to fetch the most fresh data from the server, and reset the app state with that fresh data. Therefore you shouldn't store Vuex state in localStorage or cookies to get around this. If you do that, you now have your app state in two places, and also the user thinks they have the most fresh data when they don't.
The best way to handle a browser refresh event in a SPA is to bootstrap your state from scratch. When your app loads after a refresh, it should notice that the user is not logged in (since that is the default state), and should log them in by negotiating with the server behind the scenes, such as by sending the session cookie and receiving confirmation (or redirecting to login page if the session has expired). It should also re-fetch all relevant data from the server and store that in Vuex again.
you can use both localStorage and cookies.due to problems that I had with LocalStorage i recommand cookies. Vue has a rich plugin called Vue-cookies.
you can set and get data by these commands:
var user = { id:1, name:'Journal',session:'25j_7Sl6xDq2Kc3ym0fmrSSk2xV2XkUkX' };
this.$cookies.set('user',user);
// print user name
console.log(this.$cookies.get('user').name)
more details:
Github

How to render googlemaps in a dojo content pane

I have an spring/dojo application. The requirement is to locate an address in a stackcontainer/content-pane, in map. First thought was to have the redirection to google maps and CORS & ACCESS_ORIGIN issues are being thrown.
Can anyone guide us?
Not sure what you mean with "redirection to google maps", and I am not familiar with spring.
Below an example that I built after some googling (among others here), uses the google maps javascript api, and displays a map in a ContentPane (at least in my environment). You'll need to provide your google API key and adapt config to your environment:
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Google maps demo</title>
<link rel="stylesheet" href="dojo-release-1.12.2-src/dijit/themes/claro/claro.css" media="screen">
</head>
<body class="claro">
<div id='theMap'></div>
<script>var dojoConfig ={
baseUrl: "", isDebug: true, async: true,
packages: [{"name": "dojo", "location": "dojo-release-1.12.2-src/dojo"}, {"name": "dijit", "location": "dojo-release-1.12.2-src/dijit"},
],
};
</script>
<script src="dojo-release-1.12.2-src/dojo/dojo.js"></script>
<script>
require([
'dijit/layout/ContentPane',
'myApp/gmapsLoader!http://maps.google.com/maps/api/js?v3&key=YOUR_GOOGLE_API_KEY'
], function (ContentPane) {
var theMap = new ContentPane({style: {height: '1000px'}}, 'theMap');
var mapOptions = {
center : new google.maps.LatLng(-34.397, 150.644),
zoom : 8,
mapTypeId : google.maps.MapTypeId.ROADMAP
};
var theMapContent = new google.maps.Map(theMap.domNode, mapOptions);
theMap.startup();
});
</script>
</body>
</html>
It has a dependency upon gmapLoader, which is described here (under the name async.js).
jc

I would like to be able to show more than one video on the page

I took the Github xAPI script for "playing a youtube video" and tried to modify it to show two videos instead of one. Ultimately I would like to list five or six videos in this page. Unfortunately I cannot get it to show more than one video at a time. Instead it only shows one video and that is the second one that I have listed. Can someone tell me how I can modify this code to list more than one video? Also, I changed my LRS credentials before posting this question for obvious reasons. Many thanks.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=2.0">
<meta name="description" content="A shorthand syntax for communicating xAPI Statements">
<meta name="author" content="ADL">
<link rel="icon" href="favicon.ico">
<title>xAPI Youtube Video Tracking</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
</head>
<body>
<section class="container">
<div class="page-header">
<h1 class="text-primary"><i class="fa fa-youtube"></i> xAPI Youtube Video Tracking</h1>
<h3 class="text-muted">Send Youtube Video interactions to an LRS with xAPI</h3>
</div>
<div class="row">
<div class="form-group col-md-12">
<div id="player"></div>
<div id="player2"></div>
<p>This example uses minimal javascript and the youtube iframe API.</p>
<p>Statements are built with xapi-youtube-statements.js and dispatched to an LRS with xapiwrapper.min.js using a custom ADL.XAPIYoutubeStatements.onStateChangeCallback function.</p>
<p>You can view statements with the statement viewer.</p>
</div><!-- .col-md-12 -->
</div><!-- .row -->
</section><!-- .container -->
<script type="text/javascript" src="lib/xapiwrapper.min.js"></script>
<script type="text/javascript" src="src/xapi-youtube-statements.js"></script>
<script>
var video = "6hwHKOYCYL4"; // Change this to your video ID
var videoName = "Microlearning vs Traditional Learning";
var video2 = "SUJkBCHB4vQ"; // Change this to your video ID
var videoName2 = "Micro Learning is a BIG deal";
// "global" variables read by ADL.XAPIYoutubeStatements
ADL.XAPIYoutubeStatements.changeConfig({
"actor": {"mbox":"mailto:john.menken#syniverse.com", "name":"John M."},
"videoActivity": {"id":"https://www.youtube.com/watch?v=" + video, "definition":{"name": {"en-US":videoName}} }
});
ADL.XAPIYoutubeStatements.changeConfig({
"actor": {"mbox":"mailto:john.menken#syniverse.com", "name":"John M."},
"videoActivity": {"id":"https://www.youtube.com/watch?v=" + video2, "definition":{"name": {"en-US":videoName2}} }
});
function initYT() {
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: video,
playerVars: { 'autoplay': 0 },
events: {
'onReady': ADL.XAPIYoutubeStatements.onPlayerReady,
'onStateChange': ADL.XAPIYoutubeStatements.onStateChange
}
});
}
var player2;
function onYouTubeIframeAPIReady() {
player2 = new YT.Player('player2', {
height: '390',
width: '640',
videoId: video2,
playerVars: { 'autoplay': 0 },
events: {
'onReady': ADL.XAPIYoutubeStatements.onPlayerReady,
'onStateChange': ADL.XAPIYoutubeStatements.onStateChange
}
});
}
initYT();
// Auth for the LRS
var conf = {
"endpoint" : "https://www2.test.com/test/ScormEngineInterface/TCAPI/",
"auth" : "Basic " + toBase64("test:test"),
};
ADL.XAPIWrapper.changeConfig(conf);
/*
* Custom Callbacks
*/
ADL.XAPIYoutubeStatements.onPlayerReadyCallback = function(stmt) {
console.log("on ready callback");
}
// Dispatch Youtube statements with XAPIWrapper
ADL.XAPIYoutubeStatements.onStateChangeCallback = function(event, stmt) {
console.log(stmt);
if (stmt) {
stmt['timestamp'] = (new Date()).toISOString();
ADL.XAPIWrapper.sendStatement(stmt, function(){});
} else {
console.warn("no statement found in callback for event: " + event);
}
}
</script>
</body>
</html>
You are overwriting the onYouTubeIframeAPIReady callback immediately upon setting the first one, so that when the iframe is ready the first callback is no longer the value of that function. (Have to think asynchronously.) That function should only ever get called once, and should only have a single definition. To make this work, you need to move the instantiation of player2 into the onYouTubeIframeAPIReady function. (This portion is a duplicate of onYouTubeIframeAPIReady called once but multiple videos needed on a page)
Also note that ADL's wrapper is effectively using a singleton for communications with the LRS, so you are going to get all of your statements for both videos with the activity as the object from the second video (because it is the later call to changeConfig). I don't see a way around this other than to wrap their state change handler with your own function that calls changeConfig each time the event occurs, and even then you'd have the potential for a race condition.

AngularJS: Requesting JSON Data with AJAX

I'm learning AngularJS and Json and have a small page that needs to display data retrived from .json file in controller.
When opening the page I have error:
Unexpected token t at Object.parse (native)...
This is my html:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="../Scripts/angular.min.js"></script>
<script src="../Scripts/Controllers/app.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="PostsCtrl">
<ul ng-repeat="post in posts">
<li>{{post.title}}</li>
</ul>
</div>
</body>
</html>
This is a controller:
var app = angular.module('myApp', []);
app.controller("PostsCtrl", function ($scope, $http) {
$http.get('data/posts.json').
success(function (data, status, headers, config) {
$scope.posts = data;
}).
error(function (data, status, headers, config) {
});
});
This is .json
[
{title: "Title1"},
{title: "Title2"},
{title: "Title3"}
]
I'm not sure why I get an error.
It seems like you are re parsing a json object with JSON.parse()?
or
json isnt valid
try
[
{"title": "Title1"},
{"title": "Title2"},
{"title": "Title3"}
]

Kendo UI: Can't add a node to treeview when using custom schema

I have a kendo tree initialized with the following datasource:
var dataSource = new kendo.data.HierarchicalDataSource({
transport: {
read: {
url: '/Quota/Home/GetTemplateHierarchy',
dataType: 'json',
data: { hierarchyID: hierarchyID, quotaSetID: quotaSetID, batchSize: 10 }
}
},
schema: {
model: {
id: 'id',
hasChildren: 'hasChildren',
children: 'items',
fields: {
text: 'text'
}
}
}
});
Does anyone know how to add and create a new node for this datasource? I've tried the generic treeview.append({ text: "Boo"}) but it doesn't do anything. I've successfully removed nodes, but can't seem to add any. The documentation is not clear as to how to add anything when using custom schemas.
Not sure what do you want to be the text of the node that you want to display. So I will guess that you want to display the only element in the schema nodelevel
The data in that case should be: { nodelevel : 99 }
Following a complete example where I have an initial node and then I append sub-nodes to the selected node.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Tree View</title>
<!-- Kendo UI Web styles-->
<link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css"/>
<link href="styles/kendo.default.css" rel="stylesheet" type="text/css"/>
<!-- Kendo UI Web scripts-->
<script src="js/jquery.min.js" type="text/javascript"></script>
<script src="js/kendo.web.min.js" type="text/javascript"></script>
<script>
$(document).ready(function () {
var count = 0;
var data = [
{ nodelevel: count++ }
];
var dataSource = new kendo.data.HierarchicalDataSource({
data :data,
schema:{
model:{
id :'id',
hasChildren:'hasChildren',
children :'items',
fields :{
nodelevel:{
type :'number',
editable:true,
nullable:false
}
}
}
}
});
var tree = $("#tree").kendoTreeView({
dataSource :dataSource,
dataTextField:"nodelevel"
}).data("kendoTreeView");
$("#add").click(function () {
var selected = tree.select();
if (selected.length > 0) {
tree.append({ nodelevel: count++ }, selected);
}
});
});
</script>
</head>
<body>
Add to selected
<div id="tree"></div>
</body>
</html>
Paul, I'd like to propose another solution...
<!-- Kendo UI Web styles-->
<link href="../styles/kendo.common.min.css" rel="stylesheet" type="text/css"/>
<link href="../styles/kendo.default.min.css" rel="stylesheet" type="text/css"/>
<!-- Kendo UI Web scripts-->
<script src="../js/jquery.min.js" type="text/javascript"></script>
<script src="../js/kendo.web.min.js" type="text/javascript"></script>
<!-- Local Styles -->
<style type="text/css">
</style>
<!-- Initialize Form Elements -->
<script type="text/javascript">
$(document).ready(function () {
function loadMore() {
var uid = $(this).data("uid");
var node = tree.findByUid(uid);
tree.insertBefore(content, node);
tree.remove(node);
addLoadMore(".k-i-pencil");
}
function addLoadMore(clss) {
$(clss, tree.element).closest(".k-item").on("click", loadMore);
}
var content = [
{
text :"node1",
items:[
{ text:"node1.1" },
{ text:"node1.2" },
{ text:"node1.3", spriteCssClass:"k-icon k-i-pencil" },
{ text:"node1.4" }
]
}
];
var tree = $("#tree").kendoTreeView({
dataSource:content
}).data("kendoTreeView");
addLoadMore(".k-i-pencil");
});
</script>
</head>
<body>
<div id="tree"></div>
</body>
</html>
Here I create a tree with a content loaded from JSON (it should be replaced by your ajaxAntiForgery). There is one node in the tree that has an icon (k-i-pencil). Then I call a function addLoadMore that intercepts clicks on the node with k-i-pencil and add new content to this node -using insertBefore for inserting the new content before the content with k-i-pencil and then removes the old node).
I think that this example is pretty similar to what you are doing with your button.
So, take a look into loadMore function to see how I detect the node corresponding to where I clicked (I extract the uid and the find the node with this uid by using tree.findByUid).
Finally I remove the original node (invoking tree.remove) and set again the interceptor for the new nodes with k-i-pencil.
Hopefully this is pretty close to what you have.
The treeview.append should work and append new node to the root level if you do not specify a note to append it to.
You could also use the dataSource.insert({text:"foo"}) or dataSource.add.

Resources