Add function to download part of the Data tree as a file (#458)

This commit is contained in:
Hetu Nandu 2020-08-28 17:37:37 +05:30 committed by GitHub
parent 8722e246e0
commit f7ec5209cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 188 additions and 2 deletions

View File

@ -49,6 +49,7 @@
"codemirror": "^5.55.0",
"copy-to-clipboard": "^3.3.1",
"craco-alias": "^2.1.1",
"downloadjs": "^1.4.7",
"eslint": "^6.4.0",
"fast-deep-equal": "^3.1.1",
"flow-bin": "^0.91.0",
@ -158,6 +159,7 @@
"@storybook/preset-create-react-app": "^3.1.4",
"@storybook/react": "^5.3.19",
"@types/codemirror": "^0.0.96",
"@types/downloadjs": "^1.4.2",
"@types/jest": "^24.0.22",
"@types/react-beautiful-dnd": "^11.0.4",
"@types/react-select": "^3.0.5",

View File

@ -32,6 +32,17 @@ const ALERT_STYLE_OPTIONS = [
{ label: "Error", value: "'error'", id: "error" },
{ label: "Warning", value: "'warning'", id: "warning" },
];
const FILE_TYPE_OPTIONS = [
{ label: "Plain text", value: "'text/plain'", id: "text/plain" },
{ label: "HTML", value: "'text/html'", id: "text/html" },
{ label: "CSV", value: "'text/csv'", id: "text/csv" },
{ label: "JSON", value: "'application/json'", id: "application/json" },
{ label: "JPEG", value: "'image/jpeg'", id: "image/jpeg" },
{ label: "PNG", value: "'image/png'", id: "image/png" },
{ label: "SVG", value: "'image/svg+xml'", id: "image/svg+xml" },
];
const ACTION_TRIGGER_REGEX = /^{{([\s\S]*?)\(([\s\S]*?)\)}}$/g;
//Old Regex:: /\(\) => ([\s\S]*?)(\([\s\S]*?\))/g;
const ACTION_ANONYMOUS_FUNC_REGEX = /\(\) => (({[\s\S]*?})|([\s\S]*?)(\([\s\S]*?\)))/g;
@ -162,6 +173,73 @@ const storeValueTextGetter = (value: string) => {
return "";
};
const downloadDataSetter = (changeValue: any, currentValue: string): string => {
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
const args = matches[0][2].split(",");
args[0] = `'${changeValue}'`;
const result = currentValue.replace(
ACTION_TRIGGER_REGEX,
`{{$1(${args.join(",")})}}`,
);
return result;
};
const downloadDataGetter = (value: string) => {
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
if (matches.length) {
const funcArgs = matches[0][2];
const arg = funcArgs.split(",")[0];
return arg.substring(1, arg.length - 1);
}
return "";
};
const downloadFileNameSetter = (
changeValue: any,
currentValue: string,
): string => {
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
const args = matches[0][2].split(",");
args[1] = `'${changeValue}'`;
return currentValue.replace(
ACTION_TRIGGER_REGEX,
`{{$1(${args.join(",")})}}`,
);
};
const downloadFileNameGetter = (value: string) => {
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
if (matches.length) {
const funcArgs = matches[0][2];
const arg = funcArgs.split(",")[1];
return arg ? arg.substring(1, arg.length - 1) : "";
}
return "";
};
const downloadFileTypeSetter = (
changeValue: any,
currentValue: string,
): string => {
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
const args = matches[0][2].split(",");
args[2] = changeValue as string;
return currentValue.replace(
ACTION_TRIGGER_REGEX,
`{{$1(${args.join(",")})}}`,
);
};
const downloadFileTypeGetter = (value: string) => {
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
if (matches.length) {
const funcArgs = matches[0][2];
const arg = funcArgs.split(",")[2];
return arg ? arg.trim() : "";
}
return "";
};
type ActionCreatorProps = {
value: string;
isValid: boolean;
@ -178,6 +256,7 @@ const ActionType = {
navigateTo: "navigateTo",
showAlert: "showAlert",
storeValue: "storeValue",
download: "download",
};
type ActionType = typeof ActionType[keyof typeof ActionType];
@ -282,6 +361,9 @@ const FieldType = {
ALERT_TYPE_SELECTOR_FIELD: "ALERT_TYPE_SELECTOR_FIELD",
KEY_TEXT_FIELD: "KEY_TEXT_FIELD",
VALUE_TEXT_FIELD: "VALUE_TEXT_FIELD",
DOWNLOAD_DATA_FIELD: "DOWNLOAD_DATA_FIELD",
DOWNLOAD_FILE_NAME_FIELD: "DOWNLOAD_FILE_NAME_FIELD",
DOWNLOAD_FILE_TYPE_FIELD: "DOWNLOAD_FILE_TYPE_FIELD",
};
type FieldType = typeof FieldType[keyof typeof FieldType];
@ -404,6 +486,22 @@ const fieldConfigs: FieldConfigs = {
setter: storeValueTextSetter,
view: ViewTypes.TEXT_VIEW,
},
[FieldType.DOWNLOAD_DATA_FIELD]: {
getter: downloadDataGetter,
setter: downloadDataSetter,
view: ViewTypes.TEXT_VIEW,
},
[FieldType.DOWNLOAD_FILE_NAME_FIELD]: {
getter: downloadFileNameGetter,
setter: downloadFileNameSetter,
view: ViewTypes.TEXT_VIEW,
},
[FieldType.DOWNLOAD_FILE_TYPE_FIELD]: {
getter: downloadFileTypeGetter,
setter: (option: any, currentValue: string) =>
downloadFileTypeSetter(option.value, currentValue),
view: ViewTypes.SELECTOR_VIEW,
},
};
const baseOptions: any = [
@ -440,6 +538,10 @@ const baseOptions: any = [
label: "Store Value",
value: ActionType.storeValue,
},
{
label: "Download",
value: ActionType.download,
},
];
function getOptionsWithChildren(
options: TreeDropdownOption[],
@ -567,6 +669,19 @@ function getFieldFromValue(
},
);
}
if (value.indexOf("download") !== -1) {
fields.push(
{
field: FieldType.DOWNLOAD_DATA_FIELD,
},
{
field: FieldType.DOWNLOAD_FILE_NAME_FIELD,
},
{
field: FieldType.DOWNLOAD_FILE_TYPE_FIELD,
},
);
}
return fields;
}
@ -606,6 +721,7 @@ function renderField(props: {
case FieldType.CLOSE_MODAL_FIELD:
case FieldType.PAGE_SELECTOR_FIELD:
case FieldType.ALERT_TYPE_SELECTOR_FIELD:
case FieldType.DOWNLOAD_FILE_TYPE_FIELD:
let label = "";
let defaultText = "Select Action";
let options = props.apiOptionTree;
@ -655,10 +771,15 @@ function renderField(props: {
defaultText = "Select Page";
}
if (fieldType === FieldType.ALERT_TYPE_SELECTOR_FIELD) {
label = "type";
label = "Type";
options = ALERT_STYLE_OPTIONS;
defaultText = "Select type";
}
if (fieldType === FieldType.DOWNLOAD_FILE_TYPE_FIELD) {
label = "Type";
options = FILE_TYPE_OPTIONS;
defaultText = "Select file type (optional)";
}
viewElement = (view as (props: SelectorViewProps) => JSX.Element)({
options: options,
label: label,
@ -695,6 +816,8 @@ function renderField(props: {
case FieldType.URL_FIELD:
case FieldType.KEY_TEXT_FIELD:
case FieldType.VALUE_TEXT_FIELD:
case FieldType.DOWNLOAD_DATA_FIELD:
case FieldType.DOWNLOAD_FILE_NAME_FIELD:
let fieldLabel = "";
if (fieldType === FieldType.ALERT_TEXT_FIELD) {
fieldLabel = "Message";
@ -704,6 +827,10 @@ function renderField(props: {
fieldLabel = "Key";
} else if (fieldType === FieldType.VALUE_TEXT_FIELD) {
fieldLabel = "Value";
} else if (fieldType === FieldType.DOWNLOAD_DATA_FIELD) {
fieldLabel = "Data to download";
} else if (fieldType === FieldType.DOWNLOAD_FILE_NAME_FIELD) {
fieldLabel = "File name with extension";
}
viewElement = (view as (props: TextViewProps) => JSX.Element)({
label: fieldLabel,

View File

@ -197,6 +197,14 @@ export class DataTreeFactory {
};
};
actionPaths.push("storeValue");
dataTree.download = function(data: string, name: string, type: string) {
return {
type: "DOWNLOAD",
payload: { data, name, type },
};
};
actionPaths.push("download");
}
dataTree.pageList = pageList;

View File

@ -15,11 +15,11 @@ import {
all,
call,
put,
race,
select,
take,
takeEvery,
takeLatest,
race,
} from "redux-saga/effects";
import {
evaluateDataTreeWithFunctions,
@ -75,6 +75,8 @@ import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants";
import { updateAppStore } from "actions/pageActions";
import { getAppStoreName } from "constants/AppConstants";
import downloadjs from "downloadjs";
import { getType, Types } from "utils/TypeHelpers";
function* navigateActionSaga(
action: { pageNameOrUrl: string; params: Record<string, string> },
@ -134,6 +136,36 @@ function* storeValueLocally(
}
}
function* downloadSaga(
action: { data: any; name: string; type: string },
event: ExecuteActionPayloadEvent,
) {
try {
const { data, name, type } = action;
if (!name) {
AppToaster.show({
message: "Download failed. File name was not provided",
type: "error",
});
return;
}
const dataType = getType(data);
if (dataType === Types.ARRAY || dataType === Types.OBJECT) {
const jsonString = JSON.stringify(data, null, 2);
downloadjs(jsonString, name, type);
} else {
downloadjs(data, name, type);
}
if (event.callback) event.callback({ success: true });
} catch (err) {
AppToaster.show({
message: `Download failed. ${err}`,
type: "error",
});
if (event.callback) event.callback({ success: false });
}
}
export const getActionTimeout = (
state: AppState,
actionId: string,
@ -383,6 +415,9 @@ function* executeActionTriggers(
case "STORE_VALUE":
yield call(storeValueLocally, trigger.payload, event);
break;
case "DOWNLOAD":
yield call(downloadSaga, trigger.payload, event);
break;
default:
yield put(
executeActionError({

View File

@ -237,4 +237,8 @@ export const GLOBAL_FUNCTIONS = {
"!doc": "Store key value data locally",
"!type": "fn(key: string, value: any) -> void",
},
download: {
"!doc": "Download anything as a file",
"!type": "fn(data: any, fileName: string, fileType?: string) -> void",
},
};

View File

@ -3590,6 +3590,11 @@
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.1.tgz#506d5781b9bcab81bd9a878b198aec7dee2a6033"
"@types/downloadjs@^1.4.2":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@types/downloadjs/-/downloadjs-1.4.2.tgz#6734e60840cd8df3f0f8937c80e67efe7de6f886"
integrity sha512-9UWO+nrRhwyKcFe45SFc98sWI8RGwpVwtZiZzi0W/dWdrp1SiUWFNANLlAXZb5QCMFDbeRiAQEhRn5btLd4a4w==
"@types/eslint-visitor-keys@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@ -7332,6 +7337,11 @@ dotenv@^6.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
downloadjs@^1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/downloadjs/-/downloadjs-1.4.7.tgz#f69f96f940e0d0553dac291139865a3cd0101e3c"
integrity sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw=
duplexer@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"