Google Analytics Reporting API v4 missing required authentication credential - google-analytics

I'm new to using this API, and I am struggling with retrieving my data using Google Analytics Reporting API v4. I am only attempting to retrieve the analytics for a website so I can build a dashboard. I am following the example here: Hello Analytics Reporting API v4; JavaScript quick start for web applications
I am able to make a request, however, I keep getting a 400 error that is might be this: 400: Invalid Credentials
`Invalid JSON payload received. Unknown name "express" at 'report_requests[0].metrics[0]': Cannot find field.
handleError # Test.js:64
h.o0 # cb=gapi.loaded_0:53
xs # cb=gapi.loaded_0:56
Wq # cb=gapi.loaded_0:56
_.C.uea # cb=gapi.loaded_0:55
Ap # cb=gapi.loaded_0:49`
I have no idea what I am doing wrong, and I am hoping someone can point me in the right direction.
This is a react application. I am making the request after the component mounts.
import React, { Component } from 'react';
import GoogleLogin from 'react-google-login';
import $ from 'jquery';
const VIEW_ID = '17414592';
const CLIENT_ID = "936270024581-stgn130l17v21s6vjch9p751hiqbovac.apps.googleusercontent.com";
const DISC_DOCS = 'https://analyticsreporting.googleapis.com/$discovery/rest?version=v4';
export default class Test extends Component {
constructor(props) {
super(props)
this.printResults = this.printResults.bind(this);
this.handleRequest = this.handleRequest.bind(this);
this.handleError = this.handleError.bind(this);
}
//when component mounts, create a google sign in button.
componentDidMount() {
//load gapi
$.getScript("https://apis.google.com/js/client:platform.js")
.done(() => {
window.gapi.signin2.render('my-signin2', {
'scope': 'profile email',
'width': 240,
'height': 50,
'longtitle': true,
'theme': 'dark',
'onsuccess': this.handleRequest,
'onfailure': this.handleError
});
})
}
//on success, make a request to get google analytics data
handleRequest() {
window.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: [{ express: 'ga:sessions' }]
}
]
}
}).then(this.printResults, this.handleError)
}
//log the data
printResults(response) {
console.log(response)
}
//or the error if there is one
handleError(reason) {
console.error(reason)
console.error(reason.result.error.message);
}
//render it all
render() {
return (
<div>
<div id="my-signin2"></div>
</div>
)
}
}
What am I doing wrong? How can I properly do a batchRequest to retrieve all data from 7 days using Google Analytics Reporting APIv4? I feel as though I am using all required fields, however, I do not know what I am missing. Can you point me in the right direction?

I'm answering my own question in case anyone else needs help using google analytics reporting api v4 with react. This is what I needed to do in order to construct a gapi button that would then make a basic request. The error I was creating was from a typo. Instead of metrics:[{expression: 'ga:sessions'}] I was using metrics:[{express: 'ga:sessions'}].
Here is a component that will create a simple request. Please note you have to change the VIEW_ID to your value.
import React, { Component } from 'react';
import $ from 'jquery';
const VIEW_ID = '17414592';
export default class Test extends Component {
constructor(props) {
super(props)
this.printResults = this.printResults.bind(this);
this.handleRequest = this.handleRequest.bind(this);
this.handleError = this.handleError.bind(this);
}
//when component mounts, create a google sign in button.
componentDidMount() {
//load gapi
$.getScript("https://apis.google.com/js/client:platform.js")
.done(() => {
window.gapi.signin2.render('my-signin2', {
'scope': 'profile email',
'width': 240,
'height': 50,
'longtitle': true,
'theme': 'dark',
'onsuccess': this.handleRequest,
'onfailure': this.handleError
});
})
}
//on success, make a request to get google analytics data
handleRequest() {
window.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' }]
}
]
}
}).then(this.printResults, this.handleError)
}
//log the data
printResults(response) {
console.log(response)
}
//or the error if there is one
handleError(reason) {
console.error(reason)
console.error(reason.result.error.message);
}
//render it all
render() {
return (
<div>
<div id="my-signin2"></div>
</div>
)
}
}

401: Invalid Credentials Invalid authorization header. The access
token you're using is either expired or invalid.
This basically means that you need to sign in again.
Click the Sign in button, and authorize access to Google Analytics.
Remember access tokens are only valid for an hour your going to have to sign it in again when ever it expires.

Related

With Strapi 4 how can I get each users music events

I'm using strapi 4 with nextjs.
In the app strapi holds music events for each user and each user should be able add and retrieve there own music events.
I am having trouble retrieving
each users music events from strapi 4
I have a custom route and custom controller
The custom route is in a file called custom-event.js and works ok it is as follows:
module.exports = {
routes: [
{
method: 'GET',
path: '/events/me',
handler: 'custom-controller.me',
config: {
me: {
auth: true,
policies: [],
middlewares: [],
}
}
},
],
}
The controller id a file called custom-controller.js and is as follows:
module.exports = createCoreController(modelUid, ({strapi }) => ({
async me(ctx) {
try {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{messages: [{ id: 'No authorization header was found'}]}
])
}
// The line below works ok
console.log('user', user);
// The problem seems to be the line below
const data = await strapi.services.events.find({ user: user.id})
// This line does not show at all
console.log('data', data);
if (!data) {
return ctx.notFound()
}
return sanitizeEntity(data, { model: strapi.models.events })
} catch(err) {
ctx.body = err
}
}
}))
Note there are two console.logs the first console.log works it outputs the user info
The second console.log outputs the data it does not show at all. The result I get back
using insomnia is a 200 status and an empty object {}
The following line in the custom-controller.js seems to be where the problem lies it works for strapi 3 but does not seem to work for strapi 4
const data = await strapi.services.events.find({ user: user.id})
After struggling for long time, days infact, I eventually got it working. Below is the code I came up with. I found I needed two queries to the database, because I could not get the events to populate the images with one query. So I got the event ids and then used the event ids in a events query to get the events and images.
Heres the code below:
const utils = require('#strapi/utils')
const { sanitize } = utils
const { createCoreController } = require("#strapi/strapi").factories;
const modelUid = "api::event.event"
module.exports = createCoreController(modelUid, ({strapi }) => ({
async me(ctx) {
try {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{messages: [{ id: 'No authorization header was found'}]}
])
}
// Get event ids
const events = await strapi
.db
.query('plugin::users-permissions.user')
.findMany({
where: {
id: user.id
},
populate: {
events: { select: 'id'}
}
})
if (!events) {
return ctx.notFound()
}
// Get the events into a format for the query
const newEvents = events[0].events.map(evt => ({ id: { $eq: evt.id}}))
// use the newly formatted newEvents in a query to get the users
// events and images
const eventsAndMedia = await strapi.db.query(modelUid).findMany({
where: {
$or: newEvents
},
populate: {image: true}
})
return sanitize.contentAPI.output(eventsAndMedia,
strapi.getModel(modelUid))
} catch(err) {
return ctx.internalServerError(err.message)
}
}
}))

How to connect google analytics to Nuxt3 app?

I have a problem. I try to connect my Nuxt3 app with Google Analytics.
right now I do it by adding to nuxt.config.ts following code
export default defineNuxtConfig({
buildModules: [
'#nuxtjs/google-analytics'
],
googleAnalytics: {
id: process.env.GOOGLE_ANALYTICS_ID
},
})
but unfortunately I get following error when I try to build my app
ERROR Error compiling template: { 17:53:04
ssr: false,
src: 'C:\\Users\\szczu\\Elektryk\\node_modules\\#nuxtjs\\google-analytics\\lib\\plugin.js',
fileName: 'google-analytics.js',
options: {
dev: true,
debug: {
sendHitTask: true
},
id: undefined
},
filename: 'google-analytics.js',
dst: 'C:/Users/szczu/Elektryk/.nuxt/google-analytics.js'
}
ERROR serialize is not defined 17:53:04
at eval (eval at <anonymous> (node_modules\lodash.template\index.js:1550:12), <anonymous>:7:1)
at compileTemplate (/C:/Users/szczu/Elektryk/node_modules/#nuxt/kit/dist/index.mjs:493:45)
at async /C:/Users/szczu/Elektryk/node_modules/nuxt3/dist/chunks/index.mjs:1296:22
at async Promise.all (index 11)
at async generateApp (/C:/Users/szczu/Elektryk/node_modules/nuxt3/dist/chunks/index.mjs:1295:3)
at async _applyPromised (/C:/Users/szczu/Elektryk/node_modules/perfect-debounce/dist/index.mjs:54:10)
Does anyone have an idea how to fix it?
Try the vue-vtag-next package as a plugin
yarn add --dev vue-gtag-next
Create a plugin file plugins/vue-gtag.client.js
import VueGtag from 'vue-gtag-next'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueGtag, {
property: {
id: 'GA_MEASUREMENT_ID'
}
})
})
Late reply, but i would like to add for any future viewers.
The above solution only worked for me when the $router was passed. Please find below sample code.
Please also note:
The package being used, 'vue-gtag' instead of 'vue-gtag-next'.
You have to pass config object instead of property for the 'vue-gtag' package
import VueGtag from 'vue-gtag'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueGtag, {
config: {
id: 'GA_MEASUREMENT_ID',
},
}, nuxtApp.$router)
})
found this solution https://github.com/nuxt/framework/discussions/5702
.. And also you may use nuxt.config to provide app.head.script with children attribute on the app level:
import { defineNuxtConfig } from "nuxt";
export default defineNuxtConfig({
app: {
head: {
script: [{ children: 'console.log("test3");' }],
},
},
});
import VueGtag from 'vue-gtag-next'
export default defineNuxtPlugin(async (nuxtApp) => {
const { data: { value: {google_id, google_sv, yandex_id, privacy_policy} } } = await useMyApi("/api/main/site-metriks/");
nuxtApp.vueApp.use(VueGtag, {
property: {
id: google_id
}
})
})
For Nuxt 3:
Install vue-gtm: npm i #gtm-support/vue-gtm
Create file in /plugins/vue-gtm.client.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(createGtm({
id: 'GTM-ID',
defer: false, // Script can be set to `defer` to speed up page load at the cost of less accurate results (in case visitor leaves before script is loaded, which is unlikely but possible). Defaults to false, so the script is loaded `async` by default
compatibility: false, // Will add `async` and `defer` to the script tag to not block requests for old browsers that do not support `async`
nonce: '2726c7f26c', // Will add `nonce` to the script tag
enabled: true, // defaults to true. Plugin can be disabled by setting this to false for Ex: enabled: !!GDPR_Cookie (optional)
debug: true, // Whether or not display console logs debugs (optional)
loadScript: true, // Whether or not to load the GTM Script (Helpful if you are including GTM manually, but need the dataLayer functionality in your components) (optional)
vueRouter: useRouter(), // Pass the router instance to automatically sync with router (optional)
//ignoredViews: ['homepage'], // Don't trigger events for specified router names (optional)
trackOnNextTick: false, // Whether or not call trackView in Vue.nextTick
}))
})
Nuxt would automatically pick up this plugin and you're done.

paypal not redirecting to the paypal site but inks are getting in the response in Meteor

I am new to Meteor and integrating Paypal(Which i never had done).
from Client side in meteor -
I am calling method on button click.
<MDBBtn onClick={(e) => callPaypal(e)} color="primary" type="submit">
Add and Continue to PayPal
</MDBBtn>
And this callpaypal() method ->
import { Link as ReactRouterLink } from 'react-router-dom'
const callPaypal = (e) => {
e.preventDefault();
Meteor.call('createPayalPayment', (err, res) => {
console.log(res[1].href) **FIRST CONSOLE**
if (res) {
let link = res[1];
if (link.href) {
return <ReactRouterLink to={`${link.href}`} />
}
}
})
}
Calling createPayalPayment method from server ->
import { Config } from "./paypal_config";
createPayalPayment() {
var data = {
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"redirect_urls": {
// "return_url": `${Meteor.absoluteUrl('/execute'), { "replaceLocalhost": "true" }}`,
"return_url": "http://127.0.0.1:3000/execute",
"cancel_url": "http://172.20.10.5:3000/cancel"
},
"transactions": [{
"amount": {
"currency": "USD",
"total": "1.00"
},
"description": "This is the payment description."
}]
};
paypal.configure(Config);
var ppCreate = Meteor.wrapAsync(paypal.payment.create.bind(paypal.payment));
var ppExecute = Meteor.wrapAsync(paypal.payment.execute.bind(paypal.payment));
var response = ppCreate(data);
if (response.state !== 'created') {
console.log('not created!!!!!!!!!!!!!!!!')
}
else {
console.log(response); **SECOND CONSOLE**
return response.links;
}
}
And here is my Paypal config ->
export const Config = {
'mode': 'sandbox',
'client_id': 'client_Id',
'client_secret': 'secret'
};
FIRST CONSOLE --> 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-1HR12649X9688931M'
SECOND CONSOLE -->>
{ id: 'PAYID-L2IJO4I8GE24787GF168351L',
intent: 'sale',
state: 'created',
payer: { payment_method: 'paypal' },
transactions:
[ { amount: [Object],
description: 'This is the payment description.',
related_resources: [] } ],
create_time: '2020-04-10T15:57:37Z',
links:
[ { href: 'https://api.sandbox.paypal.com/v1/payments/payment/PAYID-L2IJO4I8GE24787GF168351L',
rel: 'self',
method: 'GET' },
{ href: 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-1HR12649X9688931M',
rel: 'approval_url',
method: 'REDIRECT' },
{ href: 'https://api.sandbox.paypal.com/v1/payments/payment/PAYID-L2IJO4I8GE24787GF168351L/execute',
rel: 'execute',
method: 'POST' } ],
httpStatusCode: 201
}
As the links[2].href is the URL, where the paypal should be redirect here and user can login to the account. But It is not redirecting. So I am manually redirecting to this link in callPaypal() method just below the First console.
But Still the router is unable to redirect to the link maybe Outer Domain Issue or whatever even it's not showing error.
Please Is there any way that the paypal redirect itself to paypal login? I have already wasted my 2 days on this and still have nothing.
Thanks.
I added the Redirect URL in my paypal developer account for this project.
It looks like you're using an old, redirect-based PayPal integration, so my recommendation is trying the new in-context experience: https://developer.paypal.com/demo/checkout/#/pattern/server
Notice the two fetch calls to '/demo/..' placeholders, which would need to be replaced with actual routes on your server. The first should return a PayID (or newer v2/orders ID), and the second should execute/capture that ID.
This integration is superior because your site stays loaded in the background, and the buyer is able to checkout and pay without 'leaving' it.
On the server side, it looks like you may be using the old deprecated v1 PayPal-node-SDK, which there is no reason to do for a new integration. Instead, use the v2 Checkout-NodeJS-SDK

Is there a way to show related model ids without sideloading or embedding data

My understanding is that using serializeIds: 'always' will give me this data, but it does not.
Here's what I'm expecting:
{
id="1"
title="some title"
customerId="2"
}
Instead the output I'm receiving is:
{
id="1"
title="some title"
}
My code looks something like this:
import {
Server,
Serializer,
Model,
belongsTo,
hasMany,
Factory
} from "miragejs";
import faker from "faker";
const ApplicationSerializer = Serializer.extend({
// don't want a root prop
root: false,
// true required to have root:false
embed: true,
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always"
});
export function makeServer() {
let server = newServer({
models: {
invoice: Model.extend({
customer: belongsTo()
}),
customer: Model.extend({
invoices: hasMany()
})
},
factories: {
invoice: Factory.extend({
title(i) {
return `Invoice ${i}`;
},
afterCreate(invoice, server) {
if (!invoice.customer) {
invoice.update({
customer: server.create("customer")
});
}
}
}),
customer: Factory.extend({
name() {
let fullName = () =>
`${faker.name.firstName()} ${faker.name.lastName()}`;
return fullName;
}
})
},
seeds(server) {
server.createList("invoice", 10);
},
serializers: {
application: ApplicationSerializer,
invoice: ApplicationSerializer.extend({
include: ["customer"]
})
},
routes() {
this.namespace = "api";
this.get("/auth");
}
});
}
Changing the config to root: true, embed: false, provides the correct output in the invoice models, but adds the root and sideloads the customer, which I don't want.
You've run into some strange behavior with how how serializeIds interacts with embed.
First, it's confusing why you need to set embed: true when you're just trying to disable the root. The reason is because embed defaults to false, so if you remove the root and try to include related resources, Mirage doesn't know where to put them. This is a confusing mix of options and Mirage should really have different "modes" that take this into account.
Second, it seems that when embed is true, Mirage basically ignores the serializeIds option, since it thinks your resources will always be embedded. (The idea here is that a foreign key is used to fetch related resources separately, but when they're embedded they always come over together.) This is also confusing and doesn't need to be the case. I've opened a tracking issue in Mirage to help address these points.
As for you today, the best way to solve this is to leave root to true and embed false, which are both the defaults, so that serializeIds works properly, and then just write your own serialize() function to remove the key for you:
const ApplicationSerializer = Serializer.extend({
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always",
serialize(resource, request) {
let json = Serializer.prototype.serialize.apply(this, arguments);
let root = resource.models ? this.keyForCollection(resource.modelName) : this.keyForModel(resource.modelName)
return json[root];
}
});
You should be able to test this out on both /invoices and /invoices/1.
Check out this REPL example and try making a request to each URL.
Here's the config from the example:
import {
Server,
Serializer,
Model,
belongsTo,
hasMany,
Factory,
} from "miragejs";
import faker from "faker";
const ApplicationSerializer = Serializer.extend({
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always",
serialize(resource, request) {
let json = Serializer.prototype.serialize.apply(this, arguments);
let root = resource.models ? this.keyForCollection(resource.modelName) : this.keyForModel(resource.modelName)
return json[root];
}
});
export default new Server({
models: {
invoice: Model.extend({
customer: belongsTo(),
}),
customer: Model.extend({
invoices: hasMany(),
}),
},
factories: {
invoice: Factory.extend({
title(i) {
return "Invoice " + i;
},
afterCreate(invoice, server) {
if (!invoice.customer) {
invoice.update({
customer: server.create("customer"),
});
}
},
}),
customer: Factory.extend({
name() {
return faker.name.firstName() + " " + faker.name.lastName();
},
}),
},
seeds(server) {
server.createList("invoice", 10);
},
serializers: {
application: ApplicationSerializer,
},
routes() {
this.resource("invoice");
},
});
Hopefully that clears things up + sorry for the confusing APIs!

In Meteor when trying to access an attribute, I get TypeError: Cannot read property in the console. But the site is working

When trying to read an attribute, meteor gives me a TypeError: Cannot read property 'featuredImage' of undefined error in the browser console. But it reads featuredImage and the site is working fine. How can I get rid of this error? Is it happening because my subscriptions are not yet ready? Is that's the case, how to fix it? (PS : Im using the flow router so I can't wait for subscriptions in the router)
My template code :
Template.About.helpers({
page: () => {
return findPage();
},
featuredImage: () => {
var thisPage = findPage();
return Images.findOne({
"_id": thisPage.featuredImage
});
}
});
function findPage() {
return Pages.findOne({
slug: 'about'
});
}
The router code :
FlowRouter.route('/about', {
name: 'about',
subscriptions: function() {
this.register('page', Meteor.subscribe('pages', 'about'));
this.register('image', Meteor.subscribe('images'));
},
action() {
BlazeLayout.render('MainLayout', {
content: 'About'
});
setTitle('About Us');
},
fastRender: true
});
The subscription is probably not ready yet. FlowRouter provides a utility for dealing with this, your helpers should look like this:
Template.About.helpers({
page: () => {
// If you only need a specific subscription to be ready
return FlowRouter.subsReady('page') && findPage() || null;
},
featuredImage: () => {
// Ensure ALL subscriptions are ready
if ( FlowRouter.subsReady() ) {
var thisPage = findPage();
return Images.findOne({
"_id": thisPage.featuredImage // Probably should be thisPage.featuredImage._id
});
}
return null;
}
});
However, for maximum performance, you should use if (FlowRouter.subsReady('page') && Flowrouter.subsReady('image')) rather than FlowRouter.subsReady() since if you have other pending subscriptions which are large, it will wait for those even though you don't need them.

Resources