Merge branch 'master' of github.com:appsmithorg/appsmith

This commit is contained in:
Arpit Mohan 2020-10-23 20:58:58 +05:30
commit d977befc54
134 changed files with 6431 additions and 2375 deletions

View File

@ -1,18 +1,18 @@
module.exports = {
parser: '@typescript-eslint/parser',
parser: "@typescript-eslint/parser",
plugins: ["react", "@typescript-eslint", "prettier", "react-hooks"],
extends: [
"plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
ecmaFeatures: {
jsx: true // Allows for the parsing of JSX
}
jsx: true, // Allows for the parsing of JSX
},
},
rules: {
"@typescript-eslint/explicit-function-return-type": 0,
@ -20,11 +20,12 @@ module.exports = {
"react-hooks/rules-of-hooks": "error",
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-var-requires": 0,
"import/no-webpack-loader-syntax": 0
},
settings: {
react: {
pragma: "React",
version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
}
}
version: "detect", // Tells eslint-plugin-react to automatically detect the version of React to use
},
},
};

View File

@ -2,7 +2,7 @@
GIT_SHA=$(eval git rev-parse HEAD)
echo $GIT_SHA
REACT_APP_SENTRY_RELEASE=$GIT_SHA craco --max-old-space-size=4096 build --config craco.build.config.js
REACT_APP_SENTRY_RELEASE=$GIT_SHA EXTEND_ESLINT=true craco --max-old-space-size=4096 build --config craco.build.config.js
rm ./build/static/js/*.js.map
echo "build finished"
echo "build finished"

View File

@ -12,5 +12,9 @@
"json": false
},
"viewportHeight": 900,
"viewportWidth": 1400
}
"viewportWidth": 1400,
"retries": {
"runMode": 2,
"openMode": 0
}
}

View File

@ -0,0 +1,62 @@
{
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 1224,
"snapColumns": 16,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0,
"bottomRow": 1280,
"containerStyle": "none",
"snapRows": 33,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"dynamicBindings": {},
"version": 6,
"minHeight": 1292,
"parentColumnSpace": 1,
"leftColumn": 0,
"children": [
{
"isVisible": true,
"text": "Label",
"textStyle": "LABEL",
"textAlign": "LEFT",
"widgetName": "Text1",
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 74,
"parentRowSpace": 40,
"leftColumn": 3,
"rightColumn": 7,
"topRow": 4,
"bottomRow": 5,
"parentId": "0",
"widgetId": "pcznwg0g8k"
},
{
"isVisible": true,
"text": "{{Text1.text}}",
"textStyle": "LABEL",
"textAlign": "LEFT",
"widgetName": "Text2",
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 74,
"parentRowSpace": 40,
"leftColumn": 3,
"rightColumn": 7,
"topRow": 6,
"bottomRow": 7,
"parentId": "0",
"widgetId": "tgnz7xg7a3",
"dynamicBindings": {
"text": true
}
}
]
},
"layoutOnLoadActions": []
}

View File

@ -81,59 +81,6 @@
"widgetId": "xlrmeiioaa"
}
],
"blueprint": {
"view": [
{
"type": "TEXT_WIDGET",
"size": {
"rows": 1,
"cols": 12
},
"position": {
"top": 0,
"left": 0
},
"props": {
"text": "Form",
"textStyle": "HEADING"
}
},
{
"type": "FORM_BUTTON_WIDGET",
"size": {
"rows": 1,
"cols": 4
},
"position": {
"top": 11,
"left": 12
},
"props": {
"text": "Submit",
"buttonStyle": "PRIMARY_BUTTON",
"disabledWhenInvalid": true,
"resetFormOnClick": true
}
},
{
"type": "FORM_BUTTON_WIDGET",
"size": {
"rows": 1,
"cols": 4
},
"position": {
"top": 11,
"left": 8
},
"props": {
"text": "Reset",
"buttonStyle": "SECONDARY_BUTTON",
"disabledWhenInvalid": false,
"resetFormOnClick": true
}
}
]
},
"minHeight": 520,
"type": "CANVAS_WIDGET",
"isLoading": false,
@ -147,76 +94,6 @@
"widgetId": "sidaue1kdu"
}
],
"blueprint": {
"view": [
{
"type": "CANVAS_WIDGET",
"position": {
"top": 0,
"left": 0
},
"props": {
"containerStyle": "none",
"canExtend": false,
"detachFromLayout": true,
"children": [],
"blueprint": {
"view": [
{
"type": "TEXT_WIDGET",
"size": {
"rows": 1,
"cols": 12
},
"position": {
"top": 0,
"left": 0
},
"props": {
"text": "Form",
"textStyle": "HEADING"
}
},
{
"type": "FORM_BUTTON_WIDGET",
"size": {
"rows": 1,
"cols": 4
},
"position": {
"top": 11,
"left": 12
},
"props": {
"text": "Submit",
"buttonStyle": "PRIMARY_BUTTON",
"disabledWhenInvalid": true,
"resetFormOnClick": true
}
},
{
"type": "FORM_BUTTON_WIDGET",
"size": {
"rows": 1,
"cols": 4
},
"position": {
"top": 11,
"left": 8
},
"props": {
"text": "Reset",
"buttonStyle": "SECONDARY_BUTTON",
"disabledWhenInvalid": false,
"resetFormOnClick": true
}
}
]
}
}
}
]
},
"type": "FORM_WIDGET",
"isLoading": false,
"parentColumnSpace": 74,
@ -230,4 +107,4 @@
}
]
}
}
}

View File

@ -0,0 +1,19 @@
const dsl = require("../../../fixtures/SimpleBinding.json");
const widgetsPage = require("../../../locators/Widgets.json");
describe("Binding the multiple widgets and validating default data", function() {
before(() => {
cy.addDsl(dsl);
});
it("Checks if delete will remove bindings", function() {
cy.get(widgetsPage.textWidget)
.first()
.click({ force: true });
cy.get("body").type("{del}", { force: true });
cy.get(widgetsPage.textWidget)
.first()
.should("not.have.text", "Label");
});
});

View File

@ -49,6 +49,7 @@ describe("Binding the multiple Widgets and validating NavigateTo Page", function
cy.get(publish.inputGrp)
.first()
.type("123");
cy.get(widgetsPage.chartWidget).should("be.visible");
});
});

View File

@ -20,6 +20,8 @@ describe("Form reset functionality", function() {
.contains("Reset")
.click();
cy.wait(500);
cy.get(".tr")
.eq(2)
.should("not.have.class", "selected-row");

View File

@ -14,7 +14,7 @@
"searchloc": "input[placeholder='Enter location to search']",
"imagecontainer": ".t--draggable-imagewidget span.t--widget-name",
"imageinner": ".t--draggable-imagewidget img",
"chartInnerText": ".t--draggable-chartwidget text",
"chartInnerText": ".t--property-control-title",
"inputChartValue": ".t--property-control-chartdata .CodeMirror textarea",
"chartButton": ".t--property-control-chartdata button",
"rectangleChart": ".t--draggable-chartwidget g rect",

View File

@ -882,8 +882,8 @@ Cypress.Commands.add(
Cypress.Commands.add("widgetText", (text, inputcss, innercss) => {
cy.get(commonlocators.editWidgetName)
.dblclick({ force: true })
.type(text, { force: true })
.click({ force: true })
.type(text)
.type("{enter}");
cy.get(inputcss)
.first()
@ -1174,23 +1174,7 @@ Cypress.Commands.add("getAlert", alertcss => {
.contains("Success")
.click({ force: true });
});
Cypress.Commands.add("widgetText", (text, inputcss, innercss) => {
cy.get(commonlocators.editWidgetName)
.dblclick({ force: true })
.type(text)
.type("{enter}");
cy.get(inputcss)
.first()
.trigger("mouseover", { force: true });
cy.get(innercss).should("have.text", text);
});
Cypress.Commands.add("radioInput", (index, text) => {
cy.get(widgetsPage.RadioInput)
.eq(index)
.click()
.clear()
.type(text);
});
Cypress.Commands.add("tabVerify", (index, text) => {
cy.get(".t--property-control-tabs input")
.eq(index)
@ -1213,26 +1197,6 @@ Cypress.Commands.add("togglebarDisable", value => {
.uncheck({ force: true })
.should("not.checked");
});
Cypress.Commands.add("radiovalue", (value, value2) => {
cy.get(value)
.click()
.clear()
.type(value2);
});
Cypress.Commands.add("optionValue", (value, value2) => {
cy.get(value)
.click()
.clear()
.type(value2);
});
Cypress.Commands.add("dropdownDynamic", text => {
cy.wait(2000);
cy.get("ul[class='bp3-menu']")
.first()
.contains(text)
.click({ force: true })
.should("have.text", text);
});
Cypress.Commands.add("getAlert", alertcss => {
cy.get(commonlocators.dropdownSelectButton).click({ force: true });
@ -1251,16 +1215,7 @@ Cypress.Commands.add("getAlert", alertcss => {
.contains("Success")
.click({ force: true });
});
Cypress.Commands.add("widgetText", (text, inputcss, innercss) => {
cy.get(commonlocators.editWidgetName)
.dblclick({ force: true })
.type(text)
.type("{enter}");
cy.get(inputcss)
.first()
.trigger("mouseover", { force: true });
cy.get(innercss).should("have.text", text);
});
Cypress.Commands.add("radioInput", (index, text) => {
cy.get(widgetsPage.RadioInput)
.eq(index)
@ -1648,8 +1603,6 @@ Cypress.Commands.add("startServerAndRoutes", () => {
cy.route("DELETE", "/api/v1/applications/*").as("deleteApp");
cy.route("DELETE", "/api/v1/actions/*").as("deleteAction");
cy.route("DELETE", "/api/v1/pages/*").as("deletePage");
cy.route("GET", "/api/v1/plugins/*/form").as("getPluginForm");
cy.route("POST", "/api/v1/datasources").as("createDatasource");
cy.route("POST", "/api/v1/datasources/test").as("testDatasource");
cy.route("PUT", "/api/v1/datasources/*").as("saveDatasource");

View File

@ -117,14 +117,14 @@
"typescript": "^3.9.2",
"unescape-js": "^1.1.4",
"url-search-params-polyfill": "^8.0.0",
"workerize-loader": "^1.2.0"
"worker-loader": "^3.0.2"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "REACT_APP_BASE_URL=https://release-api.appsmith.com REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=dev.appsmith.com craco start",
"start": "EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=dev.appsmith.com craco start",
"build": "./build.sh",
"build-local": "craco --max-old-space-size=4096 build --config craco.build.config.js",
"build-staging": " REACT_APP_ENVIRONMENT=STAGING craco --max-old-space-size=4096 build --config craco.build.config.js",
"build-staging": "REACT_APP_ENVIRONMENT=STAGING craco --max-old-space-size=4096 build --config craco.build.config.js",
"test": "CYPRESS_BASE_URL=https://dev.appsmith.com cypress/test.sh",
"test:ci": "CYPRESS_BASE_URL=https://dev.appsmith.com cypress/test.sh --env=ci",
"eject": "react-scripts eject",
@ -209,4 +209,4 @@
"pre-commit": "lint-staged"
}
}
}
}

View File

@ -140,7 +140,6 @@
};
</script>
<script rel="prefetch" type="text/javascript" src="/shims/realms-shim.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.4.0/tinymce.min.js" referrerpolicy="origin"></script>
</body>

File diff suppressed because one or more lines are too long

View File

@ -6,3 +6,8 @@ export const batchAction = (action: ReduxAction<any>) => ({
});
export type BatchAction<T> = ReduxAction<ReduxAction<T>>;
export const batchActionSuccess = (actions: ReduxAction<any>[]) => ({
type: ReduxActionTypes.BATCH_UPDATES_SUCCESS,
payload: actions,
});

View File

@ -32,6 +32,14 @@ export const fetchPage = (pageId: string): ReduxAction<FetchPageRequest> => {
};
};
export const fetchPublishedPage = (pageId: string, bustCache = false) => ({
type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT,
payload: {
pageId,
bustCache,
},
});
export const fetchPageSuccess = () => {
return {
type: ReduxActionTypes.FETCH_PAGE_SUCCESS,

View File

@ -1,4 +1,3 @@
import _ from "lodash";
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import {
REQUEST_TIMEOUT_MS,

View File

@ -104,7 +104,7 @@ const Hit = (props: { hit: { path: string } }) => {
const DefaultHelpMenuItem = (props: {
item: { label: string; link?: string; id?: string; icon: React.ReactNode };
onSelect: Function;
onSelect: () => void;
}) => {
return (
<li className="ais-Hits-item">

View File

@ -1,14 +1,7 @@
import { bindingHint } from "components/editorComponents/CodeEditor/hintHelpers";
import { MockCodemirrorEditor } from "../../../../test/__mocks__/CodeMirrorEditorMock";
import RealmExecutor from "jsExecution/RealmExecutor";
jest.mock("jsExecution/RealmExecutor");
describe("hint helpers", () => {
beforeAll(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
RealmExecutor.mockClear();
});
describe("binding hint helper", () => {
it("is initialized correctly", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore

View File

@ -83,6 +83,10 @@ const ToastComponent = (props: Props) => {
const Toaster = {
show: (config: Props) => {
if (typeof config.message !== "string") {
console.error("Toast message needs to be a string");
return;
}
toast(
<ToastComponent
{...config}

View File

@ -24,14 +24,13 @@ import {
const Wrapper = styled.div`
.dynamic-text-field {
border-radius: 4px;
border: none;
font-size: 14px;
height: calc(100vh / 4);
}
&& {
.CodeMirror-lines {
padding: 16px 20px;
padding: 10px;
}
}
`;

View File

@ -12,6 +12,7 @@ const FIELD_VALUES: Record<
},
CANVAS_WIDGET: {},
ICON_WIDGET: {},
SKELETON_WIDGET: {},
CONTAINER_WIDGET: {
backgroundColor: "string",
isVisible: "boolean",

View File

@ -71,6 +71,10 @@ export const HelpMap = {
path: "",
searchKey: "",
},
SKELETON_WIDGET: {
path: "",
searchKey: "",
},
TABS_WIDGET: {
path: "",
searchKey: "Tabs",

View File

@ -285,6 +285,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
UNDO_DELETE_WIDGET: "UNDO_DELETE_WIDGET",
CUT_SELECTED_WIDGET: "CUT_SELECTED_WIDGET",
WIDGET_ADD_CHILDREN: "WIDGET_ADD_CHILDREN",
SET_EVALUATED_TREE: "SET_EVALUATED_TREE",
BATCH_UPDATES_SUCCESS: "BATCH_UPDATES_SUCCESS",
};
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];

View File

@ -20,6 +20,7 @@ export enum WidgetTypes {
ICON_WIDGET = "ICON_WIDGET",
FILE_PICKER_WIDGET = "FILE_PICKER_WIDGET",
VIDEO_WIDGET = "VIDEO_WIDGET",
SKELETON_WIDGET = "SKELETON_WIDGET",
}
export type WidgetType = keyof typeof WidgetTypes;

View File

@ -18,6 +18,7 @@ export const VALIDATION_TYPES = {
ACTION_SELECTOR: "ACTION_SELECTOR",
ARRAY_ACTION_SELECTOR: "ARRAY_ACTION_SELECTOR",
SELECTED_TAB: "SELECTED_TAB",
DEFAULT_OPTION_VALUE: "DEFAULT_OPTION_VALUE",
};
export type ValidationResponse = {

View File

@ -17,7 +17,7 @@ export type ActionDescription<T> = {
payload: T;
};
type ActionDispatcher<T, A extends string[]> = (
export type ActionDispatcher<T, A extends string[]> = (
...args: A
) => ActionDescription<T>;
@ -76,60 +76,39 @@ type DataTreeSeed = {
};
export class DataTreeFactory {
static create(
{ actions, widgets, widgetsMeta, pageList, appData }: DataTreeSeed,
// TODO(hetu)
// temporary fix for not getting functions while normal evals which crashes the app
// need to remove this after we get a proper solve
withFunctions?: boolean,
): DataTree {
static create({
actions,
widgets,
widgetsMeta,
pageList,
appData,
}: DataTreeSeed): DataTree {
const dataTree: DataTree = {};
const actionPaths = [];
actions.forEach(a => {
const config = a.config;
actions.forEach(action => {
let dynamicBindingPathList: Property[] = [];
// update paths
if (
config.dynamicBindingPathList &&
config.dynamicBindingPathList.length
action.config.dynamicBindingPathList &&
action.config.dynamicBindingPathList.length
) {
dynamicBindingPathList = config.dynamicBindingPathList.map(d => ({
...d,
key: `config.${d.key}`,
}));
dynamicBindingPathList = action.config.dynamicBindingPathList.map(
d => ({
...d,
key: `config.${d.key}`,
}),
);
}
dataTree[config.name] = {
...a,
actionId: config.id,
name: config.name,
pluginType: config.pluginType,
config: config.actionConfiguration,
dataTree[action.config.name] = {
run: {},
actionId: action.config.id,
name: action.config.name,
pluginType: action.config.pluginType,
config: action.config.actionConfiguration,
dynamicBindingPathList,
data: a.data ? a.data.body : {},
run: withFunctions
? function(
this: DataTreeAction,
onSuccess: string,
onError: string,
params = "",
) {
return {
type: "RUN_ACTION",
payload: {
actionId: this.actionId,
onSuccess: onSuccess ? `{{${onSuccess.toString()}}}` : "",
onError: onError ? `{{${onError.toString()}}}` : "",
params,
},
};
}
: {},
data: action.data ? action.data.body : {},
ENTITY_TYPE: ENTITY_TYPE.ACTION,
isLoading: action.isLoading,
};
if (withFunctions) {
actionPaths.push(`${config.name}.run`);
}
dataTree.actionPaths && dataTree.actionPaths.push();
});
Object.keys(widgets).forEach(w => {
const widget = { ...widgets[w] };
@ -165,63 +144,7 @@ export class DataTreeFactory {
};
});
if (withFunctions) {
dataTree.navigateTo = function(
pageNameOrUrl: string,
params: Record<string, unknown>,
) {
return {
type: "NAVIGATE_TO",
payload: { pageNameOrUrl, params },
};
};
actionPaths.push("navigateTo");
dataTree.showAlert = function(message: string, style: string) {
return {
type: "SHOW_ALERT",
payload: { message, style },
};
};
actionPaths.push("showAlert");
// dataTree.url = url;
dataTree.showModal = function(modalName: string) {
return {
type: "SHOW_MODAL_BY_NAME",
payload: { modalName },
};
};
actionPaths.push("showModal");
dataTree.closeModal = function(modalName: string) {
return {
type: "CLOSE_MODAL",
payload: { modalName },
};
};
actionPaths.push("closeModal");
dataTree.storeValue = function(key: string, value: string) {
return {
type: "STORE_VALUE",
payload: { key, value },
};
};
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;
dataTree.actionPaths = actionPaths;
dataTree.appsmith = { ...appData } as DataTreeAppsmith;
(dataTree.appsmith as DataTreeAppsmith).ENTITY_TYPE = ENTITY_TYPE.APPSMITH;
return dataTree;

View File

@ -1,107 +0,0 @@
import RealmExecutor from "./RealmExecutor";
import moment from "moment-timezone";
import { ActionDescription } from "entities/DataTree/dataTreeFactory";
import { btoa, atob, version as BASE64LIBVERSION } from "js-base64";
import { VERSION as lodashVersion } from "lodash";
export type JSExecutorGlobal = Record<string, object>;
export type JSExecutorResult = {
result: any;
triggers?: ActionDescription<any>[];
};
export interface JSExecutor {
execute: (
src: string,
data: JSExecutorGlobal,
callbackData?: any,
) => JSExecutorResult;
registerLibrary: (accessor: string, lib: any) => void;
unRegisterLibrary: (accessor: string) => void;
}
enum JSExecutorType {
REALM,
}
export type ExtraLibrary = {
version: string;
docsURL: string;
displayName: string;
accessor: string;
lib: any;
};
export const extraLibraries: ExtraLibrary[] = [
{
accessor: "_",
lib: window._,
version: lodashVersion,
docsURL: `https://lodash.com/docs/${lodashVersion}`,
displayName: "lodash",
},
{
accessor: "moment",
lib: moment,
version: moment.version,
docsURL: `https://momentjs.com/docs/`,
displayName: "moment",
},
{
accessor: "btoa",
lib: btoa,
version: BASE64LIBVERSION,
docsURL: "https://github.com/dankogai/js-base64#readme",
displayName: "btoa",
},
{
accessor: "atob",
lib: atob,
version: BASE64LIBVERSION,
docsURL: "https://github.com/dankogai/js-base64#readme",
displayName: "atob",
},
];
class JSExecutionManager {
currentExecutor: JSExecutor;
executors: Record<JSExecutorType, JSExecutor>;
registerLibrary(accessor: string, lib: any) {
Object.keys(this.executors).forEach(type => {
const executor = this.executors[(type as any) as JSExecutorType];
executor.registerLibrary(accessor, lib);
});
}
unRegisterLibrary(accessor: string) {
Object.keys(this.executors).forEach(type => {
const executor = this.executors[(type as any) as JSExecutorType];
executor.unRegisterLibrary(accessor);
});
}
switchExecutor(type: JSExecutorType) {
const executor = this.executors[type];
if (!executor) {
throw new Error("Executor does not exist");
}
this.currentExecutor = executor;
}
constructor() {
const realmExecutor = new RealmExecutor();
this.executors = {
[JSExecutorType.REALM]: realmExecutor,
};
this.currentExecutor = realmExecutor;
extraLibraries.forEach(config => {
this.registerLibrary(config.accessor, config.lib);
});
}
evaluateSync(
jsSrc: string,
data: JSExecutorGlobal,
callbackData?: any,
): JSExecutorResult {
return this.currentExecutor.execute(jsSrc, data, callbackData);
}
}
const JSExecutionManagerSingleton = new JSExecutionManager();
export default JSExecutionManagerSingleton;

View File

@ -1,112 +0,0 @@
import {
JSExecutorGlobal,
JSExecutor,
JSExecutorResult,
} from "./JSExecutionManagerSingleton";
import JSONFn from "json-fn";
import log from "loglevel";
declare let Realm: any;
export default class RealmExecutor implements JSExecutor {
rootRealm: any;
createSafeObject: any;
extrinsics: any[] = [];
createSafeFunction: (unsafeFn: Function) => Function;
libraries: Record<string, any> = {};
constructor() {
this.rootRealm = Realm.makeRootRealm();
this.registerLibrary("JSONFn", JSONFn);
this.createSafeFunction = this.rootRealm.evaluate(`
(function createSafeFunction(unsafeFn) {
return function safeFn(...args) {
return unsafeFn(...args);
}
})
`);
// After parsing the data we add a triggers list on the global scope to
// push to it during any script execution
// We replace all action descriptor functions with our pusher function
// which has reference to the triggers via binding
this.createSafeObject = this.rootRealm.evaluate(
`
(function createSafeObject(unsafeObject) {
const safeObject = JSONFn.parse(JSONFn.stringify(unsafeObject));
if(safeObject.actionPaths) {
safeObject.triggers = [];
const pusher = function (action, ...payload) {
const actionPayload = action(...payload);
this.triggers.push(actionPayload);
}
safeObject.actionPaths.forEach(path => {
const action = _.get(safeObject, path);
const entity = _.get(safeObject, path.split(".")[0])
if(action) {
_.set(safeObject, path, pusher.bind(safeObject, action.bind(entity)))
}
})
}
return safeObject
})
`,
);
}
registerLibrary(accessor: string, lib: any) {
this.rootRealm.global[accessor] = lib;
}
unRegisterLibrary(accessor: string) {
this.rootRealm.global[accessor] = null;
}
private convertToMainScope(result: any) {
if (typeof result === "object") {
if (Array.isArray(result)) {
return Object.assign([], result);
}
return Object.assign({}, result);
}
return result;
}
execute(
sourceText: string,
data: JSExecutorGlobal,
callbackData?: any,
): JSExecutorResult {
const safeCallbackData = this.createSafeObject(callbackData || {});
const safeData = this.createSafeObject(data);
try {
// We create a closed function and evaluate that
// This is to send any triggers received during evaluations
// triggers should already be defined in the safeData
const scriptToEvaluate = `
function closedFunction () {
const result = ${sourceText};
return { result, triggers }
}
closedFunction()
`;
const scriptWithCallback = `
function callback (script) {
const userFunction = script;
const result = userFunction(CALLBACK_DATA);
return { result, triggers };
}
callback(${sourceText});
`;
const script = callbackData ? scriptWithCallback : scriptToEvaluate;
const data = callbackData
? { ...safeData, CALLBACK_DATA: safeCallbackData }
: safeData;
const { result, triggers } = this.rootRealm.evaluate(script, data);
return {
result: this.convertToMainScope(result),
triggers,
};
} catch (e) {
log.debug(`Error: "${e.message}" when evaluating {{${sourceText}}}`);
log.debug(e);
return { result: undefined, triggers: [] };
}
}
}

View File

@ -434,6 +434,12 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
mapCenter: { lat: -34.397, long: 150.644 },
defaultMarkers: [{ lat: -34.397, long: 150.644, title: "Test A" }],
},
SKELETON_WIDGET: {
isLoading: true,
rows: 1,
columns: 1,
widgetName: "Skeleton",
},
},
configVersion: 1,
};

View File

@ -1,7 +1,6 @@
import React, { Component } from "react";
import { RouteComponentProps, Link } from "react-router-dom";
import { connect } from "react-redux";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import { getIsFetchingPage } from "selectors/appViewSelectors";
import styled from "styled-components";
import { ContainerWidgetProps } from "widgets/ContainerWidget";
@ -18,6 +17,11 @@ import {
} from "selectors/editorSelectors";
import ConfirmRunModal from "pages/Editor/ConfirmRunModal";
import { getCurrentApplication } from "selectors/applicationSelectors";
import {
isPermitted,
PERMISSION_TYPE,
} from "../Applications/permissionHelpers";
import { fetchPublishedPage } from "actions/pageActions";
const Section = styled.section`
background: ${props => props.theme.colors.bodyBG};
@ -33,15 +37,10 @@ type AppViewerPageContainerProps = {
currentPageName?: string;
currentAppName?: string;
fetchPage: (pageId: string, bustCache?: boolean) => void;
currentAppPermissions?: string[];
} & RouteComponentProps<AppViewerRouteParams>;
class AppViewerPageContainer extends Component<AppViewerPageContainerProps> {
componentDidMount() {
const { pageId } = this.props.match.params;
if (pageId) {
this.props.fetchPage(pageId, true);
}
}
componentDidUpdate(previously: AppViewerPageContainerProps) {
const { pageId } = this.props.match.params;
if (
@ -52,6 +51,28 @@ class AppViewerPageContainer extends Component<AppViewerPageContainerProps> {
}
}
render() {
let appsmithEditorLink;
if (
this.props.currentAppPermissions &&
isPermitted(
this.props.currentAppPermissions,
PERMISSION_TYPE.MANAGE_APPLICATION,
)
) {
appsmithEditorLink = (
<p>
Please add widgets to this page in the&nbsp;
<Link
to={BUILDER_PAGE_URL(
this.props.match.params.applicationId,
this.props.match.params.pageId,
)}
>
Appsmith Editor
</Link>
</p>
);
}
const pageNotFound = (
<Centered>
<NonIdealState
@ -63,19 +84,7 @@ class AppViewerPageContainer extends Component<AppViewerPageContainerProps> {
/>
}
title="This page seems to be blank"
description={
<p>
Please add widgets to this page in the&nbsp;
<Link
to={BUILDER_PAGE_URL(
this.props.match.params.applicationId,
this.props.match.params.pageId,
)}
>
Appsmith Editor
</Link>
</p>
}
description={appsmithEditorLink}
/>
</Centered>
);
@ -118,19 +127,14 @@ const mapStateToProps = (state: AppState) => {
widgets: getCanvasWidgetDsl(state),
currentPageName: getCurrentPageName(state),
currentAppName: currentApp?.name,
currentAppPermissions: currentApp?.userPermissions,
};
return props;
};
const mapDispatchToProps = (dispatch: any) => ({
fetchPage: (pageId: string, bustCache = false) =>
dispatch({
type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_INIT,
payload: {
pageId,
bustCache,
},
}),
dispatch(fetchPublishedPage(pageId, bustCache)),
});
export default connect(

View File

@ -80,9 +80,69 @@ const OrgSection = styled.div``;
const PaddingWrapper = styled.div`
width: ${props => props.theme.card.minWidth + props.theme.spaces[5] * 2}px;
margin: ${props => props.theme.spaces[6] + 1}px
${props => props.theme.spaces[12] + 2}px
margin: ${props => props.theme.spaces[6] + 1}px 0px
${props => props.theme.spaces[6] + 1}px 0px;
@media screen and (min-width: 1500px) {
margin-right: ${props => props.theme.spaces[12] - 1}px;
.bp3-card {
width: ${props => props.theme.card.minWidth}px;
height: ${props => props.theme.card.minHeight}px;
}
}
@media screen and (min-width: 1500px) and (max-width: 1512px) {
width: ${props => props.theme.card.minWidth + props.theme.spaces[4] * 2}px;
margin-right: ${props => props.theme.spaces[12] - 1}px;
.bp3-card {
width: ${props => props.theme.card.minWidth - 5}px;
height: ${props => props.theme.card.minHeight - 5}px;
}
}
@media screen and (min-width: 1478px) and (max-width: 1500px) {
width: ${props => props.theme.card.minWidth + props.theme.spaces[4] * 2}px;
margin-right: ${props => props.theme.spaces[11] + 1}px;
.bp3-card {
width: ${props => props.theme.card.minWidth - 8}px;
height: ${props => props.theme.card.minHeight - 8}px;
}
}
@media screen and (min-width: 1447px) and (max-width: 1477px) {
width: ${props => props.theme.card.minWidth + props.theme.spaces[3] * 2}px;
margin-right: ${props => props.theme.spaces[11] - 4}px;
.bp3-card {
width: ${props => props.theme.card.minWidth - 8}px;
height: ${props => props.theme.card.minHeight - 8}px;
}
}
@media screen and (min-width: 1417px) and (max-width: 1446px) {
width: ${props => props.theme.card.minWidth + props.theme.spaces[3] * 2}px;
margin-right: ${props => props.theme.spaces[11] - 8}px;
.bp3-card {
width: ${props => props.theme.card.minWidth - 11}px;
height: ${props => props.theme.card.minHeight - 11}px;
}
}
@media screen and (min-width: 1400px) and (max-width: 1417px) {
width: ${props => props.theme.card.minWidth + props.theme.spaces[2] * 2}px;
margin-right: ${props => props.theme.spaces[11] - 12}px;
.bp3-card {
width: ${props => props.theme.card.minWidth - 15}px;
height: ${props => props.theme.card.minHeight - 15}px;
}
}
@media screen and (max-width: 1400px) {
width: ${props => props.theme.card.minWidth + props.theme.spaces[2] * 2}px;
margin-right: ${props => props.theme.spaces[11] - 16}px;
.bp3-card {
width: ${props => props.theme.card.minWidth - 15}px;
height: ${props => props.theme.card.minHeight - 15}px;
}
}
`;
const StyledDialog = styled(Dialog)<{ setMaxWidth?: boolean }>`

View File

@ -9,7 +9,7 @@ import {
DataTree,
} from "entities/DataTree/dataTreeFactory";
import { useSelector } from "react-redux";
import { evaluateDataTreeWithoutFunctions } from "selectors/dataTreeSelectors";
import { getDataTree } from "selectors/dataTreeSelectors";
import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
@ -31,7 +31,7 @@ export const EntityProperties = (props: {
);
});
let entity: any;
const dataTree: DataTree = useSelector(evaluateDataTreeWithoutFunctions);
const dataTree: DataTree = useSelector(getDataTree);
if (props.isCurrentPage && dataTree[props.entityName]) {
entity = dataTree[props.entityName];
} else if (props.entity) {
@ -71,7 +71,7 @@ export const EntityProperties = (props: {
case ENTITY_TYPE.WIDGET:
const type: Exclude<
Partial<WidgetType>,
"CANVAS_WIDGET" | "ICON_WIDGET"
"CANVAS_WIDGET" | "ICON_WIDGET" | "SKELETON_WIDGET"
> = entity.type;
config = entityDefinitions[type];

View File

@ -1,10 +1,10 @@
import React, { useState } from "react";
import styled from "styled-components";
import { extraLibraries } from "jsExecution/JSExecutionManagerSingleton";
import { Collapse, Icon, IconName, Tooltip } from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
import { Colors } from "constants/Colors";
import { BindingText } from "pages/Editor/APIEditor/Form";
import { extraLibraries } from "utils/DynamicBindingUtils";
const Wrapper = styled.div`
font-size: 13px;

View File

@ -93,7 +93,7 @@ export const getWidgetProperies = (
entityDefinitions[
widgetProps.type as Exclude<
Partial<WidgetType>,
"CANVAS_WIDGET" | "ICON_WIDGET"
"CANVAS_WIDGET" | "ICON_WIDGET" | "SKELETON_WIDGET"
>
];

View File

@ -219,6 +219,7 @@ const TabContainerView = styled.div`
.react-tabs__tab-panel {
border: 1px solid #ebeff2;
overflow: scroll;
}
.react-tabs__tab-list {
margin: 0px;
@ -226,9 +227,7 @@ const TabContainerView = styled.div`
`;
const SettingsWrapper = styled.div`
padding-left: 15px;
padding-top: 8px;
padding-bottom: 8px;
padding: 5px 10px;
`;
const AddWidgetButton = styled(BaseButton)`
@ -246,6 +245,10 @@ const OutputHeader = styled.div`
align-items: center;
`;
const FieldWrapper = styled.div`
margin-top: 15px;
`;
type QueryFormProps = {
onDeleteClick: () => void;
onRunClick: () => void;
@ -484,23 +487,28 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
{
key: "query",
title: "Query",
panelComponent:
editorConfig && editorConfig.length > 0 ? (
editorConfig.map(renderEachConfig)
) : (
<>
<ErrorMessage>An unexpected error occurred</ErrorMessage>
<Tag
round
intent="warning"
interactive
minimal
onClick={() => window.location.reload()}
>
Refresh
</Tag>
</>
),
panelComponent: (
<SettingsWrapper>
{editorConfig && editorConfig.length > 0 ? (
editorConfig.map(renderEachConfig)
) : (
<>
<ErrorMessage>
An unexpected error occurred
</ErrorMessage>
<Tag
round
intent="warning"
interactive
minimal
onClick={() => window.location.reload()}
>
Refresh
</Tag>
</>
)}
</SettingsWrapper>
),
},
{
key: "settings",
@ -573,13 +581,13 @@ const renderEachConfig = (section: any): any => {
try {
const { configProperty } = propertyControlOrSection;
return (
<div key={configProperty} style={{ marginTop: "8px" }}>
<FieldWrapper key={configProperty}>
{FormControlFactory.createControl(
{ ...propertyControlOrSection },
{},
false,
)}
</div>
</FieldWrapper>
);
} catch (e) {
console.log(e);

View File

@ -2,7 +2,7 @@ import { createReducer } from "utils/AppsmithUtils";
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
import { UpdateWidgetMetaPropertyPayload } from "actions/metaActions";
export type MetaState = Record<string, object>;
export type MetaState = Record<string, Record<string, unknown>>;
const initialState: MetaState = {};

View File

@ -26,6 +26,7 @@ import { MapWidgetProps } from "widgets/MapWidget";
import { ModalWidgetProps } from "widgets/ModalWidget";
import { IconWidgetProps } from "widgets/IconWidget";
import { VideoWidgetProps } from "widgets/VideoWidget";
import { SkeletonWidgetProps } from "../../widgets/SkeletonWidget";
const initialState: WidgetConfigReducerState = WidgetConfigResponse;
@ -74,6 +75,7 @@ export interface WidgetConfigReducerState {
CANVAS_WIDGET: Partial<ContainerWidgetProps<WidgetProps>> &
WidgetConfigProps;
ICON_WIDGET: Partial<IconWidgetProps> & WidgetConfigProps;
SKELETON_WIDGET: Partial<SkeletonWidgetProps> & WidgetConfigProps;
};
configVersion: number;
}

View File

@ -0,0 +1,21 @@
import { createReducer } from "utils/AppsmithUtils";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
export type EvaluationDependencyState = {
dependencyMap: Record<string, Array<string>>;
dependencyTree: Array<[string, string]>;
};
const initialState: EvaluationDependencyState = {
dependencyMap: {},
dependencyTree: [],
};
const evaluationDependencyReducer = createReducer(initialState, {
[ReduxActionTypes.SET_EVALUATION_DEPENDENCIES]: (
state: EvaluationDependencyState,
action: ReduxAction<EvaluationDependencyState>,
) => action.payload,
});
export default evaluationDependencyReducer;

View File

@ -0,0 +1,8 @@
import { combineReducers } from "redux";
import evaluatedTreeReducer from "./treeReducer";
import evaluationDependencyReducer from "./dependencyReducer";
export default combineReducers({
tree: evaluatedTreeReducer,
dependencies: evaluationDependencyReducer,
});

View File

@ -0,0 +1,16 @@
import { createReducer } from "utils/AppsmithUtils";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
export type EvaluatedTreeState = DataTree;
const initialState: EvaluatedTreeState = {};
const evaluatedTreeReducer = createReducer(initialState, {
[ReduxActionTypes.SET_EVALUATED_TREE]: (
state: EvaluatedTreeState,
action: ReduxAction<DataTree>,
) => action.payload,
});
export default evaluatedTreeReducer;

View File

@ -1,6 +1,7 @@
import { combineReducers } from "redux";
import entityReducer from "./entityReducers";
import uiReducer from "./uiReducers";
import evaluationsReducer from "./evalutationReducers";
import { reducer as formReducer } from "redux-form";
import { CanvasWidgetsReduxState } from "./entityReducers/canvasWidgetsReducer";
import { EditorReduxState } from "./uiReducers/editorReducer";
@ -34,10 +35,13 @@ import { PageDSLsReduxState } from "./uiReducers/pageDSLReducer";
import { ConfirmRunActionReduxState } from "./uiReducers/confirmRunActionReducer";
import { AppDataState } from "reducers/entityReducers/appReducer";
import { DatasourceNameReduxState } from "./uiReducers/datasourceNameReducer";
import { EvaluatedTreeState } from "./evalutationReducers/treeReducer";
import { EvaluationDependencyState } from "./evalutationReducers/dependencyReducer";
const appReducer = combineReducers({
entities: entityReducer,
ui: uiReducer,
evaluations: evaluationsReducer,
form: formReducer,
});
@ -80,4 +84,8 @@ export interface AppState {
meta: MetaState;
app: AppDataState;
};
evaluations: {
tree: EvaluatedTreeState;
dependencies: EvaluationDependencyState;
};
}

View File

@ -22,15 +22,7 @@ import {
takeEvery,
takeLatest,
} from "redux-saga/effects";
import {
evaluateDataTreeWithFunctions,
evaluateDataTreeWithoutFunctions,
} from "selectors/dataTreeSelectors";
import {
getDynamicBindings,
getDynamicValue,
isDynamicValue,
} from "utils/DynamicBindingUtils";
import { getDynamicBindings, isDynamicValue } from "utils/DynamicBindingUtils";
import {
ActionDescription,
RunActionPayload,
@ -52,8 +44,8 @@ import {
import {
executeApiActionRequest,
executeApiActionSuccess,
updateAction,
showRunActionConfirmModal,
updateAction,
} from "actions/actionActions";
import { Action, RestAction } from "entities/Action";
import ActionAPI, {
@ -83,6 +75,7 @@ import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
import { getCurrentApplication } from "selectors/applicationSelectors";
import { evaluateDynamicTrigger, evaluateSingleValue } from "./evaluationsSaga";
function* navigateActionSaga(
action: { pageNameOrUrl: string; params: Record<string, string> },
@ -204,10 +197,7 @@ const isErrorResponse = (response: ActionApiResponse) => {
};
export function* evaluateDynamicBoundValueSaga(path: string): any {
log.debug("Evaluating data tree to get action binding value");
const tree = yield select(evaluateDataTreeWithoutFunctions);
const dynamicResult = getDynamicValue(`{{${path}}}`, tree);
return dynamicResult.result;
return yield call(evaluateSingleValue, `{{${path}}}`);
}
const EXECUTION_PARAM_PATH = "this.params";
@ -483,15 +473,20 @@ function* executeActionTriggers(
function* executeAppAction(action: ReduxAction<ExecuteActionPayload>) {
const { dynamicString, event, responseData } = action.payload;
log.debug("Evaluating data tree to get action trigger");
log.debug({ dynamicString });
const tree = yield select(evaluateDataTreeWithFunctions);
log.debug({ tree });
const { triggers } = getDynamicValue(dynamicString, tree, responseData, true);
log.debug({ dynamicString, responseData });
const triggers = yield call(
evaluateDynamicTrigger,
dynamicString,
responseData,
);
log.debug({ triggers });
if (triggers && triggers.length) {
yield all(
triggers.map(trigger => call(executeActionTriggers, trigger, event)),
triggers.map((trigger: ActionDescription<any>) =>
call(executeActionTriggers, trigger, event),
),
);
} else {
if (event.callback) event.callback({ success: true });

View File

@ -2,6 +2,7 @@
import _ from "lodash";
import { put, debounce, takeEvery, all } from "redux-saga/effects";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import { batchActionSuccess } from "../actions/batchActions";
const BATCH_PRIORITY = {
[ReduxActionTypes.SET_META_PROP]: {
@ -62,6 +63,7 @@ function* executeBatchSaga() {
yield put(sagaAction);
}
}
yield put(batchActionSuccess(batch));
}
}
}

View File

@ -11,6 +11,7 @@ import { fetchEditorConfigs } from "actions/configsActions";
import {
fetchPage,
fetchPageList,
fetchPublishedPage,
setAppMode,
updateAppStore,
} from "actions/pageActions";
@ -26,6 +27,7 @@ import { validateResponse } from "./ErrorSagas";
import { extractCurrentDSL } from "utils/WidgetPropsUtils";
import { APP_MODE } from "reducers/entityReducers/appReducer";
import { getAppStoreName } from "constants/AppConstants";
import { getDefaultPageId } from "./selectors";
const getAppStore = (appId: string) => {
const appStoreName = getAppStoreName(appId);
@ -146,7 +148,7 @@ export function* populatePageDSLsSaga() {
}
export function* initializeAppViewerSaga(
action: ReduxAction<{ pageId: string; applicationId: string }>,
action: ReduxAction<{ applicationId: string }>,
) {
const { applicationId } = action.payload;
yield all([
@ -160,16 +162,23 @@ export function* initializeAppViewerSaga(
take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS),
]);
yield put(setAppMode(APP_MODE.PUBLISHED));
yield put(updateAppStore(getAppStore(applicationId)));
const pageId = yield select(getDefaultPageId);
if (pageId) {
yield put(fetchPublishedPage(pageId, true));
yield take(ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS);
yield put(setAppMode(APP_MODE.PUBLISHED));
yield put(updateAppStore(getAppStore(applicationId)));
yield put({
type: ReduxActionTypes.INITIALIZE_PAGE_VIEWER_SUCCESS,
});
if ("serviceWorker" in navigator) {
yield put({
type: ReduxActionTypes.FETCH_ALL_PUBLISHED_PAGES,
type: ReduxActionTypes.INITIALIZE_PAGE_VIEWER_SUCCESS,
});
if ("serviceWorker" in navigator) {
yield put({
type: ReduxActionTypes.FETCH_ALL_PUBLISHED_PAGES,
});
}
}
}

View File

@ -69,7 +69,7 @@ import {
fetchActionsForPage,
setActionsToExecuteOnPageLoad,
} from "actions/actionActions";
import { clearCaches } from "utils/DynamicBindingUtils";
import { clearEvalCache } from "./evaluationsSaga";
import { UrlDataState } from "reducers/entityReducers/appReducer";
import { getQueryParams } from "utils/AppsmithUtils";
import PerformanceTracker, {
@ -162,7 +162,7 @@ export function* fetchPageSaga(
const isValidResponse = yield validateResponse(fetchPageResponse);
if (isValidResponse) {
// Clear any existing caches
clearCaches();
yield call(clearEvalCache);
// Set url params
yield call(setDataUrl);
// Get Canvas payload
@ -225,7 +225,7 @@ export function* fetchPublishedPageSaga(
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
// Clear any existing caches
clearCaches();
yield call(clearEvalCache);
// Set url params
yield call(setDataUrl);
// Get Canvas payload

View File

@ -1,4 +1,5 @@
import { ApplicationPagePayload } from "api/ApplicationApi";
export const getDefaultPageId = (
pages?: ApplicationPagePayload[],
): string | undefined => {

View File

@ -57,7 +57,6 @@ import {
RenderModes,
WidgetType,
} from "constants/WidgetConstants";
import ValidationFactory from "utils/ValidationFactory";
import WidgetConfigResponse from "mockResponses/WidgetConfigResponse";
import {
saveCopiedWidgets,
@ -78,6 +77,9 @@ import {
getCurrentPageId,
} from "selectors/editorSelectors";
import { forceOpenPropertyPane } from "actions/widgetActions";
import { getDataTree } from "selectors/dataTreeSelectors";
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import { validateProperty } from "./evaluationsSaga";
function getChildWidgetProps(
parent: FlattenedWidgetProps,
@ -160,6 +162,7 @@ function* generateChildWidgets(
);
}
widget.parentId = parent.widgetId;
delete widget.blueprint;
return { widgetId: widget.widgetId, widgets };
}
@ -631,7 +634,9 @@ function* setWidgetDynamicPropertySaga(
yield put(updateWidgetProperty(widgetId, propertyName, value));
} else {
delete dynamicProperties[propertyName];
const { parsed } = ValidationFactory.validateWidgetProperty(
// TODO (hetu) can we eliminate this use of validation
const { parsed } = yield call(
validateProperty,
widget.type,
propertyName,
propertyValue,
@ -668,6 +673,39 @@ function* resetChildrenMetaSaga(action: ReduxAction<{ widgetId: string }>) {
const childId = childrenIds[childIndex];
yield put(resetWidgetMetaProperty(childId));
}
yield call(resetEvaluatedWidgetMetaProperties, childrenIds);
}
// This is needed because evaluation takes some time and we can reset the props
// in the evaluated value much faster like this
function* resetEvaluatedWidgetMetaProperties(widgetIds: string[]) {
const evaluatedDataTree = yield select(getDataTree);
const updates: Record<string, DataTreeWidget> = {};
for (const index in widgetIds) {
const widgetId = widgetIds[index];
const widget = _.find(evaluatedDataTree, { widgetId }) as DataTreeWidget;
const widgetToUpdate = { ...widget };
const metaPropsMap = WidgetFactory.getWidgetMetaPropertiesMap(widget.type);
const defaultPropertiesMap = WidgetFactory.getWidgetDefaultPropertiesMap(
widget.type,
);
Object.keys(metaPropsMap).forEach(metaProp => {
if (metaProp in defaultPropertiesMap) {
widgetToUpdate[metaProp] = widget[defaultPropertiesMap[metaProp]];
} else {
widgetToUpdate[metaProp] = metaPropsMap[metaProp];
}
});
updates[widget.widgetName] = widgetToUpdate;
}
const newEvaluatedDataTree = {
...evaluatedDataTree,
...updates,
};
yield put({
type: ReduxActionTypes.SET_EVALUATED_TREE,
payload: newEvaluatedDataTree,
});
}
function* updateCanvasSize(
@ -997,6 +1035,12 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction<string>) {
parentRowSpace: 1,
parentColumnSpace: 1,
isLoading: false,
props: {
tableData: `{{${queryName}.data}}`,
dynamicBindings: {
tableData: true,
},
},
};
const {
leftColumn,
@ -1035,14 +1079,6 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction<string>) {
payload: { widgetId: newWidget.newWidgetId },
});
yield put(forceOpenPropertyPane(newWidget.newWidgetId));
yield put(
updateWidgetPropertyRequest(
newWidget.newWidgetId,
"tableData",
`{{${queryName}.data}}`,
RenderModes.CANVAS,
),
);
} catch (error) {
AppToaster.show({
message: "Failed to add the widget",

View File

@ -0,0 +1,227 @@
import {
all,
call,
fork,
put,
select,
take,
takeLatest,
} from "redux-saga/effects";
import { eventChannel, EventChannel } from "redux-saga";
import {
ReduxAction,
ReduxActionErrorTypes,
ReduxActionTypes,
} from "constants/ReduxActionConstants";
import {
getDataTree,
getUnevaluatedDataTree,
} from "selectors/dataTreeSelectors";
import WidgetFactory, { WidgetTypeConfigMap } from "../utils/WidgetFactory";
import Worker from "worker-loader!../workers/evaluation.worker";
import {
EVAL_WORKER_ACTIONS,
EvalError,
EvalErrorTypes,
} from "../utils/DynamicBindingUtils";
import { ToastType } from "react-toastify";
import { AppToaster } from "../components/editorComponents/ToastComponent";
import log from "loglevel";
import _ from "lodash";
import { WidgetType } from "../constants/WidgetConstants";
import { WidgetProps } from "../widgets/BaseWidget";
let evaluationWorker: Worker;
let workerChannel: EventChannel<any>;
let widgetTypeConfigMap: WidgetTypeConfigMap;
const initEvaluationWorkers = () => {
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
evaluationWorker = new Worker();
workerChannel = eventChannel(emitter => {
evaluationWorker.addEventListener("message", emitter);
// The subscriber must return an unsubscribe function
return () => {
evaluationWorker.removeEventListener("message", emitter);
};
});
};
const evalErrorHandler = (errors: EvalError[]) => {
errors.forEach(error => {
if (error.type === EvalErrorTypes.DEPENDENCY_ERROR) {
AppToaster.show({
message: error.message,
type: ToastType.ERROR,
});
}
log.debug(error);
});
};
function* evaluateTreeSaga() {
const unEvalTree = yield select(getUnevaluatedDataTree);
log.debug({ unEvalTree });
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.EVAL_TREE,
dataTree: unEvalTree,
widgetTypeConfigMap,
});
const workerResponse = yield take(workerChannel);
const { errors, dataTree } = workerResponse.data;
const parsedDataTree = JSON.parse(dataTree);
log.debug({ dataTree: parsedDataTree });
evalErrorHandler(errors);
yield put({
type: ReduxActionTypes.SET_EVALUATED_TREE,
payload: parsedDataTree,
});
}
export function* evaluateSingleValue(binding: string) {
if (evaluationWorker) {
const evalTree = yield select(getDataTree);
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.EVAL_SINGLE,
dataTree: evalTree,
binding,
});
const workerResponse = yield take(workerChannel);
const { errors, value } = workerResponse.data;
evalErrorHandler(errors);
return value;
}
}
export function* evaluateDynamicTrigger(
dynamicTrigger: string,
callbackData: any,
) {
if (evaluationWorker) {
const unEvalTree = yield select(getUnevaluatedDataTree);
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.EVAL_TRIGGER,
dataTree: unEvalTree,
dynamicTrigger,
callbackData,
});
const workerResponse = yield take(workerChannel);
const { errors, triggers } = workerResponse.data;
evalErrorHandler(errors);
return triggers;
}
return [];
}
export function* clearEvalCache() {
if (evaluationWorker) {
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.CLEAR_CACHE,
});
yield take(workerChannel);
return true;
}
}
export function* clearEvalPropertyCache(propertyPath: string) {
if (evaluationWorker) {
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.CLEAR_PROPERTY_CACHE,
propertyPath,
});
yield take(workerChannel);
}
}
export function* validateProperty(
widgetType: WidgetType,
property: string,
value: any,
props: WidgetProps,
) {
if (evaluationWorker) {
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY,
widgetType,
property,
value,
props,
});
return yield take(workerChannel);
}
return { isValid: true, parsed: value };
}
const EVALUATE_REDUX_ACTIONS = [
// Actions
ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS,
ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
ReduxActionErrorTypes.FETCH_ACTIONS_VIEW_MODE_ERROR,
ReduxActionTypes.FETCH_ACTIONS_FOR_PAGE_SUCCESS,
ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS,
ReduxActionTypes.CREATE_ACTION_SUCCESS,
ReduxActionTypes.UPDATE_ACTION_PROPERTY,
ReduxActionTypes.DELETE_ACTION_SUCCESS,
ReduxActionTypes.COPY_ACTION_SUCCESS,
ReduxActionTypes.MOVE_ACTION_SUCCESS,
ReduxActionTypes.RUN_ACTION_REQUEST,
ReduxActionTypes.RUN_ACTION_SUCCESS,
ReduxActionErrorTypes.RUN_ACTION_ERROR,
ReduxActionTypes.EXECUTE_API_ACTION_REQUEST,
ReduxActionTypes.EXECUTE_API_ACTION_SUCCESS,
ReduxActionErrorTypes.EXECUTE_ACTION_ERROR,
// App Data
ReduxActionTypes.SET_APP_MODE,
ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
ReduxActionTypes.SET_URL_DATA,
ReduxActionTypes.UPDATE_APP_STORE,
// Widgets
ReduxActionTypes.UPDATE_LAYOUT,
ReduxActionTypes.UPDATE_WIDGET_PROPERTY,
ReduxActionTypes.UPDATE_WIDGET_NAME_SUCCESS,
// Widget Meta
ReduxActionTypes.SET_META_PROP,
ReduxActionTypes.RESET_WIDGET_META,
// Pages
ReduxActionTypes.FETCH_PAGE_SUCCESS,
ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
// Batches
ReduxActionTypes.BATCH_UPDATES_SUCCESS,
];
function* evaluationChangeListenerSaga() {
initEvaluationWorkers();
yield call(evaluateTreeSaga);
while (true) {
const action: ReduxAction<any> = yield take(EVALUATE_REDUX_ACTIONS);
// When batching success action happens, we need to only evaluate
// if the batch had any action we need to evaluate properties for
if (action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS) {
const batchedActionTypes = action.payload.map(
(batchedAction: ReduxAction<any>) => batchedAction.type,
);
if (
_.intersection(EVALUATE_REDUX_ACTIONS, batchedActionTypes).length === 0
) {
continue;
}
}
log.debug(`Evaluating`, { action });
yield fork(evaluateTreeSaga);
}
// TODO(hetu) need an action to stop listening and evaluate (exit app)
}
export default function* evaluationSagaListeners() {
yield all([
takeLatest(
ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS,
evaluationChangeListenerSaga,
),
takeLatest(
ReduxActionTypes.INITIALIZE_PAGE_VIEWER_SUCCESS,
evaluationChangeListenerSaga,
),
]);
}

View File

@ -19,6 +19,7 @@ import queryPaneSagas from "./QueryPaneSagas";
import modalSagas from "./ModalSagas";
import batchSagas from "./BatchSagas";
import themeSagas from "./ThemeSaga";
import evaluationsSaga from "./evaluationsSaga";
export function* rootSaga() {
yield all([
@ -42,5 +43,6 @@ export function* rootSaga() {
spawn(modalSagas),
spawn(batchSagas),
spawn(themeSagas),
spawn(evaluationsSaga),
]);
}

View File

@ -55,11 +55,8 @@ export const getWidgetNamePrefix = (
return state.entities.widgetConfig.config[type].widgetName;
};
export const getDefaultPageId = (state: AppState, pageId?: string): string => {
const { pages } = state.entities.pageList;
const page = pages.find(page => page.pageId === pageId);
return page ? page.pageId : pages[0].pageId;
};
export const getDefaultPageId = (state: AppState): string | undefined =>
state.entities.pageList.defaultPageId;
export const getExistingWidgetNames = createSelector(
getWidgets,

View File

@ -1,7 +1,6 @@
import { createSelector } from "reselect";
import { getActionsForCurrentPage, getAppData } from "./entitiesSelector";
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import { getEvaluatedDataTree } from "utils/DynamicBindingUtils";
import { DataTree, DataTreeFactory } from "entities/DataTree/dataTreeFactory";
import { getWidgets, getWidgetsMeta } from "sagas/selectors";
import * as log from "loglevel";
@ -10,6 +9,7 @@ import { getPageList } from "./appViewSelectors";
import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
import { AppState } from "../reducers";
export const getUnevaluatedDataTree = createSelector(
getActionsForCurrentPage,
@ -22,43 +22,26 @@ export const getUnevaluatedDataTree = createSelector(
PerformanceTransactionName.CONSTRUCT_UNEVAL_TREE,
);
const pageList = pageListPayload || [];
const unevalTree = DataTreeFactory.create(
{
actions,
widgets,
widgetsMeta,
pageList,
appData,
},
true,
);
const unevalTree = DataTreeFactory.create({
actions,
widgets,
widgetsMeta,
pageList,
appData,
});
PerformanceTracker.stopTracking();
return unevalTree;
},
);
export const evaluateDataTree = createSelector(
getUnevaluatedDataTree,
(dataTree: DataTree): DataTree => {
PerformanceTracker.startTracking(
PerformanceTransactionName.DATA_TREE_EVALUATION,
);
const evalDataTree = getEvaluatedDataTree(dataTree);
PerformanceTracker.stopTracking();
return evalDataTree;
},
);
export const evaluateDataTreeWithFunctions = evaluateDataTree;
export const evaluateDataTreeWithoutFunctions = evaluateDataTree;
export const getDataTree = (state: AppState) => state.evaluations.tree;
// For autocomplete. Use actions cached responses if
// there isn't a response already
export const getDataTreeForAutocomplete = createSelector(
evaluateDataTreeWithoutFunctions,
getDataTree,
getActionsForCurrentPage,
(tree: DataTree, actions: ActionDataState) => {
log.debug("Evaluating data tree to get autocomplete values");
const cachedResponses: Record<string, any> = {};
if (actions && actions.length) {
actions.forEach(action => {

View File

@ -2,27 +2,33 @@ import { createSelector } from "reselect";
import { AppState } from "reducers";
import { WidgetConfigReducerState } from "reducers/entityReducers/widgetConfigReducer";
import { WidgetCardProps, WidgetProps } from "widgets/BaseWidget";
import {
WIDGET_STATIC_PROPS,
WidgetCardProps,
WidgetProps,
} from "widgets/BaseWidget";
import { WidgetSidebarReduxState } from "reducers/uiReducers/widgetSidebarReducer";
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
import { getEntities } from "./entitiesSelector";
import {
FlattenedWidgetProps,
CanvasWidgetsReduxState,
FlattenedWidgetProps,
} from "reducers/entityReducers/canvasWidgetsReducer";
import { PageListReduxState } from "reducers/entityReducers/pageListReducer";
import { OccupiedSpace } from "constants/editorConstants";
import { evaluateDataTreeWithoutFunctions } from "selectors/dataTreeSelectors";
import { getDataTree } from "selectors/dataTreeSelectors";
import _ from "lodash";
import { ContainerWidgetProps } from "widgets/ContainerWidget";
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import { getActions } from "sagas/selectors";
import { DataTreeWidget, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { getActions, getWidgetsMeta } from "sagas/selectors";
import * as log from "loglevel";
import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
import { getCanvasWidgets } from "./entitiesSelector";
import { MetaState } from "../reducers/entityReducers/metaReducer";
import { WidgetTypes } from "../constants/WidgetConstants";
const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig;
const getWidgetSideBar = (state: AppState) => state.ui.widgetSidebar;
@ -104,26 +110,28 @@ export const getWidgetCards = createSelector(
);
export const getCanvasWidgetDsl = createSelector(
getEntities,
evaluateDataTreeWithoutFunctions,
getCanvasWidgets,
getDataTree,
(
entities: AppState["entities"],
canvasWidgets: CanvasWidgetsReduxState,
evaluatedDataTree,
): ContainerWidgetProps<WidgetProps> => {
PerformanceTracker.startTracking(
PerformanceTransactionName.CONSTRUCT_CANVAS_DSL,
);
log.debug("Evaluating data tree to get canvas widgets");
log.debug({ evaluatedDataTree });
const widgets = { ...entities.canvasWidgets };
Object.keys(widgets).forEach(widgetKey => {
const evaluatedWidget = _.find(evaluatedDataTree, {
widgetId: widgetKey,
});
const widgets: Record<string, DataTreeWidget> = {};
Object.keys(canvasWidgets).forEach(widgetKey => {
const canvasWidget = canvasWidgets[widgetKey];
const evaluatedWidget = evaluatedDataTree[
canvasWidget.widgetName
] as DataTreeWidget;
if (evaluatedWidget) {
widgets[widgetKey] = evaluatedWidget as DataTreeWidget;
widgets[widgetKey] = createCanvasWidget(canvasWidget, evaluatedWidget);
} else {
widgets[widgetKey] = createLoadingWidget(canvasWidget);
}
});
const denormalizedWidgets = CanvasWidgetsNormalizer.denormalize("0", {
canvasWidgets: widgets,
});
@ -197,3 +205,32 @@ export const getActionById = createSelector(
}
},
);
const createCanvasWidget = (
canvasWidget: FlattenedWidgetProps,
evaluatedWidget: DataTreeWidget,
) => {
const widgetStaticProps = _.pick(
canvasWidget,
Object.keys(WIDGET_STATIC_PROPS),
);
return {
...evaluatedWidget,
...widgetStaticProps,
};
};
const createLoadingWidget = (
canvasWidget: FlattenedWidgetProps,
): DataTreeWidget => {
const widgetStaticProps = _.pick(
canvasWidget,
Object.keys(WIDGET_STATIC_PROPS),
) as WidgetProps;
return {
...widgetStaticProps,
type: WidgetTypes.SKELETON_WIDGET,
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
isLoading: true,
};
};

View File

@ -10,6 +10,7 @@ import { Datasource } from "api/DatasourcesApi";
import { Action } from "entities/Action";
import { find } from "lodash";
import ImageAlt from "assets/images/placeholder-image.svg";
import { CanvasWidgetsReduxState } from "../reducers/entityReducers/canvasWidgetsReducer";
export const getEntities = (state: AppState): AppState["entities"] =>
state.entities;
@ -267,3 +268,6 @@ export const isActionDirty = (id: string) =>
});
export const getAppData = (state: AppState) => state.entities.app;
export const getCanvasWidgets = (state: AppState): CanvasWidgetsReduxState =>
state.entities.canvasWidgets;

View File

@ -1,14 +1,17 @@
import { createSelector } from "reselect";
import { AppState } from "reducers";
import { PropertyPaneReduxState } from "reducers/uiReducers/propertyPaneReducer";
import { PropertyPaneConfigState } from "reducers/entityReducers/propertyPaneConfigReducer";
import {
PropertyPaneConfigState,
PropertySection,
} from "reducers/entityReducers/propertyPaneConfigReducer";
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { PropertySection } from "reducers/entityReducers/propertyPaneConfigReducer";
import { WidgetProps } from "widgets/BaseWidget";
import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import _ from "lodash";
import { evaluateDataTreeWithoutFunctions } from "selectors/dataTreeSelectors";
import { getDataTree } from "selectors/dataTreeSelectors";
import * as log from "loglevel";
import { getCanvasWidgets } from "./entitiesSelector";
const getPropertyPaneState = (state: AppState): PropertyPaneReduxState =>
state.ui.propertyPane;
@ -16,9 +19,6 @@ const getPropertyPaneState = (state: AppState): PropertyPaneReduxState =>
const getPropertyPaneConfig = (state: AppState): PropertyPaneConfigState =>
state.entities.propertyConfig;
const getCanvasWidgets = (state: AppState): CanvasWidgetsReduxState =>
state.entities.canvasWidgets;
export const getCurrentWidgetId = createSelector(
getPropertyPaneState,
(propertyPane: PropertyPaneReduxState) => propertyPane.widgetId,
@ -37,12 +37,11 @@ export const getCurrentWidgetProperties = createSelector(
export const getWidgetPropsForPropertyPane = createSelector(
getCurrentWidgetProperties,
evaluateDataTreeWithoutFunctions,
getDataTree,
(
widget: WidgetProps | undefined,
evaluatedTree: DataTree,
): WidgetProps | undefined => {
log.debug("Evaluating data tree to get property pane validations");
if (!widget) return undefined;
const evaluatedWidget = _.find(evaluatedTree, {
widgetId: widget.widgetId,

View File

@ -1,28 +1,11 @@
import _ from "lodash";
import _, { VERSION as lodashVersion } from "lodash";
import {
DATA_BIND_REGEX,
DATA_BIND_REGEX_GLOBAL,
} from "constants/BindingsConstants";
import ValidationFactory from "./ValidationFactory";
import JSExecutionManagerSingleton, {
JSExecutorResult,
} from "jsExecution/JSExecutionManagerSingleton";
import unescapeJS from "unescape-js";
import toposort from "toposort";
import {
DataTree,
DataTreeEntity,
DataTreeWidget,
ENTITY_TYPE,
} from "entities/DataTree/dataTreeFactory";
import equal from "fast-deep-equal/es6";
import WidgetFactory from "utils/WidgetFactory";
import { AppToaster } from "components/editorComponents/ToastComponent";
import { ToastType } from "react-toastify";
import { Action } from "entities/Action";
import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
import moment from "moment-timezone";
import { atob, btoa, version as BASE64LIBVERSION } from "js-base64";
type StringTuple = [string, string];
@ -77,27 +60,6 @@ export function getDynamicStringSegments(dynamicString: string): string[] {
return stringSegments;
}
const getAllPaths = (
tree: Record<string, any>,
prefix = "",
): Record<string, true> => {
return Object.keys(tree).reduce((res: Record<string, true>, el): Record<
string,
true
> => {
if (Array.isArray(tree[el])) {
const key = `${prefix}${el}`;
return { ...res, [key]: true };
} else if (typeof tree[el] === "object" && tree[el] !== null) {
const key = `${prefix}${el}`;
return { ...res, [key]: true, ...getAllPaths(tree[el], `${key}.`) };
} else {
const key = `${prefix}${el}`;
return { ...res, [key]: true };
}
}, {});
};
export const getDynamicBindings = (
dynamicString: string,
): { stringSegments: string[]; jsSnippets: string[] } => {
@ -120,671 +82,64 @@ export const getDynamicBindings = (
return { stringSegments: stringSegments, jsSnippets: paths };
};
// Paths are expected to have "{name}.{path}" signature
// Also returns any action triggers found after evaluating value
export const evaluateDynamicBoundValue = (
data: DataTree,
path: string,
callbackData?: any,
): JSExecutorResult => {
const unescapedJS = unescapeJS(path).replace(/(\r\n|\n|\r)/gm, "");
return JSExecutionManagerSingleton.evaluateSync(
unescapedJS,
data,
callbackData,
);
};
// For creating a final value where bindings could be in a template format
export const createDynamicValueString = (
binding: string,
subBindings: string[],
subValues: string[],
): string => {
// Replace the string with the data tree values
let finalValue = binding;
subBindings.forEach((b, i) => {
let value = subValues[i];
if (Array.isArray(value) || _.isObject(value)) {
value = JSON.stringify(value);
}
try {
if (JSON.parse(value)) {
value = value.replace(/\\([\s\S])|(")/g, "\\$1$2");
}
} catch (e) {
// do nothing
}
finalValue = finalValue.replace(b, value);
});
return finalValue;
};
export const getDynamicValue = (
dynamicBinding: string,
data: DataTree,
callBackData?: any,
includeTriggers = false,
): JSExecutorResult => {
// Get the {{binding}} bound values
const { stringSegments, jsSnippets } = getDynamicBindings(dynamicBinding);
if (stringSegments.length) {
// Get the Data Tree value of those "binding "paths
const values = jsSnippets.map((jsSnippet, index) => {
if (jsSnippet) {
const result = evaluateDynamicBoundValue(data, jsSnippet, callBackData);
if (includeTriggers) {
return result;
} else {
return { result: result.result };
}
} else {
return { result: stringSegments[index], triggers: [] };
}
});
// if it is just one binding, no need to create template string
if (stringSegments.length === 1) return values[0];
// else return a string template with bindings
const templateString = createDynamicValueString(
dynamicBinding,
stringSegments,
values.map(v => v.result),
);
return {
result: templateString,
};
}
return { result: undefined, triggers: [] };
};
export const getValidatedTree = (tree: any) => {
return Object.keys(tree).reduce((tree, entityKey: string) => {
const entity = tree[entityKey];
if (entity && entity.type) {
const parsedEntity = { ...entity };
Object.keys(entity).forEach((property: string) => {
const hasEvaluatedValue = _.has(
parsedEntity,
`evaluatedValues.${property}`,
);
const hasValidation = _.has(parsedEntity, `invalidProps.${property}`);
const isSpecialField = [
"dynamicBindings",
"dynamicTriggers",
"dynamicProperties",
"evaluatedValues",
"invalidProps",
"validationMessages",
].includes(property);
const isDynamicField =
_.has(parsedEntity, `dynamicBindings.${property}`) ||
_.has(parsedEntity, `dynamicTriggers.${property}`);
if (
!isSpecialField &&
!isDynamicField &&
(!hasValidation || !hasEvaluatedValue)
) {
const value = entity[property];
// Pass it through parse
const {
parsed,
isValid,
message,
transformed,
} = ValidationFactory.validateWidgetProperty(
entity.type,
property,
value,
entity,
tree,
);
parsedEntity[property] = parsed;
if (!hasEvaluatedValue) {
const evaluatedValue = isValid
? parsed
: _.isUndefined(transformed)
? value
: transformed;
const safeEvaluatedValue = removeFunctions(evaluatedValue);
_.set(
parsedEntity,
`evaluatedValues.${property}`,
safeEvaluatedValue,
);
}
const hasValidation = _.has(parsedEntity, `invalidProps.${property}`);
if (!hasValidation && !isValid) {
_.set(parsedEntity, `invalidProps.${property}`, true);
_.set(parsedEntity, `validationMessages.${property}`, message);
}
}
});
return { ...tree, [entityKey]: parsedEntity };
}
return tree;
}, tree);
};
let dependencyTreeCache: any = {};
let cachedDataTreeString = "";
export function getEvaluatedDataTree(dataTree: DataTree): DataTree {
// Create Dependencies DAG
const dataTreeString = JSON.stringify(dataTree);
// Stringify before doing a fast equals because the data tree has functions and fast equal will always treat those as changed values
// Better solve will be to prune functions
const shouldCreateDependencyTree = !equal(
dataTreeString,
cachedDataTreeString,
);
PerformanceTracker.startTracking(
PerformanceTransactionName.CREATE_DEPENDENCIES,
{ isCacheMiss: shouldCreateDependencyTree },
);
if (shouldCreateDependencyTree) {
cachedDataTreeString = dataTreeString;
dependencyTreeCache = createDependencyTree(dataTree);
}
const {
dependencyMap,
sortedDependencies,
dependencyTree,
} = dependencyTreeCache;
PerformanceTracker.stopTracking();
// Evaluate Tree
PerformanceTracker.startTracking(
PerformanceTransactionName.SORTED_DEPENDENCY_EVALUATION,
{
dependencies: sortedDependencies,
dependencyCount: sortedDependencies.length,
dataTreeSize: cachedDataTreeString.length,
},
);
const evaluatedTree = dependencySortedEvaluateDataTree(
dataTree,
dependencyMap,
sortedDependencies,
);
PerformanceTracker.stopTracking();
// Set Loading Widgets
PerformanceTracker.startTracking(
PerformanceTransactionName.SET_WIDGET_LOADING,
);
const treeWithLoading = setTreeLoading(evaluatedTree, dependencyTree);
PerformanceTracker.stopTracking();
// Validate Widgets
PerformanceTracker.startTracking(
PerformanceTransactionName.VALIDATE_DATA_TREE,
);
const validated = getValidatedTree(treeWithLoading);
PerformanceTracker.stopTracking();
// dataTreeCache = validated;
return validated;
export enum EvalErrorTypes {
DEPENDENCY_ERROR = "DEPENDENCY_ERROR",
EVAL_PROPERTY_ERROR = "EVAL_PROPERTY_ERROR",
EVAL_TREE_ERROR = "EVAL_TREE_ERROR",
UNESCAPE_STRING_ERROR = "UNESCAPE_STRING_ERROR",
EVAL_ERROR = "EVAL_ERROR",
}
type DynamicDependencyMap = Record<string, Array<string>>;
export const createDependencyTree = (
dataTree: DataTree,
): {
sortedDependencies: Array<string>;
dependencyTree: Array<StringTuple>;
dependencyMap: DynamicDependencyMap;
} => {
const dependencyMap: DynamicDependencyMap = {};
const allKeys = getAllPaths(dataTree);
Object.keys(dataTree).forEach(entityKey => {
const entity = dataTree[entityKey];
if (entity && "ENTITY_TYPE" in entity) {
if (entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET) {
// Set default property dependency
const defaultProperties = WidgetFactory.getWidgetDefaultPropertiesMap(
entity.type,
);
Object.keys(defaultProperties).forEach(property => {
dependencyMap[`${entityKey}.${property}`] = [
`${entityKey}.${defaultProperties[property]}`,
];
});
if (entity.dynamicBindings) {
Object.keys(entity.dynamicBindings).forEach(propertyName => {
// using unescape to remove new lines from bindings which interfere with our regex extraction
const unevalPropValue = _.get(entity, propertyName);
const { jsSnippets } = getDynamicBindings(unevalPropValue);
const existingDeps =
dependencyMap[`${entityKey}.${propertyName}`] || [];
dependencyMap[`${entityKey}.${propertyName}`] = existingDeps.concat(
jsSnippets.filter(jsSnippet => !!jsSnippet),
);
});
}
if (entity.dynamicTriggers) {
Object.keys(entity.dynamicTriggers).forEach(prop => {
dependencyMap[`${entityKey}.${prop}`] = [];
});
}
}
if (entity.ENTITY_TYPE === ENTITY_TYPE.ACTION) {
if (entity.dynamicBindingPathList.length) {
entity.dynamicBindingPathList.forEach(prop => {
// using unescape to remove new lines from bindings which interfere with our regex extraction
const unevalPropValue = _.get(entity, prop.key);
const { jsSnippets } = getDynamicBindings(unevalPropValue);
const existingDeps =
dependencyMap[`${entityKey}.${prop.key}`] || [];
dependencyMap[`${entityKey}.${prop.key}`] = existingDeps.concat(
jsSnippets.filter(jsSnippet => !!jsSnippet),
);
});
}
}
}
});
Object.keys(dependencyMap).forEach(key => {
dependencyMap[key] = _.flatten(
dependencyMap[key].map(path => calculateSubDependencies(path, allKeys)),
);
});
const dependencyTree: Array<StringTuple> = [];
Object.keys(dependencyMap).forEach((key: string) => {
if (dependencyMap[key].length) {
dependencyMap[key].forEach(dep => dependencyTree.push([key, dep]));
} else {
// Set no dependency
dependencyTree.push([key, ""]);
}
});
try {
// sort dependencies and remove empty dependencies
const sortedDependencies = toposort(dependencyTree)
.reverse()
.filter(d => !!d);
return { sortedDependencies, dependencyMap, dependencyTree };
} catch (e) {
console.error(e);
AppToaster.show({
message: e.message,
type: ToastType.ERROR,
});
return { sortedDependencies: [], dependencyMap: {}, dependencyTree: [] };
}
export type EvalError = {
type: EvalErrorTypes;
message: string;
context?: Record<string, any>;
};
const calculateSubDependencies = (
path: string,
all: Record<string, true>,
): Array<string> => {
const subDeps: Array<string> = [];
const identifiers = path.match(/[a-zA-Z_$][a-zA-Z_$0-9.]*/g) || [path];
identifiers.forEach((identifier: string) => {
if (all.hasOwnProperty(identifier)) {
subDeps.push(identifier);
} else {
const subIdentifiers =
identifier.match(/[a-zA-Z_$][a-zA-Z_$0-9]*/g) || [];
let current = "";
for (let i = 0; i < subIdentifiers.length; i++) {
const key = `${current}${current ? "." : ""}${subIdentifiers[i]}`;
if (key in all) {
current = key;
} else {
break;
}
}
if (current && current.includes(".")) subDeps.push(current);
}
});
return _.uniq(subDeps);
export enum EVAL_WORKER_ACTIONS {
EVAL_TREE = "EVAL_TREE",
EVAL_SINGLE = "EVAL_SINGLE",
EVAL_TRIGGER = "EVAL_TRIGGER",
CLEAR_PROPERTY_CACHE = "CLEAR_PROPERTY_CACHE",
CLEAR_CACHE = "CLEAR_CACHE",
VALIDATE_PROPERTY = "VALIDATE_PROPERTY",
}
export type ExtraLibrary = {
version: string;
docsURL: string;
displayName: string;
accessor: string;
lib: any;
};
export const setTreeLoading = (
dataTree: DataTree,
dependencyMap: Array<StringTuple>,
) => {
const widgets: string[] = [];
const isLoadingActions: string[] = [];
// Fetch all actions that are in loading state
Object.keys(dataTree).forEach(e => {
const entity = dataTree[e];
if (entity && "ENTITY_TYPE" in entity) {
if (entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET) {
widgets.push(e);
} else if (
entity.ENTITY_TYPE === ENTITY_TYPE.ACTION &&
entity.isLoading
) {
isLoadingActions.push(e);
}
}
});
// get all widget dependencies of those actions
isLoadingActions
.reduce(
(allEntities: string[], curr) =>
allEntities.concat(getEntityDependencies(dependencyMap, curr, widgets)),
[],
)
// set loading to true for those widgets
.forEach(w => {
const entity = dataTree[w] as DataTreeWidget;
entity.isLoading = true;
});
return dataTree;
};
export const getEntityDependencies = (
dependencyMap: Array<StringTuple>,
entity: string,
entities: string[],
): Array<string> => {
const entityDeps: Record<string, string[]> = dependencyMap
.map(d => [d[1].split(".")[0], d[0].split(".")[0]])
.filter(d => d[0] !== d[1])
.reduce((deps: Record<string, string[]>, dep) => {
const key: string = dep[0];
const value: string = dep[1];
return {
...deps,
[key]: deps[key] ? deps[key].concat(value) : [value],
};
}, {});
if (entity in entityDeps) {
const recFind = (
keys: Array<string>,
deps: Record<string, string[]>,
): Array<string> => {
let allDeps: string[] = [];
keys
.filter(k => entities.includes(k))
.forEach(e => {
allDeps = allDeps.concat([e]);
if (e in deps) {
allDeps = allDeps.concat([...recFind(deps[e], deps)]);
}
});
return allDeps;
};
return recFind(entityDeps[entity], entityDeps);
}
return [];
};
const dynamicPropValueCache: Map<
string,
export const extraLibraries: ExtraLibrary[] = [
{
unEvaluated: any;
evaluated: any;
}
> = new Map();
const parsedValueCache: Map<
string,
accessor: "_",
lib: _,
version: lodashVersion,
docsURL: `https://lodash.com/docs/${lodashVersion}`,
displayName: "lodash",
},
{
value: any;
version: number;
}
> = new Map();
const getDynamicPropValueCache = (propertyPath: string) =>
dynamicPropValueCache.get(propertyPath);
const getParsedValueCache = (propertyPath: string) =>
parsedValueCache.get(propertyPath) || {
value: undefined,
version: 0,
};
export const clearPropertyCache = (propertyPath: string) =>
parsedValueCache.delete(propertyPath);
const dependencyCache: Map<string, any[]> = new Map();
export const clearCaches = () => {
dynamicPropValueCache.clear();
dependencyCache.clear();
parsedValueCache.clear();
};
function getCurrentDependencyValues(
propertyDependencies: Array<string>,
currentTree: DataTree,
currentPropertyPath: string,
): Array<string> {
return propertyDependencies
? propertyDependencies
.map((path: string) => {
//*** Remove current path from data tree because cached value contains evaluated version while this contains unevaluated version */
const cleanDataTree = _.omit(currentTree, [currentPropertyPath]);
return _.get(cleanDataTree, path);
})
.filter((data: any) => {
return data !== undefined;
})
: [];
}
function evaluateDynamicProperty(
propertyPath: string,
currentTree: DataTree,
unEvalPropertyValue: any,
currentDependencyValues: Array<string>,
cachedDependencyValues?: Array<string>,
): any {
const cacheObj = getDynamicPropValueCache(propertyPath);
const isCacheHit =
cacheObj &&
equal(cacheObj.unEvaluated, unEvalPropertyValue) &&
cachedDependencyValues !== undefined &&
equal(currentDependencyValues, cachedDependencyValues);
if (isCacheHit && cacheObj) {
return cacheObj.evaluated;
} else {
const dynamicResult = getDynamicValue(unEvalPropertyValue, currentTree);
dynamicPropValueCache.set(propertyPath, {
evaluated: dynamicResult.result,
unEvaluated: unEvalPropertyValue,
});
dependencyCache.set(propertyPath, currentDependencyValues);
return dynamicResult.result;
}
}
function validateAndParseWidgetProperty(
propertyPath: string,
widget: DataTreeWidget,
currentTree: DataTree,
evalPropertyValue: any,
unEvalPropertyValue: string,
currentDependencyValues: Array<string>,
cachedDependencyValues?: Array<string>,
): any {
const propertyName = propertyPath.split(".")[1];
let valueToValidate = evalPropertyValue;
if (widget.dynamicTriggers && propertyName in widget.dynamicTriggers) {
const { triggers } = getDynamicValue(
unEvalPropertyValue,
currentTree,
undefined,
true,
);
valueToValidate = triggers;
}
const {
parsed,
isValid,
message,
transformed,
} = ValidationFactory.validateWidgetProperty(
widget.type,
propertyName,
valueToValidate,
widget,
currentTree,
);
const evaluatedValue = isValid
? parsed
: _.isUndefined(transformed)
? evalPropertyValue
: transformed;
const safeEvaluatedValue = removeFunctions(evaluatedValue);
_.set(widget, `evaluatedValues.${propertyName}`, safeEvaluatedValue);
if (!isValid) {
_.set(widget, `invalidProps.${propertyName}`, true);
_.set(widget, `validationMessages.${propertyName}`, message);
}
if (widget.dynamicTriggers && propertyName in widget.dynamicTriggers) {
return unEvalPropertyValue;
} else {
const parsedCache = getParsedValueCache(propertyPath);
if (
!equal(parsedCache.value, parsed) ||
(cachedDependencyValues !== undefined &&
!equal(currentDependencyValues, cachedDependencyValues))
) {
parsedValueCache.set(propertyPath, {
value: parsed,
version: Date.now(),
});
}
return parsed;
}
}
function isWidget(entity: DataTreeEntity): boolean {
return "ENTITY_TYPE" in entity && entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET;
}
export function dependencySortedEvaluateDataTree(
dataTree: DataTree,
dependencyMap: DynamicDependencyMap,
sortedDependencies: Array<string>,
): DataTree {
const tree = _.cloneDeep(dataTree);
try {
return sortedDependencies.reduce(
(currentTree: DataTree, propertyPath: string) => {
// PerformanceTracker.startTracking(PerformanceTransactionName.EVALUATE_BINDING, { binding: propertyPath }, true)
const entityName = propertyPath.split(".")[0];
const entity: DataTreeEntity = currentTree[entityName];
const unEvalPropertyValue = _.get(currentTree as any, propertyPath);
let evalPropertyValue;
const propertyDependencies = dependencyMap[propertyPath];
const currentDependencyValues = getCurrentDependencyValues(
propertyDependencies,
currentTree,
propertyPath,
);
const cachedDependencyValues = dependencyCache.get(propertyPath);
const requiresEval = isDynamicValue(unEvalPropertyValue);
if (requiresEval) {
try {
evalPropertyValue = evaluateDynamicProperty(
propertyPath,
currentTree,
unEvalPropertyValue,
currentDependencyValues,
cachedDependencyValues,
);
} catch (e) {
console.error(e);
evalPropertyValue = undefined;
}
} else {
evalPropertyValue = unEvalPropertyValue;
// If we have stored any previous dependency cache, clear it
// since it is no longer a binding
if (cachedDependencyValues && cachedDependencyValues.length) {
dependencyCache.set(propertyPath, []);
}
}
if (isWidget(entity)) {
const widgetEntity: DataTreeWidget = entity as DataTreeWidget;
const propertyName = propertyPath.split(".")[1];
if (propertyName) {
let parsedValue = validateAndParseWidgetProperty(
propertyPath,
widgetEntity,
currentTree,
evalPropertyValue,
unEvalPropertyValue,
currentDependencyValues,
cachedDependencyValues,
);
const defaultPropertyMap = WidgetFactory.getWidgetDefaultPropertiesMap(
widgetEntity.type,
);
const hasDefaultProperty = propertyName in defaultPropertyMap;
if (hasDefaultProperty) {
const defaultProperty = defaultPropertyMap[propertyName];
parsedValue = overwriteDefaultDependentProps(
defaultProperty,
parsedValue,
propertyPath,
widgetEntity,
);
}
// PerformanceTracker.stopTracking();
return _.set(currentTree, propertyPath, parsedValue);
}
// PerformanceTracker.stopTracking();
return _.set(currentTree, propertyPath, evalPropertyValue);
} else {
// PerformanceTracker.stopTracking();
return _.set(currentTree, propertyPath, evalPropertyValue);
}
},
tree,
);
} catch (e) {
console.error(e);
return tree;
}
}
const overwriteDefaultDependentProps = (
defaultProperty: string,
propertyValue: any,
propertyPath: string,
entity: DataTreeWidget,
) => {
const defaultPropertyCache = getParsedValueCache(
`${entity.widgetName}.${defaultProperty}`,
);
const propertyCache = getParsedValueCache(propertyPath);
if (
propertyValue === undefined ||
propertyCache.version < defaultPropertyCache.version
) {
return defaultPropertyCache.value;
}
return propertyValue;
};
// We need to remove functions from data tree to avoid any unexpected identifier while JSON parsing
// Check issue https://github.com/appsmithorg/appsmith/issues/719
const removeFunctions = (value: any) => {
if (_.isFunction(value)) {
return "Function call";
} else if (_.isObject(value) && _.some(value, _.isFunction)) {
return JSON.parse(JSON.stringify(value));
} else {
return value;
}
};
/*
Need to evaluated values
Need to validate widget values
Need to replace with default values
*/
accessor: "moment",
lib: moment,
version: moment.version,
docsURL: `https://momentjs.com/docs/`,
displayName: "moment",
},
{
accessor: "btoa",
lib: btoa,
version: BASE64LIBVERSION,
docsURL: "https://github.com/dankogai/js-base64#readme",
displayName: "btoa",
},
{
accessor: "atob",
lib: atob,
version: BASE64LIBVERSION,
docsURL: "https://github.com/dankogai/js-base64#readme",
displayName: "atob",
},
];

View File

@ -1,4 +1,3 @@
// // import RealmExecutor from "jsExecution/RealmExecutor";
// import {
// mockExecute,
// mockRegisterLibrary,
@ -12,12 +11,6 @@
// import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
// import { RenderModes, WidgetTypes } from "constants/WidgetConstants";
//
// jest.mock("jsExecution/RealmExecutor", () => {
// return jest.fn().mockImplementation(() => {
// return { execute: mockExecute, registerLibrary: mockRegisterLibrary };
// });
// });
//
// beforeAll(() => {
// mockRegisterLibrary.mockClear();
// mockExecute.mockClear();

View File

@ -1,10 +1,9 @@
import WidgetBuilderRegistry from "./WidgetRegistry";
import PropertyControlRegistry from "./PropertyControlRegistry";
import ValidationRegistry from "./ValidationRegistry";
export const editorInitializer = async () => {
WidgetBuilderRegistry.registerWidgetBuilders();
PropertyControlRegistry.registerPropertyControlBuilders();
ValidationRegistry.registerInternalValidators();
const moment = await import("moment-timezone");
moment.tz.setDefault(moment.tz.guess());

View File

@ -1,60 +0,0 @@
import { WidgetType } from "constants/WidgetConstants";
import WidgetFactory from "./WidgetFactory";
import {
VALIDATION_TYPES,
ValidationResponse,
ValidationType,
Validator,
} from "constants/WidgetValidation";
import { WidgetProps } from "widgets/BaseWidget";
import { DataTree } from "entities/DataTree/dataTreeFactory";
export const BASE_WIDGET_VALIDATION = {
isLoading: VALIDATION_TYPES.BOOLEAN,
isVisible: VALIDATION_TYPES.BOOLEAN,
isDisabled: VALIDATION_TYPES.BOOLEAN,
};
export type WidgetPropertyValidationType = Record<
string,
ValidationType | Validator
>;
class ValidationFactory {
static validationMap: Map<ValidationType, Validator> = new Map();
static registerValidator(
validationType: ValidationType,
validator: Validator,
) {
this.validationMap.set(validationType, validator);
}
static validateWidgetProperty(
widgetType: WidgetType,
property: string,
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse {
// TODO WIDGETFACTORY
const propertyValidationTypes = WidgetFactory.getWidgetPropertyValidationMap(
widgetType,
);
const validationTypeOrValidator = propertyValidationTypes[property];
let validator;
if (typeof validationTypeOrValidator === "function") {
validator = validationTypeOrValidator;
} else {
validator = this.validationMap.get(validationTypeOrValidator);
}
if (validator) {
return validator(value, props, dataTree);
} else {
return { isValid: true, parsed: value };
}
}
}
export default ValidationFactory;

View File

@ -1,13 +0,0 @@
import ValidationFactory from "./ValidationFactory";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { VALIDATORS } from "./Validators";
class ValidationRegistry {
static registerInternalValidators() {
Object.keys(VALIDATION_TYPES).forEach(type => {
ValidationFactory.registerValidator(type, VALIDATORS[type]);
});
}
}
export default ValidationRegistry;

View File

@ -1,523 +0,0 @@
import _ from "lodash";
import {
ISO_DATE_FORMAT,
VALIDATION_TYPES,
ValidationResponse,
ValidationType,
Validator,
} from "constants/WidgetValidation";
import moment from "moment";
import { WIDGET_TYPE_VALIDATION_ERROR } from "constants/messages";
import { WidgetProps } from "widgets/BaseWidget";
import { DataTree } from "entities/DataTree/dataTreeFactory";
export const VALIDATORS: Record<ValidationType, Validator> = {
[VALIDATION_TYPES.TEXT]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
let parsed = value;
if (_.isUndefined(value) || value === null) {
return {
isValid: true,
parsed: value,
message: "",
};
}
if (_.isObject(value)) {
return {
isValid: false,
parsed: JSON.stringify(value, null, 2),
message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`,
};
}
let isValid = _.isString(value);
if (!isValid) {
try {
parsed = _.toString(value);
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to string`);
console.error(e);
return {
isValid: false,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.REGEX]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed, message } = VALIDATORS[VALIDATION_TYPES.TEXT](
value,
props,
dataTree,
);
if (isValid) {
try {
new RegExp(parsed);
} catch (e) {
return {
isValid: false,
parsed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: regex`,
};
}
}
return { isValid, parsed, message };
},
[VALIDATION_TYPES.NUMBER]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
let parsed = value;
if (_.isUndefined(value)) {
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
let isValid = _.isNumber(value);
if (!isValid) {
try {
parsed = _.toNumber(value);
if (isNaN(parsed)) {
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to number`);
console.error(e);
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.BOOLEAN]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
let parsed = value;
if (_.isUndefined(value)) {
return {
isValid: false,
parsed: false,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
const isBoolean = _.isBoolean(value);
const isStringTrueFalse = value === "true" || value === "false";
const isValid = isBoolean || isStringTrueFalse;
if (isStringTrueFalse) parsed = value !== "false";
if (!isValid) {
return {
isValid: isValid,
parsed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.OBJECT]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
let parsed = value;
if (_.isUndefined(value)) {
return {
isValid: false,
parsed: {},
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Object`,
};
}
let isValid = _.isObject(value);
if (!isValid) {
try {
parsed = JSON.parse(value);
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to object`);
console.error(e);
return {
isValid: false,
parsed: {},
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Object`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.ARRAY]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
let parsed = value;
try {
if (_.isUndefined(value)) {
return {
isValid: false,
parsed: [],
transformed: undefined,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
if (_.isString(value)) {
parsed = JSON.parse(parsed as string);
}
if (!Array.isArray(parsed)) {
return {
isValid: false,
parsed: [],
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
return { isValid: true, parsed, transformed: parsed };
} catch (e) {
console.error(e);
return {
isValid: false,
parsed: [],
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
},
[VALIDATION_TYPES.TABS_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`,
};
} else if (!_.every(parsed, datum => _.isObject(datum))) {
return {
isValid: false,
parsed: [],
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.TABLE_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, transformed, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed: [],
transformed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
};
}
const isValidTableData = _.every(parsed, datum => {
return (
_.isObject(datum) &&
Object.keys(datum).filter(key => _.isString(key) && key.length === 0)
.length === 0
);
});
if (!isValidTableData) {
return {
isValid: false,
parsed: [],
transformed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.CHART_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Chart Data`,
};
}
let validationMessage = "";
let index = 0;
const isValidChartData = _.every(
parsed,
(datum: { name: string; data: any }) => {
const validatedResponse: {
isValid: boolean;
parsed: Record<string, unknown>;
message?: string;
} = VALIDATORS[VALIDATION_TYPES.ARRAY](datum.data, props, dataTree);
validationMessage = `${index}##${WIDGET_TYPE_VALIDATION_ERROR}: [{ "x": "val", "y": "val" }]`;
let isValidChart = validatedResponse.isValid;
if (validatedResponse.isValid) {
datum.data = validatedResponse.parsed;
isValidChart = _.every(
datum.data,
(chartPoint: { x: string; y: any }) => {
return (
_.isObject(chartPoint) &&
_.isString(chartPoint.x) &&
!_.isUndefined(chartPoint.y)
);
},
);
}
index++;
return isValidChart;
},
);
if (!isValidChartData) {
return {
isValid: false,
parsed: [],
transformed: parsed,
message: validationMessage,
};
}
return { isValid, parsed, transformed: parsed };
},
[VALIDATION_TYPES.MARKERS]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
};
} else if (!_.every(parsed, datum => _.isObject(datum))) {
return {
isValid: false,
parsed: [],
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.OPTIONS_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Options Data`,
};
}
const isValidOption = (option: { label: any; value: any }) =>
_.isString(option.label) &&
_.isString(option.value) &&
!_.isEmpty(option.label) &&
!_.isEmpty(option.value);
const hasOptions = _.every(parsed, (datum: { label: any; value: any }) => {
if (_.isObject(datum)) {
return isValidOption(datum);
} else {
return false;
}
});
const validOptions = parsed.filter(isValidOption);
const uniqValidOptions = _.uniqBy(validOptions, "value");
if (!hasOptions || uniqValidOptions.length !== validOptions.length) {
return {
isValid: false,
parsed: uniqValidOptions,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Options Data`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.DATE]: (
dateString: string,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const today = moment()
.hour(0)
.minute(0)
.second(0)
.millisecond(0);
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const todayDateString = today.format(dateFormat);
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
: "",
};
}
const isValid = moment(dateString, dateFormat).isValid();
const parsed = isValid ? dateString : todayDateString;
return {
isValid,
parsed,
message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
};
},
[VALIDATION_TYPES.ACTION_SELECTOR]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
if (Array.isArray(value) && value.length) {
return {
isValid: true,
parsed: undefined,
transformed: "Function Call",
};
}
/*
if (_.isString(value)) {
if (value.indexOf("navigateTo") !== -1) {
const pageNameOrUrl = modalGetter(value);
if (dataTree) {
if (isDynamicValue(pageNameOrUrl)) {
return {
isValid: true,
parsed: value,
};
}
const isPage =
(dataTree.pageList as PageListPayload).findIndex(
page => page.pageName === pageNameOrUrl,
) !== -1;
const isValidUrl = URL_REGEX.test(pageNameOrUrl);
if (!(isValidUrl || isPage)) {
return {
isValid: false,
parsed: value,
message: `${NAVIGATE_TO_VALIDATION_ERROR}`,
};
}
}
}
}
*/
return {
isValid: false,
parsed: undefined,
transformed: "undefined",
message: "Not a function call",
};
},
[VALIDATION_TYPES.ARRAY_ACTION_SELECTOR]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed, message } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
let isValidFinal = isValid;
let finalParsed = parsed.slice();
if (isValid) {
finalParsed = parsed.map((value: any) => {
const { isValid, message } = VALIDATORS[
VALIDATION_TYPES.ACTION_SELECTOR
](value.dynamicTrigger, props, dataTree);
isValidFinal = isValidFinal && isValid;
return {
...value,
message: message,
isValid: isValid,
};
});
}
return {
isValid: isValidFinal,
parsed: finalParsed,
message: message,
};
},
[VALIDATION_TYPES.SELECTED_TAB]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const tabs =
props.tabs && _.isString(props.tabs)
? JSON.parse(props.tabs)
: props.tabs && Array.isArray(props.tabs)
? props.tabs
: [];
const tabNames = tabs.map((i: { label: string; id: string }) => i.label);
const isValidTabName = tabNames.includes(value);
return {
isValid: isValidTabName,
parsed: value,
message: isValidTabName
? ""
: `${WIDGET_TYPE_VALIDATION_ERROR}: Invalid tab name.`,
};
},
};

View File

@ -8,7 +8,7 @@ import {
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "./ValidationFactory";
} from "./WidgetValidation";
import React from "react";
type WidgetDerivedPropertyType = any;
@ -142,8 +142,33 @@ class WidgetFactory {
}
return map;
}
static getWidgetTypeConfigMap(): WidgetTypeConfigMap {
const typeConfigMap: WidgetTypeConfigMap = {};
WidgetFactory.getWidgetTypes().forEach(type => {
typeConfigMap[type] = {
validations: WidgetFactory.getWidgetPropertyValidationMap(type),
defaultProperties: WidgetFactory.getWidgetDefaultPropertiesMap(type),
derivedProperties: WidgetFactory.getWidgetDerivedPropertiesMap(type),
triggerProperties: WidgetFactory.getWidgetTriggerPropertiesMap(type),
metaProperties: WidgetFactory.getWidgetMetaPropertiesMap(type),
};
});
return typeConfigMap;
}
}
export type WidgetTypeConfigMap = Record<
string,
{
validations: WidgetPropertyValidationType;
derivedProperties: WidgetDerivedPropertyType;
triggerProperties: TriggerPropertiesMap;
defaultProperties: Record<string, string>;
metaProperties: Record<string, any>;
}
>;
export interface WidgetCreationException {
message: string;
}

View File

@ -80,6 +80,10 @@ import IconWidget, {
} from "widgets/IconWidget";
import CanvasWidget, { ProfiledCanvasWidget } from "widgets/CanvasWidget";
import SkeletonWidget, {
ProfiledSkeletonWidget,
SkeletonWidgetProps,
} from "../widgets/SkeletonWidget";
export default class WidgetBuilderRegistry {
static registerWidgetBuilders() {
WidgetFactory.registerWidgetBuilder(
@ -376,5 +380,19 @@ export default class WidgetBuilderRegistry {
IconWidget.getDefaultPropertiesMap(),
IconWidget.getMetaPropertiesMap(),
);
WidgetFactory.registerWidgetBuilder(
WidgetTypes.SKELETON_WIDGET,
{
buildWidget(widgetProps: SkeletonWidgetProps): JSX.Element {
return <ProfiledSkeletonWidget {...widgetProps} />;
},
},
SkeletonWidget.getPropertyValidationMap(),
SkeletonWidget.getDerivedPropertiesMap(),
SkeletonWidget.getTriggerPropertyMap(),
SkeletonWidget.getDefaultPropertiesMap(),
SkeletonWidget.getMetaPropertiesMap(),
);
}
}

View File

@ -0,0 +1,16 @@
import {
VALIDATION_TYPES,
ValidationType,
Validator,
} from "constants/WidgetValidation";
export const BASE_WIDGET_VALIDATION = {
isLoading: VALIDATION_TYPES.BOOLEAN,
isVisible: VALIDATION_TYPES.BOOLEAN,
isDisabled: VALIDATION_TYPES.BOOLEAN,
};
export type WidgetPropertyValidationType = Record<
string,
ValidationType | Validator
>;

View File

@ -1,6 +1,5 @@
import TernServer from "./TernServer";
import { MockCodemirrorEditor } from "../../../test/__mocks__/CodeMirrorEditorMock";
jest.mock("jsExecution/RealmExecutor");
describe("Tern server", () => {
it("Check whether the correct value is being sent to tern", () => {
@ -96,7 +95,7 @@ describe("Tern server", () => {
});
});
it(`Check whether the position is evaluated correctly for placing the selected
it(`Check whether the position is evaluated correctly for placing the selected
autocomplete value`, () => {
const ternServer = new TernServer({});

View File

@ -28,7 +28,7 @@ import ErrorBoundary from "components/editorComponents/ErrorBoundry";
import {
BASE_WIDGET_VALIDATION,
WidgetPropertyValidationType,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import {
DerivedPropertiesMap,
TriggerPropertiesMap,
@ -325,6 +325,23 @@ export interface WidgetPositionProps extends WidgetRowCols {
detachFromLayout?: boolean;
}
export const WIDGET_STATIC_PROPS = {
leftColumn: true,
rightColumn: true,
topRow: true,
bottomRow: true,
minHeight: true,
parentColumnSpace: true,
parentRowSpace: true,
children: true,
type: true,
widgetId: true,
widgetName: true,
parentId: true,
renderMode: true,
detachFromLayout: true,
};
export interface WidgetDisplayProps {
//TODO(abhinav): Some of these props are mandatory
isVisible?: boolean;

View File

@ -8,7 +8,7 @@ import { EventType } from "constants/ActionConstants";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import * as Sentry from "@sentry/react";

View File

@ -1,7 +1,7 @@
import React, { lazy, Suspense } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { WidgetPropertyValidationType } from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import Skeleton from "components/utils/Skeleton";
import * as Sentry from "@sentry/react";

View File

@ -7,7 +7,7 @@ import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import {
TriggerPropertiesMap,
DerivedPropertiesMap,

View File

@ -6,7 +6,7 @@ import DatePickerComponent from "components/designSystems/blueprint/DatePickerCo
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
DerivedPropertiesMap,

View File

@ -7,11 +7,9 @@ import _ from "lodash";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import { VALIDATORS } from "utils/Validators";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import { Intent as BlueprintIntent } from "@blueprintjs/core";
import * as Sentry from "@sentry/react";
import withMeta, { WithMeta } from "./MetaHOC";
@ -28,42 +26,7 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
// onOptionChange: VALIDATION_TYPES.ACTION_SELECTOR,
selectedOptionValueArr: VALIDATION_TYPES.ARRAY,
selectedOptionValues: VALIDATION_TYPES.ARRAY,
defaultOptionValue: (
value: string | string[],
props: WidgetProps,
dataTree?: DataTree,
) => {
let values = value;
if (props) {
if (props.selectionType === "SINGLE_SELECT") {
return VALIDATORS[VALIDATION_TYPES.TEXT](value, props, dataTree);
} else if (props.selectionType === "MULTI_SELECT") {
if (typeof value === "string") {
try {
values = JSON.parse(value);
if (!Array.isArray(values)) {
throw new Error();
}
} catch {
values = value.length ? value.split(",") : [];
if (values.length > 0) {
values = values.map(value => value.trim());
}
}
}
}
}
if (Array.isArray(values)) {
values = _.uniq(values);
}
return {
isValid: true,
parsed: values,
};
},
defaultOptionValue: VALIDATION_TYPES.DEFAULT_OPTION_VALUE,
};
}

View File

@ -10,7 +10,7 @@ import OneDrive from "@uppy/onedrive";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { EventType, ExecutionResult } from "constants/ActionConstants";
import {

View File

@ -8,7 +8,7 @@ import { EventType, ExecutionResult } from "constants/ActionConstants";
import {
BASE_WIDGET_VALIDATION,
WidgetPropertyValidationType,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import * as Sentry from "@sentry/react";

View File

@ -11,8 +11,12 @@ import withMeta from "./MetaHOC";
class FormWidget extends ContainerWidget {
checkInvalidChildren = (children: WidgetProps[]): boolean => {
return _.some(children, child => {
if ("children" in child) return this.checkInvalidChildren(child.children);
if ("isValid" in child) return !child.isValid;
if ("children" in child) {
return this.checkInvalidChildren(child.children);
}
if ("isValid" in child) {
return !child.isValid;
}
return false;
});
};

View File

@ -1,7 +1,6 @@
// import React from "react";
// import { render, fireEvent } from "@testing-library/react";
// import ImageWidget, { ImageWidgetProps } from "./ImageWidget";
// import RealmExecutor from "../jsExecution/RealmExecutor";
// import { useDrag } from "react-dnd";
// import { Provider } from "react-redux";
@ -10,8 +9,6 @@
// import "@testing-library/jest-dom";
// jest.mock("jsExecution/RealmExecutor");
// jest.mock("react-dnd", () => ({
// useDrag: jest.fn().mockReturnValue([{ isDragging: false }, jest.fn()]),
// }));

View File

@ -5,7 +5,7 @@ import ImageComponent from "components/designSystems/appsmith/ImageComponent";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import * as Sentry from "@sentry/react";
import { EventType } from "constants/ActionConstants";

View File

@ -8,7 +8,7 @@ import { EventType } from "constants/ActionConstants";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { FIELD_REQUIRED_ERROR } from "constants/messages";
import {

View File

@ -2,7 +2,7 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import MapComponent from "components/designSystems/appsmith/MapComponent";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { WidgetPropertyValidationType } from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { EventType } from "constants/ActionConstants";
import { TriggerPropertiesMap } from "utils/WidgetFactory";

View File

@ -2,13 +2,15 @@ import React from "react";
import BaseWidget, { WidgetProps } from "./BaseWidget";
import _ from "lodash";
import { EditorContext } from "../components/editorComponents/EditorContextProvider";
import { clearPropertyCache } from "../utils/DynamicBindingUtils";
import { clearEvalPropertyCache } from "sagas/evaluationsSaga";
import { ExecuteActionPayload } from "../constants/ActionConstants";
type DebouncedExecuteActionPayload = Omit<
ExecuteActionPayload,
"dynamicString"
> & { dynamicString?: string };
> & {
dynamicString?: string;
};
export interface WithMeta {
updateWidgetMetaProperty: (
@ -87,7 +89,7 @@ const withMeta = (WrappedWidget: typeof BaseWidget) => {
[...this.updatedProperties.keys()].forEach(propertyName => {
if (updateWidgetMetaProperty) {
const propertyValue = this.state[propertyName];
clearPropertyCache(`${widgetName}.${propertyName}`);
clearEvalPropertyCache(`${widgetName}.${propertyName}`);
updateWidgetMetaProperty(widgetId, propertyName, propertyValue);
this.updatedProperties.delete(propertyName);
}

View File

@ -6,7 +6,7 @@ import { EventType } from "constants/ActionConstants";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import * as Sentry from "@sentry/react";

View File

@ -2,7 +2,7 @@ import React, { lazy, Suspense } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/ActionConstants";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { WidgetPropertyValidationType } from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
TriggerPropertiesMap,

View File

@ -0,0 +1,27 @@
import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import * as Sentry from "@sentry/react";
import styled from "styled-components";
const SkeletonWrapper = styled.div`
height: 100%;
width: 100%;
`;
class SkeletonWidget extends BaseWidget<SkeletonWidgetProps, WidgetState> {
getPageView() {
return <SkeletonWrapper className="bp3-skeleton" />;
}
getWidgetType(): WidgetType {
return "SKELETON_WIDGET";
}
}
export interface SkeletonWidgetProps extends WidgetProps {
isLoading: boolean;
}
export default SkeletonWidget;
export const ProfiledSkeletonWidget = Sentry.withProfiler(SkeletonWidget);

View File

@ -15,7 +15,7 @@ import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
BASE_WIDGET_VALIDATION,
WidgetPropertyValidationType,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import Skeleton from "components/utils/Skeleton";

View File

@ -3,7 +3,7 @@ import TabsComponent from "components/designSystems/appsmith/TabsComponent";
import { WidgetType, WidgetTypes } from "constants/WidgetConstants";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import WidgetFactory, { TriggerPropertiesMap } from "utils/WidgetFactory";
import { WidgetPropertyValidationType } from "utils/ValidationFactory";
import { WidgetPropertyValidationType } from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import _ from "lodash";
import { EventType } from "constants/ActionConstants";

View File

@ -6,7 +6,7 @@ import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { DerivedPropertiesMap } from "utils/WidgetFactory";
import * as Sentry from "@sentry/react";

View File

@ -6,7 +6,7 @@ import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/ValidationFactory";
} from "utils/WidgetValidation";
import { TriggerPropertiesMap } from "utils/WidgetFactory";
import Skeleton from "components/utils/Skeleton";
import * as Sentry from "@sentry/react";

File diff suppressed because it is too large Load Diff

View File

@ -11,9 +11,3 @@ export const mockExecute = jest.fn().mockImplementation((src, data) => {
});
export const mockRegisterLibrary = jest.fn();
// jest.mock("jsExecution/RealmExecutor", () => {
// jest.fn().mockImplementation(() => {
// return { execute: mockExecute, registerLibrary: mockRegisterLibrary };
// });
// });

View File

@ -0,0 +1,7 @@
declare module "worker-loader!*" {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}

View File

@ -1836,10 +1836,10 @@
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@jest/types@^26.3.0":
version "26.3.0"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71"
integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==
"@jest/types@^26.5.2":
version "26.5.2"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.5.2.tgz#44c24f30c8ee6c7f492ead9ec3f3c62a5289756d"
integrity sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
@ -1969,9 +1969,9 @@
uuid "^3.3.2"
"@optimizely/optimizely-sdk@^4.0.0":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@optimizely/optimizely-sdk/-/optimizely-sdk-4.3.3.tgz#91db4072f8e439d997370ad48c25106610f2e4a3"
integrity sha512-tz6GyJMM4TUpLTsoyO9ZKNB5/UswtKSF4jLlBG1N6hWVi+bCJUa25M4Ok6DA0izZ2k0Y2xmUQDVmV4p23Li5Gw==
version "4.3.4"
resolved "https://registry.yarnpkg.com/@optimizely/optimizely-sdk/-/optimizely-sdk-4.3.4.tgz#b323b91dc8af9656dde8bcf696801bd71443e202"
integrity sha512-DqaEg9YwiwnfDjaDmbST2cu0/7W/yQJqQ+tBwIEwh/HqiSgs8oQJX7sNG2Ql2fFwlIzG7APkIx/oxwbXpp8LPg==
dependencies:
"@optimizely/js-sdk-datafile-manager" "^0.8.0"
"@optimizely/js-sdk-event-processor" "^0.6.0"
@ -2074,14 +2074,14 @@
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.1.0.tgz#b84b4a91cd938a688d36245b7a7db6fbc476a499"
integrity sha512-b2iE8kjjzzUo2WZ0xuE2N77kfnTds7ClrDxcz3Atz7h2XrNVoAPUoT75i7CY0st5x++70V91Y+c6RpBX9MX7Jg==
"@sentry/browser@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.25.0.tgz#4e3d2132ba1f2e2b26f73c49cbb6977ee9c9fea9"
integrity sha512-QDVUbUuTu58xCdId0eUO4YzpvrPdoUw1ryVy/Yep9Es/HD0fiSyO1Js0eQVkV/EdXtyo2pomc1Bpy7dbn2EJ2w==
"@sentry/browser@5.26.0":
version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.26.0.tgz#e90a197fb94c5f26c8e05d6a539c118f33c7d598"
integrity sha512-52kNVpy10Zd3gJRGFkhnOQvr80WJg7+XBqjMOE0//Akh4PfvEK3IqmAjVqysz6aHdruwTTivKF4ZoAxL/pA7Rg==
dependencies:
"@sentry/core" "5.25.0"
"@sentry/types" "5.25.0"
"@sentry/utils" "5.25.0"
"@sentry/core" "5.26.0"
"@sentry/types" "5.26.0"
"@sentry/utils" "5.26.0"
tslib "^1.9.3"
"@sentry/cli@^1.58.0":
@ -2095,69 +2095,69 @@
progress "^2.0.3"
proxy-from-env "^1.1.0"
"@sentry/core@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.25.0.tgz#525ad37f9e8a95603768e3b74b437d5235a51578"
integrity sha512-hY6Zmo7t/RV+oZuvXHP6nyAj/QnZr2jW0e7EbL5YKMV8q0vlnjcE0LgqFXme726OJemoLk67z+sQOJic/Ztehg==
"@sentry/core@5.26.0":
version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.26.0.tgz#9b5fe4de8a869d733ebcc77f5ec9c619f8717a51"
integrity sha512-Ubrw7K52orTVsaxpz8Su40FPXugKipoQC+zPrXcH+JIMB+o18kutF81Ae4WzuUqLfP7YB91eAlRrP608zw0EXA==
dependencies:
"@sentry/hub" "5.25.0"
"@sentry/minimal" "5.25.0"
"@sentry/types" "5.25.0"
"@sentry/utils" "5.25.0"
"@sentry/hub" "5.26.0"
"@sentry/minimal" "5.26.0"
"@sentry/types" "5.26.0"
"@sentry/utils" "5.26.0"
tslib "^1.9.3"
"@sentry/hub@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.25.0.tgz#6932535604cafaee1ac7f361b0e7c2ce8f7e7bc3"
integrity sha512-kOlOiJV8wMX50lYpzMlOXBoH7MNG0Ho4RTusdZnXZBaASq5/ljngDJkLr6uylNjceZQP21wzipCQajsJMYB7EQ==
"@sentry/hub@5.26.0":
version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.26.0.tgz#b2bbd8128cd5915f2ee59cbc29fff30272d74ec5"
integrity sha512-lAYeWvvhGYS6eQ5d0VEojw0juxGc3v4aAu8VLvMKWcZ1jXD13Bhc46u9Nvf4qAY6BAQsJDQcpEZLpzJu1bk1Qw==
dependencies:
"@sentry/types" "5.25.0"
"@sentry/utils" "5.25.0"
"@sentry/types" "5.26.0"
"@sentry/utils" "5.26.0"
tslib "^1.9.3"
"@sentry/minimal@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.25.0.tgz#447b5406b45c8c436c461abea4474d6a849ed975"
integrity sha512-9JFKuW7U+1vPO86k3+XRtJyooiVZsVOsFFO4GulBzepi3a0ckNyPgyjUY1saLH+cEHx18hu8fGgajvI8ANUF2g==
"@sentry/minimal@5.26.0":
version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.26.0.tgz#851dea3644153ed3ac4837fa8ed5661d94e7a313"
integrity sha512-mdFo3FYaI1W3KEd8EHATYx8mDOZIxeoUhcBLlH7Iej6rKvdM7p8GoECrmHPU1l6sCCPtBuz66QT5YeXc7WILsA==
dependencies:
"@sentry/hub" "5.25.0"
"@sentry/types" "5.25.0"
"@sentry/hub" "5.26.0"
"@sentry/types" "5.26.0"
tslib "^1.9.3"
"@sentry/react@^5.24.2":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.25.0.tgz#269f54db9d6f92410bee07117f8d8e03b219e068"
integrity sha512-lZwiFj+BQtmaj+Do9hcRSJcdrTisSGq2521/Xm9qGPbhsRW8uTHMJjkDgMHriYxxqXYOQrY9FisJwvkPpkroow==
version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.26.0.tgz#0462430757ac0ab0c10de803f39fb41d5ced1caa"
integrity sha512-oC1wwfhckV8HHJTs4Zot5JIwEftcltPuC8cOedenDor5SKKbMeNufKw0ZgW82j9DSZMjh053LKkIZmO7zYf8eQ==
dependencies:
"@sentry/browser" "5.25.0"
"@sentry/minimal" "5.25.0"
"@sentry/types" "5.25.0"
"@sentry/utils" "5.25.0"
"@sentry/browser" "5.26.0"
"@sentry/minimal" "5.26.0"
"@sentry/types" "5.26.0"
"@sentry/utils" "5.26.0"
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/tracing@^5.24.2":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.25.0.tgz#1cfbcf085a7a3b679f417058d09590298ddaa255"
integrity sha512-KcyHEGFpqSDubHrdWT/vF2hKkjw/ts6NpJ6tPDjBXUNz98BHdAyMKtLOFTCeJFply7/s5fyiAYu44M+M6IG3Bw==
version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.26.0.tgz#33ee0426da14836e54e7b9a8838e4d7d0cb14b70"
integrity sha512-N9qWGmKrFJYKFTZBe8zVT3Qiju0+9bbNJuyun69T+fqP3PCDh+aRlRiP+OKTJyeCZjNG5HIvIlU8wTVUDoYfjQ==
dependencies:
"@sentry/hub" "5.25.0"
"@sentry/minimal" "5.25.0"
"@sentry/types" "5.25.0"
"@sentry/utils" "5.25.0"
"@sentry/hub" "5.26.0"
"@sentry/minimal" "5.26.0"
"@sentry/types" "5.26.0"
"@sentry/utils" "5.26.0"
tslib "^1.9.3"
"@sentry/types@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.25.0.tgz#3bcf95e118d655d3f4e8bfa5f0be2e1fe4ea5307"
integrity sha512-8M4PREbcar+15wrtEqcwfcU33SS+2wBSIOd/NrJPXJPTYxi49VypCN1mZBDyWkaK+I+AuQwI3XlRPCfsId3D1A==
"@sentry/types@5.26.0":
version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.26.0.tgz#b0cbacb0b24cd86620fb296b46cf7277bb004a3e"
integrity sha512-ugpa1ePOhK55pjsyutAsa2tiJVQEyGYCaOXzaheg/3+EvhMdoW+owiZ8wupfvPhtZFIU3+FPOVz0d5k9K5d1rw==
"@sentry/utils@5.25.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.25.0.tgz#b132034be66d7381d30879d2a9e09216fed28342"
integrity sha512-Hz5spdIkMSRH5NR1YFOp5qbsY5Ud2lKhEQWlqxcVThMG5YNUc10aYv5ijL19v0YkrC2rqPjCRm7GrVtzOc7bXQ==
"@sentry/utils@5.26.0":
version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.26.0.tgz#09a3d01d91747f38f796cafeb24f8fd86e4fa05f"
integrity sha512-F2gnHIAWbjiowcAgxz3VpKxY/NQ39NTujEd/NPnRTWlRynLFg3bAV+UvZFXljhYJeN3b/zRlScNDcpCWTrtZGw==
dependencies:
"@sentry/types" "5.25.0"
"@sentry/types" "5.26.0"
tslib "^1.9.3"
"@sentry/webpack-plugin@^1.12.1":
@ -2871,9 +2871,9 @@
loader-utils "^1.2.3"
"@testing-library/dom@^7.24.2":
version "7.24.3"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.3.tgz#dae3071463cf28dc7755b43d9cf2202e34cbb85d"
integrity sha512-6eW9fUhEbR423FZvoHRwbWm9RUUByLWGayYFNVvqTnQLYvsNpBS4uEuKH9aqr3trhxFwGVneJUonehL3B1sHJw==
version "7.26.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.26.0.tgz#da4d052dc426a4ccc916303369c6e7552126f680"
integrity sha512-fyKFrBbS1IigaE3FV21LyeC7kSGF84lqTlSYdKmGaHuK2eYQ/bXVPM5vAa2wx/AU1iPD6oQHsxy2QQ17q9AMCg==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.10.3"
@ -2881,6 +2881,7 @@
aria-query "^4.2.2"
chalk "^4.1.0"
dom-accessibility-api "^0.5.1"
lz-string "^1.4.4"
pretty-format "^26.4.2"
"@testing-library/jest-dom@^5.11.4":
@ -3016,9 +3017,9 @@
"@types/node" "*"
"@types/googlemaps@^3.39.6":
version "3.39.14"
resolved "https://registry.yarnpkg.com/@types/googlemaps/-/googlemaps-3.39.14.tgz#971a7b474e7dcf6af1019fdccb91a69f74c5b262"
integrity sha512-MB8zwqarykWxCayoWcQAeKWpFTgKlCz7Z+PpFeEimAe4keQ1o1Q3Y/5r5Td52gd164QmTUHGxGlKkmUEVrdbHA==
version "3.40.0"
resolved "https://registry.yarnpkg.com/@types/googlemaps/-/googlemaps-3.40.0.tgz#786743648ab464fbeaa4cfe15230a7297273b309"
integrity sha512-KcAYVKjd5fL0Ur9G4xNL5YG/Bp5HFfdd8s/7j97eFcTyTpp7vIRcf8mtRBAIOM3QNgN2iJhSEecWTG2x8D+bnQ==
"@types/hast@^2.0.0":
version "2.3.1"
@ -3097,15 +3098,15 @@
dependencies:
jest-diff "^24.3.0"
"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5":
"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
version "7.0.6"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
"@types/lodash@^4.14.120":
version "4.14.161"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18"
integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA==
version "4.14.162"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.162.tgz#65d78c397e0d883f44afbf1f7ba9867022411470"
integrity sha512-alvcho1kRUnnD1Gcl4J+hK0eencvzq9rmzvFPRmP5rPHx9VVsJj6bKLTATPVf9ktgv4ujzh7T+XWKp+jhuODig==
"@types/mdast@^3.0.0":
version "3.0.3"
@ -3134,14 +3135,14 @@
"@types/node" "*"
"@types/node@*":
version "14.11.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.5.tgz#fecad41c041cae7f2404ad4b2d0742fdb628b305"
integrity sha512-jVFzDV6NTbrLMxm4xDSIW/gKnk8rQLF9wAzLWIOg+5nU6ACrIMndeBdXci0FGtqJbP9tQvm6V39eshc96TO2wQ==
version "14.11.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.8.tgz#fe2012f2355e4ce08bca44aeb3abbb21cf88d33f"
integrity sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==
"@types/node@^10.12.18":
version "10.17.37"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.37.tgz#40d03db879993799c3819e298b003f055e8ecafe"
integrity sha512-4c38N7p9k9yqdcANh/WExTahkBgOTmggCyrTvVcbE8ByqO3g8evt/407v/I4X/gdfUkIyZBSQh/Rc3tvuwlVGw==
version "10.17.39"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.39.tgz#ce1122758d0608de8303667cebf171f44192629b"
integrity sha512-dJLCxrpQmgyxYGcl0Ae9MTsQgI22qHHcGFj/8VKu7McJA5zQpnuGjoksnxbo1JxSjW/Nahnl13W8MYZf01CZHA==
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
@ -3266,9 +3267,9 @@
"@types/react" "*"
"@types/react-select@^3.0.5":
version "3.0.21"
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.21.tgz#089e1caa98dd653347cf4057b087957d26580d67"
integrity sha512-jVtukxaARMQCJC74ikGzpxrzxDFPkcGEwLsFIsc/bAn2rcBUlJ0WgP71ShUc/Q0nnk4ClZn2jBm2tbh6HtxB3A==
version "3.0.22"
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.22.tgz#b88306365e99fa86809a5c0ce0f1b4e8d0b626bf"
integrity sha512-fqgmC979JPr/6476Pau6QnmI9zVV664R7Q92Ld1rgTn+umtUXT5X3+PO/x6O4imCZnh7XCqZcouabWAlAQJNpQ==
dependencies:
"@types/react" "*"
"@types/react-dom" "*"
@ -3310,9 +3311,9 @@
"@types/react" "*"
"@types/react@*", "@types/react@^16.8.2":
version "16.9.51"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.51.tgz#f8aa51ffa9996f1387f63686696d9b59713d2b60"
integrity sha512-lQa12IyO+DMlnSZ3+AGHRUiUcpK47aakMMoBG8f7HGxJT8Yfe+WE128HIXaHOHVPReAW0oDS3KAI0JI2DDe1PQ==
version "16.9.52"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.52.tgz#c46c72d1a1d8d9d666f4dd2066c0e22600ccfde1"
integrity sha512-EHRjmnxiNivwhGdMh9sz1Yw9AUxTSZFxKqdBWAAzyZx3sufWwx6ogqHYh/WB1m/I4ZpjkoZLExF5QTy2ekVi/Q==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
@ -3372,9 +3373,9 @@
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
"@types/styled-components@^5.1.3":
version "5.1.3"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.3.tgz#6fab3d9c8f7d9a15cbb89d379d850c985002f363"
integrity sha512-HGpirof3WOhiX17lb61Q/tpgqn48jxO8EfZkdJ8ueYqwLbK2AHQe/G08DasdA2IdKnmwOIP1s9X2bopxKXgjRw==
version "5.1.4"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.4.tgz#11f167dbde268635c66adc89b5a5db2e69d75384"
integrity sha512-78f5Zuy0v/LTQNOYfpH+CINHpchzMMmAt9amY2YNtSgsk1TmlKm8L2Wijss/mtTrsUAVTm2CdGB8VOM65vA8xg==
dependencies:
"@types/hoist-non-react-statics" "*"
"@types/react" "*"
@ -3475,44 +3476,35 @@
"@types/yargs-parser" "*"
"@types/yargs@^15.0.0":
version "15.0.7"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.7.tgz#dad50a7a234a35ef9460737a56024287a3de1d2b"
integrity sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==
version "15.0.8"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.8.tgz#7644904cad7427eb704331ea9bf1ee5499b82e23"
integrity sha512-b0BYzFUzBpOhPjpl1wtAHU994jBeKF4TKVlT7ssFv44T617XNcPdRoG4AzHLVshLzlrF7i3lTelH7UbuNYV58Q==
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^2.10.0":
version "2.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.19.0.tgz#bf743448a4633e4b52bee0c40148ba072ab3adbd"
version "2.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9"
integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==
dependencies:
"@typescript-eslint/experimental-utils" "2.19.0"
eslint-utils "^1.4.3"
"@typescript-eslint/experimental-utils" "2.34.0"
functional-red-black-tree "^1.0.1"
regexpp "^3.0.0"
tsutils "^3.17.1"
"@typescript-eslint/eslint-plugin@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.0.tgz#0321684dd2b902c89128405cf0385e9fe8561934"
integrity sha512-RVt5wU9H/2H+N/ZrCasTXdGbUTkbf7Hfi9eLiA8vPQkzUJ/bLDCC3CsoZioPrNcnoyN8r0gT153dC++A4hKBQQ==
version "4.4.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.1.tgz#b8acea0373bd2a388ac47df44652f00bf8b368f5"
integrity sha512-O+8Utz8pb4OmcA+Nfi5THQnQpHSD2sDUNw9AxNHpuYOo326HZTtG8gsfT+EAYuVrFNaLyNb2QnUNkmTRDskuRA==
dependencies:
"@typescript-eslint/experimental-utils" "4.4.0"
"@typescript-eslint/scope-manager" "4.4.0"
"@typescript-eslint/experimental-utils" "4.4.1"
"@typescript-eslint/scope-manager" "4.4.1"
debug "^4.1.1"
functional-red-black-tree "^1.0.1"
regexpp "^3.0.0"
semver "^7.3.2"
tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@2.19.0":
version "2.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.0.tgz#d5ca732f22c009e515ba09fcceb5f2127d841568"
integrity sha512-zwpg6zEOPbhB3+GaQfufzlMUOO6GXCNZq6skk+b2ZkZAIoBhVoanWK255BS1g5x9bMwHpLhX0Rpn5Fc3NdCZdg==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.19.0"
eslint-scope "^5.0.0"
"@typescript-eslint/experimental-utils@2.34.0":
version "2.34.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f"
@ -3523,15 +3515,15 @@
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"
"@typescript-eslint/experimental-utils@4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.0.tgz#62a05d3f543b8fc5dec4982830618ea4d030e1a9"
integrity sha512-01+OtK/oWeSJTjQcyzDztfLF1YjvKpLFo+JZmurK/qjSRcyObpIecJ4rckDoRCSh5Etw+jKfdSzVEHevh9gJ1w==
"@typescript-eslint/experimental-utils@4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.1.tgz#40613b9757fa0170de3e0043254dbb077cafac0c"
integrity sha512-Nt4EVlb1mqExW9cWhpV6pd1a3DkUbX9DeyYsdoeziKOpIJ04S2KMVDO+SEidsXRH/XHDpbzXykKcMTLdTXH6cQ==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/scope-manager" "4.4.0"
"@typescript-eslint/types" "4.4.0"
"@typescript-eslint/typescript-estree" "4.4.0"
"@typescript-eslint/scope-manager" "4.4.1"
"@typescript-eslint/types" "4.4.1"
"@typescript-eslint/typescript-estree" "4.4.1"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"
@ -3546,40 +3538,27 @@
eslint-visitor-keys "^1.1.0"
"@typescript-eslint/parser@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.4.0.tgz#65974db9a75f23b036f17b37e959b5f99b659ec0"
integrity sha512-yc14iEItCxoGb7W4Nx30FlTyGpU9r+j+n1LUK/exlq2eJeFxczrz/xFRZUk2f6yzWfK+pr1DOTyQnmDkcC4TnA==
version "4.4.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.4.1.tgz#25fde9c080611f303f2f33cedb145d2c59915b80"
integrity sha512-S0fuX5lDku28Au9REYUsV+hdJpW/rNW0gWlc4SXzF/kdrRaAVX9YCxKpziH7djeWT/HFAjLZcnY7NJD8xTeUEg==
dependencies:
"@typescript-eslint/scope-manager" "4.4.0"
"@typescript-eslint/types" "4.4.0"
"@typescript-eslint/typescript-estree" "4.4.0"
"@typescript-eslint/scope-manager" "4.4.1"
"@typescript-eslint/types" "4.4.1"
"@typescript-eslint/typescript-estree" "4.4.1"
debug "^4.1.1"
"@typescript-eslint/scope-manager@4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.4.0.tgz#2f3dd27692a12cc9a046a90ba6a9d8cb7731190a"
integrity sha512-r2FIeeU1lmW4K3CxgOAt8djI5c6Q/5ULAgdVo9AF3hPMpu0B14WznBAtxrmB/qFVbVIB6fSx2a+EVXuhSVMEyA==
"@typescript-eslint/scope-manager@4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.4.1.tgz#d19447e60db2ce9c425898d62fa03b2cce8ea3f9"
integrity sha512-2oD/ZqD4Gj41UdFeWZxegH3cVEEH/Z6Bhr/XvwTtGv66737XkR4C9IqEkebCuqArqBJQSj4AgNHHiN1okzD/wQ==
dependencies:
"@typescript-eslint/types" "4.4.0"
"@typescript-eslint/visitor-keys" "4.4.0"
"@typescript-eslint/types" "4.4.1"
"@typescript-eslint/visitor-keys" "4.4.1"
"@typescript-eslint/types@4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.4.0.tgz#63440ef87a54da7399a13bdd4b82060776e9e621"
integrity sha512-nU0VUpzanFw3jjX+50OTQy6MehVvf8pkqFcURPAE06xFNFenMj1GPEI6IESvp7UOHAnq+n/brMirZdR+7rCrlA==
"@typescript-eslint/typescript-estree@2.19.0":
version "2.19.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.0.tgz#6bd7310b9827e04756fe712909f26956aac4b196"
integrity sha512-n6/Xa37k0jQdwpUszffi19AlNbVCR0sdvCs3DmSKMD7wBttKY31lhD2fug5kMD91B2qW4mQldaTEc1PEzvGu8w==
dependencies:
debug "^4.1.1"
eslint-visitor-keys "^1.1.0"
glob "^7.1.6"
is-glob "^4.0.1"
lodash "^4.17.15"
semver "^6.3.0"
tsutils "^3.17.1"
"@typescript-eslint/types@4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.4.1.tgz#c507b35cf523bc7ba00aae5f75ee9b810cdabbc1"
integrity sha512-KNDfH2bCyax5db+KKIZT4rfA8rEk5N0EJ8P0T5AJjo5xrV26UAzaiqoJCxeaibqc0c/IvZxp7v2g3difn2Pn3w==
"@typescript-eslint/typescript-estree@2.34.0":
version "2.34.0"
@ -3594,13 +3573,13 @@
semver "^7.3.2"
tsutils "^3.17.1"
"@typescript-eslint/typescript-estree@4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.0.tgz#16a2df7c16710ddd5406b32b86b9c1124b1ca526"
integrity sha512-Fh85feshKXwki4nZ1uhCJHmqKJqCMba+8ZicQIhNi5d5jSQFteWiGeF96DTjO8br7fn+prTP+t3Cz/a/3yOKqw==
"@typescript-eslint/typescript-estree@4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.1.tgz#598f6de488106c2587d47ca2462c60f6e2797cb8"
integrity sha512-wP/V7ScKzgSdtcY1a0pZYBoCxrCstLrgRQ2O9MmCUZDtmgxCO/TCqOTGRVwpP4/2hVfqMz/Vw1ZYrG8cVxvN3g==
dependencies:
"@typescript-eslint/types" "4.4.0"
"@typescript-eslint/visitor-keys" "4.4.0"
"@typescript-eslint/types" "4.4.1"
"@typescript-eslint/visitor-keys" "4.4.1"
debug "^4.1.1"
globby "^11.0.1"
is-glob "^4.0.1"
@ -3608,12 +3587,12 @@
semver "^7.3.2"
tsutils "^3.17.1"
"@typescript-eslint/visitor-keys@4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.0.tgz#0a9118344082f14c0f051342a74b42dfdb012640"
integrity sha512-oBWeroUZCVsHLiWRdcTXJB7s1nB3taFY8WGvS23tiAlT6jXVvsdAV4rs581bgdEjOhn43q6ro7NkOiLKu6kFqA==
"@typescript-eslint/visitor-keys@4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.1.tgz#1769dc7a9e2d7d2cfd3318b77ed8249187aed5c3"
integrity sha512-H2JMWhLaJNeaylSnMSQFEhT/S/FsJbebQALmoJxMPMxLtlVAMy2uJP/Z543n9IizhjRayLSqoInehCeNW9rWcw==
dependencies:
"@typescript-eslint/types" "4.4.0"
"@typescript-eslint/types" "4.4.1"
eslint-visitor-keys "^2.0.0"
"@uppy/companion-client@^1.4.1", "@uppy/companion-client@^1.5.4":
@ -4292,10 +4271,10 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4:
version "6.12.5"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
@ -4646,11 +4625,6 @@ ast-types-flow@0.0.7, ast-types-flow@^0.0.7:
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
ast-types@0.11.3:
version "0.11.3"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8"
integrity sha512-XA5o5dsNw8MhyW0Q7MWXJWc4oOzZKbdsEJq45h7c8q/d9DwWZ5F2ugUc1PuMLPGsUnphCt/cNDHu8JeBbxf1qA==
ast-types@0.13.3:
version "0.13.3"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.3.tgz#50da3f28d17bdbc7969a3a2d83a0e4a72ae755a7"
@ -4663,6 +4637,13 @@ ast-types@^0.13.2:
dependencies:
tslib "^2.0.1"
ast-types@^0.14.2:
version "0.14.2"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd"
integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==
dependencies:
tslib "^2.0.1"
astral-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
@ -4994,13 +4975,13 @@ babel-plugin-named-asset-import@^0.3.1, babel-plugin-named-asset-import@^0.3.6:
integrity sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA==
babel-plugin-react-docgen@^4.0.0, babel-plugin-react-docgen@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.0.tgz#4f425692f0ca06c73a1462274d370a3ac0637b46"
integrity sha512-B3tjZwKskcia9TsqkND+9OTjl/F5A5OBvRJ6Ktg34CONoxm+kB3CJ52wk5TjbszX9gqCPcAuc0GgkhT0CLuT/Q==
version "4.2.1"
resolved "https://registry.yarnpkg.com/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.1.tgz#7cc8e2f94e8dc057a06e953162f0810e4e72257b"
integrity sha512-UQ0NmGHj/HAqi5Bew8WvNfCk8wSsmdgNd8ZdMjBCICtyCJCq9LiqgqvjCYe570/Wg7AQArSq1VQ60Dd/CHN7mQ==
dependencies:
ast-types "^0.14.2"
lodash "^4.17.15"
react-docgen "^5.0.0"
recast "^0.14.7"
"babel-plugin-styled-components@>= 1", babel-plugin-styled-components@^1.10.7:
version "1.11.1"
@ -5685,9 +5666,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001135:
version "1.0.30001146"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001146.tgz#c61fcb1474520c1462913689201fb292ba6f447c"
integrity sha512-VAy5RHDfTJhpxnDdp2n40GPPLp3KqNrXz1QqFv4J64HvArKs8nuNMOWkB3ICOaBTU/Aj4rYAo/ytdQDDFF/Pug==
version "1.0.30001148"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001148.tgz#dc97c7ed918ab33bf8706ddd5e387287e015d637"
integrity sha512-E66qcd0KMKZHNJQt9hiLZGE3J4zuTqE1OnU53miEVtylFbwOEmeA5OsRu90noZful+XGSQOni1aT2tiqu/9yYw==
capture-exit@^2.0.0:
version "2.0.0"
@ -5829,9 +5810,9 @@ chokidar@^2.0.4, chokidar@^2.1.8:
fsevents "^1.2.7"
chokidar@^3.3.0, chokidar@^3.4.1:
version "3.4.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d"
integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==
version "3.4.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
@ -5839,7 +5820,7 @@ chokidar@^3.3.0, chokidar@^3.4.1:
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.4.0"
readdirp "~3.5.0"
optionalDependencies:
fsevents "~2.1.2"
@ -6077,21 +6058,21 @@ color-name@^1.0.0, color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.5.2:
version "1.5.3"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc"
integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==
color-string@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6"
integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
color@^3.0.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10"
integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==
version "3.1.3"
resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
dependencies:
color-convert "^1.9.1"
color-string "^1.5.2"
color-string "^1.5.4"
colorette@^1.2.1:
version "1.2.1"
@ -6602,9 +6583,9 @@ css-what@2.1:
integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
css-what@^3.2.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.1.tgz#81cb70b609e4b1351b1e54cbc90fd9c54af86e2e"
integrity sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==
version "3.4.2"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==
css.escape@^1.5.1:
version "1.5.1"
@ -7170,9 +7151,9 @@ doctypes@^1.1.0:
integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=
dom-accessibility-api@^0.5.1:
version "0.5.3"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.3.tgz#0ea493c924d4070dfbf531c4aaca3d7a2c601aab"
integrity sha512-yfqzAi1GFxK6EoJIZKgxqJyK6j/OjEFEUi2qkNThD/kUhoCFSG1izq31B5xuxzbJBGw9/67uPtkPMYAzWL7L7Q==
version "0.5.4"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166"
integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==
dom-converter@^0.2:
version "0.2.0"
@ -7349,9 +7330,9 @@ ejs@^3.1.5:
jake "^10.6.1"
electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.571:
version "1.3.578"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz#e6671936f4571a874eb26e2e833aa0b2c0b776e0"
integrity sha512-z4gU6dA1CbBJsAErW5swTGAaU2TBzc2mPAonJb00zqW1rOraDo2zfBMDRvaz9cVic+0JEZiYbHWPw/fTaZlG2Q==
version "1.3.579"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.579.tgz#58bf17499de6edf697e1442017d8569bce0d301a"
integrity sha512-9HaGm4UDxCtcmIqWWdv79pGgpRZWTqr+zg6kxp0MelSHfe1PNjrI8HXy1HgTSy4p0iQETGt8/ElqKFLW008BSA==
elegant-spinner@^1.0.1:
version "1.0.1"
@ -7587,9 +7568,9 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3:
ext "^1.1.2"
escalade@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e"
integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
@ -7740,15 +7721,15 @@ eslint-plugin-react@7.19.0:
xregexp "^4.3.0"
eslint-plugin-react@^7.21.3:
version "7.21.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.3.tgz#71655d2af5155b19285ec929dd2cdc67a4470b52"
integrity sha512-OI4GwTCqyIb4ipaOEGLWdaOHCXZZydStAsBEPB2e1ZfNM37bojpgO1BoOQbFb0eLVz3QLDx7b+6kYcrxCuJfhw==
version "7.21.4"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.4.tgz#31060b2e5ff82b12e24a3cc33edb7d12f904775c"
integrity sha512-uHeQ8A0hg0ltNDXFu3qSfFqTNPXm1XithH6/SY318UX76CMj7Q599qWpgmMhVQyvhq36pm7qvoN3pb6/3jsTFg==
dependencies:
array-includes "^3.1.1"
array.prototype.flatmap "^1.2.3"
doctrine "^2.1.0"
has "^1.0.3"
jsx-ast-utils "^2.4.1"
jsx-ast-utils "^2.4.1 || ^3.0.0"
object.entries "^1.1.2"
object.fromentries "^2.0.2"
object.values "^1.1.1"
@ -11056,7 +11037,7 @@ jstransformer@1.0.0:
is-promise "^2.0.0"
promise "^7.0.1"
jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3, jsx-ast-utils@^2.4.1:
jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3:
version "2.4.1"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e"
integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==
@ -11064,6 +11045,14 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3, jsx-ast-utils@^2.4.1:
array-includes "^3.1.1"
object.assign "^4.1.0"
"jsx-ast-utils@^2.4.1 || ^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891"
integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==
dependencies:
array-includes "^3.1.1"
object.assign "^4.1.1"
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@ -11543,6 +11532,11 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
magic-string@^0.25.0, magic-string@^0.25.5, magic-string@^0.25.7:
version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
@ -12195,9 +12189,9 @@ namespace-emitter@^2.0.1:
integrity sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==
nan@^2.12.1, nan@^2.13.2:
version "2.14.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
version "2.14.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nanoid@^2.0.4:
version "2.1.11"
@ -13977,11 +13971,11 @@ pretty-format@^25.2.1, pretty-format@^25.5.0:
react-is "^16.12.0"
pretty-format@^26.4.2:
version "26.4.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.4.2.tgz#d081d032b398e801e2012af2df1214ef75a81237"
integrity sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==
version "26.5.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.5.2.tgz#5d896acfdaa09210683d34b6dc0e6e21423cd3e1"
integrity sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==
dependencies:
"@jest/types" "^26.3.0"
"@jest/types" "^26.5.2"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^16.12.0"
@ -13992,9 +13986,9 @@ pretty-hrtime@^1.0.3:
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
prismjs@^1.21.0, prismjs@^1.8.4:
version "1.21.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.21.0.tgz#36c086ec36b45319ec4218ee164c110f9fc015a3"
integrity sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw==
version "1.22.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.22.0.tgz#73c3400afc58a823dd7eed023f8e1ce9fd8977fa"
integrity sha512-lLJ/Wt9yy0AiSYBf212kK3mM5L8ycwlyTlSxHBAneXLR0nzFMlZ5y7riFPF3E33zXOF2IH95xdY5jIyZbM9z/w==
optionalDependencies:
clipboard "^2.0.0"
@ -14005,7 +13999,7 @@ prismjs@~1.17.0:
optionalDependencies:
clipboard "^2.0.0"
private@^0.1.8, private@~0.1.5:
private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
@ -14087,9 +14081,9 @@ prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8,
react-is "^16.8.1"
property-information@^5.0.0, property-information@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.5.0.tgz#4dc075d493061a82e2b7d096f406e076ed859943"
integrity sha512-RgEbCx2HLa1chNgvChcx+rrCWD0ctBmGSE0M7lVm1yyv4UbvbrWoXp/BkVLZefzjrRBGW8/Js6uh/BnlHXFyjA==
version "5.6.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==
dependencies:
xtend "^4.0.0"
@ -14407,9 +14401,9 @@ react-app-polyfill@^1.0.6:
whatwg-fetch "^3.0.0"
react-base-table@^1.9.1:
version "1.11.3"
resolved "https://registry.yarnpkg.com/react-base-table/-/react-base-table-1.11.3.tgz#9b77b84347e905ec68bd0b5e9639ac4c41b53e5e"
integrity sha512-JBYtRQBVrNxdP2QpuRyi3+FXRViTmNuCHeV6LwTpWY10x/AwqtGmamNdtYI5hZ95f8lIvo2nTfZeRgOmdyveQw==
version "1.12.0"
resolved "https://registry.yarnpkg.com/react-base-table/-/react-base-table-1.12.0.tgz#3fc39f1cc7d1a4b349572e1d2b283626987d5dcc"
integrity sha512-CaS8iI8JyK5rGjggfupZWKBPypNxlXrjOi+ndUsLMCJaoKCgUXZSlYv2oihq44c10MgaH/eEgEvkMV6dIGa59w==
dependencies:
"@babel/runtime" "^7.0.0"
classnames "^2.2.5"
@ -14959,9 +14953,9 @@ react-syntax-highlighter@^11.0.2:
refractor "^2.4.1"
react-table@^7.0.0:
version "7.5.2"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.5.2.tgz#d82ceee3d4d40b119159bce1708f084a95d3435c"
integrity sha512-qiceR/gQUOBsO/q1z1wZ3RbRvkRt39Gbzo631HiPuWmo+eTmTgaXDqLGzCmU+bOr81PB6kDxXhoWQR8hiWaUJA==
version "7.6.0"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.6.0.tgz#83d765b96505b5332d108a2e0c27ab653f5a78c3"
integrity sha512-16kRTypBWz9ZwLnPWA8hc3eIC64POzO9GaMBiKaCcVM+0QOQzt0G7ebzGUM8SW0CYUpVM+glv1kMXrWj9tr3Sw==
react-tabs@^3.0.0:
version "3.1.1"
@ -15159,10 +15153,10 @@ readdirp@~3.2.0:
dependencies:
picomatch "^2.0.4"
readdirp@~3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada"
integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies:
picomatch "^2.2.1"
@ -15183,16 +15177,6 @@ recast@0.19.1:
private "^0.1.8"
source-map "~0.6.1"
recast@^0.14.7:
version "0.14.7"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.7.tgz#4f1497c2b5826d42a66e8e3c9d80c512983ff61d"
integrity sha512-/nwm9pkrcWagN40JeJhkPaRxiHXBRkXyRh/hgU088Z/v+qCy+zIHHY6bC6o7NaKAxPqtE6nD8zBH1LfU0/Wx6A==
dependencies:
ast-types "0.11.3"
esprima "~4.0.0"
private "~0.1.5"
source-map "~0.6.1"
recast@^0.18.1:
version "0.18.10"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.18.10.tgz#605ebbe621511eb89b6356a7e224bff66ed91478"
@ -15927,6 +15911,15 @@ schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6
ajv "^6.12.4"
ajv-keywords "^3.5.2"
schema-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
dependencies:
"@types/json-schema" "^7.0.6"
ajv "^6.12.5"
ajv-keywords "^3.5.2"
scriptjs@^2.5.8:
version "2.5.9"
resolved "https://registry.yarnpkg.com/scriptjs/-/scriptjs-2.5.9.tgz#343915cd2ec2ed9bfdde2b9875cd28f59394b35f"
@ -17305,14 +17298,14 @@ ts-pnp@^1.1.2, ts-pnp@^1.1.6:
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.14.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.0.tgz#d624983f3e2c5e0b55307c3dd6c86acd737622c6"
integrity sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0, tslib@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.2.tgz#462295631185db44b21b1ea3615b63cd1c038242"
integrity sha512-wAH28hcEKwna96/UacuWaVspVLkg4x1aDM9JlzqaQTOFczCktkVAb5fmXChgandR1EraDPs2w8P+ozM+oafwxg==
version "2.0.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==
tslib@~1.13.0:
version "1.13.0"
@ -17837,9 +17830,9 @@ void-elements@^3.1.0:
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
vue-docgen-api@^4.1.0:
version "4.32.4"
resolved "https://registry.yarnpkg.com/vue-docgen-api/-/vue-docgen-api-4.32.4.tgz#a74f4dfce99a078a4a87dcb779c6cdd480eeab01"
integrity sha512-dtPOg9uCnclBOiWASMDMBjYhrXbRDlADZNTMkc5NtF1eSXzltN7GvB7P3YO92M5IvUeHwq84A9BqGBBbye5oWg==
version "4.33.0"
resolved "https://registry.yarnpkg.com/vue-docgen-api/-/vue-docgen-api-4.33.0.tgz#59630636373ee3f06b0a36820d6235fdb9adfbae"
integrity sha512-kNIAE8kKsZtOqROqDyMGa30AVGcwo2D2g+Z2thuVbEcOAqRc4eM72PoFwqX9ZRqQSdj4TnqIGI3EfF4KHTZ/EQ==
dependencies:
"@babel/parser" "^7.6.0"
"@babel/types" "^7.6.0"
@ -17851,7 +17844,7 @@ vue-docgen-api@^4.1.0:
pug "^3.0.0"
recast "0.19.1"
ts-map "^1.0.3"
vue-inbrowser-compiler-utils "^4.32.1"
vue-inbrowser-compiler-utils "^4.33.0"
vue-docgen-loader@^1.3.0-beta.0:
version "1.5.0"
@ -17863,10 +17856,10 @@ vue-docgen-loader@^1.3.0-beta.0:
loader-utils "^1.2.3"
querystring "^0.2.0"
vue-inbrowser-compiler-utils@^4.32.1:
version "4.32.1"
resolved "https://registry.yarnpkg.com/vue-inbrowser-compiler-utils/-/vue-inbrowser-compiler-utils-4.32.1.tgz#d8774a4b7e91677d4d17d441485f5eafc77bc65d"
integrity sha512-IL8rBV3lCyHErqD8sBdQhWz3zJ/wLzG6JfoSzZ3K6HShS5QqIQfJN0GESvzIos6EGvmtByEf4TTJnjm12b51VQ==
vue-inbrowser-compiler-utils@^4.33.0:
version "4.33.0"
resolved "https://registry.yarnpkg.com/vue-inbrowser-compiler-utils/-/vue-inbrowser-compiler-utils-4.33.0.tgz#3bb510a7931c62d076a035f71dc31a5329e4e015"
integrity sha512-9VxLYc+J8o3Z1H6GpnPYD9J2lMcP4ahi1BoYTcRM6Yw0F/17Gbypog3yVCyh1VO4uDoIldi4b9ZF33zvR5KEvg==
dependencies:
camelcase "^5.3.1"
@ -18522,6 +18515,14 @@ worker-loader@^2.0.0:
loader-utils "^1.0.0"
schema-utils "^0.4.0"
worker-loader@^3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.4.tgz#bbc0af0af7e2d972c1105001ab215497c00b98ca"
integrity sha512-puFIebctLf/xB5Vex9QTX4Zr+wR6hQZqgVEg7QeUTA0I8XzTmGr620ryvY8E448C/hJ+eE+NKiIX9xyFcQNFbQ==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
worker-rpc@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5"
@ -18529,13 +18530,6 @@ worker-rpc@^0.1.0:
dependencies:
microevent.ts "~0.1.1"
workerize-loader@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/workerize-loader/-/workerize-loader-1.3.0.tgz#4995cf2ff2b45dd6dc60e4411e63f5ae2c704d36"
integrity sha512-utWDc8K6embcICmRBUUkzanPgKBb8yM1OHfh6siZfiMsswE8wLCa9CWS+L7AARz0+Th4KH4ZySrqer/OJ9WuWw==
dependencies:
loader-utils "^2.0.0"
wrap-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba"

View File

@ -1,4 +1,14 @@
package com.appsmith.external.pluginExceptions;
public class StaleConnectionException extends RuntimeException {
public StaleConnectionException() {
}
public StaleConnectionException(String message) {
super(message);
}
public StaleConnectionException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,5 @@
plugin.id=dynamo-plugin
plugin.class=com.external.plugins.DynamoPlugin
plugin.version=1.0-SNAPSHOT
plugin.provider=tech@appsmith.com
plugin.dependencies=

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.external.plugins</groupId>
<artifactId>dynamoPlugin</artifactId>
<version>1.0-SNAPSHOT</version>
<name>dynamoPlugin</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<plugin.id>dynamo-plugin</plugin.id>
<plugin.class>com.external.plugins.DynamoPlugin</plugin.class>
<plugin.version>1.0-SNAPSHOT</plugin.version>
<plugin.provider>tech@appsmith.com</plugin.provider>
<plugin.dependencies/>
</properties>
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
<version>0.6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.appsmith</groupId>
<artifactId>interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
<version>2.15.3</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.15.0-rc2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.2.11.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<minimizeJar>false</minimizeJar>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,364 @@
package com.external.plugins;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.DatasourceTestResult;
import com.appsmith.external.models.Endpoint;
import com.appsmith.external.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.plugins.BasePlugin;
import com.appsmith.external.plugins.PluginExecutor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.BooleanUtils;
import org.pf4j.Extension;
import org.pf4j.PluginWrapper;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.SdkField;
import software.amazon.awssdk.core.SdkPojo;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
public class DynamoPlugin extends BasePlugin {
public DynamoPlugin(PluginWrapper wrapper) {
super(wrapper);
}
/**
* Dynamo plugin receives the query as json of the following format:
* {
* "action": "GetItem",
* "parameters": {...} // Depends on the action above.
* }
*
* DynamoDB actions and parameters reference:
* https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations_Amazon_DynamoDB.html
*/
@Slf4j
@Extension
public static class DynamoPluginExecutor implements PluginExecutor<DynamoDbClient> {
@Override
public Mono<ActionExecutionResult> execute(DynamoDbClient ddb,
DatasourceConfiguration datasourceConfiguration,
ActionConfiguration actionConfiguration) {
ActionExecutionResult result = new ActionExecutionResult();
final String action = actionConfiguration.getPath();
if (StringUtils.isEmpty(action)) {
return Mono.error(new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Missing action name (like `ListTables`, `GetItem` etc.)."
));
}
final String body = actionConfiguration.getBody();
Map<String, Object> parameters = null;
try {
if (!StringUtils.isEmpty(body)) {
parameters = objectMapper.readValue(body, HashMap.class);
}
} catch (IOException e) {
final String message = "Error parsing the JSON body: " + e.getMessage();
log.warn(message, e);
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message));
}
final Class<?> requestClass;
try {
requestClass = Class.forName("software.amazon.awssdk.services.dynamodb.model." + action + "Request");
} catch (ClassNotFoundException e) {
return Mono.error(new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Unknown action: `" + action + "`. Note that action names are case-sensitive."
));
}
try {
final Method actionExecuteMethod = DynamoDbClient.class.getMethod(
// Convert `ListTables` to `listTables`, which is the name of the method to execute this action.
toLowerCamelCase(action),
requestClass
);
final DynamoDbResponse response = (DynamoDbResponse) actionExecuteMethod.invoke(ddb, plainToSdk(parameters, requestClass));
result.setBody(sdkToPlain(response));
} catch (AppsmithPluginException | InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) {
final String message = "Error executing the DynamoDB Action: " + (e.getCause() == null ? e : e.getCause()).getMessage();
log.warn(message, e);
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message));
}
result.setIsExecutionSuccess(true);
return Mono.just(result);
}
@Override
public Mono<DynamoDbClient> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
final DynamoDbClientBuilder builder = DynamoDbClient.builder();
if (!CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) {
final Endpoint endpoint = datasourceConfiguration.getEndpoints().get(0);
builder.endpointOverride(URI.create("http://" + endpoint.getHost() + ":" + endpoint.getPort()));
}
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
if (authentication == null || StringUtils.isEmpty(authentication.getDatabaseName())) {
return Mono.error(new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Missing region in datasource."
));
}
builder.region(Region.of(authentication.getDatabaseName()));
builder.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(authentication.getUsername(), authentication.getPassword())
));
return Mono.justOrEmpty(builder.build());
}
@Override
public void datasourceDestroy(DynamoDbClient client) {
if (client != null) {
client.close();
}
}
@Override
public Set<String> validateDatasource(@NonNull DatasourceConfiguration datasourceConfiguration) {
Set<String> invalids = new HashSet<>();
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
if (authentication == null) {
invalids.add("Missing AWS Access Key ID and Secret Access Key.");
} else {
if (StringUtils.isEmpty(authentication.getUsername())) {
invalids.add("Missing AWS Access Key ID.");
}
if (StringUtils.isEmpty(authentication.getPassword())) {
invalids.add("Missing AWS Secret Access Key.");
}
if (StringUtils.isEmpty(authentication.getDatabaseName())) {
invalids.add("Missing region configuration.");
}
}
return invalids;
}
@Override
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
return datasourceCreate(datasourceConfiguration)
.map(client -> {
client.close();
return true;
})
.defaultIfEmpty(false)
.map(isValid -> BooleanUtils.isTrue(isValid)
? new DatasourceTestResult()
: new DatasourceTestResult("Unable to create DynamoDB Client.")
);
}
}
private static String toLowerCamelCase(String action) {
return action.substring(0, 1).toLowerCase() + action.substring(1);
}
/**
* Given a map that conforms to what a valid DynamoDB request should look like, this function will convert into
* a DynamoDBRequest object from AWS SDK. This is done using Java's reflection API.
* @param mapping Mapping object representing the request details.
* @param type Request type that should be created. Eg., ListTablesRequest.class, PutItemRequest.class etc.
* @param <T> Type param of the request class.
* @return An object of the request class, containing details of the request from the mapping.
* @throws IllegalAccessException Thrown if any of the SDK methods' contracts change.
* @throws InvocationTargetException Thrown if any of the SDK methods' contracts change.
* @throws NoSuchMethodException Thrown if any of the SDK methods' contracts change.
* @throws ClassNotFoundException Thrown if any of the builder class could not be found corresponding to the action class.
*/
public static <T> T plainToSdk(Map<String, Object> mapping, Class<T> type)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException,
AppsmithPluginException, ClassNotFoundException {
final Class<?> builderType = Class.forName(type.getName() + "$Builder");
final Object builder = type.getMethod("builder").invoke(null);
if (mapping != null) {
for (final Map.Entry<String, Object> entry : mapping.entrySet()) {
final String setterName = getSetterMethodName(entry.getKey());
Object value = entry.getValue();
if (value instanceof String) {
// AWS SDK has two data types that are represented as Strings in JSON, namely strings and binary.
// We look at the parameter types for the setter method to decide which it should be, and then set
// convert the value if needed before calling the setter.
final Method setterMethod = findMethod(builderType, method -> {
final Class<?>[] parameterTypes = method.getParameterTypes();
return method.getName().equals(setterName)
&& (SdkBytes.class.isAssignableFrom(parameterTypes[0]) || String.class.isAssignableFrom(parameterTypes[0]));
});
if (setterMethod == null) {
throw new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Invalid attribute/value by name " + entry.getKey()
);
}
if (SdkBytes.class.isAssignableFrom(setterMethod.getParameterTypes()[0])) {
value = SdkBytes.fromUtf8String((String) value);
}
setterMethod.invoke(builder, value);
} else if (value instanceof Boolean
|| value instanceof Integer
|| value instanceof Float
|| value instanceof Double) {
// These data types have a setter method that takes a the value as is. Nothing fancy here.
builderType.getMethod(setterName, value.getClass()).invoke(builder, value);
} else if (value instanceof Map) {
// For maps, we go recursive, applying this transformation to each value, and replacing with the
// result in the map. Generic types in the setter method's signature are used to convert the values.
final Method setterMethod = findMethod(builderType, m -> m.getName().equals(setterName));
final ParameterizedType valueType = (ParameterizedType) setterMethod.getGenericParameterTypes()[0];
final Map<String, Object> transformedMap = new HashMap<>();
for (final Map.Entry<String, Object> innerEntry : ((Map<String, Object>) value).entrySet()) {
Object innerValue = innerEntry.getValue();
if (innerValue instanceof Map) {
innerValue = plainToSdk((Map) innerValue, (Class<?>) valueType.getActualTypeArguments()[1]);
}
transformedMap.put(innerEntry.getKey(), innerValue);
}
value = transformedMap;
if (!Map.class.isAssignableFrom((Class<?>) valueType.getRawType())) {
// Some setters don't take a plain map. For example, some require an `AttributeValue` instance
// for objects that are just maps in JSON. So, we make that conversion here.
value = plainToSdk((Map) value, (Class<T>) valueType.getRawType());
}
setterMethod.invoke(builder, value);
} else if (value instanceof Collection) {
// For linear collections, the process is similar to that of maps.
final Collection<Object> valueAsCollection = (Collection) value;
// Find method by name and exclude the varargs version of the method.
final Method setterMethod = findMethod(builderType, m -> m.getName().equals(setterName) && !m.getParameterTypes()[0].getName().startsWith("[L"));
final ParameterizedType valueType = (ParameterizedType) setterMethod.getGenericParameterTypes()[0];
final Collection<Object> reTypedList = new ArrayList<>();
for (final Object innerValue : valueAsCollection) {
if (innerValue instanceof Map) {
reTypedList.add(plainToSdk((Map) innerValue, (Class<?>) valueType.getActualTypeArguments()[0]));
} else if (innerValue instanceof String && SdkBytes.class.isAssignableFrom((Class<?>) valueType.getActualTypeArguments()[0])) {
reTypedList.add(SdkBytes.fromUtf8String((String) innerValue));
} else {
reTypedList.add(innerValue);
}
}
setterMethod.invoke(builder, reTypedList);
} else {
throw new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Unknown value type while deserializing:" + value.getClass().getName()
);
}
}
}
return (T) builderType.getMethod("build").invoke(builder);
}
private static Method findMethod(Class<?> builderType, Predicate<Method> predicate) {
return Arrays.stream(builderType.getMethods())
.filter(predicate)
.findFirst()
.orElse(null);
}
/**
* Computes the name of the setter method in AWS SDK that will set the value of the field given by the argument.
* @param key Name of the field for which to compute the setter method's name.
* @return Name of the setter method that will set the value of the given `key` field.
*/
private static String getSetterMethodName(final String key) {
if ("NULL".equals(key)) {
// Since `null` is a reserved word in Java, AWS SDK uses `nul` for this field.
return "nul";
} else if (isUpperCase(key)) {
return key.toLowerCase();
} else {
return toLowerCamelCase(key);
}
}
private static Map<String, Object> sdkToPlain(SdkPojo response) {
final Map<String, Object> plain = new HashMap<>();
for (final SdkField<?> field : response.sdkFields()) {
Object value = field.getValueOrDefault(response);
if (value instanceof SdkPojo) {
value = sdkToPlain((SdkPojo) value);
} else if (value instanceof Map) {
final Map<String, Object> valueAsMap = (Map) value;
final Map<String, Object> plainMap = new HashMap<>();
for (final Map.Entry<String, Object> entry : valueAsMap.entrySet()) {
final var key = entry.getKey();
Object innerValue = entry.getValue();
if (innerValue instanceof SdkPojo) {
innerValue = sdkToPlain((SdkPojo) innerValue);
}
plainMap.put(key, innerValue);
}
value = plainMap;
}
plain.put(field.memberName(), value);
}
return plain;
}
private static boolean isUpperCase(String s) {
for (char c : s.toCharArray()) {
if (!Character.isUpperCase(c)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,188 @@
{
"editor": [
{
"sectionName": "",
"id": 1,
"children": [
{
"label": "Action",
"configProperty": "actionConfiguration.path",
"controlType": "DROP_DOWN",
"isRequired": true,
"initialValue": "GetItem",
"options": [
{
"label": "BatchGetItem",
"value":"BatchGetItem"
},
{
"label": "BatchWriteItem",
"value": "BatchWriteItem"
},
{
"label": "CreateBackup",
"value": "CreateBackup"
},
{
"label": "CreateGlobalTable",
"value": "CreateGlobalTable"
},
{
"label": "CreateTable",
"value": "CreateTable"
},
{
"label": "DeleteBackup",
"value": "DeleteBackup"
},
{
"label": "DeleteItem",
"value": "DeleteItem"
},
{
"label": "DeleteTable",
"value": "DeleteTable"
},
{
"label": "DescribeBackup",
"value": "DescribeBackup"
},
{
"label": "DescribeContinuousBackups",
"value": "DescribeContinuousBackups"
},
{
"label": "DescribeContributorInsights",
"value": "DescribeContributorInsights"
},
{
"label": "DescribeEndpoints",
"value": "DescribeEndpoints"
},
{
"label": "DescribeGlobalTable",
"value": "DescribeGlobalTable"
},
{
"label": "DescribeGlobalTableSettings",
"value": "DescribeGlobalTableSettings"
},
{
"label": "DescribeLimits",
"value": "DescribeLimits"
},
{
"label": "DescribeTable",
"value": "DescribeTable"
},
{
"label": "DescribeTableReplicaAutoScaling",
"value": "DescribeTableReplicaAutoScaling"
},
{
"label": "DescribeTimeToLive",
"value": "DescribeTimeToLive"
},
{
"label": "GetItem",
"value": "GetItem"
},
{
"label": "ListBackups",
"value": "ListBackups"
},
{
"label": "ListContributorInsights",
"value": "ListContributorInsights"
},
{
"label": "ListGlobalTables",
"value": "ListGlobalTables"
},
{
"label": "ListTables",
"value": "ListTables"
},
{
"label": "ListTagsOfResource",
"value": "ListTagsOfResource"
},
{
"label": "PutItem",
"value": "PutItem"
},
{
"label": "Query",
"value": "Query"
},
{
"label": "RestoreTableFromBackup",
"value": "RestoreTableFromBackup"
},
{
"label": "RestoreTableToPointInTime",
"value": "RestoreTableToPointInTime"
},
{
"label": "Scan",
"value": "Scan"
},
{
"label": "TagResource",
"value": "TagResource"
},
{
"label": "TransactGetItems",
"value": "TransactGetItems"
},
{
"label": "TransactWriteItems",
"value": "TransactWriteItems"
},
{
"label": "UntagResource",
"value": "UntagResource"
},
{
"label": "UpdateContinuousBackups",
"value": "UpdateContinuousBackups"
},
{
"label": "UpdateContributorInsights",
"value": "UpdateContributorInsights"
},
{
"label": "UpdateGlobalTable",
"value": "UpdateGlobalTable"
},
{
"label": "UpdateGlobalTableSettings",
"value": "UpdateGlobalTableSettings"
},
{
"label": "UpdateItem",
"value": "UpdateItem"
},
{
"label": "UpdateTable",
"value": "UpdateTable"
},
{
"label": "UpdateTableReplicaAutoScaling",
"value": "UpdateTableReplicaAutoScaling"
},
{
"label": "UpdateTimeToLive",
"value": "UpdateTimeToLive"
}
]
},
{
"label": "",
"configProperty": "actionConfiguration.body",
"controlType": "QUERY_DYNAMIC_TEXT"
}
]
}
]
}

View File

@ -0,0 +1,177 @@
{
"form": [
{
"sectionName": "Details",
"id": 1,
"children": [
{
"label": "Region",
"configProperty": "datasourceConfiguration.authentication.databaseName",
"controlType": "DROP_DOWN",
"isRequired": true,
"initialValue": "AP_SOUTH_1",
"options": [
{
"label": "ap-south-1",
"value": "ap-south-1"
},
{
"label": "eu-south-1",
"value": "eu-south-1"
},
{
"label": "us-gov-east-1",
"value": "us-gov-east-1"
},
{
"label": "ca-central-1",
"value": "ca-central-1"
},
{
"label": "eu-central-1",
"value": "eu-central-1"
},
{
"label": "us-west-1",
"value": "us-west-1"
},
{
"label": "us-west-2",
"value": "us-west-2"
},
{
"label": "af-south-1",
"value": "af-south-1"
},
{
"label": "eu-north-1",
"value": "eu-north-1"
},
{
"label": "eu-west-3",
"value": "eu-west-3"
},
{
"label": "eu-west-2",
"value": "eu-west-2"
},
{
"label": "eu-west-1",
"value": "eu-west-1"
},
{
"label": "ap-northeast-2",
"value": "ap-northeast-2"
},
{
"label": "ap-northeast-1",
"value": "ap-northeast-1"
},
{
"label": "me-south-1",
"value": "me-south-1"
},
{
"label": "sa-east-1",
"value": "sa-east-1"
},
{
"label": "ap-east-1",
"value": "ap-east-1"
},
{
"label": "cn-north-1",
"value": "cn-north-1"
},
{
"label": "us-gov-west-1",
"value": "us-gov-west-1"
},
{
"label": "ap-southeast-1",
"value": "ap-southeast-1"
},
{
"label": "ap-southeast-2",
"value": "ap-southeast-2"
},
{
"label": "us-iso-east-1",
"value": "us-iso-east-1"
},
{
"label": "us-east-1",
"value": "us-east-1"
},
{
"label": "us-east-2",
"value": "us-east-2"
},
{
"label": "cn-northwest-1",
"value": "cn-northwest-1"
},
{
"label": "us-isob-east-1",
"value": "us-isob-east-1"
},
{
"label": "aws-global",
"value": "aws-global"
},
{
"label": "aws-cn-global",
"value": "aws-cn-global"
},
{
"label": "aws-us-gov-global",
"value": "aws-us-gov-global"
},
{
"label": "aws-iso-global",
"value": "aws-iso-global"
},
{
"label": "aws-iso-b-global",
"value": "aws-iso-b-global"
}
]
},
{
"sectionName": "Endpoint Override (Overrides above region)",
"children": [
{
"label": "Host Address (for overriding endpoint only)",
"configProperty": "datasourceConfiguration.endpoints[*].host",
"controlType": "KEYVALUE_ARRAY",
"validationMessage": "Please enter a valid host",
"validationRegex": "^((?![/:]).)*$"
},
{
"label": "Port",
"configProperty": "datasourceConfiguration.endpoints[*].port",
"dataType": "NUMBER",
"controlType": "KEYVALUE_ARRAY"
}
]
},
{
"label": "AWS Access Key ID",
"configProperty": "datasourceConfiguration.authentication.username",
"controlType": "INPUT_TEXT",
"isRequired": true,
"placeholderText": "",
"initialValue": ""
},
{
"label": "AWS Secret Access Key",
"configProperty": "datasourceConfiguration.authentication.password",
"controlType": "INPUT_TEXT",
"isRequired": true,
"placeholderText": "",
"initialValue": ""
}
]
}
]
}

View File

@ -0,0 +1,206 @@
package com.external.plugins;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.Endpoint;
import lombok.extern.log4j.Log4j;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@Log4j
public class DynamoPluginTest {
private final static DynamoPlugin.DynamoPluginExecutor pluginExecutor = new DynamoPlugin.DynamoPluginExecutor();
@SuppressWarnings("rawtypes")
@ClassRule
public static GenericContainer container = new GenericContainer(CompletableFuture.completedFuture("amazon/dynamodb-local"))
.withExposedPorts(8000);
private final static DatasourceConfiguration dsConfig = new DatasourceConfiguration();
@BeforeClass
public static void setUp() {
final String host = "localhost";
final Integer port = container.getMappedPort(8000);
final StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
AwsBasicCredentials.create("dummy", "dummy")
);
DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.AP_SOUTH_1)
.endpointOverride(URI.create("http://" + host + ":" + port))
.credentialsProvider(credentialsProvider)
.build();
ddb.createTable(CreateTableRequest.builder()
.tableName("cities")
.attributeDefinitions(
AttributeDefinition.builder().attributeName("Id").attributeType(ScalarAttributeType.S).build()
)
.keySchema(
KeySchemaElement.builder().attributeName("Id").keyType(KeyType.HASH).build()
)
.provisionedThroughput(
ProvisionedThroughput.builder().readCapacityUnits(5L).writeCapacityUnits(5L).build()
)
.build());
ddb.putItem(PutItemRequest.builder()
.tableName("cities")
.item(Map.of(
"Id", AttributeValue.builder().s("1").build(),
"City", AttributeValue.builder().s("New Delhi").build()
))
.build());
ddb.putItem(PutItemRequest.builder()
.tableName("cities")
.item(Map.of(
"Id", AttributeValue.builder().s("2").build(),
"City", AttributeValue.builder().s("Bangalore").build()
))
.build());
System.out.println(ddb.listTables());
Endpoint endpoint = new Endpoint();
endpoint.setHost(host);
endpoint.setPort(port.longValue());
dsConfig.setAuthentication(new AuthenticationDTO());
dsConfig.getAuthentication().setUsername("dummy");
dsConfig.getAuthentication().setPassword("dummy");
dsConfig.getAuthentication().setDatabaseName(Region.AP_SOUTH_1.toString());
dsConfig.setEndpoints(List.of(endpoint));
}
private Mono<ActionExecutionResult> execute(String action, String jsonActionConfiguration) {
ActionConfiguration actionConfiguration = new ActionConfiguration();
actionConfiguration.setPath(action);
actionConfiguration.setBody(jsonActionConfiguration);
return pluginExecutor
.datasourceCreate(dsConfig)
.flatMap(conn -> pluginExecutor.execute(conn, dsConfig, actionConfiguration));
}
@Test
public void testListTables() {
StepVerifier.create(execute("ListTables", null))
.assertNext(result -> {
assertNotNull(result);
assertTrue(result.getIsExecutionSuccess());
assertNotNull(result.getBody());
assertArrayEquals(
new String[]{"cities"},
((Map<String, List<String>>) result.getBody()).get("TableNames").toArray()
);
})
.verifyComplete();
}
@Test
public void testGetItem() {
final String body = "{\n" +
" \"TableName\": \"cities\",\n" +
" \"Key\": {\n" +
" \"Id\": {\n" +
" \"S\": \"1\"\n" +
" }\n" +
" }\n" +
"}\n";
StepVerifier.create(execute("GetItem", body))
.assertNext(result -> {
assertNotNull(result);
assertTrue(result.getIsExecutionSuccess());
assertNotNull(result.getBody());
final Map<String, Map<String, Object>> item = ((Map<String, Map<String, Map<String, Object>>>) result.getBody()).get("Item");
assertEquals("New Delhi", item.get("City").get("S"));
})
.verifyComplete();
}
@Test
public void testPutItem() {
final String body = "{\n" +
" \"TableName\": \"cities\",\n" +
" \"Item\": {\n" +
" \"Id\": {\n" +
" \"S\": \"9\"\n" +
" },\n" +
" \"City\": {\n" +
" \"S\": \"Mumbai\"\n" +
" }\n" +
" }\n" +
"}\n";
StepVerifier.create(execute("PutItem", body))
.assertNext(result -> {
assertNotNull(result);
assertTrue(result.getIsExecutionSuccess());
assertNotNull(result.getBody());
assertNotNull(((Map<String, List<String>>) result.getBody()).get("Attributes"));
})
.verifyComplete();
}
@Test
public void testUpdateItem() {
final String body = "{\n" +
" \"TableName\": \"cities\",\n" +
" \"Key\": {\n" +
" \"Id\": {\n" +
" \"S\": \"2\"\n" +
" }\n" +
" },\n" +
" \"UpdateExpression\": \"set City = :new_city\",\n" +
" \"ExpressionAttributeValues\": {\n" +
" \":new_city\": {\n" +
" \"S\": \"Bengaluru\"\n" +
" }\n" +
" },\n" +
" \"ReturnValues\": \"ALL_NEW\"\n" +
"}\n";
StepVerifier.create(execute("UpdateItem", body))
.assertNext(result -> {
assertNotNull(result);
assertTrue(result.getIsExecutionSuccess());
assertNotNull(result.getBody());
final Map<String, Map<String, Object>> attributes = ((Map<String, Map<String, Map<String, Object>>>) result.getBody()).get("Attributes");
assertEquals("Bengaluru", attributes.get("City").get("S"));
})
.verifyComplete();
}
}

View File

@ -0,0 +1,129 @@
package com.external.plugins;
import org.junit.Test;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import java.util.List;
import java.util.Map;
import static com.external.plugins.DynamoPlugin.plainToSdk;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class PlainToSdkTests {
@Test
public void testListTablesNull() throws Exception {
final ListTablesRequest request = plainToSdk(
null,
ListTablesRequest.class
);
assertNotNull(request);
assertNull(request.exclusiveStartTableName());
assertNull(request.limit());
}
@Test
public void testListTables() throws Exception {
final ListTablesRequest request = plainToSdk(
Map.of(
"ExclusiveStartTableName", "table_name",
"Limit", 1
),
ListTablesRequest.class
);
assertNotNull(request);
assertEquals("table_name", request.exclusiveStartTableName());
assertEquals(1, request.limit().intValue());
}
@Test
public void testPutItem() throws Exception {
final PutItemRequest request = plainToSdk(
Map.of(
"ConditionalOperator", "conditional operator value",
"ConditionExpression", "conditional expression value",
"ExpressionAttributeNames", Map.of(
"#P", "Percentile"
),
"ExpressionAttributeValues", Map.of(
":token1", Map.of("S", "value1"),
":token2", Map.of("N", "42")
),
"Item", Map.of(
"one", Map.of("B", "binary blob as a string"),
"two", Map.of("BOOL", true),
"three", Map.of("BS", List.of("binary blob 1", "binary blob 2")),
"four", Map.of("L", List.of(
Map.of("S", "string in list 1"),
Map.of("S", "string in list 2"),
Map.of("S", "string in list 3")
)),
"five", Map.of("M", Map.of(
"key1", "val1",
"key2", "val2"
)),
"six", Map.of("N", "1234"),
"seven", Map.of("NS", List.of("12", "34", "56")),
"eight", Map.of("NULL", true),
"nine", Map.of("S", "string value")
),
"TableName", "table_name"
),
PutItemRequest.class
);
assertNotNull(request);
assertEquals("conditional operator value", request.conditionalOperatorAsString());
assertEquals("conditional expression value", request.conditionExpression());
assertEquals(9, request.item().size());
assertEquals("table_name", request.tableName());
}
@Test
public void testGetItem() throws Exception {
final GetItemRequest request = plainToSdk(
Map.of(
"AttributesToGet", List.of("one", "two", "three"),
"ConsistentRead", true,
"ExpressionAttributeNames", Map.of(
"#P", "Percentile"
),
"Key", Map.of(
"one", Map.of("B", "binary blob as a string"),
"two", Map.of("BOOL", true),
"three", Map.of("BS", List.of("binary blob 1", "binary blob 2")),
"four", Map.of("L", List.of(
Map.of("S", "string in list 1"),
Map.of("S", "string in list 2"),
Map.of("S", "string in list 3")
)),
"five", Map.of("M", Map.of(
"key1", "val1",
"key2", "val2"
)),
"six", Map.of("N", "1234"),
"seven", Map.of("NS", List.of("12", "34", "56")),
"eight", Map.of("NULL", true),
"nine", Map.of("S", "string value")
),
"TableName", "table_name"
),
GetItemRequest.class
);
assertNotNull(request);
assertArrayEquals(new String[]{"one", "two", "three"}, request.attributesToGet().toArray());
assertTrue(request.consistentRead());
assertEquals(9, request.key().size());
assertEquals("table_name", request.tableName());
}
}

View File

@ -0,0 +1,5 @@
plugin.id=elasticsearch-plugin
plugin.class=com.external.plugins.ElasticSearchPlugin
plugin.version=1.0-SNAPSHOT
plugin.provider=tech@appsmith.com
plugin.dependencies=

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.external.plugins</groupId>
<artifactId>elasticSearchPlugin</artifactId>
<version>1.0-SNAPSHOT</version>
<name>elasticSearchPlugin</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<plugin.id>elasticsearch-plugin</plugin.id>
<plugin.class>com.external.plugins.ElasticSearchPlugin</plugin.class>
<plugin.version>1.0-SNAPSHOT</plugin.version>
<plugin.provider>tech@appsmith.com</plugin.provider>
<plugin.dependencies/>
</properties>
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
<version>0.6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.appsmith</groupId>
<artifactId>interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.9.2</version>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.3.5.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.15.0-rc2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>elasticsearch</artifactId>
<version>1.15.0-rc2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<minimizeJar>false</minimizeJar>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,202 @@
package com.external.plugins;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.DatasourceTestResult;
import com.appsmith.external.models.Endpoint;
import com.appsmith.external.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.plugins.BasePlugin;
import com.appsmith.external.plugins.PluginExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.message.BasicHeader;
import org.apache.http.nio.entity.NStringEntity;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.pf4j.Extension;
import org.pf4j.PluginWrapper;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ElasticSearchPlugin extends BasePlugin {
public ElasticSearchPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Slf4j
@Extension
public static class ElasticSearchPluginExecutor implements PluginExecutor<RestClient> {
@Override
public Mono<ActionExecutionResult> execute(RestClient client,
DatasourceConfiguration datasourceConfiguration,
ActionConfiguration actionConfiguration) {
final ActionExecutionResult result = new ActionExecutionResult();
String body = actionConfiguration.getBody();
final String path = actionConfiguration.getPath();
final Request request = new Request(actionConfiguration.getHttpMethod().toString(), path);
ContentType contentType = ContentType.APPLICATION_JSON;
if (isBulkQuery(path)) {
contentType = ContentType.create("application/x-ndjson");
// If body is a JSON Array, convert it to an ND-JSON string.
if (body != null && body.trim().startsWith("[")) {
final StringBuilder ndJsonBuilder = new StringBuilder();
try {
List<Object> commands = objectMapper.readValue(body, ArrayList.class);
for (Object object : commands) {
ndJsonBuilder.append(objectMapper.writeValueAsString(object)).append("\n");
}
} catch (IOException e) {
final String message = "Error converting array to ND-JSON: " + e.getMessage();
log.warn(message, e);
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message));
}
body = ndJsonBuilder.toString();
}
}
if (body != null) {
request.setEntity(new NStringEntity(body, contentType));
}
try {
final String responseBody = new String(
client.performRequest(request).getEntity().getContent().readAllBytes());
result.setBody(objectMapper.readValue(responseBody, HashMap.class));
} catch (IOException e) {
final String message = "Error performing request: " + e.getMessage();
log.warn(message, e);
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message));
}
result.setIsExecutionSuccess(true);
return Mono.just(result);
}
private static boolean isBulkQuery(String path) {
return path.split("\\?", 1)[0].matches(".*\\b_bulk$");
}
@Override
public Mono<RestClient> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
final List<HttpHost> hosts = new ArrayList<>();
for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) {
hosts.add(new HttpHost(endpoint.getHost(), endpoint.getPort().intValue(), "http"));
}
final RestClientBuilder clientBuilder = RestClient.builder(hosts.toArray(new HttpHost[]{}));
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
if (authentication != null
&& !StringUtils.isEmpty(authentication.getUsername())
&& !StringUtils.isEmpty(authentication.getPassword())) {
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(authentication.getUsername(), authentication.getPassword())
);
clientBuilder
.setHttpClientConfigCallback(
httpClientBuilder -> httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider)
);
}
if (!CollectionUtils.isEmpty(datasourceConfiguration.getHeaders())) {
clientBuilder.setDefaultHeaders(
(Header[]) datasourceConfiguration.getHeaders()
.stream()
.map(h -> new BasicHeader(h.getKey(), h.getValue()))
.toArray()
);
}
return Mono.just(clientBuilder.build());
}
@Override
public void datasourceDestroy(RestClient client) {
try {
client.close();
} catch (IOException e) {
log.warn("Error closing connection to ElasticSearch.", e);
}
}
@Override
public Set<String> validateDatasource(DatasourceConfiguration datasourceConfiguration) {
Set<String> invalids = new HashSet<>();
if (CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) {
invalids.add("No endpoint provided. Please provide a host:port where ElasticSearch is reachable.");
}
return invalids;
}
@Override
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
return datasourceCreate(datasourceConfiguration)
.map(client -> {
if (client == null) {
return new DatasourceTestResult("Null client object to ElasticSearch.");
}
// This HEAD request is to check if an index exists. It response with 200 if the index exists,
// 404 if it doesn't. We just check for either of these two.
// Ref: https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-exists.html
Request request = new Request("HEAD", "/potentially-missing-index?local=true");
final Response response;
try {
response = client.performRequest(request);
} catch (IOException e) {
return new DatasourceTestResult("Error running HEAD request: " + e.getMessage());
}
final StatusLine statusLine = response.getStatusLine();
try {
client.close();
} catch (IOException e) {
log.warn("Error closing ElasticSearch client that was made for testing.", e);
}
if (statusLine.getStatusCode() != 404 && statusLine.getStatusCode() != 200) {
return new DatasourceTestResult(
"Unexpected response from ElasticSearch: " + statusLine);
}
return new DatasourceTestResult();
})
.onErrorResume(error -> Mono.just(new DatasourceTestResult(error.getMessage())));
}
}
}

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