feat(Google Analytics Node): Overhaul for google analytics node
This commit is contained in:
@@ -0,0 +1,488 @@
|
||||
import { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const dimensionDropdown: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Dimension',
|
||||
name: 'listName',
|
||||
type: 'options',
|
||||
default: 'date',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Browser',
|
||||
value: 'browser',
|
||||
},
|
||||
{
|
||||
name: 'Campaign',
|
||||
value: 'campaignName',
|
||||
},
|
||||
{
|
||||
name: 'City',
|
||||
value: 'city',
|
||||
},
|
||||
{
|
||||
name: 'Country',
|
||||
value: 'country',
|
||||
},
|
||||
{
|
||||
name: 'Date',
|
||||
value: 'date',
|
||||
},
|
||||
{
|
||||
name: 'Device Category',
|
||||
value: 'deviceCategory',
|
||||
},
|
||||
{
|
||||
name: 'Item Name',
|
||||
value: 'itemName',
|
||||
},
|
||||
{
|
||||
name: 'Language',
|
||||
value: 'language',
|
||||
},
|
||||
{
|
||||
name: 'Page Location',
|
||||
value: 'pageLocation',
|
||||
},
|
||||
{
|
||||
name: 'Source / Medium',
|
||||
value: 'sourceMedium',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Other dimensions…',
|
||||
value: 'other',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Name or ID',
|
||||
name: 'name',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDimensionsGA4',
|
||||
loadOptionsDependsOn: ['propertyId.value'],
|
||||
},
|
||||
default: 'date',
|
||||
description:
|
||||
'The name of the dimension. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['other'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const metricDropdown: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Metric',
|
||||
name: 'listName',
|
||||
type: 'options',
|
||||
default: 'totalUsers',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: '1 Day Active Users',
|
||||
value: 'active1DayUsers',
|
||||
},
|
||||
{
|
||||
name: '28 Day Active Users',
|
||||
value: 'active28DayUsers',
|
||||
},
|
||||
{
|
||||
name: '7 Day Active Users',
|
||||
value: 'active7DayUsers',
|
||||
},
|
||||
{
|
||||
name: 'Checkouts',
|
||||
value: 'checkouts',
|
||||
},
|
||||
{
|
||||
name: 'Events',
|
||||
value: 'eventCount',
|
||||
},
|
||||
{
|
||||
name: 'Page Views',
|
||||
value: 'screenPageViews',
|
||||
},
|
||||
{
|
||||
name: 'Session Duration',
|
||||
value: 'userEngagementDuration',
|
||||
},
|
||||
{
|
||||
name: 'Sessions',
|
||||
value: 'sessions',
|
||||
},
|
||||
{
|
||||
name: 'Sessions per User',
|
||||
value: 'sessionsPerUser',
|
||||
},
|
||||
{
|
||||
name: 'Total Users',
|
||||
value: 'totalUsers',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Other metrics…',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Custom metric…',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Name or ID',
|
||||
name: 'name',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getMetricsGA4',
|
||||
loadOptionsDependsOn: ['propertyId.value'],
|
||||
},
|
||||
default: 'totalUsers',
|
||||
hint: 'If expression is specified, name can be any string that you would like',
|
||||
description:
|
||||
'The name of the metric. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['other'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: 'custom_metric',
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['custom'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const dimensionsFilterExpressions: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Expression',
|
||||
name: 'expression',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Expression',
|
||||
options: [
|
||||
{
|
||||
displayName: 'String Filter',
|
||||
name: 'stringFilter',
|
||||
values: [
|
||||
...dimensionDropdown,
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Case Sensitive',
|
||||
name: 'caseSensitive',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Match Type',
|
||||
name: 'matchType',
|
||||
type: 'options',
|
||||
default: 'EXACT',
|
||||
options: [
|
||||
{
|
||||
name: 'Begins With',
|
||||
value: 'BEGINS_WITH',
|
||||
},
|
||||
{
|
||||
name: 'Contains Value',
|
||||
value: 'CONTAINS',
|
||||
},
|
||||
{
|
||||
name: 'Ends With',
|
||||
value: 'ENDS_WITH',
|
||||
},
|
||||
{
|
||||
name: 'Exact Match',
|
||||
value: 'EXACT',
|
||||
},
|
||||
{
|
||||
name: 'Full Match for the Regular Expression',
|
||||
value: 'FULL_REGEXP',
|
||||
},
|
||||
{
|
||||
name: 'Partial Match for the Regular Expression',
|
||||
value: 'PARTIAL_REGEXP',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'In List Filter',
|
||||
name: 'inListFilter',
|
||||
values: [
|
||||
...dimensionDropdown,
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'values',
|
||||
type: 'string',
|
||||
default: '',
|
||||
hint: 'Comma separated list of values. Must be non-empty.',
|
||||
},
|
||||
{
|
||||
displayName: 'Case Sensitive',
|
||||
name: 'caseSensitive',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Numeric Filter',
|
||||
name: 'numericFilter',
|
||||
values: [
|
||||
...dimensionDropdown,
|
||||
{
|
||||
displayName: 'Value Type',
|
||||
name: 'valueType',
|
||||
type: 'options',
|
||||
default: 'doubleValue',
|
||||
options: [
|
||||
{
|
||||
name: 'Double Value',
|
||||
value: 'doubleValue',
|
||||
},
|
||||
{
|
||||
name: 'Integer Value',
|
||||
value: 'int64Value',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
default: 'EQUAL',
|
||||
options: [
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'EQUAL',
|
||||
},
|
||||
{
|
||||
name: 'Greater Than',
|
||||
value: 'GREATER_THAN',
|
||||
},
|
||||
{
|
||||
name: 'Greater than or Equal',
|
||||
value: 'GREATER_THAN_OR_EQUAL',
|
||||
},
|
||||
{
|
||||
name: 'Less Than',
|
||||
value: 'LESS_THAN',
|
||||
},
|
||||
{
|
||||
name: 'Less than or Equal',
|
||||
value: 'LESS_THAN_OR_EQUAL',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const dimensionFilterField: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Dimensions Filters',
|
||||
name: 'dimensionFiltersUI',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
placeholder: 'Add Filter',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filter Expressions',
|
||||
name: 'filterExpressions',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Filter Expression Type',
|
||||
name: 'filterExpressionType',
|
||||
type: 'options',
|
||||
default: 'andGroup',
|
||||
options: [
|
||||
{
|
||||
name: 'And Group',
|
||||
value: 'andGroup',
|
||||
},
|
||||
{
|
||||
name: 'Or Group',
|
||||
value: 'orGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
...dimensionsFilterExpressions,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const metricsFilterExpressions: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Expression',
|
||||
name: 'expression',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Expression',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Between Filter',
|
||||
name: 'betweenFilter',
|
||||
values: [
|
||||
...metricDropdown,
|
||||
{
|
||||
displayName: 'Value Type',
|
||||
name: 'valueType',
|
||||
type: 'options',
|
||||
default: 'doubleValue',
|
||||
options: [
|
||||
{
|
||||
name: 'Double Value',
|
||||
value: 'doubleValue',
|
||||
},
|
||||
{
|
||||
name: 'Integer Value',
|
||||
value: 'int64Value',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'From Value',
|
||||
name: 'fromValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'To Value',
|
||||
name: 'toValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Numeric Filter',
|
||||
name: 'numericFilter',
|
||||
values: [
|
||||
...metricDropdown,
|
||||
{
|
||||
displayName: 'Value Type',
|
||||
name: 'valueType',
|
||||
type: 'options',
|
||||
default: 'doubleValue',
|
||||
options: [
|
||||
{
|
||||
name: 'Double Value',
|
||||
value: 'doubleValue',
|
||||
},
|
||||
{
|
||||
name: 'Integer Value',
|
||||
value: 'int64Value',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
default: 'EQUAL',
|
||||
options: [
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'EQUAL',
|
||||
},
|
||||
{
|
||||
name: 'Greater Than',
|
||||
value: 'GREATER_THAN',
|
||||
},
|
||||
{
|
||||
name: 'Greater than or Equal',
|
||||
value: 'GREATER_THAN_OR_EQUAL',
|
||||
},
|
||||
{
|
||||
name: 'Less Than',
|
||||
value: 'LESS_THAN',
|
||||
},
|
||||
{
|
||||
name: 'Less than or Equal',
|
||||
value: 'LESS_THAN_OR_EQUAL',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const metricsFilterField: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Metrics Filters',
|
||||
name: 'metricsFiltersUI',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
placeholder: 'Add Filter',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filter Expressions',
|
||||
name: 'filterExpressions',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Filter Expression Type',
|
||||
name: 'filterExpressionType',
|
||||
type: 'options',
|
||||
default: 'andGroup',
|
||||
options: [
|
||||
{
|
||||
name: 'And Group',
|
||||
value: 'andGroup',
|
||||
},
|
||||
{
|
||||
name: 'Or Group',
|
||||
value: 'orGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
...metricsFilterExpressions,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,55 @@
|
||||
import { INodeProperties } from 'n8n-workflow';
|
||||
import * as getga4 from './get.ga4.operation';
|
||||
import * as getuniversal from './get.universal.operation';
|
||||
|
||||
export { getga4, getuniversal };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Return the analytics data',
|
||||
action: 'Get a report',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
},
|
||||
{
|
||||
displayName: 'Property Type',
|
||||
name: 'propertyType',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
description:
|
||||
'Google Analytics 4 is the latest version. Universal Analytics is an older version that is not fully functional after the end of June 2023.',
|
||||
options: [
|
||||
{
|
||||
name: 'Google Analytics 4',
|
||||
value: 'ga4',
|
||||
},
|
||||
{
|
||||
name: 'Universal Analytics',
|
||||
value: 'universal',
|
||||
},
|
||||
],
|
||||
default: 'ga4',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...getga4.description,
|
||||
...getuniversal.description,
|
||||
];
|
||||
@@ -0,0 +1,620 @@
|
||||
import { IExecuteFunctions } from 'n8n-core';
|
||||
import { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import {
|
||||
checkDuplicates,
|
||||
defaultEndDate,
|
||||
defaultStartDate,
|
||||
prepareDateRange,
|
||||
processFilters,
|
||||
simplifyGA4,
|
||||
} from '../../helpers/utils';
|
||||
import { googleApiRequest, googleApiRequestAllItems } from '../../transport';
|
||||
import {
|
||||
dimensionDropdown,
|
||||
dimensionFilterField,
|
||||
metricDropdown,
|
||||
metricsFilterField,
|
||||
} from './FiltersDescription';
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'propertyId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
description: 'The Property of Google Analytics',
|
||||
hint: "If this doesn't work, try changing the 'Property Type' field above",
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'Select a property...',
|
||||
typeOptions: {
|
||||
searchListMethod: 'searchProperties',
|
||||
searchFilterRequired: false,
|
||||
searchable: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'https://analytics.google.com/analytics/...',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '.*analytics\\.google\\.com\\/analytics.*\\/p([0-9]{1,})(?:\\/.*|)*',
|
||||
errorMessage: 'Not a valid Google Analytics URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: '.*analytics\\.google\\.com\\/analytics.*\\/p([0-9]{1,})(?:\\/.*|)',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: '123456',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[0-9]{1,}',
|
||||
errorMessage: 'Not a valid Google Analytics Property ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
url: '=https://analytics.google.com/analytics/web/#/p{{$value}}/',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Date Range',
|
||||
name: 'dateRange',
|
||||
type: 'options',
|
||||
required: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Last 7 Days',
|
||||
value: 'last7days',
|
||||
},
|
||||
{
|
||||
name: 'Last 30 Days',
|
||||
value: 'last30days',
|
||||
},
|
||||
{
|
||||
name: 'Today',
|
||||
value: 'today',
|
||||
},
|
||||
{
|
||||
name: 'Yesterday',
|
||||
value: 'yesterday',
|
||||
},
|
||||
{
|
||||
name: 'Last Complete Calendar Week',
|
||||
value: 'lastCalendarWeek',
|
||||
},
|
||||
{
|
||||
name: 'Last Complete Calendar Month',
|
||||
value: 'lastCalendarMonth',
|
||||
},
|
||||
{
|
||||
name: 'Custom',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
default: 'last7days',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Start',
|
||||
name: 'startDate',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: defaultStartDate(),
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
dateRange: ['custom'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'End',
|
||||
name: 'endDate',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: defaultEndDate(),
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
dateRange: ['custom'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Metrics',
|
||||
name: 'metricsGA4',
|
||||
type: 'fixedCollection',
|
||||
default: { metricValues: [{ listName: 'totalUsers' }] },
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Metric',
|
||||
description:
|
||||
'The quantitative measurements of a report. For example, the metric eventCount is the total number of events. Requests are allowed up to 10 metrics.',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'metricValues',
|
||||
values: [
|
||||
...metricDropdown,
|
||||
{
|
||||
displayName: 'Expression',
|
||||
name: 'expression',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'A mathematical expression for derived metrics. For example, the metric Event count per user is eventCount/totalUsers.',
|
||||
placeholder: 'e.g. eventCount/totalUsers',
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['custom'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Invisible',
|
||||
name: 'invisible',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['custom'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'Whether a metric is invisible in the report response. If a metric is invisible, the metric will not produce a column in the response, but can be used in metricFilter, orderBys, or a metric expression.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
displayName: 'Dimensions to split by',
|
||||
name: 'dimensionsGA4',
|
||||
type: 'fixedCollection',
|
||||
default: { dimensionValues: [{ listName: 'date' }] },
|
||||
// default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Dimension',
|
||||
description:
|
||||
'Dimensions are attributes of your data. For example, the dimension city indicates the city from which an event originates. Dimension values in report responses are strings; for example, the city could be "Paris" or "New York". Requests are allowed up to 9 dimensions.',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'dimensionValues',
|
||||
values: [...dimensionDropdown],
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['report'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['report'],
|
||||
propertyType: ['ga4'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 1000,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-simplify
|
||||
displayName: 'Simplify Output',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['report'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['ga4'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Currency Code',
|
||||
name: 'currencyCode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'A currency code in ISO4217 format, such as "AED", "USD", "JPY". If the field is empty, the report uses the property\'s default currency.',
|
||||
},
|
||||
...dimensionFilterField,
|
||||
{
|
||||
displayName: 'Metric Aggregation',
|
||||
name: 'metricAggregations',
|
||||
type: 'multiOptions',
|
||||
default: [],
|
||||
options: [
|
||||
{
|
||||
name: 'MAXIMUM',
|
||||
value: 'MAXIMUM',
|
||||
},
|
||||
{
|
||||
name: 'MINIMUM',
|
||||
value: 'MINIMUM',
|
||||
},
|
||||
{
|
||||
name: 'TOTAL',
|
||||
value: 'TOTAL',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/simple': [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
...metricsFilterField,
|
||||
{
|
||||
displayName: 'Keep Empty Rows',
|
||||
name: 'keepEmptyRows',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether false or unspecified, each row with all metrics equal to 0 will not be returned. If true, these rows will be returned if they are not separately removed by a filter.',
|
||||
},
|
||||
{
|
||||
displayName: 'Order By',
|
||||
name: 'orderByUI',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Order',
|
||||
description: 'Specifies how rows are ordered in the response',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Metric Order By',
|
||||
name: 'metricOrderBy',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Descending',
|
||||
name: 'desc',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether true, sorts by descending order',
|
||||
},
|
||||
{
|
||||
displayName: 'Metric Name or ID',
|
||||
name: 'metricName',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getMetricsGA4',
|
||||
loadOptionsDependsOn: ['propertyId.value'],
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Sorts by metric values. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Dimmension Order By',
|
||||
name: 'dimmensionOrderBy',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Descending',
|
||||
name: 'desc',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether true, sorts by descending order',
|
||||
},
|
||||
{
|
||||
displayName: 'Dimmension Name or ID',
|
||||
name: 'dimensionName',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDimensionsGA4',
|
||||
loadOptionsDependsOn: ['propertyId.value'],
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Sorts by metric values. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Order Type',
|
||||
name: 'orderType',
|
||||
type: 'options',
|
||||
default: 'ORDER_TYPE_UNSPECIFIED',
|
||||
options: [
|
||||
{
|
||||
name: 'Alphanumeric',
|
||||
value: 'ALPHANUMERIC',
|
||||
description: 'Alphanumeric sort by Unicode code point',
|
||||
},
|
||||
{
|
||||
name: 'Case Insensitive Alphanumeric',
|
||||
value: 'CASE_INSENSITIVE_ALPHANUMERIC',
|
||||
description:
|
||||
'Case insensitive alphanumeric sort by lower case Unicode code point',
|
||||
},
|
||||
{
|
||||
name: 'Numeric',
|
||||
value: 'NUMERIC',
|
||||
description: 'Dimension values are converted to numbers before sorting',
|
||||
},
|
||||
{
|
||||
name: 'Unspecified',
|
||||
value: 'ORDER_TYPE_UNSPECIFIED',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Return Property Quota',
|
||||
name: 'returnPropertyQuota',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
"Whether to return the current state of this Analytics Property's quota. Quota is returned in PropertyQuota.",
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/simple': [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//migration guide: https://developers.google.com/analytics/devguides/migration/api/reporting-ua-to-ga4#core_reporting
|
||||
const propertyId = this.getNodeParameter('propertyId', index, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||
const dateRange = this.getNodeParameter('dateRange', index) as string;
|
||||
const metricsGA4 = this.getNodeParameter('metricsGA4', index, {}) as IDataObject;
|
||||
const dimensionsGA4 = this.getNodeParameter('dimensionsGA4', index, {}) as IDataObject;
|
||||
const simple = this.getNodeParameter('simple', index) as boolean;
|
||||
|
||||
let responseData: IDataObject[] = [];
|
||||
|
||||
const qs: IDataObject = {};
|
||||
const body: IDataObject = {
|
||||
dateRanges: prepareDateRange.call(this, dateRange, index),
|
||||
};
|
||||
|
||||
if (metricsGA4.metricValues) {
|
||||
const metrics = (metricsGA4.metricValues as IDataObject[]).map((metric) => {
|
||||
switch (metric.listName) {
|
||||
case 'other':
|
||||
return { name: metric.name };
|
||||
case 'custom':
|
||||
const newMetric = {
|
||||
name: metric.name,
|
||||
expression: metric.expression,
|
||||
invisible: metric.invisible,
|
||||
};
|
||||
|
||||
if (newMetric.invisible === false) {
|
||||
delete newMetric.invisible;
|
||||
}
|
||||
|
||||
if (newMetric.expression === '') {
|
||||
delete newMetric.expression;
|
||||
}
|
||||
|
||||
return newMetric;
|
||||
default:
|
||||
return { name: metric.listName };
|
||||
}
|
||||
});
|
||||
if (metrics.length) {
|
||||
checkDuplicates.call(this, metrics, 'name', 'metrics');
|
||||
body.metrics = metrics;
|
||||
}
|
||||
}
|
||||
|
||||
if (dimensionsGA4.dimensionValues) {
|
||||
const dimensions = (dimensionsGA4.dimensionValues as IDataObject[]).map((dimension) => {
|
||||
switch (dimension.listName) {
|
||||
case 'other':
|
||||
return { name: dimension.name };
|
||||
default:
|
||||
return { name: dimension.listName };
|
||||
}
|
||||
});
|
||||
if (dimensions.length) {
|
||||
checkDuplicates.call(this, dimensions, 'name', 'dimensions');
|
||||
body.dimensions = dimensions;
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFields.currencyCode) {
|
||||
body.currencyCode = additionalFields.currencyCode;
|
||||
}
|
||||
|
||||
if (additionalFields.dimensionFiltersUI) {
|
||||
const { filterExpressionType, expression } = (
|
||||
additionalFields.dimensionFiltersUI as IDataObject
|
||||
).filterExpressions as IDataObject;
|
||||
if (expression) {
|
||||
body.dimensionFilter = {
|
||||
[filterExpressionType as string]: {
|
||||
expressions: processFilters(expression as IDataObject),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFields.metricsFiltersUI) {
|
||||
const { filterExpressionType, expression } = (additionalFields.metricsFiltersUI as IDataObject)
|
||||
.filterExpressions as IDataObject;
|
||||
if (expression) {
|
||||
body.metricFilter = {
|
||||
[filterExpressionType as string]: {
|
||||
expressions: processFilters(expression as IDataObject),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFields.metricAggregations) {
|
||||
body.metricAggregations = additionalFields.metricAggregations;
|
||||
}
|
||||
|
||||
if (additionalFields.keepEmptyRows) {
|
||||
body.keepEmptyRows = additionalFields.keepEmptyRows;
|
||||
}
|
||||
|
||||
if (additionalFields.orderByUI) {
|
||||
let orderBys: IDataObject[] = [];
|
||||
const metricOrderBy = (additionalFields.orderByUI as IDataObject)
|
||||
.metricOrderBy as IDataObject[];
|
||||
const dimmensionOrderBy = (additionalFields.orderByUI as IDataObject)
|
||||
.dimmensionOrderBy as IDataObject[];
|
||||
if (metricOrderBy) {
|
||||
orderBys = orderBys.concat(
|
||||
metricOrderBy.map((order) => {
|
||||
return {
|
||||
desc: order.desc,
|
||||
metric: {
|
||||
metricName: order.metricName,
|
||||
},
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (dimmensionOrderBy) {
|
||||
orderBys = orderBys.concat(
|
||||
dimmensionOrderBy.map((order) => {
|
||||
return {
|
||||
desc: order.desc,
|
||||
dimension: {
|
||||
dimensionName: order.dimensionName,
|
||||
orderType: order.orderType,
|
||||
},
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
body.orderBys = orderBys;
|
||||
}
|
||||
|
||||
if (additionalFields.returnPropertyQuota) {
|
||||
body.returnPropertyQuota = additionalFields.returnPropertyQuota;
|
||||
}
|
||||
|
||||
const method = 'POST';
|
||||
const endpoint = `/v1beta/properties/${propertyId}:runReport`;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await googleApiRequestAllItems.call(this, '', method, endpoint, body, qs);
|
||||
} else {
|
||||
body.limit = this.getNodeParameter('limit', 0);
|
||||
responseData = [await googleApiRequest.call(this, method, endpoint, body, qs)];
|
||||
}
|
||||
|
||||
if (responseData?.length && simple) {
|
||||
responseData = simplifyGA4(responseData[0]);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: index } },
|
||||
);
|
||||
|
||||
return executionData;
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
import { IExecuteFunctions } from 'n8n-core';
|
||||
import { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { IData, IDimension, IMetric } from '../../helpers/Interfaces';
|
||||
import {
|
||||
checkDuplicates,
|
||||
defaultEndDate,
|
||||
defaultStartDate,
|
||||
merge,
|
||||
prepareDateRange,
|
||||
simplify,
|
||||
} from '../../helpers/utils';
|
||||
import { googleApiRequest, googleApiRequestAllItems } from '../../transport';
|
||||
|
||||
const dimensionDropdown: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Dimension',
|
||||
name: 'listName',
|
||||
type: 'options',
|
||||
default: 'ga:date',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Browser',
|
||||
value: 'ga:browser',
|
||||
},
|
||||
{
|
||||
name: 'Campaign',
|
||||
value: 'ga:campaign',
|
||||
},
|
||||
{
|
||||
name: 'City',
|
||||
value: 'ga:city',
|
||||
},
|
||||
{
|
||||
name: 'Country',
|
||||
value: 'ga:country',
|
||||
},
|
||||
{
|
||||
name: 'Date',
|
||||
value: 'ga:date',
|
||||
},
|
||||
{
|
||||
name: 'Device Category',
|
||||
value: 'ga:deviceCategory',
|
||||
},
|
||||
{
|
||||
name: 'Item Name',
|
||||
value: 'ga:productName',
|
||||
},
|
||||
{
|
||||
name: 'Language',
|
||||
value: 'ga:language',
|
||||
},
|
||||
{
|
||||
name: 'Page',
|
||||
value: 'ga:pagePath',
|
||||
},
|
||||
{
|
||||
name: 'Source / Medium',
|
||||
value: 'ga:sourceMedium',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Other dimensions…',
|
||||
value: 'other',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Name or ID',
|
||||
name: 'name',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDimensions',
|
||||
loadOptionsDependsOn: ['viewId.value'],
|
||||
},
|
||||
default: 'ga:date',
|
||||
description:
|
||||
'Name of the dimension to fetch, for example ga:browser. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['other'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'View',
|
||||
name: 'viewId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
description: 'The View of Google Analytics',
|
||||
hint: "If this doesn't work, try changing the 'Property Type' field above",
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'Select a view...',
|
||||
typeOptions: {
|
||||
searchListMethod: 'searchViews',
|
||||
searchFilterRequired: false,
|
||||
searchable: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'https://analytics.google.com/analytics/...',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '.*analytics.google.com/analytics.*p[0-9]{1,}.*',
|
||||
errorMessage: 'Not a valid Google Analytics URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: '.*analytics.google.com/analytics.*p([0-9]{1,})',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: '123456',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[0-9]{1,}',
|
||||
errorMessage: 'Not a valid Google Analytics View ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['universal'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Date Range',
|
||||
name: 'dateRange',
|
||||
type: 'options',
|
||||
required: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Last 7 Days',
|
||||
value: 'last7days',
|
||||
},
|
||||
{
|
||||
name: 'Last 30 Days',
|
||||
value: 'last30days',
|
||||
},
|
||||
{
|
||||
name: 'Today',
|
||||
value: 'today',
|
||||
},
|
||||
{
|
||||
name: 'Yesterday',
|
||||
value: 'yesterday',
|
||||
},
|
||||
{
|
||||
name: 'Last Complete Calendar Week',
|
||||
value: 'lastCalendarWeek',
|
||||
},
|
||||
{
|
||||
name: 'Last Complete Calendar Month',
|
||||
value: 'lastCalendarMonth',
|
||||
},
|
||||
{
|
||||
name: 'Custom',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
default: 'last7days',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['universal'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Start',
|
||||
name: 'startDate',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: defaultStartDate(),
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['universal'],
|
||||
dateRange: ['custom'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'End',
|
||||
name: 'endDate',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: defaultEndDate(),
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['universal'],
|
||||
dateRange: ['custom'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Metrics',
|
||||
name: 'metricsUA',
|
||||
type: 'fixedCollection',
|
||||
default: { metricValues: [{ listName: 'ga:users' }] },
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add metric',
|
||||
description: 'Metrics in the request',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Metric',
|
||||
name: 'metricValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Metric',
|
||||
name: 'listName',
|
||||
type: 'options',
|
||||
default: 'ga:users',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Checkouts',
|
||||
value: 'ga:productCheckouts',
|
||||
},
|
||||
{
|
||||
name: 'Events',
|
||||
value: 'ga:totalEvents',
|
||||
},
|
||||
{
|
||||
name: 'Page Views',
|
||||
value: 'ga:pageviews',
|
||||
},
|
||||
{
|
||||
name: 'Session Duration',
|
||||
value: 'ga:sessionDuration',
|
||||
},
|
||||
{
|
||||
name: 'Sessions',
|
||||
value: 'ga:sessions',
|
||||
},
|
||||
{
|
||||
name: 'Sessions per User',
|
||||
value: 'ga:sessionsPerUser',
|
||||
},
|
||||
{
|
||||
name: 'Total Users',
|
||||
value: 'ga:users',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Other metrics…',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
name: 'Custom metric…',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Name or ID',
|
||||
name: 'name',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getMetrics',
|
||||
loadOptionsDependsOn: ['viewId.value'],
|
||||
},
|
||||
default: 'ga:users',
|
||||
hint: 'If expression is specified, name can be any string that you would like',
|
||||
description:
|
||||
'The name of the metric. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['other'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: 'custom_metric',
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['custom'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Expression',
|
||||
name: 'expression',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. ga:totalRefunds/ga:users',
|
||||
description:
|
||||
'Learn more about Google Analytics <a href="https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet#Metric">metric expressions</a>',
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['custom'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Formatting Type',
|
||||
name: 'formattingType',
|
||||
type: 'options',
|
||||
default: 'INTEGER',
|
||||
description: 'Specifies how the metric expression should be formatted',
|
||||
options: [
|
||||
{
|
||||
name: 'Currency',
|
||||
value: 'CURRENCY',
|
||||
},
|
||||
{
|
||||
name: 'Float',
|
||||
value: 'FLOAT',
|
||||
},
|
||||
{
|
||||
name: 'Integer',
|
||||
value: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'Percent',
|
||||
value: 'PERCENT',
|
||||
},
|
||||
{
|
||||
name: 'Time',
|
||||
value: 'TIME',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
listName: ['custom'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['universal'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
displayName: 'Dimensions to split by',
|
||||
name: 'dimensionsUA',
|
||||
type: 'fixedCollection',
|
||||
default: { dimensionValues: [{ listName: 'ga:date' }] },
|
||||
// default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Dimension',
|
||||
description:
|
||||
'Dimensions are attributes of your data. For example, the dimension ga:city indicates the city, for example, "Paris" or "New York", from which a session originates.',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'dimensionValues',
|
||||
values: [...dimensionDropdown],
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['universal'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['report'],
|
||||
propertyType: ['universal'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['report'],
|
||||
propertyType: ['universal'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 1000,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-simplify
|
||||
displayName: 'Simplify Output',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['report'],
|
||||
propertyType: ['universal'],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['report'],
|
||||
operation: ['get'],
|
||||
propertyType: ['universal'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Dimension Filters',
|
||||
name: 'dimensionFiltersUi',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Dimension Filter',
|
||||
description: 'Dimension Filters in the request',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filterValues',
|
||||
values: [
|
||||
...dimensionDropdown,
|
||||
// https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet#Operator
|
||||
{
|
||||
displayName: 'Operator',
|
||||
name: 'operator',
|
||||
type: 'options',
|
||||
default: 'EXACT',
|
||||
description: 'Operator to use in combination with value',
|
||||
options: [
|
||||
{
|
||||
name: 'Begins With',
|
||||
value: 'BEGINS_WITH',
|
||||
},
|
||||
{
|
||||
name: 'Ends With',
|
||||
value: 'ENDS_WITH',
|
||||
},
|
||||
{
|
||||
name: 'Equals (Number)',
|
||||
value: 'NUMERIC_EQUAL',
|
||||
},
|
||||
{
|
||||
name: 'Exactly Matches',
|
||||
value: 'EXACT',
|
||||
},
|
||||
{
|
||||
name: 'Greater Than (Number)',
|
||||
value: 'NUMERIC_GREATER_THAN',
|
||||
},
|
||||
{
|
||||
name: 'Less Than (Number)',
|
||||
value: 'NUMERIC_LESS_THAN',
|
||||
},
|
||||
{
|
||||
name: 'Partly Matches',
|
||||
value: 'PARTIAL',
|
||||
},
|
||||
{
|
||||
name: 'Regular Expression',
|
||||
value: 'REGEXP',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'expressions',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '',
|
||||
description:
|
||||
'String or <a href="https://support.google.com/analytics/answer/1034324?hl=en">regular expression</a> to match against',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Hide Totals',
|
||||
name: 'hideTotals',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to hide the total of all metrics for all the matching rows, for every date range',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/simple': [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Hide Value Ranges',
|
||||
name: 'hideValueRanges',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to hide the minimum and maximum across all matching rows',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/simple': [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Include Empty Rows',
|
||||
name: 'includeEmptyRows',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the response exclude rows if all the retrieved metrics are equal to zero',
|
||||
},
|
||||
{
|
||||
displayName: 'Use Resource Quotas',
|
||||
name: 'useResourceQuotas',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to enable resource based quotas',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/simple': [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
index: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet
|
||||
// const viewId = this.getNodeParameter('viewId', index) as string;
|
||||
const viewId = this.getNodeParameter('viewId', index, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
const dateRange = this.getNodeParameter('dateRange', index) as string;
|
||||
const metricsUA = this.getNodeParameter('metricsUA', index) as IDataObject;
|
||||
const dimensionsUA = this.getNodeParameter('dimensionsUA', index) as IDataObject;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||
const simple = this.getNodeParameter('simple', index) as boolean;
|
||||
|
||||
let responseData;
|
||||
|
||||
const qs: IDataObject = {};
|
||||
const body: IData = {
|
||||
viewId,
|
||||
dateRanges: prepareDateRange.call(this, dateRange, index),
|
||||
};
|
||||
|
||||
if (metricsUA.metricValues) {
|
||||
const metrics = (metricsUA.metricValues as IDataObject[]).map((metric) => {
|
||||
switch (metric.listName) {
|
||||
case 'other':
|
||||
return {
|
||||
alias: metric.name,
|
||||
expression: metric.name,
|
||||
};
|
||||
case 'custom':
|
||||
const newMetric = {
|
||||
alias: metric.name,
|
||||
expression: metric.expression,
|
||||
formattingType: metric.formattingType,
|
||||
};
|
||||
return newMetric;
|
||||
default:
|
||||
return {
|
||||
alias: metric.listName,
|
||||
expression: metric.listName,
|
||||
};
|
||||
}
|
||||
});
|
||||
if (metrics.length) {
|
||||
checkDuplicates.call(this, metrics, 'alias', 'metrics');
|
||||
body.metrics = metrics as IMetric[];
|
||||
}
|
||||
}
|
||||
|
||||
if (dimensionsUA.dimensionValues) {
|
||||
const dimensions = (dimensionsUA.dimensionValues as IDataObject[]).map((dimension) => {
|
||||
switch (dimension.listName) {
|
||||
case 'other':
|
||||
return { name: dimension.name };
|
||||
default:
|
||||
return { name: dimension.listName };
|
||||
}
|
||||
});
|
||||
if (dimensions.length) {
|
||||
checkDuplicates.call(this, dimensions, 'name', 'dimensions');
|
||||
body.dimensions = dimensions as IDimension[];
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFields.useResourceQuotas) {
|
||||
qs.useResourceQuotas = additionalFields.useResourceQuotas;
|
||||
}
|
||||
|
||||
if (additionalFields.dimensionFiltersUi) {
|
||||
const dimensionFilters = (additionalFields.dimensionFiltersUi as IDataObject)
|
||||
.filterValues as IDataObject[];
|
||||
if (dimensionFilters) {
|
||||
dimensionFilters.forEach((filter) => {
|
||||
filter.expressions = [filter.expressions];
|
||||
switch (filter.listName) {
|
||||
case 'other':
|
||||
filter.dimensionName = filter.name;
|
||||
delete filter.name;
|
||||
delete filter.listName;
|
||||
break;
|
||||
default:
|
||||
filter.dimensionName = filter.listName;
|
||||
delete filter.listName;
|
||||
}
|
||||
});
|
||||
body.dimensionFilterClauses = { filters: dimensionFilters };
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFields.includeEmptyRows) {
|
||||
Object.assign(body, { includeEmptyRows: additionalFields.includeEmptyRows });
|
||||
}
|
||||
if (additionalFields.hideTotals) {
|
||||
Object.assign(body, { hideTotals: additionalFields.hideTotals });
|
||||
}
|
||||
if (additionalFields.hideValueRanges) {
|
||||
Object.assign(body, { hideTotals: additionalFields.hideTotals });
|
||||
}
|
||||
|
||||
const method = 'POST';
|
||||
const endpoint = '/v4/reports:batchGet';
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await googleApiRequestAllItems.call(
|
||||
this,
|
||||
'reports',
|
||||
method,
|
||||
endpoint,
|
||||
{ reportRequests: [body] },
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
body.pageSize = this.getNodeParameter('limit', 0);
|
||||
responseData = await googleApiRequest.call(
|
||||
this,
|
||||
method,
|
||||
endpoint,
|
||||
{ reportRequests: [body] },
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.reports;
|
||||
}
|
||||
|
||||
if (simple) {
|
||||
responseData = simplify(responseData);
|
||||
} else if (returnAll && responseData.length > 1) {
|
||||
responseData = merge(responseData);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: index } },
|
||||
);
|
||||
|
||||
return executionData;
|
||||
}
|
||||
Reference in New Issue
Block a user