I'm creating a website-dashboard for internal use at my company. We've a TV-screen in our office and I want to display some stats of our 8 most popular websites. It should contain 8 graphs of last weeks users/sessions per site. I also want to display the active-users for each site.
I've managed to come up with a script and wrote (with some help) a function so I could easily call the different sites by just changing the view id's of the corresponding properties, instead of copying the same code eight times.
The problem I'm facing is a 'Quota Error: User Rate Limit Exceeded.' error in the console, because of the 8 sites. It's working fine with 2 or 3, but 8 seems to be too much. I've already raised the Requests per 100 seconds per user. too 1000.
Is there any way I could reduce the number of API calls by rewrite some of my code? Or is there another way to achieve this dashboard-kind-of-thing?
`
<div class="container-fluid">
<div class="row">
<div id="auth-button"></div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="-container"></figure>
<ol class="Chartjs-legend" id="legend-1-container"></ol>
<div id="active-users-container1"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-2-container"></ol>
<div id="active-users-container2"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-3-container"></ol>
<div id="active-users-container3"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-4-container"></ol>
<div id="active-users-container4"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-5-container"></ol>
<div id="active-users-container5"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-6-container"></ol>
<div id="active-users-container6"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-7-container"></ol>
<div id="active-users-container7"></div>
</div>
<div class="col-lg-4 dashitem">
<h1>name</h1>
<figure class="Chartjs-figure" id="[name]-container"></figure>
<ol class="Chartjs-legend" id="legend-8-container"></ol>
<div id="active-users-container8"></div>
</div>
</div>
</div>
{literal}
<script>
gapi.analytics.ready(function() {
var CLIENT_ID = '[client-id]';
gapi.analytics.auth.authorize({
container: 'auth-button',
clientid: CLIENT_ID,
userInfoLabel:""
});
gapi.analytics.auth.on('success', function(response) {
//hide the auth-button
document.querySelector("#auth-button").style.display='none';
});
function renderIndividualChart(id, chartId, legendId, activeUsersId) {
// Adjust `now` to experiment with different days, for testing only...
var now = moment(); // .subtract(3, 'day');
var thisWeek = query({
'ids': "ga:" + id,
'dimensions': 'ga:date,ga:nthDay',
'metrics': 'ga:users',
'start-date': moment(now).subtract(1, 'day').day(0).format('YYYY-MM-DD'),
'end-date': moment(now).format('YYYY-MM-DD')
});
var lastWeek = query({
'ids': "ga:" + id,
'dimensions': 'ga:date,ga:nthDay',
'metrics': 'ga:users',
'start-date': moment(now).subtract(1, 'day').day(0).subtract(1, 'week')
.format('YYYY-MM-DD'),
'end-date': moment(now).subtract(1, 'day').day(6).subtract(1, 'week')
.format('YYYY-MM-DD')
});
Promise.all([thisWeek, lastWeek]).then(function(results) {
var data1 = results[0].rows.map(function(row) { return +row[2]; });
var data2 = results[1].rows.map(function(row) { return +row[2]; });
var labels = results[1].rows.map(function(row) { return +row[0]; });
labels = labels.map(function(label) {
return moment(label, 'YYYYMMDD').format('ddd');
});
var data = {
labels : labels,
datasets : [
{
label: 'Vorige Week',
fillColor : 'rgba(220,220,220,0.5)',
strokeColor : 'rgba(220,220,220,1)',
pointColor : 'rgba(220,220,220,1)',
pointStrokeColor : '#fff',
data : data2
},
{
label: 'Deze Week',
fillColor : 'rgba(151,187,205,0.5)',
strokeColor : 'rgba(151,187,205,1)',
pointColor : 'rgba(151,187,205,1)',
pointStrokeColor : '#fff',
data : data1
}
]
};
new Chart(makeCanvas(chartId)).Line(data);
generateLegend(legendId, data.datasets);
});
var activeUsers = new gapi.analytics.ext.ActiveUsers({
ids: "ga:" + id,
container: activeUsersId,
pollingInterval: 5
});
//activeUsers.execute();
}
function query(params) {
return new Promise(function(resolve, reject) {
var data = new gapi.analytics.report.Data({query: params});
data.once('success', function(response) { resolve(response); })
.once('error', function(response) { reject(response); })
.execute();
});
}
function makeCanvas(id) {
var container = document.getElementById(id);
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
container.innerHTML = '';
canvas.width = container.offsetWidth;
canvas.height = container.offsetHeight;
container.appendChild(canvas);
return ctx;
}
function generateLegend(id, items) {
var legend = document.getElementById(id);
legend.innerHTML = items.map(function(item) {
var color = item.color || item.fillColor;
var label = item.label;
return '<li><i style="background:' + color + '"></i>' +
escapeHtml(label) + '</li>';
}).join('');
}
// Set some global Chart.js defaults.
Chart.defaults.global.animationSteps = 60;
Chart.defaults.global.animationEasing = 'easeInOutQuart';
Chart.defaults.global.responsive = true;
Chart.defaults.global.maintainAspectRatio = false;
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
renderIndividualChart(xxx9412, '[name]-container', 'legend-1-container', 'active-users-container1');
renderIndividualChart(xxx11510, '[name]-container', 'legend-2-container', 'active-users-container2');
renderIndividualChart(xxx8011, '[name]-container', 'legend-3-container', 'active-users-container3');
renderIndividualChart(xxx4711, '[name]-container', 'legend-4-container', 'active-users-container4');
renderIndividualChart(xxx4009, '[name]-container', 'legend-5-container', 'active-users-container5');
renderIndividualChart(xxx61037, '[name]-container', 'legend-6-container', 'active-users-container6');
renderIndividualChart(xxx0969, '[name]-container', 'legend-7-container', 'active-users-container7');
renderIndividualChart(xxx5362, '[name]-container', 'legend-8-container', 'active-users-container8');
});
</script>
`
I'm guessing you are exceeding the 10QPS per IP address limit. Since you are sending about 16 (8 * 2) calls all at once, see General Quota limits.
Be aware that there are also limits on the number of calls per day (50,000) and per Analytics View (10,000) and limits on the number of concurrent requests per view (10).
I think the easiest thing to do is to chain the calls one after the other instead of firing all the calls all at once. Depending on how fast the RPC is you might still run into the qps limits. The better thing to do is to add rate limiting on the GA data calls and also add some retry logic with exponential backoff on quota error.
I have been trying various methods to display data from a Meteor fetch within a React template. I don't fully understand why this is so hard to do.
When I use getMeteorData within the template it doesn't display the data when using {this.data.user._id}
getMeteorData() {
return {
user: Meteor.user()
};
},
This is the same story when using componentDidMount.
I have followed the react & meteor tutorial on the meteor website and they use props. This however feels a lot of work for simply pulling and fetching data.
This is an example of an attempt
export var MenuLoggedIn = React.createClass({
displayName: 'MenuLoggedIn',
mixins: [
Router.State, Router.Navigation
],
getMeteorData() {
return {
user: Meteor.user()
};
},
getInitialState(){
return {
avatarImagePreview: null,
avatarImage: null
};
},
render: function() {
return (
<div className="container">
<div className="menu-structure col-md-12">
{this.data.user._id}
<div className="left-menu pull-left">
<img className="logo" src="/img/logo.png"/>
<h1 className="eventburger-title">eventburger</h1>
</div>
<div className="right-menu">
<div className="search-bar">
<i className="fa fa-search"></i>
<input type="text" name="Search" placeholder="Harpist, Photographer, Caterer..."/>
</div>
{this.data
? <img src="/img/avatar.png"/>
: <img src="/img/placeholder-person.jpg"/>}
<span>{this.data}
Feeney</span>
<a href="/app/inbox">
<i className="fa fa-envelope"></i>
</a>
<a className="head-spacing" href="#" onClick={this.logout}>LOG OUT</a>
</div>
</div>
</div>
);
},
logout: function(e) {
e.preventDefault();
Meteor.logout();
window.location.href = "/login"; //Need to be moved to History
}
});
You need to use the ReactMeteorData mixin to make getMeteorData() work.
mixins: [
Router.State, Router.Navigation, ReactMeteorData
],
https://atmospherejs.com/meteor/react-meteor-data
The code below should insert the selected item value in the Tasks collection, retain the info for later use, and a headerLabel should show the task selected. I am not able to get the headerLabel to show the task when the click .menuItem function runs. Thanks
Tasks = new Mongo.Collection('tasks');
Template.mainMenu.events({
'click .menuItem': function(event){
Tasks.insert({menuItem: $(event.currentTarget).data('value')});
}
});
Template.header.helpers({
headerLabel: function( id ){
return Tasks.findOne({_id: id}).menuItem;
},
tasks: function(){
return Tasks.find();
}
});
<template name="mainMenu">
<div class="container">
<div class="row">
<section class="col-xs-12">
<div class="list-group">
{{#each menuItems}}
<a href="#" class="list-group-item menuItem" data-value={{menuItem}}>
<img src="/abc.png">
{{menuItem}} <span class="badge">></span>
</a>
{{/each}}
</div>
</section>
</div>
<template name="header">
<h1><button class="col-xs-2 mainMenu" type="button">☰</button></h1>
<h3>
<label class="col-xs-8 text-center">
{{#if headerLabel}} {{headerLabel}} {{else}} Select an item {{/if}}
</label>
</h3>
<h1><button class="col-xs-2" type="button">⋮</button></h1>
</template>
Assuming that the click can happen multiple times, you'll need to pass an _id for the appropriate task into your helper:
Tasks = new Mongo.Collection('tasks');
Template.mainMenu.events({
'click .menuItem': function(event){
Tasks.insert({menuItem: $(event.currentTarget).data('value')});
}
});
Template.header.helpers({
headerLabel: function( id ){
var task = Tasks.findOne({_id: id});
if( task ) {
return task.menuItem;
}
},
tasks: function() {
return Tasks.find();
}
});
So what I'm doing there is saying find one task's menuItem that has the ID passed to the helper. I also added a tasks helper to get all tasks. So your template might look something like:
<template name="mainMenu">
{{#each task in tasks}}
<h4>{{headerLabel task._id}}</h4>
<!-- Additional code here... -->
{{/each}}
</template>
You'll obviously need to customize the HTML to your specific situation, but that should give you the gist. Hope that helps!
Im creating a an instant messenger app and im having a little trouble routing it. So, once you go into the app. There is a list of available users. You can click on a user and start chatting. The issue I have is once I click send, the console show an Uncaught TypeError: Cannot read property 'value' of undefined. Im not sure what im doing wrong here. Also I need help show the chat messages sent above. As if you can see the recent and previous messages. Here are my codes. Any examples and helps would be great.
HTML
minstant
<body>
</body>
<!-- this is the main template used by iron:router to build the page -->
<template name="ApplicationLayout">
{{> yield "header"}}
<div class="container">
{{> yield "main"}}
</div>
</template>
<!-- top level template for the nav bar -->
<template name="navbar">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/">
Minstant!
</a>
</div>
<div class="nav navbar-nav">
{{> loginButtons}}
</div>
</div>
</nav>
</template>
<!-- Top level template for the lobby page -->
<template name="lobby_page">
{{> available_user_list}}
</template>
<!-- display a list of users -->
<template name="available_user_list">
<h2>Choose someone to chat with:</h2>
<div class="row">
{{#each users}}
{{> available_user}}
{{/each}}
</div>
</template>
<!-- display an individual user -->
<template name="available_user">
<div class="col-md-2">
<div class="user_avatar">
{{#if isMyUser _id}}
<div class="user_avatar">{{getUsername _id}} (YOU)
<br/>
<img src="/{{profile.avatar}}" class="avatar_img">
</div>
{{else}}
<a href="/chat/{{_id}}">
{{getUsername _id}}
<br/>
<img src="/{{profile.avatar}}" class="avatar_img">
</a>
{{/if}}
</div>
</div>
</template>
<!-- Top level template for the chat page -->
<template name="chat_page">
<h2>Type in the box below to send a message!</h2>
<div class="row">
<div class="col-md-12">
<div class="well well-lg">
{{#each recentMessages}}
{{> message}}
{{/each}}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form class="new-message">
<input class="input" type="text" name="chat" placeholder="type a message here...">
<button class="btn btn-default">Send</button>
</form>
</div>
</div>
</template>
<!-- simple template that displays a message -->
<template name="message">
<div class = "container">
<div class = "row">
<div class = "username">{{username}}
<img src="/{{profile.avatar}}" class="avatar_img">
</div>
<div class = "message-text"> said: {{messageText}}</div>
</div>
</div>
</template>
Here is my JS
Messages = new Mongo.Collection("messages");
if (Meteor.isClient) {
Meteor.subscribe("messages");
Meteor.subscribe("userStatus");
// set up the main template the the router will use to build pages
Router.configure({
layoutTemplate: 'ApplicationLayout'
});
// specify the top level route, the page users see when they arrive at the site
Router.route('/', function () {
console.log("rendering root /");
this.render("navbar", {to:"header"});
this.render("lobby_page", {to:"main"});
});
// specify a route that allows the current user to chat to another users
Router.route('/chat/:_id', function () {
this.render("navbar", {to:"header"});
this.render("chat_page", {to:"main"});
});
///
// helper functions
///
Template.available_user_list.helpers({
users:function(){
return Meteor.users.find();
}
})
Template.available_user.helpers({
getUsername:function(userId){
user = Meteor.users.findOne({_id:userId});
return user.profile.username;
},
isMyUser:function(userId){
if (userId == Meteor.userId()){
return true;
}
else {
return false;
}
}
})
Template.chat_page.helpers({
recentMessages: function () {
return Messages.find({}, {sort: {createdAt: 1}});
return Meteor.users.find();
},
});
Template.chat_page.events({
// this event fires when the user sends a message on the chat page
'submit .new-message':function(event){
event.preventDefault();
var text= event.target.text.value;
// stop the form from triggering a page reload
event.target.text.value = "";
// see if we can find a chat object in the database
// to which we'll add the message
Meteor.call("SendMessage", text);
},
});
};
Meteor.methods({
sendMessage: function (messageText) {
if (! Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Messages.insert({
messageText: messageText,
createdAt: new Date(),
username: Meteor.user().username
});
}
});
// start up script that creates some users for testing
// users have the username 'user1#test.com' .. 'user8#test.com'
// and the password test123
if (Meteor.isServer) {
Meteor.startup(function () {
if (!Meteor.users.findOne()){
for (var i=1;i<9;i++){
var email = "user"+i+"#test.com";
var username = "user"+i;
var avatar = "ava"+i+".png"
console.log("creating a user with password 'test123' and username/ email: "+email);
Meteor.users.insert({profile:{username:username, avatar:avatar}, emails: [{address:email}],services:{ password:{"bcrypt" : "$2a$10$I3erQ084OiyILTv8ybtQ4ON6wusgPbMZ6.P33zzSDei.BbDL.Q4EO"}}});
}
}
},
),
Meteor.publish("messages", function () {
return Messages.find();
});
Meteor.publish("userStatus", function() {
return Meteor.users.find({ "status.online": true });
});
};
The error is with your form submit code. In the console you can see the error is in Template.chat_page.events.submit .new-message on line 73. That will take you right to the error code:
'submit .new-message':function(event){
event.preventDefault();
var text = event.target.text.value;
// stop the form from triggering a page reload
event.target.text.value = "";
}
event.target.text.value doesn't exist. event.target does, but there is no field for text. There is textContent, and you can access the children of the target (which in this case is the form). Put in a console.log(event); and figure out what you are trying to access within the javascript console and then use that to determine what your code should look like. This might work for you:
'submit .new-message':function(event){
event.preventDefault();
var text = event.target.elements.chat.value;
// stop the form from triggering a page reload
event.target.text.value = "";
}
event.target.elements.chat.value comes from the name field of the <input>.
As mentioned in the title, the modal does not show up.
The content of the form is loaded via formly and the content of the template seems to load, but it only shows the modal very thin, with the overlay but not the content.
I have a main controller in which I have:
$scope.add = function(){
$modal.open({
templateUrl: 'app/js/templates/popupAddCarForm.html',
controller: 'FormsController',
controllerAs: 'vm',
backdrop: 'static',
resolve: {
formData: function(){
return {
fields: getFormFields(),
model: {}
}
}
}
});
};
My html is like so:
<script type="text/ng-template" id="popupAddCarForm">
<div class="modal">
<div class="modal-dialog">
<div class="modal-header">
<h3 class="modal-title">Adauga masina</h3>
</div>
<div class="modal-body">
<form name="vm.addCarForm">
<formly-form model="vm.formData.model" fields="vm.formData.fields">
</formly-form>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-default" type="submit" >Adauga</button>
</div>
</div>
</div>
And my form controller like so:
davidintercar.controller('FormsController',
function($modalInstance, formData) {
var vm = this;
//debugger;
vm.formData = formData;
vm.originalFields = angular.copy(vm.formData.fields);
}
);
The result is like so:
LATER EDIT:
In order to rid ourselfes of other doubts, here is the code from the demo:
app.controller('ModalInstanceCtrl', function ($modalInstance, formData) {
var vm = this;
debugger;
// function assignment
vm.ok = ok;
vm.cancel = cancel;
// variable assignment
vm.formData = formData;
vm.originalFields = angular.copy(vm.formData.fields);
// function definition
function ok() {
$modalInstance.close(vm.formData.model);
}
function cancel() {
$modalInstance.dismiss('cancel');
};
});
Link: angular-formly.com/#/example/integrations/ui-bootstrap-modal
LATER, LATER EDIT:
Plunker: http://plnkr.co/edit/8wgL4t2oXsFFeLBKGGW8?p=preview
Folder Structure:
--app
----js
------controller
------services
------templates
------view
----app.js
intex.html
My popupAddCarForm.html is in the templates directory, but as you see in the plunker, it does not render my loaded content, even in the same directory although a separate template file.
The modal template don't need to have the modal and modal-dialog layer - they will be generated by bootstrap.
<script type="text/ng-template" id="popupAddCarForm.html">
<div class="modal-header">test
<h3 class="modal-title">Adauga masina</h3>
</div>
<div class="modal-body">
<form name="vm.addCarForm">
<formly-form model="vm.formData.model" fields="vm.formData.fields">
</formly-form>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-default" type="submit" >Adauga</button>
</div>
</script>