mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-23 11:44:06 -08:00
⚡ Add file:search operation to Dropbox Node (#1494)
* ⚡ Add search resource to Dropbox Node * 📚 Add breaking change instructions * ⚡ Add missing credentials * ⚡ Add "simple" parameter to the operation search:query * ⚡ Update breaking change message * ⚡ Improvement Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
263813a8f9
commit
e1dbb72929
|
@ -2,6 +2,18 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 0.111.0
|
||||
|
||||
### What changed?
|
||||
In the Dropbox node, now all operations are performed relative to the user's root directory.
|
||||
|
||||
### When is action necessary?
|
||||
If you are using the `folder:list` operation with the parameter `Folder Path` empty (root path) and have a Team Space in your Dropbox account.
|
||||
|
||||
### How to upgrade:
|
||||
Open the Dropbox node, go to the `folder:list` operation, and make sure your logic is taking into account the team folders in the response.
|
||||
|
||||
|
||||
## 0.105.0
|
||||
|
||||
### What changed?
|
||||
|
|
|
@ -7,6 +7,7 @@ const scopes = [
|
|||
'files.content.write',
|
||||
'files.content.read',
|
||||
'sharing.read',
|
||||
'account_info.read',
|
||||
];
|
||||
|
||||
export class DropboxOAuth2Api implements ICredentialType {
|
||||
|
@ -41,7 +42,7 @@ export class DropboxOAuth2Api implements ICredentialType {
|
|||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'token_access_type=offline',
|
||||
default: 'token_access_type=offline&force_reapprove=true',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
|
|
|
@ -11,21 +11,24 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
dropboxApiRequest
|
||||
dropboxApiRequest,
|
||||
dropboxpiRequestAllItems,
|
||||
getRootDirectory,
|
||||
simplify,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class Dropbox implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Dropbox',
|
||||
name: 'dropbox',
|
||||
icon: 'file:dropbox.png',
|
||||
icon: 'file:dropbox.svg',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Access data on Dropbox',
|
||||
defaults: {
|
||||
name: 'Dropbox',
|
||||
color: '#0062ff',
|
||||
color: '#007ee5',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
|
@ -84,6 +87,10 @@ export class Dropbox implements INodeType {
|
|||
name: 'Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
},
|
||||
],
|
||||
default: 'file',
|
||||
description: 'The resource to operate on.',
|
||||
|
@ -176,6 +183,27 @@ export class Dropbox implements INodeType {
|
|||
description: 'The operation to perform.',
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Query',
|
||||
value: 'query',
|
||||
},
|
||||
],
|
||||
default: 'query',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// file
|
||||
// ----------------------------------
|
||||
|
@ -419,7 +447,189 @@ export class Dropbox implements INodeType {
|
|||
description: 'Name of the binary property which contains<br />the data for the file to be uploaded.',
|
||||
},
|
||||
|
||||
|
||||
// ----------------------------------
|
||||
// search:query
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'query',
|
||||
],
|
||||
resource: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: ' The string to search for. May match across multiple fields based on the request arguments.',
|
||||
},
|
||||
{
|
||||
displayName: 'File Status',
|
||||
name: 'fileStatus',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Active',
|
||||
value: 'active',
|
||||
},
|
||||
{
|
||||
name: 'Deleted',
|
||||
value: 'deleted',
|
||||
},
|
||||
],
|
||||
default: 'active',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'query',
|
||||
],
|
||||
resource: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: ' The string to search for. May match across multiple fields based on the request arguments.',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'query',
|
||||
],
|
||||
resource: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'search',
|
||||
],
|
||||
operation: [
|
||||
'query',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'query',
|
||||
],
|
||||
resource: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'When set to true a simplify version of the response will be used else the raw data.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'search',
|
||||
],
|
||||
operation: [
|
||||
'query',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'File Categories',
|
||||
name: 'file_categories',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: 'Audio (mp3, wav, mid, etc.)',
|
||||
value: 'audio',
|
||||
},
|
||||
{
|
||||
name: 'Document (doc, docx, txt, etc.)',
|
||||
value: 'document',
|
||||
},
|
||||
{
|
||||
name: 'Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
{
|
||||
name: 'Image (jpg, png, gif, etc.)',
|
||||
value: 'image',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
name: 'Dropbox Paper',
|
||||
value: 'paper',
|
||||
},
|
||||
{
|
||||
name: 'PDF',
|
||||
value: 'pdf',
|
||||
},
|
||||
{
|
||||
name: 'Presentation (ppt, pptx, key, etc.)',
|
||||
value: 'presentation',
|
||||
},
|
||||
{
|
||||
name: 'Spreadsheet (xlsx, xls, csv, etc.)',
|
||||
value: 'spreadsheet',
|
||||
},
|
||||
{
|
||||
name: 'Video (avi, wmv, mp4, etc.)',
|
||||
value: 'video',
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
},
|
||||
{
|
||||
displayName: 'File Extensions',
|
||||
name: 'file_extensions',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Multiple can be set separated by comma. Example: jpg,pdf',
|
||||
},
|
||||
{
|
||||
displayName: 'Folder',
|
||||
name: 'path',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'If this field is not specified, this module searches the entire Dropbox',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// folder
|
||||
|
@ -469,7 +679,97 @@ export class Dropbox implements INodeType {
|
|||
placeholder: '/invoices/2019/',
|
||||
description: 'The path of which to list the content.',
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'list',
|
||||
],
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'list',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Include Deleted',
|
||||
name: 'include_deleted',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'If true, the results will include entries for files and folders that used to exist but were deleted. The default for this field is False.',
|
||||
},
|
||||
{
|
||||
displayName: 'Include Shared Members ',
|
||||
name: 'include_has_explicit_shared_members',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'If true, the results will include a flag for each file indicating whether or not that file has any explicit members. The default for this field is False.',
|
||||
},
|
||||
{
|
||||
displayName: 'Include Mounted Folders ',
|
||||
name: 'include_mounted_folders',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'If true, the results will include entries under mounted folders which includes app folder, shared folder and team folder. The default for this field is True.',
|
||||
},
|
||||
{
|
||||
displayName: 'Include Non Downloadable Files ',
|
||||
name: 'include_non_downloadable_files',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'If true, include files that are not downloadable, i.e. Google Docs. The default for this field is True.',
|
||||
},
|
||||
{
|
||||
displayName: 'Recursive',
|
||||
name: 'recursive',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'If true, the list folder operation will be applied recursively to all subfolders and the response will contain contents of all subfolders. The default for this field is False.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -484,11 +784,24 @@ export class Dropbox implements INodeType {
|
|||
|
||||
let endpoint = '';
|
||||
let requestMethod = '';
|
||||
let returnAll = false;
|
||||
let property = '';
|
||||
let body: IDataObject | Buffer;
|
||||
let options;
|
||||
const query: IDataObject = {};
|
||||
|
||||
const headers: IDataObject = {};
|
||||
let headers: IDataObject = {};
|
||||
let simple = false;
|
||||
|
||||
// get the root directory to set it as the default search folder
|
||||
const { root_info: { root_namespace_id } } = await getRootDirectory.call(this);
|
||||
|
||||
headers = {
|
||||
'dropbox-api-path-root': JSON.stringify({
|
||||
'.tag': 'root',
|
||||
'root': root_namespace_id,
|
||||
}),
|
||||
};
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
body = {};
|
||||
|
@ -545,7 +858,6 @@ export class Dropbox implements INodeType {
|
|||
body = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
} else if (resource === 'folder') {
|
||||
if (operation === 'create') {
|
||||
// ----------------------------------
|
||||
|
@ -564,20 +876,65 @@ export class Dropbox implements INodeType {
|
|||
// list
|
||||
// ----------------------------------
|
||||
|
||||
returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
|
||||
property = 'entries';
|
||||
|
||||
requestMethod = 'POST';
|
||||
body = {
|
||||
path: this.getNodeParameter('path', i) as string,
|
||||
limit: 2000,
|
||||
limit: 1000,
|
||||
};
|
||||
|
||||
// TODO: If more files than the max-amount exist it has to be possible to
|
||||
// also request them.
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', 0) as number;
|
||||
body.limit = limit;
|
||||
}
|
||||
|
||||
Object.assign(body, filters);
|
||||
|
||||
endpoint = 'https://api.dropboxapi.com/2/files/list_folder';
|
||||
|
||||
}
|
||||
} else if (resource === 'search') {
|
||||
if (operation === 'query') {
|
||||
// ----------------------------------
|
||||
// query
|
||||
// ----------------------------------
|
||||
|
||||
returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
|
||||
simple = this.getNodeParameter('simple', 0) as boolean;
|
||||
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
|
||||
property = 'matches';
|
||||
|
||||
requestMethod = 'POST';
|
||||
body = {
|
||||
query: this.getNodeParameter('query', i) as string,
|
||||
options: {
|
||||
filename_only: true,
|
||||
},
|
||||
};
|
||||
|
||||
if (filters.file_extensions) {
|
||||
filters.file_extensions = (filters.file_extensions as string).split(',');
|
||||
}
|
||||
|
||||
Object.assign(body.options, filters);
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
Object.assign(body.options, { max_results: limit });
|
||||
}
|
||||
|
||||
endpoint = 'https://api.dropboxapi.com/2/files/search_v2';
|
||||
}
|
||||
}
|
||||
if (['file', 'folder'].includes(resource)) {
|
||||
if (['file', 'folder', 'search'].includes(resource)) {
|
||||
if (operation === 'copy') {
|
||||
// ----------------------------------
|
||||
// copy
|
||||
|
@ -625,7 +982,13 @@ export class Dropbox implements INodeType {
|
|||
options = { encoding: null };
|
||||
}
|
||||
|
||||
let responseData = await dropboxApiRequest.call(this, requestMethod, endpoint, body, query, headers, options);
|
||||
let responseData;
|
||||
|
||||
if (returnAll === true) {
|
||||
responseData = await dropboxpiRequestAllItems.call(this, property, requestMethod, endpoint, body, query, headers);
|
||||
} else {
|
||||
responseData = await dropboxApiRequest.call(this, requestMethod, endpoint, body, query, headers, options);
|
||||
}
|
||||
|
||||
if (resource === 'file' && operation === 'upload') {
|
||||
responseData = JSON.parse(responseData);
|
||||
|
@ -665,7 +1028,11 @@ export class Dropbox implements INodeType {
|
|||
'content_hash': 'contentHash',
|
||||
};
|
||||
|
||||
for (const item of responseData.entries) {
|
||||
if (returnAll === false) {
|
||||
responseData = responseData.entries;
|
||||
}
|
||||
|
||||
for (const item of responseData) {
|
||||
const newItem: IDataObject = {};
|
||||
|
||||
// Get the props and save them under a proper name
|
||||
|
@ -677,8 +1044,14 @@ export class Dropbox implements INodeType {
|
|||
|
||||
returnData.push(newItem as IDataObject);
|
||||
}
|
||||
} else if (resource === 'search' && operation === 'query') {
|
||||
if (returnAll === true) {
|
||||
returnData.push.apply(returnData, (simple === true) ? simplify(responseData) : responseData);
|
||||
} else {
|
||||
returnData.push.apply(returnData, (simple === true) ? simplify(responseData[property]) : responseData[property]);
|
||||
}
|
||||
} else {
|
||||
returnData.push(responseData as IDataObject);
|
||||
returnData.push(responseData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
* @param {object} body
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function dropboxApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query: IDataObject = {}, headers?: object, option: IDataObject = {}): Promise<any> {// tslint:disable-line:no-any
|
||||
export async function dropboxApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query: IDataObject = {}, headers: object = {}, option: IDataObject = {}): Promise<any> {// tslint:disable-line:no-any
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers,
|
||||
|
@ -67,3 +67,51 @@ export async function dropboxApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function dropboxpiRequestAllItems(this: IExecuteFunctions | IHookFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
const paginationEndpoint: IDataObject = {
|
||||
'folder': 'https://api.dropboxapi.com/2/files/list_folder/continue',
|
||||
'search': 'https://api.dropboxapi.com/2/files/search/continue_v2',
|
||||
};
|
||||
|
||||
let responseData;
|
||||
do {
|
||||
responseData = await dropboxApiRequest.call(this, method, endpoint, body, query, headers);
|
||||
const cursor = responseData.cursor;
|
||||
if (cursor !== undefined) {
|
||||
endpoint = paginationEndpoint[resource] as string;
|
||||
body = { cursor };
|
||||
}
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
} while (
|
||||
responseData.has_more !== false
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function getRootDirectory(this: IHookFunctions | IExecuteFunctions) {
|
||||
return dropboxApiRequest.call(this, 'POST', 'https://api.dropboxapi.com/2/users/get_current_account', {});
|
||||
}
|
||||
|
||||
export function simplify(data: IDataObject[]) {
|
||||
const results = [];
|
||||
for (const element of data) {
|
||||
const { '.tag': key } = element?.metadata as IDataObject;
|
||||
const metadata = (element?.metadata as IDataObject)[key as string] as IDataObject;
|
||||
delete element.metadata;
|
||||
Object.assign(element, metadata);
|
||||
if ((element?.match_type as IDataObject)['.tag']) {
|
||||
element.match_type = (element?.match_type as IDataObject)['.tag'] as string;
|
||||
}
|
||||
results.push(element);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
1
packages/nodes-base/nodes/Dropbox/dropbox.svg
Normal file
1
packages/nodes-base/nodes/Dropbox/dropbox.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 67 62" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#A" x="1" y="1"/><symbol id="A" overflow="visible"><path d="M18.874.02L0 12.18l13.066 10.526L32 11.032m-32 22l18.874 12.4L32 34.422 13.066 22.686M32 34.422l13.188 11.01L64 33.152 50.994 22.686M64 12.28L45.188 0 32 11.01l18.994 11.674M32.06 36.778L18.872 47.726l-5.686-3.69v4.174L32.06 59.522 50.934 48.21v-4.174l-5.686 3.69" stroke="none" fill="#007ee5" fill-rule="nonzero"/></symbol></svg>
|
After Width: | Height: | Size: 603 B |
Loading…
Reference in a new issue