🔀 Merge master

This commit is contained in:
Iván Ovejero 2023-01-20 15:46:51 +01:00
commit 3f03b59f0a
225 changed files with 816 additions and 477 deletions

View file

@ -1,3 +1,23 @@
# [0.212.0](https://github.com/n8n-io/n8n/compare/n8n@0.211.2...n8n@0.212.0) (2023-01-19)
### Bug Fixes
* **core:** Revert rule @typescript-eslint/prefer-nullish-coalescing ([e667df7](https://github.com/n8n-io/n8n/commit/e667df783c8c396fc40ff14de704b1e0def4a699))
* **editor:** Allow special chars in node selector completion ([#5196](https://github.com/n8n-io/n8n/issues/5196)) ([b718464](https://github.com/n8n-io/n8n/commit/b718464b1f28e52ffb0b12e4b927d8fe3678d02a))
* **GitLab Node:** Update credential test endpoint ([#5166](https://github.com/n8n-io/n8n/issues/5166)) ([e275306](https://github.com/n8n-io/n8n/commit/e275306c64a410c154e586532e35d25a583f75b4))
* **Gmail Trigger Node:** Filter by labels not working ([#5173](https://github.com/n8n-io/n8n/issues/5173)) ([026f3a5](https://github.com/n8n-io/n8n/commit/026f3a532d30dcf79b76c9f9cff709e6af0eb9ee))
* **HTTP Request Node:** Bug - node requires string instead of json ([8f49f49](https://github.com/n8n-io/n8n/commit/8f49f494ae66ce933f0fce3c3b43ce99baa1b728))
* **HTTP Request Node:** Response format to text is ignored for JSON responses ([8dbe615](https://github.com/n8n-io/n8n/commit/8dbe6159d04c963e7858d31d64721ddc0911ea36))
### Features
* **core:** Add Prometheus metrics for n8n events and api invocations (experimental) ([#5177](https://github.com/n8n-io/n8n/issues/5177)) ([9b032d6](https://github.com/n8n-io/n8n/commit/9b032d68bc8a7a45aae73e9442315e872902d50a)), closes [#5187](https://github.com/n8n-io/n8n/issues/5187)
* **Item Lists Node:** Table tranformation ([5426690](https://github.com/n8n-io/n8n/commit/5426690791ead70085681ef31f229fbe15c7d656))
## [0.211.2](https://github.com/n8n-io/n8n/compare/n8n@0.211.1...n8n@0.211.2) (2023-01-17)

View file

@ -1,6 +1,6 @@
{
"name": "n8n",
"version": "0.211.2",
"version": "0.212.0",
"private": true,
"homepage": "https://n8n.io",
"engines": {

View file

@ -1,6 +1,6 @@
{
"name": "n8n",
"version": "0.211.2",
"version": "0.212.0",
"description": "n8n Workflow Automation Tool",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -136,6 +136,7 @@
"dotenv": "^8.0.0",
"express": "^4.16.4",
"express-openapi-validator": "^4.13.6",
"express-prom-bundle": "^6.6.0",
"fast-glob": "^3.2.5",
"flatted": "^3.2.4",
"google-timezones-json": "^1.0.2",
@ -163,8 +164,8 @@
"luxon": "^3.1.0",
"mysql2": "~2.3.3",
"n8n-core": "~0.151.1",
"n8n-editor-ui": "~0.177.1",
"n8n-nodes-base": "~0.209.2",
"n8n-editor-ui": "~0.178.0",
"n8n-nodes-base": "~0.210.0",
"n8n-workflow": "~0.133.1",
"nodemailer": "^6.7.1",
"oauth-1.0a": "^2.2.6",

View file

@ -182,6 +182,7 @@ export class InternalHooksClass implements IInternalHooksClass {
workflow: IWorkflowBase,
nodeName: string,
): Promise<void> {
const nodeInWorkflow = workflow.nodes.find((node) => node.name === nodeName);
void eventBus.sendNodeEvent({
eventName: 'n8n.node.started',
payload: {
@ -189,6 +190,7 @@ export class InternalHooksClass implements IInternalHooksClass {
nodeName,
workflowId: workflow.id?.toString(),
workflowName: workflow.name,
nodeType: nodeInWorkflow?.type,
},
});
}
@ -198,6 +200,7 @@ export class InternalHooksClass implements IInternalHooksClass {
workflow: IWorkflowBase,
nodeName: string,
): Promise<void> {
const nodeInWorkflow = workflow.nodes.find((node) => node.name === nodeName);
void eventBus.sendNodeEvent({
eventName: 'n8n.node.finished',
payload: {
@ -205,6 +208,7 @@ export class InternalHooksClass implements IInternalHooksClass {
nodeName,
workflowId: workflow.id?.toString(),
workflowName: workflow.name,
nodeType: nodeInWorkflow?.type,
},
});
}

View file

@ -73,7 +73,6 @@ import jwt from 'jsonwebtoken';
import jwks from 'jwks-rsa';
// @ts-ignore
import timezones from 'google-timezones-json';
import promClient, { Registry } from 'prom-client';
import history from 'connect-history-api-fallback';
import config from '@/config';
@ -154,6 +153,7 @@ import { licenseController } from './license/license.controller';
import { corsMiddleware } from './middlewares/cors';
import { initEvents } from './events';
import { AbstractServer } from './AbstractServer';
import { configureMetrics } from './metrics';
const exec = promisify(callbackExec);
@ -321,15 +321,7 @@ class Server extends AbstractServer {
}
async configure(): Promise<void> {
const enableMetrics = config.getEnv('endpoints.metrics.enable');
let register: Registry;
if (enableMetrics) {
const prefix = config.getEnv('endpoints.metrics.prefix');
register = new promClient.Registry();
register.setDefaultLabels({ prefix });
promClient.collectDefaultMetrics({ register });
}
configureMetrics(this.app);
this.frontendSettings.isNpmAvailable = await exec('npm --version')
.then(() => true)
@ -590,17 +582,6 @@ class Server extends AbstractServer {
this.app.use(`/${this.restEndpoint}/nodes`, nodesController);
}
// ----------------------------------------
// Metrics
// ----------------------------------------
if (enableMetrics) {
this.app.get('/metrics', async (req: express.Request, res: express.Response) => {
const response = await register.metrics();
res.setHeader('Content-Type', register.contentType);
ResponseHelper.sendSuccessResponse(res, response, true, 200);
});
}
// ----------------------------------------
// Workflow
// ----------------------------------------

View file

@ -88,7 +88,9 @@ export function executeErrorWorkflow(
let pastExecutionUrl: string | undefined;
if (executionId !== undefined) {
pastExecutionUrl = `${WebhookHelpers.getWebhookBaseUrl()}execution/${executionId}`;
pastExecutionUrl = `${WebhookHelpers.getWebhookBaseUrl()}workflow/${
workflowData.id
}/executions/${executionId}`;
}
if (fullRunData.data.resultData.error !== undefined) {

View file

@ -573,7 +573,7 @@ export const schema = {
format: 'Boolean',
default: false,
env: 'N8N_METRICS',
doc: 'Enable metrics endpoint',
doc: 'Enable /metrics endpoint. Default: false',
},
prefix: {
format: String,
@ -581,6 +581,54 @@ export const schema = {
env: 'N8N_METRICS_PREFIX',
doc: 'An optional prefix for metric names. Default: n8n_',
},
includeDefaultMetrics: {
format: Boolean,
default: true,
env: 'N8N_METRICS_INCLUDE_DEFAULT_METRICS',
doc: 'Whether to expose default system and node.js metrics. Default: true',
},
includeWorkflowIdLabel: {
format: Boolean,
default: false,
env: 'N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL',
doc: 'Whether to include a label for the workflow ID on workflow metrics. Default: false',
},
includeNodeTypeLabel: {
format: Boolean,
default: false,
env: 'N8N_METRICS_INCLUDE_NODE_TYPE_LABEL',
doc: 'Whether to include a label for the node type on node metrics. Default: false',
},
includeCredentialTypeLabel: {
format: Boolean,
default: false,
env: 'N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL',
doc: 'Whether to include a label for the credential type on credential metrics. Default: false',
},
includeApiEndpoints: {
format: Boolean,
default: false,
env: 'N8N_METRICS_INCLUDE_API_ENDPOINTS',
doc: 'Whether to expose metrics for API endpoints. Default: false',
},
includeApiPathLabel: {
format: Boolean,
default: false,
env: 'N8N_METRICS_INCLUDE_API_PATH_LABEL',
doc: 'Whether to include a label for the path of API invocations. Default: false',
},
includeApiMethodLabel: {
format: Boolean,
default: false,
env: 'N8N_METRICS_INCLUDE_API_METHOD_LABEL',
doc: 'Whether to include a label for the HTTP method (GET, POST, ...) of API invocations. Default: false',
},
includeApiStatusCodeLabel: {
format: Boolean,
default: false,
env: 'N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL',
doc: 'Whether to include a label for the HTTP status code (200, 404, ...) of API invocations. Default: false',
},
},
rest: {
format: String,

View file

@ -35,6 +35,11 @@ export interface EventPayloadAudit extends AbstractEventPayload {
userEmail?: string;
firstName?: string;
lastName?: string;
credentialName?: string;
credentialType?: string;
credentialId?: string;
workflowId?: string;
workflowName?: string;
}
export interface EventMessageAuditOptions extends AbstractEventMessageOptions {

View file

@ -11,6 +11,11 @@ export type EventNamesNodeType = typeof eventNamesNode[number];
// --------------------------------------
export interface EventPayloadNode extends AbstractEventPayload {
msg?: string;
executionId: string;
nodeName: string;
workflowId?: string;
workflowName: string;
nodeType?: string;
}
export interface EventMessageNodeOptions extends AbstractEventMessageOptions {

View file

@ -6,7 +6,10 @@ import { MessageEventBusLogWriter } from '../MessageEventBusWriter/MessageEventB
import EventEmitter from 'events';
import config from '@/config';
import * as Db from '@/Db';
import { messageEventBusDestinationFromDb } from '../MessageEventBusDestination/Helpers.ee';
import {
messageEventBusDestinationFromDb,
incrementPrometheusMetric,
} from '../MessageEventBusDestination/Helpers.ee';
import uniqby from 'lodash.uniqby';
import { EventMessageConfirmSource } from '../EventMessageClasses/EventMessageConfirm';
import {
@ -205,6 +208,10 @@ class MessageEventBus extends EventEmitter {
}
private async emitMessage(msg: EventMessageTypes) {
if (config.getEnv('endpoints.metrics.enable')) {
await incrementPrometheusMetric(msg);
}
// generic emit for external modules to capture events
// this is for internal use ONLY and not for use with custom destinations!
this.emit('message', msg);

View file

@ -1,6 +1,13 @@
/* eslint-disable import/no-cycle */
import { MessageEventBusDestinationTypeNames } from 'n8n-workflow';
import type { EventDestinations } from '@/databases/entities/MessageEventBusDestinationEntity';
import { promClient } from '@/metrics';
import {
EventMessageTypeNames,
LoggerProxy,
MessageEventBusDestinationTypeNames,
} from 'n8n-workflow';
import config from '../../config';
import type { EventMessageTypes } from '../EventMessageClasses';
import type { MessageEventBusDestination } from './MessageEventBusDestination.ee';
import { MessageEventBusDestinationSentry } from './MessageEventBusDestinationSentry.ee';
import { MessageEventBusDestinationSyslog } from './MessageEventBusDestinationSyslog.ee';
@ -24,3 +31,85 @@ export function messageEventBusDestinationFromDb(
}
return null;
}
const prometheusCounters: Record<string, promClient.Counter<string> | null> = {};
function getMetricNameForEvent(event: EventMessageTypes): string {
const prefix = config.getEnv('endpoints.metrics.prefix');
return prefix + event.eventName.replace('n8n.', '').replace(/\./g, '_') + '_total';
}
function getLabelValueForNode(nodeType: string): string {
return nodeType.replace('n8n-nodes-', '').replace(/\./g, '_');
}
function getLabelValueForCredential(credentialType: string): string {
return credentialType.replace(/\./g, '_');
}
function getLabelsForEvent(event: EventMessageTypes): Record<string, string> {
switch (event.__type) {
case EventMessageTypeNames.audit:
if (event.eventName.startsWith('n8n.audit.user.credentials')) {
return config.getEnv('endpoints.metrics.includeCredentialTypeLabel')
? {
credential_type: getLabelValueForCredential(
event.payload.credentialType ?? 'unknown',
),
}
: {};
}
if (event.eventName.startsWith('n8n.audit.workflow')) {
return config.getEnv('endpoints.metrics.includeWorkflowIdLabel')
? { workflow_id: event.payload.workflowId?.toString() ?? 'unknown' }
: {};
}
break;
case EventMessageTypeNames.node:
return config.getEnv('endpoints.metrics.includeNodeTypeLabel')
? { node_type: getLabelValueForNode(event.payload.nodeType ?? 'unknown') }
: {};
case EventMessageTypeNames.workflow:
return config.getEnv('endpoints.metrics.includeWorkflowIdLabel')
? { workflow_id: event.payload.workflowId?.toString() ?? 'unknown' }
: {};
}
return {};
}
function getCounterSingletonForEvent(event: EventMessageTypes) {
if (!prometheusCounters[event.eventName]) {
const metricName = getMetricNameForEvent(event);
if (!promClient.validateMetricName(metricName)) {
LoggerProxy.debug(`Invalid metric name: ${metricName}. Ignoring it!`);
prometheusCounters[event.eventName] = null;
return null;
}
const counter = new promClient.Counter({
name: metricName,
help: `Total number of ${event.eventName} events.`,
labelNames: Object.keys(getLabelsForEvent(event)),
});
promClient.register.registerMetric(counter);
prometheusCounters[event.eventName] = counter;
}
return prometheusCounters[event.eventName];
}
export async function incrementPrometheusMetric(event: EventMessageTypes): Promise<void> {
const counter = getCounterSingletonForEvent(event);
if (!counter) {
return;
}
counter.inc(getLabelsForEvent(event));
}

View file

@ -32,6 +32,7 @@ import {
} from 'n8n-workflow';
import { User } from '../databases/entities/User';
import * as ResponseHelper from '@/ResponseHelper';
import { EventMessageNode, EventMessageNodeOptions } from './EventMessageClasses/EventMessageNode';
export const eventBusRouter = express.Router();
@ -116,6 +117,9 @@ eventBusRouter.post(
case EventMessageTypeNames.audit:
msg = new EventMessageAudit(req.body as EventMessageAuditOptions);
break;
case EventMessageTypeNames.node:
msg = new EventMessageNode(req.body as EventMessageNodeOptions);
break;
case EventMessageTypeNames.generic:
default:
msg = new EventMessageGeneric(req.body);

View file

@ -0,0 +1,71 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import config from '@/config';
import { N8N_VERSION } from '@/constants';
import * as ResponseHelper from '@/ResponseHelper';
import express from 'express';
import promBundle from 'express-prom-bundle';
import promClient from 'prom-client';
import semverParse from 'semver/functions/parse';
export { promClient };
export function configureMetrics(app: express.Application) {
if (!config.getEnv('endpoints.metrics.enable')) {
return;
}
setupDefaultMetrics();
setupN8nVersionMetric();
setupApiMetrics(app);
mountMetricsEndpoint(app);
}
function setupN8nVersionMetric() {
const n8nVersion = semverParse(N8N_VERSION || '0.0.0');
if (n8nVersion) {
const versionGauge = new promClient.Gauge({
name: config.getEnv('endpoints.metrics.prefix') + 'version_info',
help: 'n8n version info.',
labelNames: ['version', 'major', 'minor', 'patch'],
});
versionGauge.set(
{
version: 'v' + n8nVersion.version,
major: n8nVersion.major,
minor: n8nVersion.minor,
patch: n8nVersion.patch,
},
1,
);
}
}
function setupDefaultMetrics() {
if (config.getEnv('endpoints.metrics.includeDefaultMetrics')) {
promClient.collectDefaultMetrics();
}
}
function setupApiMetrics(app: express.Application) {
if (config.getEnv('endpoints.metrics.includeApiEndpoints')) {
const metricsMiddleware = promBundle({
autoregister: false,
includeUp: false,
includePath: config.getEnv('endpoints.metrics.includeApiPathLabel'),
includeMethod: config.getEnv('endpoints.metrics.includeApiMethodLabel'),
includeStatusCode: config.getEnv('endpoints.metrics.includeApiStatusCodeLabel'),
});
app.use(['/rest/', '/webhook/', 'webhook-test/', '/api/'], metricsMiddleware);
}
}
function mountMetricsEndpoint(app: express.Application) {
app.get('/metrics', async (req: express.Request, res: express.Response) => {
const response = await promClient.register.metrics();
res.setHeader('Content-Type', promClient.register.contentType);
ResponseHelper.sendSuccessResponse(res, response, true, 200);
});
}

View file

@ -178,7 +178,7 @@ test('GET /eventbus/destination all returned destinations should exist in eventb
}
});
test('should send message to syslog ', async () => {
test.skip('should send message to syslog', async () => {
const testMessage = new EventMessageGeneric({ eventName: 'n8n.test.message', id: uuid() });
config.set('enterprise.features.logStreaming', true);
// await cleanLogs();
@ -217,7 +217,7 @@ test('should send message to syslog ', async () => {
});
});
test('should confirm send message if there are no subscribers', async () => {
test.skip('should confirm send message if there are no subscribers', async () => {
const testMessageUnsubscribed = new EventMessageGeneric({
eventName: 'n8n.test.unsub',
id: uuid(),

View file

@ -1,6 +1,6 @@
{
"name": "n8n-editor-ui",
"version": "0.177.1",
"version": "0.178.0",
"description": "Workflow Editor UI for n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",

View file

@ -0,0 +1,54 @@
import { IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils';
import { IDataObject, MessageEventBusDestinationOptions } from 'n8n-workflow';
export async function saveDestinationToDb(
context: IRestApiContext,
destination: MessageEventBusDestinationOptions,
subscribedEvents: string[] = [],
) {
if (destination.id) {
const data: IDataObject = {
...destination,
subscribedEvents,
};
return makeRestApiRequest(context, 'POST', '/eventbus/destination', data);
}
}
export async function deleteDestinationFromDb(context: IRestApiContext, destinationId: string) {
return makeRestApiRequest(context, 'DELETE', `/eventbus/destination?id=${destinationId}`);
}
export async function sendTestMessageToDestination(
context: IRestApiContext,
destination: MessageEventBusDestinationOptions,
) {
if (destination.id) {
const data: IDataObject = {
...destination,
};
return makeRestApiRequest(context, 'GET', '/eventbus/testmessage', data);
}
}
export async function getEventNamesFromBackend(context: IRestApiContext): Promise<string[]> {
return makeRestApiRequest(context, 'GET', '/eventbus/eventnames');
}
export async function getDestinationsFromBackend(
context: IRestApiContext,
): Promise<MessageEventBusDestinationOptions[]> {
return makeRestApiRequest(context, 'GET', '/eventbus/destination');
}
export async function getExecutionEvents(context: IRestApiContext, executionId: string) {
return makeRestApiRequest(context, 'GET', `/eventbus/execution/${executionId}`);
}
export async function recoverExecutionDataFromEvents(
context: IRestApiContext,
executionId: string,
) {
return makeRestApiRequest(context, 'GET', `/eventbus/execution-recover/${executionId}`);
}

View file

@ -51,7 +51,6 @@ import mixins from 'vue-typed-mixins';
import { EnterpriseEditionFeature } from '@/constants';
import { showMessage } from '@/mixins/showMessage';
import { useLogStreamingStore } from '../../stores/logStreamingStore';
import { restApi } from '@/mixins/restApi';
import Vue from 'vue';
import { mapStores } from 'pinia';
import {
@ -59,7 +58,6 @@ import {
defaultMessageEventBusDestinationOptions,
MessageEventBusDestinationOptions,
} from 'n8n-workflow';
import { saveDestinationToDb } from './Helpers.ee';
import { BaseTextKey } from '../../plugins/i18n';
export const DESTINATION_LIST_ITEM_ACTIONS = {
@ -67,7 +65,7 @@ export const DESTINATION_LIST_ITEM_ACTIONS = {
DELETE: 'delete',
};
export default mixins(showMessage, restApi).extend({
export default mixins(showMessage).extend({
data() {
return {
EnterpriseEditionFeature,
@ -142,7 +140,7 @@ export default mixins(showMessage, restApi).extend({
this.saveDestination();
},
async saveDestination() {
await saveDestinationToDb(this.restApi(), this.nodeParameters);
await this.logStreamingStore.saveDestination(this.nodeParameters);
},
async onAction(action: string) {
if (action === DESTINATION_LIST_ITEM_ACTIONS.OPEN) {

View file

@ -178,7 +178,6 @@ import mixins from 'vue-typed-mixins';
import { useLogStreamingStore } from '../../stores/logStreamingStore';
import { useNDVStore } from '../../stores/ndv';
import { useWorkflowsStore } from '../../stores/workflows';
import { restApi } from '../../mixins/restApi';
import ParameterInputList from '@/components/ParameterInputList.vue';
import NodeCredentials from '@/components/NodeCredentials.vue';
import { IMenuItem, INodeUi, ITab, IUpdateInformation } from '../../Interface';
@ -200,7 +199,7 @@ import Modal from '@/components/Modal.vue';
import { showMessage } from '@/mixins/showMessage';
import { useUIStore } from '../../stores/ui';
import { useUsersStore } from '../../stores/users';
import { destinationToFakeINodeUi, saveDestinationToDb, sendTestMessage } from './Helpers.ee';
import { destinationToFakeINodeUi } from './Helpers.ee';
import {
webhookModalDescription,
sentryModalDescription,
@ -212,7 +211,7 @@ import SaveButton from '../SaveButton.vue';
import EventSelection from '@/components/SettingsLogStreaming/EventSelection.ee.vue';
import { Checkbox } from 'element-ui';
export default mixins(showMessage, restApi).extend({
export default mixins(showMessage).extend({
name: 'event-destination-settings-modal',
props: {
modalName: String,
@ -427,12 +426,14 @@ export default mixins(showMessage, restApi).extend({
this.nodeParameters = deepCopy(nodeParameters);
this.workflowsStore.updateNodeProperties({
name: this.node.name,
properties: { parameters: this.nodeParameters as unknown as IDataObject },
properties: { parameters: this.nodeParameters as unknown as IDataObject, position: [0, 0] },
});
this.logStreamingStore.updateDestination(this.nodeParameters);
if (this.hasOnceBeenSaved) {
this.logStreamingStore.updateDestination(this.nodeParameters);
}
},
async sendTestEvent() {
this.testMessageResult = await sendTestMessage(this.restApi(), this.nodeParameters);
this.testMessageResult = await this.logStreamingStore.sendTestMessage(this.nodeParameters);
this.testMessageSent = true;
},
async removeThis() {
@ -467,12 +468,14 @@ export default mixins(showMessage, restApi).extend({
if (this.unchanged || !this.destination.id) {
return;
}
await saveDestinationToDb(this.restApi(), this.nodeParameters);
this.hasOnceBeenSaved = true;
this.testMessageSent = false;
this.unchanged = true;
this.$props.eventBus.$emit('destinationWasSaved', this.destination.id);
this.uiStore.stateIsDirty = false;
const saveResult = await this.logStreamingStore.saveDestination(this.nodeParameters);
if (saveResult === true) {
this.hasOnceBeenSaved = true;
this.testMessageSent = false;
this.unchanged = true;
this.$props.eventBus.$emit('destinationWasSaved', this.destination.id);
this.uiStore.stateIsDirty = false;
}
},
},
});

View file

@ -1,6 +1,5 @@
import { INodeCredentials, INodeParameters, MessageEventBusDestinationOptions } from 'n8n-workflow';
import { INodeUi, IRestApi } from '../../Interface';
import { useLogStreamingStore } from '../../stores/logStreamingStore';
import { INodeUi } from '../../Interface';
export function destinationToFakeINodeUi(
destination: MessageEventBusDestinationOptions,
@ -20,39 +19,3 @@ export function destinationToFakeINodeUi(
},
} as INodeUi;
}
export async function saveDestinationToDb(
restApi: IRestApi,
destination: MessageEventBusDestinationOptions,
) {
const logStreamingStore = useLogStreamingStore();
if (destination.id) {
const data: MessageEventBusDestinationOptions = {
...destination,
subscribedEvents: logStreamingStore.getSelectedEvents(destination.id),
};
try {
await restApi.makeRestApiRequest('POST', '/eventbus/destination', data);
} catch (error) {
console.log(error);
}
logStreamingStore.updateDestination(destination);
}
}
export async function sendTestMessage(
restApi: IRestApi,
destination: MessageEventBusDestinationOptions,
) {
if (destination.id) {
try {
const sendResult = await restApi.makeRestApiRequest('GET', '/eventbus/testmessage', {
id: destination.id,
});
return sendResult;
} catch (error) {
console.log(error);
}
return false;
}
}

View file

@ -1,5 +1,13 @@
import { deepCopy, MessageEventBusDestinationOptions } from 'n8n-workflow';
import { defineStore } from 'pinia';
import {
deleteDestinationFromDb,
getDestinationsFromBackend,
getEventNamesFromBackend,
saveDestinationToDb,
sendTestMessageToDestination,
} from '../api/eventbus.ee';
import { useRootStore } from './n8nRootStore';
export interface EventSelectionItem {
selected: boolean;
@ -8,18 +16,19 @@ export interface EventSelectionItem {
label: string;
}
export interface EventSelectionGroup extends EventSelectionItem {
interface EventSelectionGroup extends EventSelectionItem {
children: EventSelectionItem[];
}
export interface TreeAndSelectionStoreItem {
interface DestinationStoreItem {
destination: MessageEventBusDestinationOptions;
selectedEvents: Set<string>;
eventGroups: EventSelectionGroup[];
isNew: boolean;
}
export interface DestinationSettingsStore {
[key: string]: TreeAndSelectionStoreItem;
[key: string]: DestinationStoreItem;
}
export const useLogStreamingStore = defineStore('logStreaming', {
@ -51,13 +60,15 @@ export const useLogStreamingStore = defineStore('logStreaming', {
return destinations;
},
updateDestination(destination: MessageEventBusDestinationOptions) {
this.$patch((state) => {
if (destination.id && destination.id in this.items) {
state.items[destination.id].destination = destination;
}
// to trigger refresh
state.items = deepCopy(state.items);
});
if (destination.id && destination.id in this.items) {
this.$patch((state) => {
if (destination.id && destination.id in this.items) {
state.items[destination.id].destination = destination;
}
// to trigger refresh
state.items = deepCopy(state.items);
});
}
},
removeDestination(destinationId: string) {
if (!destinationId) return;
@ -159,7 +170,8 @@ export const useLogStreamingStore = defineStore('logStreaming', {
destination,
selectedEvents: new Set<string>(),
eventGroups: [],
} as TreeAndSelectionStoreItem;
isNew: false,
} as DestinationStoreItem;
}
this.items[destination.id]?.selectedEvents?.clear();
if (destination.subscribedEvents) {
@ -173,6 +185,44 @@ export const useLogStreamingStore = defineStore('logStreaming', {
);
}
},
async saveDestination(destination: MessageEventBusDestinationOptions): Promise<boolean> {
if (destination.id) {
const rootStore = useRootStore();
const selectedEvents = this.getSelectedEvents(destination.id);
try {
await saveDestinationToDb(rootStore.getRestApiContext, destination, selectedEvents);
this.updateDestination(destination);
return true;
} catch (e) {
return false;
}
}
return false;
},
async sendTestMessage(destination: MessageEventBusDestinationOptions) {
if (destination.id) {
const rootStore = useRootStore();
const testResult = await sendTestMessageToDestination(
rootStore.getRestApiContext,
destination,
);
return testResult;
}
return false;
},
async fetchEventNames(): Promise<string[]> {
const rootStore = useRootStore();
return getEventNamesFromBackend(rootStore.getRestApiContext);
},
async fetchDestinations(): Promise<MessageEventBusDestinationOptions[]> {
const rootStore = useRootStore();
return getDestinationsFromBackend(rootStore.getRestApiContext);
},
async deleteDestination(destinationId: string) {
const rootStore = useRootStore();
await deleteDestinationFromDb(rootStore.getRestApiContext, destinationId);
this.removeDestination(destinationId);
},
},
});

View file

@ -90,7 +90,6 @@ import { useSettingsStore } from '../stores/settings';
import { useUIStore } from '../stores/ui';
import { LOG_STREAM_MODAL_KEY, EnterpriseEditionFeature } from '../constants';
import Vue from 'vue';
import { restApi } from '../mixins/restApi';
import {
deepCopy,
defaultMessageEventBusDestinationOptions,
@ -99,7 +98,7 @@ import {
import PageViewLayout from '@/components/layouts/PageViewLayout.vue';
import EventDestinationCard from '@/components/SettingsLogStreaming/EventDestinationCard.ee.vue';
export default mixins(restApi).extend({
export default mixins().extend({
name: 'SettingsLogStreamingView',
props: {},
components: {
@ -125,7 +124,7 @@ export default mixins(restApi).extend({
this.uiStore.nodeViewInitialized = false;
// fetch Destination data from the backend
await this.getDestinationDataFromREST();
await this.getDestinationDataFromBackend();
// since we are not really integrated into the hooks, we listen to the store and refresh the destinations
this.logStreamingStore.$onAction(({ name, after }) => {
@ -174,18 +173,18 @@ export default mixins(restApi).extend({
},
},
methods: {
async getDestinationDataFromREST(): Promise<any> {
async getDestinationDataFromBackend(): Promise<void> {
this.logStreamingStore.clearEventNames();
this.logStreamingStore.clearDestinationItemTrees();
this.allDestinations = [];
const eventNamesData = await this.restApi().makeRestApiRequest('get', '/eventbus/eventnames');
const eventNamesData = await this.logStreamingStore.fetchEventNames();
if (eventNamesData) {
for (const eventName of eventNamesData) {
this.logStreamingStore.addEventName(eventName);
}
}
const destinationData: MessageEventBusDestinationOptions[] =
await this.restApi().makeRestApiRequest('get', '/eventbus/destination');
await this.logStreamingStore.fetchDestinations();
if (destinationData) {
for (const destination of destinationData) {
this.logStreamingStore.addDestination(destination);
@ -218,11 +217,7 @@ export default mixins(restApi).extend({
},
async onRemove(destinationId?: string) {
if (!destinationId) return;
await this.restApi().makeRestApiRequest(
'DELETE',
`/eventbus/destination?id=${destinationId}`,
);
this.logStreamingStore.removeDestination(destinationId);
await this.logStreamingStore.deleteDestination(destinationId);
const foundNode = this.workflowsStore.getNodeByName(destinationId);
if (foundNode) {
this.workflowsStore.removeNode(foundNode);

View file

@ -29,6 +29,7 @@ module.exports = {
'@typescript-eslint/restrict-template-expressions': 'off', //1152 errors, better to fix in separate PR
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }],
'@typescript-eslint/prefer-nullish-coalescing': 'off',
},
overrides: [

View file

@ -1,4 +1,4 @@
import { ICredentialType, INodeProperties } from 'n8n-workflow';
import { IAuthenticateGeneric, ICredentialType, INodeProperties } from 'n8n-workflow';
export class LinearApi implements ICredentialType {
name = 'linearApi';
@ -16,4 +16,13 @@ export class LinearApi implements ICredentialType {
default: '',
},
];
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
Authorization: '={{$credentials.apiKey}}',
},
},
};
}

View file

@ -32,7 +32,7 @@ export async function acuitySchedulingApiRequest(
method,
qs,
body,
uri: uri ?? `https://acuityscheduling.com/api/v1${resource}`,
uri: uri || `https://acuityscheduling.com/api/v1${resource}`,
json: true,
};

View file

@ -27,7 +27,7 @@ export async function affinityApiRequest(
method,
body,
qs: query,
uri: uri ?? `${endpoint}${resource}`,
uri: uri || `${endpoint}${resource}`,
json: true,
};
if (!Object.keys(body).length) {

View file

@ -33,7 +33,7 @@ export async function agileCrmApiRequest(
password: credentials.apiKey as string,
},
qs: query,
uri: uri ?? `https://${credentials.subdomain}.agilecrm.com/dev/${endpoint}`,
uri: uri || `https://${credentials.subdomain}.agilecrm.com/dev/${endpoint}`,
json: true,
};
@ -113,7 +113,7 @@ export async function agileCrmApiRequestUpdate(
username: credentials.email as string,
password: credentials.apiKey as string,
},
uri: uri ?? baseUri,
uri: uri || baseUri,
json: true,
};

View file

@ -34,7 +34,7 @@ export async function apiRequest(
uri?: string,
option: IDataObject = {},
): Promise<any> {
query = query ?? {};
query = query || {};
// For some reason for some endpoints the bearer auth does not work
// and it returns 404 like for the /meta request. So we always send
@ -46,7 +46,7 @@ export async function apiRequest(
method,
body,
qs: query,
uri: uri ?? `https://api.airtable.com/v0/${endpoint}`,
uri: uri || `https://api.airtable.com/v0/${endpoint}`,
useQuerystring: false,
json: true,
};

View file

@ -441,7 +441,7 @@ export class ApiTemplateIo implements INodeType {
const fileName = responseData.download_url.split('/').pop();
const binaryData = await this.helpers.prepareBinaryData(
data,
options.fileName ?? fileName,
options.fileName || fileName,
);
responseData = {
json: responseData,
@ -525,7 +525,7 @@ export class ApiTemplateIo implements INodeType {
const fileName = responseData.download_url.split('/').pop();
const binaryData = await this.helpers.prepareBinaryData(
imageData,
options.fileName ?? fileName,
options.fileName || fileName,
);
responseData = {
json: responseData,

View file

@ -28,7 +28,7 @@ export async function asanaApiRequest(
method,
body: { data: body },
qs: query,
url: uri ?? `https://app.asana.com/api/1.0${endpoint}`,
url: uri || `https://app.asana.com/api/1.0${endpoint}`,
json: true,
};

View file

@ -28,7 +28,7 @@ export async function autopilotApiRequest(
method,
body,
qs: query,
uri: uri ?? `${endpoint}${resource}`,
uri: uri || `${endpoint}${resource}`,
json: true,
};
if (!Object.keys(body).length) {

View file

@ -44,7 +44,7 @@ export async function awsApiRequest(
const endpoint = new URL(getEndpointForService(service, credentials) + path);
// Sign AWS API request with the user credentials
const signOpts = { headers: headers ?? {}, host: endpoint.host, method, path, body } as Request;
const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request;
const securityHeaders = {
accessKeyId: `${credentials.accessKeyId}`.trim(),
secretAccessKey: `${credentials.secretAccessKey}`.trim(),

View file

@ -26,7 +26,7 @@ export async function bannerbearApiRequest(
method,
body,
qs: query,
uri: uri ?? `https://api.bannerbear.com/v2${resource}`,
uri: uri || `https://api.bannerbear.com/v2${resource}`,
json: true,
};
if (!Object.keys(body).length) {

View file

@ -26,7 +26,7 @@ export async function bitbucketApiRequest(
},
qs,
body,
uri: uri ?? `https://api.bitbucket.org/2.0${resource}`,
uri: uri || `https://api.bitbucket.org/2.0${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -25,7 +25,7 @@ export async function bitlyApiRequest(
method,
qs,
body,
uri: uri ?? `https://api-ssl.bitly.com/v4${resource}`,
uri: uri || `https://api-ssl.bitly.com/v4${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -138,7 +138,7 @@ export async function loadResource(this: ILoadOptionsFunctions, resource: string
data.forEach(({ id, name, externalId }: { id: string; name: string; externalId?: string }) => {
returnData.push({
name: externalId ?? name ?? id,
name: externalId || name || id,
value: id,
});
});

View file

@ -26,7 +26,7 @@ export async function boxApiRequest(
method,
body,
qs,
uri: uri ?? `https://api.box.com/2.0${resource}`,
uri: uri || `https://api.box.com/2.0${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -28,7 +28,7 @@ export async function brandfetchApiRequest(
method,
qs,
body,
uri: uri ?? `https://api.brandfetch.io/v1${resource}`,
uri: uri || `https://api.brandfetch.io/v1${resource}`,
json: true,
};

View file

@ -46,7 +46,7 @@ export async function calendlyApiRequest(
method,
body,
qs: query,
uri: uri ?? `${endpoint}${resource}`,
uri: uri || `${endpoint}${resource}`,
json: true,
};

View file

@ -28,7 +28,7 @@ export async function circleciApiRequest(
method,
qs,
body,
uri: uri ?? `https://circleci.com/api/v2${resource}`,
uri: uri || `https://circleci.com/api/v2${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -28,7 +28,7 @@ export async function webexApiRequest(
method,
body,
qs,
uri: uri ?? `https://webexapis.com/v1${resource}`,
uri: uri || `https://webexapis.com/v1${resource}`,
json: true,
};
try {

View file

@ -28,7 +28,7 @@ export async function citrixADCApiRequest(
method,
body,
qs,
uri: uri ?? `${url.replace(new RegExp('/$'), '')}/nitro/v1${resource}`,
uri: uri || `${url.replace(new RegExp('/$'), '')}/nitro/v1${resource}`,
json: true,
};

View file

@ -26,7 +26,7 @@ export async function clearbitApiRequest(
method,
qs,
body,
uri: uri ?? `https://${api}.clearbit.com${resource}`,
uri: uri || `https://${api}.clearbit.com${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -32,7 +32,7 @@ export async function clickupApiRequest(
method,
qs,
body,
uri: uri ?? `https://api.clickup.com/api/v2${resource}`,
uri: uri || `https://api.clickup.com/api/v2${resource}`,
json: true,
};

View file

@ -22,7 +22,7 @@ export async function cockpitApiRequest(
token: credentials.accessToken,
},
body,
uri: uri ?? `${credentials.url}/api${resource}`,
uri: uri || `${credentials.url}/api${resource}`,
json: true,
};

View file

@ -19,7 +19,7 @@ export async function codaApiRequest(
method,
qs,
body,
uri: uri ?? `https://coda.io/apis/v1${resource}`,
uri: uri || `https://coda.io/apis/v1${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -22,7 +22,7 @@ export async function coinGeckoApiRequest(
method,
body,
qs,
uri: uri ?? `https://api.coingecko.com/api/v3${endpoint}`,
uri: uri || `https://api.coingecko.com/api/v3${endpoint}`,
json: true,
};

View file

@ -47,14 +47,14 @@ function compareItems(
differentKeys.forEach((key) => {
switch (resolve) {
case 'preferInput1':
different[key] = item1.json[key] ?? null;
different[key] = item1.json[key] || null;
break;
case 'preferInput2':
different[key] = item2.json[key] ?? null;
different[key] = item2.json[key] || null;
break;
default:
const input1 = item1.json[key] ?? null;
const input2 = item2.json[key] ?? null;
const input1 = item1.json[key] || null;
const input2 = item2.json[key] || null;
if (skipFields.includes(key)) {
skipped[key] = { input1, input2 };
} else {
@ -89,7 +89,7 @@ function combineItems(
if (disableDotNotation) {
entry.json[field] = match.json[field];
} else {
const value = get(match.json, field) ?? null;
const value = get(match.json, field) || null;
set(entry, `json.${field}`, value);
}
});

View file

@ -22,7 +22,7 @@ export async function contentfulApiRequest(
method,
qs,
body,
uri: uri ?? `https://${isPreview ? 'preview' : 'cdn'}.contentful.com${resource}`,
uri: uri || `https://${isPreview ? 'preview' : 'cdn'}.contentful.com${resource}`,
json: true,
};

View file

@ -23,7 +23,7 @@ export async function convertKitApiRequest(
method,
qs,
body,
uri: uri ?? `https://api.convertkit.com/v3${endpoint}`,
uri: uri || `https://api.convertkit.com/v3${endpoint}`,
json: true,
};

View file

@ -27,7 +27,7 @@ export async function cortexApiRequest(
headers: {},
method,
qs: query,
uri: uri ?? `${credentials.host}/api${resource}`,
uri: uri || `${credentials.host}/api${resource}`,
body,
json: true,
};

View file

@ -430,7 +430,7 @@ export class DateTime implements INodeType {
newDate = moment.unix(currentDate as unknown as number);
} else {
if (options.fromTimezone || options.toTimezone) {
const fromTimezone = options.fromTimezone ?? workflowTimezone;
const fromTimezone = options.fromTimezone || workflowTimezone;
if (options.fromFormat) {
newDate = moment.tz(
currentDate,

View file

@ -25,7 +25,7 @@ export async function deepLApiRequest(
method,
form: body,
qs,
uri: uri ?? `${credentials.apiPlan === 'pro' ? proApiEndpoint : freeApiEndpoint}${resource}`,
uri: uri || `${credentials.apiPlan === 'pro' ? proApiEndpoint : freeApiEndpoint}${resource}`,
json: true,
};

View file

@ -29,7 +29,7 @@ export async function demioApiRequest(
method,
qs,
body,
uri: uri ?? `https://my.demio.com/api/v1${resource}`,
uri: uri || `https://my.demio.com/api/v1${resource}`,
json: true,
};

View file

@ -33,7 +33,7 @@ export async function dhlApiRequest(
method,
qs,
body,
uri: uri ?? `https://api-eu.dhl.com${path}`,
uri: uri || `https://api-eu.dhl.com${path}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -19,7 +19,7 @@ export async function driftApiRequest(
method,
body,
qs: query,
uri: uri ?? `https://driftapi.com${resource}`,
uri: uri || `https://driftapi.com${resource}`,
json: true,
};

View file

@ -30,7 +30,7 @@ export async function erpNextApiRequest(
method,
body,
qs: query,
uri: uri ?? `${baseUrl}${resource}`,
uri: uri || `${baseUrl}${resource}`,
json: true,
rejectUnauthorized: !credentials.allowUnauthorizedCerts,
};

View file

@ -1239,7 +1239,7 @@ export class EditImage implements INodeType {
// Combine the lines to a single string
const renderText = lines.join('\n');
const font = options.font ?? operationData.font;
const font = options.font || operationData.font;
if (font && font !== 'default') {
gmInstance = gmInstance!.font(font as string);

View file

@ -447,7 +447,7 @@ export class ElasticSecurity implements INodeType {
const body = {
comment: this.getNodeParameter('comment', i),
type: 'user',
owner: additionalFields.owner ?? 'securitySolution',
owner: additionalFields.owner || 'securitySolution',
} as IDataObject;
const caseId = this.getNodeParameter('caseId', i);

View file

@ -193,7 +193,7 @@ export class EmailSend implements INodeType {
continue;
}
attachments.push({
filename: item.binary[propertyName].fileName ?? 'unknown',
filename: item.binary[propertyName].fileName || 'unknown',
content: await this.helpers.getBinaryDataBuffer(itemIndex, propertyName),
});
}

View file

@ -54,7 +54,7 @@ export class ErrorTrigger implements INodeType {
items[0].json = {
execution: {
id,
url: `${urlParts.join('/')}/${id}`,
url: `${urlParts.join('/')}/workflow/1/${id}`,
retryOf: '34',
error: {
message: 'Example Error Message',

View file

@ -30,7 +30,7 @@ export async function eventbriteApiRequest(
method,
qs,
body,
uri: uri ?? `https://www.eventbriteapi.com/v3${resource}`,
uri: uri || `https://www.eventbriteapi.com/v3${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -38,7 +38,7 @@ async function execPromise(command: string): Promise<IExecReturnData> {
resolve(returnData);
}).on('exit', (code) => {
returnData.exitCode = code ?? 0;
returnData.exitCode = code || 0;
});
});
}

View file

@ -45,7 +45,7 @@ export async function facebookApiRequest(
qs,
body,
gzip: true,
uri: uri ?? `https://graph.facebook.com/v8.0${resource}`,
uri: uri || `https://graph.facebook.com/v8.0${resource}`,
json: true,
};

View file

@ -25,7 +25,7 @@ export async function figmaApiRequest(
headers: { 'X-FIGMA-TOKEN': credentials.accessToken },
method,
body,
uri: uri ?? `https://api.figma.com${resource}`,
uri: uri || `https://api.figma.com${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -24,7 +24,7 @@ export async function flowApiRequest(
method,
qs,
body,
uri: uri ?? `https://api.getflow.com/v2${resource}`,
uri: uri || `https://api.getflow.com/v2${resource}`,
json: true,
};
options = Object.assign({}, options, option);

View file

@ -16,7 +16,7 @@ async function getToken(
this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions,
credentials: IFormIoCredentials,
) {
const base = credentials.domain ?? 'https://formio.form.io';
const base = credentials.domain || 'https://formio.form.io';
const options = {
headers: {
'Content-Type': 'application/json',
@ -57,7 +57,7 @@ export async function formIoApiRequest(
const token = await getToken.call(this, credentials);
const base = credentials.domain ?? 'https://api.form.io';
const base = credentials.domain || 'https://api.form.io';
const options = {
headers: {

View file

@ -28,7 +28,7 @@ export async function freshdeskApiRequest(
method,
body,
qs: query,
uri: uri ?? `https://${credentials.domain}.${endpoint}${resource}`,
uri: uri || `https://${credentials.domain}.${endpoint}${resource}`,
json: true,
};
if (!Object.keys(body).length) {

View file

@ -28,7 +28,7 @@ export async function getresponseApiRequest(
method,
body,
qs,
uri: uri ?? `https://api.getresponse.com/v3${resource}`,
uri: uri || `https://api.getresponse.com/v3${resource}`,
json: true,
};
try {

View file

@ -244,7 +244,7 @@ export class GetResponse implements INodeType {
}
if (qs.sortBy) {
qs[`sort[${qs.sortBy}]`] = qs.sortOrder ?? 'ASC';
qs[`sort[${qs.sortBy}]`] = qs.sortOrder || 'ASC';
}
if (qs.exactMatch === true) {

View file

@ -37,7 +37,7 @@ export async function ghostApiRequest(
const options: OptionsWithUri = {
method,
qs: query,
uri: uri ?? `${credentials.url}/ghost/api/${version}${endpoint}`,
uri: uri || `${credentials.url}/ghost/api/${version}${endpoint}`,
body,
json: true,
};

View file

@ -291,7 +291,7 @@ export class Ghost implements INodeType {
const post: IDataObject = {};
if (contentFormat === 'html') {
post.html = updateFields.content ?? '';
post.html = updateFields.content || '';
qs.source = 'html';
delete updateFields.content;
} else {

View file

@ -22,7 +22,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://analyticsreporting.googleapis.com${endpoint}`,
uri: uri || `https://analyticsreporting.googleapis.com${endpoint}`,
json: true,
};

View file

@ -23,7 +23,7 @@ async function getAccessToken(
const signature = jwt.sign(
{
iss: credentials.email as string,
sub: credentials.delegatedEmail ?? (credentials.email as string),
sub: credentials.delegatedEmail || (credentials.email as string),
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
@ -79,7 +79,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://bigquery.googleapis.com/bigquery${resource}`,
uri: uri || `https://bigquery.googleapis.com/bigquery${resource}`,
json: true,
};
try {

View file

@ -31,7 +31,7 @@ async function getAccessToken(
const signature = jwt.sign(
{
iss: credentials.email,
sub: credentials.delegatedEmail ?? credentials.email,
sub: credentials.delegatedEmail || credentials.email,
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
@ -86,7 +86,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://www.googleapis.com/books/${resource}`,
uri: uri || `https://www.googleapis.com/books/${resource}`,
json: true,
};
try {

View file

@ -29,7 +29,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://www.googleapis.com${resource}`,
uri: uri || `https://www.googleapis.com${resource}`,
json: true,
};
try {

View file

@ -144,7 +144,7 @@ export class GoogleCalendar implements INodeType {
const timeMin = this.getNodeParameter('timeMin', i) as string;
const timeMax = this.getNodeParameter('timeMax', i) as string;
const options = this.getNodeParameter('options', i);
const outputFormat = options.outputFormat ?? 'availability';
const outputFormat = options.outputFormat || 'availability';
const tz = this.getNodeParameter('options.timezone', i, '', {
extractValue: true,
}) as string;

View file

@ -41,7 +41,7 @@ export async function getAccessToken(
const signature = jwt.sign(
{
iss: credentials.email,
sub: credentials.delegatedEmail ?? credentials.email,
sub: credentials.delegatedEmail || credentials.email,
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
@ -92,7 +92,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://chat.googleapis.com${resource}`,
uri: uri || `https://chat.googleapis.com${resource}`,
json: true,
};

View file

@ -22,7 +22,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://language.googleapis.com${endpoint}`,
uri: uri || `https://language.googleapis.com${endpoint}`,
json: true,
};

View file

@ -262,8 +262,8 @@ export class GoogleCloudNaturalLanguage implements INodeType {
if (operation === 'analyzeSentiment') {
const source = this.getNodeParameter('source', i) as string;
const options = this.getNodeParameter('options', i);
const encodingType = (options.encodingType as string | undefined) ?? 'UTF16';
const documentType = (options.documentType as string | undefined) ?? 'PLAIN_TEXT';
const encodingType = (options.encodingType as string | undefined) || 'UTF16';
const documentType = (options.documentType as string | undefined) || 'PLAIN_TEXT';
const body: IData = {
document: {

View file

@ -21,7 +21,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://people.googleapis.com/v1${resource}`,
uri: uri || `https://people.googleapis.com/v1${resource}`,
json: true,
};
try {

View file

@ -35,7 +35,7 @@ async function getAccessToken(
const signature = jwt.sign(
{
iss: credentials.email,
sub: credentials.delegatedEmail ?? credentials.email,
sub: credentials.delegatedEmail || credentials.email,
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
@ -89,7 +89,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://docs.googleapis.com/v1${endpoint}`,
uri: uri || `https://docs.googleapis.com/v1${endpoint}`,
json: true,
};

View file

@ -35,7 +35,7 @@ async function getAccessToken(
const signature = jwt.sign(
{
iss: credentials.email,
sub: credentials.delegatedEmail ?? credentials.email,
sub: credentials.delegatedEmail || credentials.email,
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
@ -91,7 +91,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://www.googleapis.com${resource}`,
uri: uri || `https://www.googleapis.com${resource}`,
json: true,
};
@ -136,8 +136,8 @@ export async function googleApiRequestAllItems(
const returnData: IDataObject[] = [];
let responseData;
query.maxResults = query.maxResults ?? 100;
query.pageSize = query.pageSize ?? 100;
query.maxResults = query.maxResults || 100;
query.pageSize = query.pageSize || 100;
do {
responseData = await googleApiRequest.call(this, method, endpoint, body, query);

View file

@ -2652,7 +2652,7 @@ export class GoogleDrive implements INodeType {
const body = {
name,
mimeType: 'application/vnd.google-apps.folder',
parents: options.parents ?? [],
parents: options.parents || [],
};
const qs = {

View file

@ -25,7 +25,7 @@ export async function googleApiRequest(
qsStringifyOptions: {
arrayFormat: 'repeat',
},
uri: uri ?? `https://firestore.googleapis.com/v1/projects${resource}`,
uri: uri || `https://firestore.googleapis.com/v1/projects${resource}`,
json: true,
};
try {

View file

@ -26,7 +26,7 @@ export async function googleApiRequest(
method,
body,
qs,
url: uri ?? `https://${projectId}.${region}/${resource}.json`,
url: uri || `https://${projectId}.${region}/${resource}.json`,
json: true,
};

View file

@ -21,7 +21,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://www.googleapis.com/admin${resource}`,
uri: uri || `https://www.googleapis.com/admin${resource}`,
json: true,
};
try {

View file

@ -116,7 +116,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://www.googleapis.com${endpoint}`,
uri: uri || `https://www.googleapis.com${endpoint}`,
qsStringifyOptions: {
arrayFormat: 'repeat',
},
@ -528,7 +528,7 @@ export async function prepareEmailAttachments(
}
attachmentsList.push({
name: binaryData.fileName ?? 'unknown',
name: binaryData.fileName || 'unknown',
content: binaryDataBuffer,
type: binaryData.mimeType,
});

View file

@ -324,7 +324,7 @@ export class GmailV1 implements INodeType {
binaryProperty,
);
attachmentsBinary.push({
name: binaryData.fileName ?? 'unknown',
name: binaryData.fileName || 'unknown',
content: binaryDataBuffer,
type: binaryData.mimeType,
});
@ -414,7 +414,7 @@ export class GmailV1 implements INodeType {
binaryProperty,
);
attachmentsBinary.push({
name: binaryData.fileName ?? 'unknown',
name: binaryData.fileName || 'unknown',
content: binaryDataBuffer,
type: binaryData.mimeType,
});
@ -489,7 +489,7 @@ export class GmailV1 implements INodeType {
const id = this.getNodeParameter('messageId', i);
const additionalFields = this.getNodeParameter('additionalFields', i);
const format = additionalFields.format ?? 'resolved';
const format = additionalFields.format || 'resolved';
if (format === 'resolved') {
qs.format = 'raw';
@ -557,7 +557,7 @@ export class GmailV1 implements INodeType {
responseData = [];
}
const format = additionalFields.format ?? 'resolved';
const format = additionalFields.format || 'resolved';
if (format !== 'ids') {
if (format === 'resolved') {
@ -658,7 +658,7 @@ export class GmailV1 implements INodeType {
binaryProperty,
);
attachmentsBinary.push({
name: binaryData.fileName ?? 'unknown',
name: binaryData.fileName || 'unknown',
content: binaryDataBuffer,
type: binaryData.mimeType,
});
@ -707,7 +707,7 @@ export class GmailV1 implements INodeType {
const id = this.getNodeParameter('messageId', i);
const additionalFields = this.getNodeParameter('additionalFields', i);
const format = additionalFields.format ?? 'resolved';
const format = additionalFields.format || 'resolved';
if (format === 'resolved') {
qs.format = 'raw';
@ -785,7 +785,7 @@ export class GmailV1 implements INodeType {
responseData = [];
}
const format = additionalFields.format ?? 'resolved';
const format = additionalFields.format || 'resolved';
if (format !== 'ids') {
if (format === 'resolved') {

View file

@ -678,7 +678,7 @@ export class GmailV2 implements INodeType {
const endpoint = `/gmail/v1/users/me/threads/${id}`;
const options = this.getNodeParameter('options', i);
const onlyMessages = options.returnOnlyMessages ?? false;
const onlyMessages = options.returnOnlyMessages || false;
const qs: IDataObject = {};
const simple = this.getNodeParameter('simple', i) as boolean;

View file

@ -490,8 +490,8 @@ export class GoogleSheetsTrigger implements INodeType {
}
const [rangeFrom, rangeTo] = range.split(':');
const cellDataFrom = rangeFrom.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) ?? [];
const cellDataTo = rangeTo.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) ?? [];
const cellDataFrom = rangeFrom.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || [];
const cellDataTo = rangeTo.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || [];
if (rangeDefinition === 'specifyRangeA1' && cellDataFrom[2] !== undefined) {
keyRange = `${cellDataFrom[1]}${+cellDataFrom[2]}:${cellDataTo[1]}${+cellDataFrom[2]}`;
@ -544,7 +544,7 @@ export class GoogleSheetsTrigger implements INodeType {
return null;
}
const addedRows = sheetData?.slice(workflowStaticData.lastIndexChecked as number) ?? [];
const addedRows = sheetData?.slice(workflowStaticData.lastIndexChecked as number) || [];
const returnData = arrayOfArraysToJson(addedRows, columns);
workflowStaticData.lastIndexChecked = sheetData.length;

View file

@ -39,7 +39,7 @@ export async function getAccessToken(
const signature = jwt.sign(
{
iss: credentials.email,
sub: credentials.delegatedEmail ?? credentials.email,
sub: credentials.delegatedEmail || credentials.email,
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
@ -94,7 +94,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://sheets.googleapis.com${resource}`,
uri: uri || `https://sheets.googleapis.com${resource}`,
json: true,
};
try {

View file

@ -117,8 +117,8 @@ export class GoogleSheetsV1 implements INodeType {
const options = this.getNodeParameter('options', 0, {});
const valueInputMode = (options.valueInputMode ?? 'RAW') as ValueInputOption;
const valueRenderMode = (options.valueRenderMode ?? 'UNFORMATTED_VALUE') as ValueRenderOption;
const valueInputMode = (options.valueInputMode || 'RAW') as ValueInputOption;
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
if (operation === 'append') {
// ----------------------------------
@ -134,7 +134,7 @@ export class GoogleSheetsV1 implements INodeType {
setData.push(item.json);
});
const usePathForKeyRow = (options.usePathForKeyRow ?? false) as boolean;
const usePathForKeyRow = (options.usePathForKeyRow || false) as boolean;
// Convert data into array format
const _data = await sheet.appendSheetData(

View file

@ -172,7 +172,7 @@ export async function execute(
const options = this.getNodeParameter('options', 0, {});
const valueRenderMode = (options.valueRenderMode ?? 'UNFORMATTED_VALUE') as ValueRenderOption;
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
const locationDefineOption = (options.locationDefine as IDataObject)?.values as IDataObject;

View file

@ -124,9 +124,9 @@ export async function execute(
const range = getRangeString(sheetName, dataLocationOnSheetOptions);
const valueRenderMode = (outputFormattingOption.general ??
const valueRenderMode = (outputFormattingOption.general ||
'UNFORMATTED_VALUE') as ValueRenderOption;
const dateTimeRenderOption = (outputFormattingOption.date ?? 'FORMATTED_STRING') as string;
const dateTimeRenderOption = (outputFormattingOption.date || 'FORMATTED_STRING') as string;
const sheetData = (await sheet.getData(
range,

View file

@ -171,7 +171,7 @@ export async function execute(
const options = this.getNodeParameter('options', 0, {});
const valueRenderMode = (options.valueRenderMode ?? 'UNFORMATTED_VALUE') as ValueRenderOption;
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
const locationDefineOptions = (options.locationDefine as IDataObject)?.values as IDataObject;

View file

@ -255,7 +255,7 @@ export class GoogleSheet {
// );
const lastRowWithData =
lastRow ??
lastRow ||
(((await this.getData(range, 'UNFORMATTED_VALUE')) as string[][]) || []).length + 1;
const response = await this.updateRows(
@ -403,10 +403,10 @@ export class GoogleSheet {
columnValuesList = sheetData.slice(dataStartRowIndex - 1).map((row) => row[keyIndex]);
} else {
const decodedRange = this.getDecodedSheetRange(range);
const startRowIndex = decodedRange.start?.row ?? dataStartRowIndex;
const endRowIndex = decodedRange.end?.row ?? '';
const startRowIndex = decodedRange.start?.row || dataStartRowIndex;
const endRowIndex = decodedRange.end?.row || '';
const keyColumn = this.getColumnWithOffset(decodedRange.start?.column ?? 'A', keyIndex);
const keyColumn = this.getColumnWithOffset(decodedRange.start?.column || 'A', keyIndex);
const keyColumnRange = `${decodedRange.name}!${keyColumn}${startRowIndex}:${keyColumn}${endRowIndex}`;
columnValuesList = await this.getData(keyColumnRange, valueRenderMode);
}
@ -446,9 +446,9 @@ export class GoogleSheet {
) {
const decodedRange = this.getDecodedSheetRange(range);
// prettier-ignore
const keyRowRange = `${decodedRange.name}!${decodedRange.start?.column ?? ''}${keyRowIndex + 1}:${decodedRange.end?.column ?? ''}${keyRowIndex + 1}`;
const keyRowRange = `${decodedRange.name}!${decodedRange.start?.column || ''}${keyRowIndex + 1}:${decodedRange.end?.column || ''}${keyRowIndex + 1}`;
const sheetDatakeyRow = columnNamesList ?? (await this.getData(keyRowRange, valueRenderMode));
const sheetDatakeyRow = columnNamesList || (await this.getData(keyRowRange, valueRenderMode));
if (sheetDatakeyRow === undefined) {
throw new NodeOperationError(
@ -469,7 +469,7 @@ export class GoogleSheet {
}
const columnValues: Array<string | number> =
columnValuesList ??
columnValuesList ||
(await this.getColumnValues(range, keyIndex, dataStartRowIndex, valueRenderMode));
const updateData: ISheetUpdateData[] = [];
@ -527,7 +527,7 @@ export class GoogleSheet {
// Property exists so add it to the data to update
// Get the column name in which the property data can be found
const columnToUpdate = this.getColumnWithOffset(
decodedRange.start?.column ?? 'A',
decodedRange.start?.column || 'A',
columnNames.indexOf(name),
);
@ -640,7 +640,7 @@ export class GoogleSheet {
const decodedRange = this.getDecodedSheetRange(range);
const columnNamesRow =
columnNamesList ??
columnNamesList ||
(await this.getData(
`${decodedRange.name}!${keyRowIndex}:${keyRowIndex}`,
'UNFORMATTED_VALUE',
@ -704,7 +704,7 @@ export class GoogleSheet {
}
private splitCellRange(cell: string, range: string): SheetCellDecoded {
const cellData = cell.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) ?? [];
const cellData = cell.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || [];
if (cellData === null || cellData.length !== 3) {
throw new NodeOperationError(

View file

@ -36,7 +36,7 @@ export async function getAccessToken(
const signature = jwt.sign(
{
iss: credentials.email,
sub: credentials.delegatedEmail ?? credentials.email,
sub: credentials.delegatedEmail || credentials.email,
scope: scopes.join(' '),
aud: 'https://oauth2.googleapis.com/token',
iat: now,
@ -91,7 +91,7 @@ export async function apiRequest(
method,
body,
qs,
uri: uri ?? `https://sheets.googleapis.com${resource}`,
uri: uri || `https://sheets.googleapis.com${resource}`,
json: true,
...option,
};

View file

@ -542,7 +542,7 @@ export class GoogleSlides implements INodeType {
return {
replaceAllText: {
replaceText: text.replaceText,
pageObjectIds: text.pageObjectIds ?? [],
pageObjectIds: text.pageObjectIds || [],
containsText: {
text: text.text,
matchCase: text.matchCase,

View file

@ -20,7 +20,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://www.googleapis.com${resource}`,
uri: uri || `https://www.googleapis.com${resource}`,
json: true,
};

View file

@ -89,7 +89,7 @@ export async function googleApiRequest(
method,
body,
qs,
uri: uri ?? `https://translation.googleapis.com${resource}`,
uri: uri || `https://translation.googleapis.com${resource}`,
json: true,
};
try {

Some files were not shown because too many files have changed in this diff Show more