This commit is contained in:
Rupenieks 2020-05-06 14:07:04 +02:00
commit 9559a8929b
14 changed files with 361 additions and 49 deletions

View file

@ -555,7 +555,7 @@ class App {
this.app.get('/rest/node-parameter-options', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<INodePropertyOptions[]> => {
const nodeType = req.query.nodeType as string;
let credentials: INodeCredentials | undefined = undefined;
const currentNodeParameters = req.query.currentNodeParameters as INodeParameters[];
const currentNodeParameters = JSON.parse('' + req.query.currentNodeParameters) as INodeParameters;
if (req.query.credentials !== undefined) {
credentials = JSON.parse(req.query.credentials as string);
}

View file

@ -387,10 +387,10 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
*
* @export
* @param {IWorkflowCredentials} credentials
* @param {INodeParameters[]} [currentNodeParameters=[]]
* @param {INodeParameters} currentNodeParameters
* @returns {Promise<IWorkflowExecuteAdditionalData>}
*/
export async function getBase(credentials: IWorkflowCredentials, currentNodeParameters: INodeParameters[] = []): Promise<IWorkflowExecuteAdditionalData> {
export async function getBase(credentials: IWorkflowCredentials, currentNodeParameters?: INodeParameters): Promise<IWorkflowExecuteAdditionalData> {
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
const timezone = config.get('generic.timezone') as string;

View file

@ -152,7 +152,7 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
throw new Error(`Node type "${node.type}" does not have any credentials of type "${type}" defined!`);
}
if (NodeHelpers.displayParameter(node.parameters, nodeCredentialDescription, node.parameters) === false) {
if (NodeHelpers.displayParameter(additionalData.currentNodeParameters || node.parameters, nodeCredentialDescription, node.parameters) === false) {
// Credentials should not be displayed so return undefined even if they would be defined
return undefined;
}
@ -666,14 +666,14 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
return getCredentials(workflow, node, type, additionalData);
},
getCurrentNodeParameter: (parameterName: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined => {
const nodeParameters = JSON.parse('' + additionalData.currentNodeParameters);
const nodeParameters = additionalData.currentNodeParameters;
if (nodeParameters && nodeParameters[parameterName]) {
return nodeParameters[parameterName];
}
return undefined;
},
getCurrentNodeParameters: (): INodeParameters | undefined => {
return JSON.parse('' + additionalData.currentNodeParameters);
return additionalData.currentNodeParameters;
},
getNode: () => {
return getNode(node);

View file

@ -504,11 +504,11 @@ export default mixins(
} else if (entry.finished === true) {
return 'The worklow execution was successful.';
} else if (entry.retryOf !== undefined) {
return `The workflow execution was a retry of "${entry.retryOf}" and did fail.<br />New retries have to be started from the original execution.`;
return `The workflow execution was a retry of "${entry.retryOf}" and failed.<br />New retries have to be started from the original execution.`;
} else if (entry.retrySuccessId !== undefined) {
return `The workflow execution did fail but the retry "${entry.retrySuccessId}" was successful.`;
return `The workflow execution failed but the retry "${entry.retrySuccessId}" was successful.`;
} else {
return 'The workflow execution did fail.';
return 'The workflow execution failed.';
}
},
async stopExecution (activeExecutionId: string) {

View file

@ -129,6 +129,10 @@ export default mixins(nodeBase, workflowHelpers).extend({
}
},
nodeSubtitle (): string | undefined {
if (this.data.notesInFlow) {
return this.data.notes;
}
if (this.nodeType !== null && this.nodeType.subtitle !== undefined) {
return this.workflow.getSimpleParameterValue(this.data as INode, this.nodeType.subtitle) as string | undefined;
}

View file

@ -142,6 +142,7 @@ export default mixins(
nodeValues: {
color: '#ff0000',
alwaysOutputData: false,
notesInFlow: false,
continueOnFail: false,
retryOnFail: false,
maxTries: 3,
@ -162,6 +163,14 @@ export default mixins(
noDataExpression: true,
description: 'Notes to save with the node.',
},
{
displayName: 'Notes In Flow',
name: 'notesInFlow',
type: 'boolean',
default: false,
noDataExpression: true,
description: 'If activated it will display the above notes in the flow as subtitle.',
},
{
displayName: 'Node Color',
name: 'color',
@ -438,6 +447,11 @@ export default mixins(
Vue.set(this.nodeValues, 'continueOnFail', this.node.continueOnFail);
}
if (this.node.notesInFlow) {
foundNodeSettings.push('notesInFlow');
Vue.set(this.nodeValues, 'notesInFlow', this.node.notesInFlow);
}
if (this.node.retryOnFail) {
foundNodeSettings.push('retryOnFail');
Vue.set(this.nodeValues, 'retryOnFail', this.node.retryOnFail);

View file

@ -79,9 +79,13 @@ export class FacebookGraphApi implements INodeType {
type: 'options',
options: [
{
name: 'Latest',
name: 'Default',
value: '',
},
{
name: 'v7.0',
value: 'v7.0',
},
{
name: 'v6.0',
value: 'v6.0',
@ -110,10 +114,6 @@ export class FacebookGraphApi implements INodeType {
name: 'v3.0',
value: 'v3.0',
},
{
name: 'v2.12',
value: 'v2.12',
},
],
default: '',
description: 'The version of the Graph API to be used in the request.',

View file

@ -11,22 +11,33 @@ import {
import {
IDataObject,
ICredentialDataDecryptedObject,
} from 'n8n-workflow';
export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any
let data; let domain;
const jiraCloudCredentials = this.getCredentials('jiraSoftwareCloudApi');
const jiraServerCredentials = this.getCredentials('jiraSoftwareServerApi');
if (jiraCloudCredentials === undefined && jiraServerCredentials === undefined) {
const jiraVersion = this.getNodeParameter('jiraVersion', 0) as string;
let jiraCredentials: ICredentialDataDecryptedObject | undefined;
if (jiraVersion === 'server') {
jiraCredentials = this.getCredentials('jiraSoftwareServerApi');
} else {
jiraCredentials = this.getCredentials('jiraSoftwareCloudApi');
}
if (jiraCredentials === undefined) {
throw new Error('No credentials got returned!');
}
if (jiraCloudCredentials !== undefined) {
domain = jiraCloudCredentials!.domain;
data = Buffer.from(`${jiraCloudCredentials!.email}:${jiraCloudCredentials!.apiToken}`).toString('base64');
if (jiraVersion === 'server') {
domain = jiraCredentials!.domain;
data = Buffer.from(`${jiraCredentials!.email}:${jiraCredentials!.password}`).toString('base64');
} else {
domain = jiraServerCredentials!.domain;
data = Buffer.from(`${jiraServerCredentials!.email}:${jiraServerCredentials!.password}`).toString('base64');
domain = jiraCredentials!.domain;
data = Buffer.from(`${jiraCredentials!.email}:${jiraCredentials!.apiToken}`).toString('base64');
}
const options: OptionsWithUri = {
headers: {
Authorization: `Basic ${data}`,

View file

@ -81,6 +81,9 @@ export const issueFields = [
},
typeOptions: {
loadOptionsMethod: 'getProjects',
loadOptionsDependsOn: [
'jiraVersion',
],
},
description: 'Project',
},
@ -171,6 +174,31 @@ export const issueFields = [
default: [],
required : false,
description: 'Labels',
displayOptions: {
show: {
'/jiraVersion': [
'cloud',
],
},
},
},
{
displayName: 'Labels',
name: 'serverLabels',
type: 'string',
default: [],
required : false,
description: 'Labels',
displayOptions: {
show: {
'/jiraVersion': [
'server',
],
},
},
typeOptions: {
multipleValues: true,
},
},
{
displayName: 'Parent Issue Key',
@ -281,6 +309,31 @@ export const issueFields = [
default: [],
required : false,
description: 'Labels',
displayOptions: {
show: {
'/jiraVersion': [
'cloud',
],
},
},
},
{
displayName: 'Labels',
name: 'serverLabels',
type: 'string',
default: [],
required : false,
description: 'Labels',
displayOptions: {
show: {
'/jiraVersion': [
'server',
],
},
},
typeOptions: {
multipleValues: true,
},
},
{
displayName: 'Parent Issue Key',

View file

@ -111,9 +111,10 @@ export class JiraSoftwareCloud implements INodeType {
// select them easily
async getProjects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const jiraCloudCredentials = this.getCredentials('jiraSoftwareCloudApi');
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
let endpoint = '/project/search';
if (jiraCloudCredentials === undefined) {
if (jiraVersion === 'server') {
endpoint = '/project';
}
let projects = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET');
@ -139,9 +140,9 @@ export class JiraSoftwareCloud implements INodeType {
const returnData: INodePropertyOptions[] = [];
const issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/issuetype', 'GET');
for (const issueType of issueTypes) {
if (issueType.scope.project.id === projectId) {
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
if (jiraVersion === 'server') {
for (const issueType of issueTypes) {
const issueTypeName = issueType.name;
const issueTypeId = issueType.id;
@ -150,7 +151,20 @@ export class JiraSoftwareCloud implements INodeType {
value: issueTypeId,
});
}
} else {
for (const issueType of issueTypes) {
if (issueType.scope.project.id === projectId) {
const issueTypeName = issueType.name;
const issueTypeId = issueType.id;
returnData.push({
name: issueTypeName,
value: issueTypeId,
});
}
}
}
return returnData;
},
@ -196,18 +210,37 @@ export class JiraSoftwareCloud implements INodeType {
// select them easily
async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
if (jiraVersion === 'server') {
// the interface call must bring username
const users = await jiraSoftwareCloudApiRequest.call(this, '/user/search', 'GET', {},
{
username: "'",
}
);
for (const user of users) {
const userName = user.displayName;
const userId = user.name;
const users = await jiraSoftwareCloudApiRequest.call(this, '/users/search', 'GET');
returnData.push({
name: userName,
value: userId,
});
}
} else {
const users = await jiraSoftwareCloudApiRequest.call(this, '/users/search', 'GET');
for (const user of users) {
const userName = user.displayName;
const userId = user.accountId;
for (const user of users) {
const userName = user.displayName;
const userId = user.accountId;
returnData.push({
name: userName,
value: userId,
});
returnData.push({
name: userName,
value: userId,
});
}
}
return returnData;
},
@ -258,6 +291,8 @@ export class JiraSoftwareCloud implements INodeType {
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
const jiraVersion = this.getNodeParameter('jiraVersion', 0) as string;
for (let i = 0; i < length; i++) {
if (resource === 'issue') {
@ -280,15 +315,24 @@ export class JiraSoftwareCloud implements INodeType {
if (additionalFields.labels) {
fields.labels = additionalFields.labels as string[];
}
if (additionalFields.serverLabels) {
fields.labels = additionalFields.serverLabels as string[];
}
if (additionalFields.priority) {
fields.priority = {
id: additionalFields.priority as string,
};
}
if (additionalFields.assignee) {
fields.assignee = {
id: additionalFields.assignee as string,
};
if (jiraVersion === 'server') {
fields.assignee = {
name: additionalFields.assignee as string,
};
} else {
fields.assignee = {
id: additionalFields.assignee as string,
};
}
}
if (additionalFields.description) {
fields.description = additionalFields.description as string;
@ -333,15 +377,24 @@ export class JiraSoftwareCloud implements INodeType {
if (updateFields.labels) {
fields.labels = updateFields.labels as string[];
}
if (updateFields.serverLabels) {
fields.labels = updateFields.serverLabels as string[];
}
if (updateFields.priority) {
fields.priority = {
id: updateFields.priority as string,
};
}
if (updateFields.assignee) {
fields.assignee = {
id: updateFields.assignee as string,
};
if (jiraVersion === 'server') {
fields.assignee = {
name: updateFields.assignee as string,
};
} else {
fields.assignee = {
id: updateFields.assignee as string,
};
}
}
if (updateFields.description) {
fields.description = updateFields.description as string;

View file

@ -97,6 +97,11 @@ export class Mattermost implements INodeType {
value: 'delete',
description: 'Soft-deletes a channel',
},
{
name: 'Members',
value: 'members',
description: 'Returns the members of a channel.',
},
{
name: 'Restore',
value: 'restore',
@ -262,6 +267,97 @@ export class Mattermost implements INodeType {
description: 'The ID of the channel to soft-delete.',
},
// ----------------------------------
// channel:members
// ----------------------------------
{
displayName: 'Team ID',
name: 'teamId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getTeams',
},
options: [],
default: '',
required: true,
displayOptions: {
show: {
operation: [
'members',
],
resource: [
'channel',
],
},
},
description: 'The Mattermost Team.',
},
{
displayName: 'Channel ID',
name: 'channelId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getChannelsInTeam',
loadOptionsDependsOn: [
'teamId',
],
},
options: [],
default: '',
required: true,
displayOptions: {
show: {
operation: [
'members',
],
resource: [
'channel',
],
},
},
description: 'The Mattermost Team.',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'members',
],
resource: [
'channel',
],
},
},
default: true,
description: 'If all results should be returned or only up to a given limit.',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: [
'members',
],
resource: [
'channel',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 100,
description: 'How many results to return.',
},
// ----------------------------------
// channel:restore
@ -1049,10 +1145,42 @@ export class Mattermost implements INodeType {
return returnData;
},
// Get all the channels in a team
async getChannelsInTeam(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const teamId = this.getCurrentNodeParameter('teamId');
const endpoint = `users/me/teams/${teamId}/channels`;
const responseData = await apiRequest.call(this, 'GET', endpoint, {});
if (responseData === undefined) {
throw new Error('No data got returned');
}
const returnData: INodePropertyOptions[] = [];
let name: string;
for (const data of responseData) {
if (data.delete_at !== 0) {
continue;
}
const channelTypes: IDataObject = {
'O': 'public',
'P': 'private',
'D': 'direct',
};
name = `${data.name} (${channelTypes[data.type as string]})`;
returnData.push({
name,
value: data.id,
});
}
return returnData;
},
async getTeams(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const endpoint = 'teams';
const endpoint = 'users/me/teams';
const responseData = await apiRequest.call(this, 'GET', endpoint, {});
if (responseData === undefined) {
@ -1156,6 +1284,19 @@ export class Mattermost implements INodeType {
const channelId = this.getNodeParameter('channelId', i) as string;
endpoint = `channels/${channelId}`;
} else if (operation === 'members') {
// ----------------------------------
// channel:members
// ----------------------------------
requestMethod = 'GET';
const channelId = this.getNodeParameter('channelId', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
endpoint = `channels/${channelId}/members`;
if (returnAll === false) {
qs.per_page = this.getNodeParameter('limit', i) as number;
}
} else if (operation === 'restore') {
// ----------------------------------
// channel:restore
@ -1353,7 +1494,7 @@ export class Mattermost implements INodeType {
}
if (returnAll === false) {
qs.limit = this.getNodeParameter('limit', i) as number;
qs.per_page = this.getNodeParameter('limit', i) as number;
}
endpoint = `/users`;

View file

@ -69,7 +69,7 @@ export class Merge implements INodeType {
{
name: 'Wait',
value: 'wait',
description: 'Waits till data of both inputs is available and will then output a single empty item. If supposed to wait for multiple nodes they have to get attached to input 2. Node will not output any data.',
description: 'Waits till data of both inputs is available and will then output a single empty item. Source Nodes must connect to both Input 1 and 2. This node only supports 2 Sources, if you need more Sources, connect multiple Merge nodes in series. This node will not output any data.',
},
],
default: 'append',

View file

@ -158,6 +158,33 @@ export class Redis implements INodeType {
description: 'The type of the key to get.',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: [
'get'
],
},
},
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Dot Notation',
name: 'dotNotation',
type: 'boolean',
default: true,
description: `By default does dot-notation get used in property names.<br />
This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.<br />
If that is not intended this can be deactivated, it will then set { "a.b": value } instead.
`,
},
],
},
// ----------------------------------
// keys
// ----------------------------------
@ -292,7 +319,7 @@ export class Redis implements INodeType {
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
// Parses the given value in a number if it is one else returns a string
function getParsedValue (value: string): string | number {
function getParsedValue(value: string): string | number {
if (value.match(/^[\d\.]+$/) === null) {
// Is a string
return value;
@ -306,7 +333,7 @@ export class Redis implements INodeType {
function convertInfoToObject(stringData: string): IDataObject {
const returnData: IDataObject = {};
let key:string, value:string;
let key: string, value: string;
for (const line of stringData.split('\n')) {
if (['#', ''].includes(line.charAt(0))) {
continue;
@ -450,7 +477,15 @@ export class Redis implements INodeType {
const keyType = this.getNodeParameter('keyType', itemIndex) as string;
const value = await getValue(client, keyGet, keyType) || null;
set(item.json, propertyName, value);
const options = this.getNodeParameter('options', itemIndex, {}) as IDataObject;
if (options.dotNotation === false) {
item.json[propertyName] = value;
} else {
set(item.json, propertyName, value);
}
returnItems.push(item);
} else if (operation === 'keys') {
const keyPattern = this.getNodeParameter('keyPattern', itemIndex) as string;
@ -467,7 +502,7 @@ export class Redis implements INodeType {
}
for (const keyName of keys) {
set(item.json, keyName, await promises[keyName]);
item.json[keyName] = await promises[keyName];
}
returnItems.push(item);
} else if (operation === 'set') {

View file

@ -289,6 +289,7 @@ export interface INode {
type: string;
position: [number, number];
disabled?: boolean;
notesInFlow?: boolean;
retryOnFail?: boolean;
maxTries?: number;
waitBetweenTries?: number;
@ -680,7 +681,7 @@ export interface IWorkflowExecuteAdditionalData {
timezone: string;
webhookBaseUrl: string;
webhookTestBaseUrl: string;
currentNodeParameters? : INodeParameters[];
currentNodeParameters? : INodeParameters;
}
export type WorkflowExecuteMode = 'cli' | 'error' | 'integrated' | 'internal' | 'manual' | 'retry' | 'trigger' | 'webhook';