Skip to content

Commit

Permalink
feat(#1130): file upload schema updates
Browse files Browse the repository at this point in the history
  • Loading branch information
helloanoop committed Feb 4, 2024
1 parent 634f9ca commit 09e7ea0
Show file tree
Hide file tree
Showing 15 changed files with 88 additions and 50 deletions.
31 changes: 19 additions & 12 deletions packages/bruno-app/src/components/FilePickerEditor/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import React from 'react';
import path from 'path';
import { useDispatch } from 'react-redux';
import { browseFiles } from 'providers/ReduxStore/slices/collections/actions';
import { IconX } from '@tabler/icons';
import { isWindowsOS } from 'utils/common/platform';

const FilePickerEditor = ({ value, onChange, collection }) => {
value = value || [];
const dispatch = useDispatch();
const filnames = value
.split('|')
const filenames = value
.filter((v) => v != null && v != '')
.map((v) => v.split('\\').pop());
const title = filnames.map((v) => `- ${v}`).join('\n');
.map((v) => {
const separator = isWindowsOS() ? '\\' : '/';
return v.split(separator).pop();
});

// title is shown when hovering over the button
const title = filenames.map((v) => `- ${v}`).join('\n');

const browse = () => {
dispatch(browseFiles())
Expand All @@ -20,13 +27,13 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
const collectionDir = collection.pathname;

if (filePath.startsWith(collectionDir)) {
return filePath.substring(collectionDir.length + 1);
return path.relative(collectionDir, filePath);
}

return filePath;
});

onChange(filePaths.join('|'));
onChange(filePaths);
})
.catch((error) => {
console.error(error);
Expand All @@ -37,14 +44,14 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
onChange('');
};

const renderButtonText = (filnames) => {
if (filnames.length == 1) {
return filnames[0];
const renderButtonText = (filenames) => {
if (filenames.length == 1) {
return filenames[0];
}
return filnames.length + ' files selected';
return filenames.length + ' files selected';
};

return filnames.length > 0 ? (
return filenames.length > 0 ? (
<div
className="btn btn-secondary px-1"
style={{ fontWeight: 400, width: '100%', textOverflow: 'ellipsis', overflowX: 'hidden' }}
Expand All @@ -54,7 +61,7 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
<IconX size={18} />
</button>
&nbsp;
{renderButtonText(filnames)}
{renderButtonText(filenames)}
</div>
) : (
<button className="btn btn-secondary px-1" style={{ width: '100%' }} onClick={browse}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import SingleLineEditor from 'components/SingleLineEditor';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import FilePickerEditor from 'components/FilePickerEditor/index';
import FilePickerEditor from 'components/FilePickerEditor';

const MultipartFormParams = ({ item, collection }) => {
const dispatch = useDispatch();
Expand All @@ -23,7 +23,8 @@ const MultipartFormParams = ({ item, collection }) => {
dispatch(
addMultipartFormParam({
itemUid: item.uid,
collectionUid: collection.uid
collectionUid: collection.uid,
type: 'text'
})
);
};
Expand All @@ -33,7 +34,7 @@ const MultipartFormParams = ({ item, collection }) => {
addMultipartFormParam({
itemUid: item.uid,
collectionUid: collection.uid,
isFile: true
type: 'file'
})
);
};
Expand Down Expand Up @@ -103,7 +104,7 @@ const MultipartFormParams = ({ item, collection }) => {
/>
</td>
<td>
{param.isFile === true ? (
{param.type === 'file' ? (
<FilePickerEditor
value={param.value}
onChange={(newValue) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ export const collectionsSlice = createSlice({
item.draft.request.body.multipartForm = item.draft.request.body.multipartForm || [];
item.draft.request.body.multipartForm.push({
uid: uuid(),
isFile: action.payload.isFile ?? false,
type: action.payload.type,
name: '',
value: '',
description: '',
Expand All @@ -638,7 +638,7 @@ export const collectionsSlice = createSlice({
}
const param = find(item.draft.request.body.multipartForm, (p) => p.uid === action.payload.param.uid);
if (param) {
param.isFile = action.payload.param.isFile;
param.type = action.payload.param.type;
param.name = action.payload.param.name;
param.value = action.payload.param.value;
param.description = action.payload.param.description;
Expand Down
5 changes: 1 addition & 4 deletions packages/bruno-app/src/utils/collections/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
return map(params, (param) => {
return {
uid: param.uid,
type: param.type,
name: param.name,
value: param.value,
description: param.description,
Expand Down Expand Up @@ -520,10 +521,6 @@ export const refreshUidsInItem = (item) => {
return item;
};

export const isLocalCollection = (collection) => {
return collection.pathname ? true : false;
};

export const deleteUidsInItem = (item) => {
delete item.uid;
const params = get(item, 'request.params', []);
Expand Down
4 changes: 0 additions & 4 deletions packages/bruno-app/src/utils/common/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ export const isElectron = () => {
return window.ipcRenderer ? true : false;
};

export const isLocalCollection = (collection) => {
return collection.pathname ? true : false;
};

export const resolveRequestFilename = (name) => {
return `${trim(name)}.bru`;
};
Expand Down
12 changes: 12 additions & 0 deletions packages/bruno-app/src/utils/importers/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ export const transformItemsInCollection = (collection) => {
}

delete item.request.query;

// from 5 feb 2024, multipartFormData needs to have a type
// this was introduced when we added support for file uploads
// below logic is to make older collection exports backward compatible
let multipartFormData = _.get(item, 'request.body.multipartForm');
if (multipartFormData) {
_.each(multipartFormData, (form) => {
if (!form.type) {
form.type = 'text';
}
});
}
}

if (item.items && item.items.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const transformInsomniaRequestItem = (request, index, allRequests) => {
each(request.body.params, (param) => {
brunoRequestItem.request.body.multipartForm.push({
uid: uuid(),
type: 'text',
name: param.name,
value: param.value,
description: param.description,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const transformOpenapiRequestItem = (request) => {
each(bodySchema.properties || {}, (prop, name) => {
brunoRequestItem.request.body.multipartForm.push({
uid: uuid(),
type: 'text',
name: name,
value: '',
description: prop.description || '',
Expand Down
3 changes: 2 additions & 1 deletion packages/bruno-app/src/utils/importers/postman-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
if (bodyMode === 'formdata') {
brunoRequestItem.request.body.mode = 'multipartForm';
each(i.request.body.formdata, (param) => {
brunoRequestItem.request.body.formUrlEncoded.push({
brunoRequestItem.request.body.multipartForm.push({
uid: uuid(),
type: 'text',
name: param.key,
value: param.value,
description: param.description,
Expand Down
2 changes: 1 addition & 1 deletion packages/bruno-cli/src/runner/prepare-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const prepareRequest = (request, collectionRoot) => {
each(enabledParams, (p) => (params[p.name] = p.value));
axiosRequest.headers['content-type'] = 'multipart/form-data';
axiosRequest.data = params;
// TODO is it needed here as well ?
// TODO: Add support for file uploads
}

if (request.body.mode === 'graphql') {
Expand Down
8 changes: 4 additions & 4 deletions packages/bruno-cli/src/runner/run-single-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const runSingleRequest = async function (
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
if (request.headers && request.headers['content-type'] === 'multipart/form-data') {
// TODO remove ?
// TODO: Add support for file uploads
const form = new FormData();
forOwn(request.data, (value, key) => {
form.append(key, value);
Expand Down Expand Up @@ -204,7 +204,7 @@ const runSingleRequest = async function (
console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`));
return {
test: {
filename: filename,
filename: filename
},
request: {
method: request.method,
Expand Down Expand Up @@ -327,7 +327,7 @@ const runSingleRequest = async function (

return {
test: {
filename: filename,
filename: filename
},
request: {
method: request.method,
Expand All @@ -351,7 +351,7 @@ const runSingleRequest = async function (
console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`));
return {
test: {
filename: filename,
filename: filename
},
request: {
method: null,
Expand Down
15 changes: 5 additions & 10 deletions packages/bruno-electron/src/ipc/network/prepare-request.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
const { get, each, filter, forOwn, extend } = require('lodash');
const { get, each, filter, extend } = require('lodash');
const decomment = require('decomment');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');

const parseFormData = (datas, collectionPath) => {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
const form = new FormData();
datas.forEach((item) => {
const value = item.value;
const name = item.name;
if (item.isFile === true) {
const filePaths = value
.toString()
.replace(/^@file\(/, '')
.replace(/\)$/, '')
.split('|');

if (item.type === 'file') {
const filePaths = value || [];
filePaths.forEach((filePath) => {
let trimmedFilePath = filePath.trim();
if (!path.isAbsolute(trimmedFilePath)) {
Expand Down Expand Up @@ -175,8 +172,6 @@ const prepareRequest = (request, collectionRoot, collectionPath) => {
}

if (request.body.mode === 'multipartForm') {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
const form = parseFormData(enabledParams, collectionPath);
extend(axiosRequest.headers, form.getHeaders());
Expand Down
6 changes: 4 additions & 2 deletions packages/bruno-lang/v2/src/bruToJson.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,11 @@ const mapPairListToKeyValPairsMultipart = (pairList = [], parseEnabled = true) =
const pairs = mapPairListToKeyValPairs(pairList, parseEnabled);

return pairs.map((pair) => {
pair.type = 'text';
if (pair.value.startsWith('@file(') && pair.value.endsWith(')')) {
pair.isFile = true;
pair.value = pair.value.replace(/^@file\(/, '').replace(/\)$/, '');
let filestr = pair.value.replace(/^@file\(/, '').replace(/\)$/, '');
pair.type = 'file';
pair.value = filestr.split('|');
}
return pair;
});
Expand Down
12 changes: 10 additions & 2 deletions packages/bruno-lang/v2/src/jsonToBru.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,17 @@ ${indentString(body.sparql)}
multipartForms
.map((item) => {
const enabled = item.enabled ? '' : '~';
const value = item.isFile ? `@file(${item.value})` : item.value;
return `${enabled}${item.name}: ${value}`;
if (item.type === 'text') {
return `${enabled}${item.name}: ${item.value}`;
}
if (item.type === 'file') {
let filepaths = item.value || [];
let filestr = filepaths.join('|');
const value = `@file(${filestr})`;
return `${enabled}${item.name}: ${value}`;
}
})
.join('\n')
)}`;
Expand Down
25 changes: 21 additions & 4 deletions packages/bruno-schema/src/collections/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const environmentsSchema = Yup.array().of(environmentSchema);

const keyValueSchema = Yup.object({
uid: uidSchema,
isFile: Yup.boolean().nullable(),
name: Yup.string().nullable(),
value: Yup.string().nullable(),
description: Yup.string().nullable(),
Expand All @@ -38,8 +37,11 @@ const varsSchema = Yup.object({
name: Yup.string().nullable(),
value: Yup.string().nullable(),
description: Yup.string().nullable(),
local: Yup.boolean(),
enabled: Yup.boolean()
enabled: Yup.boolean(),

// todo
// anoop(4 feb 2023) - nobody uses this, and it needs to be removed
local: Yup.boolean()
})
.noUnknown(true)
.strict();
Expand All @@ -56,6 +58,21 @@ const graphqlBodySchema = Yup.object({
.noUnknown(true)
.strict();

const multipartFormSchema = Yup.object({
uid: uidSchema,
type: Yup.string().oneOf(['file', 'text']).required('type is required'),
name: Yup.string().nullable(),
value: Yup.mixed().when('type', {
is: 'file',
then: Yup.array().of(Yup.string().nullable()).nullable(),
otherwise: Yup.string().nullable()
}),
description: Yup.string().nullable(),
enabled: Yup.boolean()
})
.noUnknown(true)
.strict();

const requestBodySchema = Yup.object({
mode: Yup.string()
.oneOf(['none', 'json', 'text', 'xml', 'formUrlEncoded', 'multipartForm', 'graphql', 'sparql'])
Expand All @@ -65,7 +82,7 @@ const requestBodySchema = Yup.object({
xml: Yup.string().nullable(),
sparql: Yup.string().nullable(),
formUrlEncoded: Yup.array().of(keyValueSchema).nullable(),
multipartForm: Yup.array().of(keyValueSchema).nullable(),
multipartForm: Yup.array().of(multipartFormSchema).nullable(),
graphql: graphqlBodySchema.nullable()
})
.noUnknown(true)
Expand Down

0 comments on commit 09e7ea0

Please sign in to comment.