Adding JS execution functionality.
This commit is contained in:
parent
0ffe94a298
commit
48fa52d9ba
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
<head>
|
||||
<script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
|
||||
<script type="text/javascript" src="/shims/realms-shim.umd.min.js"></script>
|
||||
<meta charset="utf-8" />
|
||||
<!-- in index.html, or however you manage your CSS files -->
|
||||
<link href="../node_modules/normalize.css/normalize.css" rel="stylesheet" />
|
||||
|
|
|
|||
10
app/client/public/shims/realms-shim.umd.min.js
vendored
Normal file
10
app/client/public/shims/realms-shim.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -27,7 +27,7 @@ export const PropertyPaneActionDropdownOptions: DropdownOption[] = [
|
|||
// { label: "Run Query", value: "QUERY" },
|
||||
];
|
||||
|
||||
export interface ActionPayload {
|
||||
export interface BaseActionPayload {
|
||||
actionId: string;
|
||||
actionType: ActionType;
|
||||
contextParams: Record<string, string>;
|
||||
|
|
@ -35,34 +35,42 @@ export interface ActionPayload {
|
|||
onError?: ActionPayload[];
|
||||
}
|
||||
|
||||
export type ActionPayload =
|
||||
| NavigateActionPayload
|
||||
| SetValueActionPayload
|
||||
| ExecuteJSActionPayload
|
||||
| DownloadDataActionPayload
|
||||
| SetValueActionPayload;
|
||||
|
||||
export type NavigationType = "NEW_TAB" | "INLINE";
|
||||
|
||||
export interface NavigateActionPayload extends ActionPayload {
|
||||
export interface NavigateActionPayload extends BaseActionPayload {
|
||||
pageUrl: string;
|
||||
navigationType: NavigationType;
|
||||
}
|
||||
|
||||
export interface ShowAlertActionPayload extends ActionPayload {
|
||||
export interface ShowAlertActionPayload extends BaseActionPayload {
|
||||
header: string;
|
||||
message: string;
|
||||
alertType: AlertType;
|
||||
intent: MessageIntent;
|
||||
}
|
||||
|
||||
export interface SetValueActionPayload extends ActionPayload {
|
||||
export interface SetValueActionPayload extends BaseActionPayload {
|
||||
header: string;
|
||||
message: string;
|
||||
alertType: AlertType;
|
||||
intent: MessageIntent;
|
||||
}
|
||||
|
||||
export interface ExecuteJSActionPayload extends ActionPayload {
|
||||
export interface ExecuteJSActionPayload extends BaseActionPayload {
|
||||
jsFunctionId: string;
|
||||
jsFunction: string;
|
||||
}
|
||||
|
||||
export type DownloadFiletype = "CSV" | "XLS" | "JSON" | "TXT";
|
||||
|
||||
export interface DownloadDataActionPayload extends ActionPayload {
|
||||
export interface DownloadDataActionPayload extends BaseActionPayload {
|
||||
data: JSON;
|
||||
fileName: string;
|
||||
fileType: DownloadFiletype;
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
// TODO (hetu): Remove useless escapes and re-enable the above lint rule
|
||||
export type NamePathBindingMap = Record<string, string>;
|
||||
export const DATA_BIND_REGEX = /{{(\s*[\w\.\[\]\d]+\s*)}}/g;
|
||||
export const DATA_BIND_JS_REGEX = /(.*?){{(\s*(.*?)\s*)}}(.*?)/g;
|
||||
export const DATA_PATH_REGEX = /[\w\.\[\]\d]+/;
|
||||
/* eslint-enable no-useless-escape */
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
FETCH_PAGE_LIST_ERROR: "FETCH_PAGE_LIST_ERROR",
|
||||
FETCH_APPLICATION_LIST_ERROR: "FETCH_APPLICATION_LIST_ERROR",
|
||||
CREATE_APPLICATION_ERROR: "CREATE_APPLICATION_ERROR",
|
||||
SAVE_JS_EXECUTION_RECORD: "SAVE_JS_EXECUTION_RECORD",
|
||||
};
|
||||
|
||||
export const ReduxFormActionTypes: { [key: string]: string } = {
|
||||
|
|
|
|||
51
app/client/src/jsExecution/JSExecutionManagerSingleton.ts
Normal file
51
app/client/src/jsExecution/JSExecutionManagerSingleton.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import RealmExecutor from "./RealmExecutor";
|
||||
|
||||
export type JSExecutorGlobal = Record<string, object>;
|
||||
export interface JSExecutor {
|
||||
execute: (src: string, data: JSExecutorGlobal) => string;
|
||||
registerLibrary: (accessor: string, lib: any) => void;
|
||||
unRegisterLibrary: (accessor: string) => void;
|
||||
}
|
||||
|
||||
enum JSExecutorType {
|
||||
REALM,
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
this.registerLibrary("_", window._);
|
||||
}
|
||||
evaluateSync(jsSrc: string, data: JSExecutorGlobal) {
|
||||
return this.currentExecutor.execute(jsSrc, data);
|
||||
}
|
||||
}
|
||||
const JSExecutionManagerSingleton = new JSExecutionManager();
|
||||
|
||||
export default JSExecutionManagerSingleton;
|
||||
42
app/client/src/jsExecution/RealmExecutor.ts
Normal file
42
app/client/src/jsExecution/RealmExecutor.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { JSExecutorGlobal, JSExecutor } from "./JSExecutionManagerSingleton";
|
||||
declare let Realm: any;
|
||||
|
||||
export default class RealmExecutor implements JSExecutor {
|
||||
rootRealm: any;
|
||||
creaetSafeObject: any;
|
||||
extrinsics: any[] = [];
|
||||
createSafeFunction: (unsafeFn: Function) => Function;
|
||||
|
||||
libraries: Record<string, any> = {};
|
||||
constructor() {
|
||||
this.rootRealm = Realm.makeRootRealm();
|
||||
this.createSafeFunction = this.rootRealm.evaluate(`
|
||||
(function createSafeFunction(unsafeFn) {
|
||||
return function safeFn(...args) {
|
||||
unsafeFn(...args);
|
||||
}
|
||||
})
|
||||
`);
|
||||
this.creaetSafeObject = this.rootRealm.evaluate(`
|
||||
(function creaetSafeObject(unsafeObject) {
|
||||
return JSON.parse(JSON.stringify(unsafeObject));
|
||||
})
|
||||
`);
|
||||
}
|
||||
registerLibrary(accessor: string, lib: any) {
|
||||
this.rootRealm.global[accessor] = lib;
|
||||
}
|
||||
unRegisterLibrary(accessor: string) {
|
||||
this.rootRealm.global[accessor] = null;
|
||||
}
|
||||
execute(sourceText: string, data: JSExecutorGlobal) {
|
||||
const safeData = this.creaetSafeObject(data);
|
||||
let result;
|
||||
try {
|
||||
result = this.rootRealm.evaluate(sourceText, safeData);
|
||||
} catch (e) {
|
||||
result = `Error: ${e}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import propertyPaneConfigReducer from "./propertyPaneConfigReducer";
|
|||
import datasourceReducer from "./datasourceReducer";
|
||||
import bindingsReducer from "./bindingsReducer";
|
||||
import pageListReducer from "./pageListReducer";
|
||||
import jsExecutionsReducer from "./jsExecutionsReducer";
|
||||
|
||||
const entityReducer = combineReducers({
|
||||
canvasWidgets: canvasWidgetsReducer,
|
||||
|
|
@ -19,6 +20,7 @@ const entityReducer = combineReducers({
|
|||
datasources: datasourceReducer,
|
||||
nameBindings: bindingsReducer,
|
||||
pageList: pageListReducer,
|
||||
jsExecutions: jsExecutionsReducer,
|
||||
});
|
||||
|
||||
export default entityReducer;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { createReducer } from "../../utils/AppsmithUtils";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxAction,
|
||||
} from "../../constants/ReduxActionConstants";
|
||||
|
||||
export type JSExecutionRecord = Record<string, string>;
|
||||
const initialState: JSExecutionRecord = {};
|
||||
const jsExecutionsReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.SAVE_JS_EXECUTION_RECORD]: (
|
||||
state: JSExecutionRecord,
|
||||
action: ReduxAction<JSExecutionRecord>,
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default jsExecutionsReducer;
|
||||
|
|
@ -12,7 +12,11 @@ import {
|
|||
takeEvery,
|
||||
takeLatest,
|
||||
} from "redux-saga/effects";
|
||||
import { ActionPayload, PageAction } from "constants/ActionConstants";
|
||||
import {
|
||||
ActionPayload,
|
||||
PageAction,
|
||||
ExecuteJSActionPayload,
|
||||
} from "constants/ActionConstants";
|
||||
import ActionAPI, {
|
||||
ActionApiResponse,
|
||||
ActionCreateUpdateResponse,
|
||||
|
|
@ -35,6 +39,7 @@ import {
|
|||
extractDynamicBoundValue,
|
||||
getDynamicBindings,
|
||||
isDynamicValue,
|
||||
NameBindingsWithData,
|
||||
} from "utils/DynamicBindingUtils";
|
||||
import { validateResponse } from "./ErrorSagas";
|
||||
import { getDataTree } from "selectors/entitiesSelector";
|
||||
|
|
@ -44,6 +49,8 @@ import {
|
|||
} from "constants/messages";
|
||||
import { getFormData } from "selectors/formSelectors";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton";
|
||||
import { getNameBindingsWithData } from "selectors/nameBindingsWithDataSelector";
|
||||
|
||||
export const getAction = (
|
||||
state: AppState,
|
||||
|
|
@ -90,6 +97,23 @@ export function* getActionParams(jsonPathKeys: string[] | undefined) {
|
|||
return mapToPropList(dynamicBindings);
|
||||
}
|
||||
|
||||
function* executeJSActionSaga(jsAction: ExecuteJSActionPayload) {
|
||||
const nameBindingsWithData: NameBindingsWithData = yield select(
|
||||
getNameBindingsWithData,
|
||||
);
|
||||
const result = JSExecutionManagerSingleton.evaluateSync(
|
||||
jsAction.jsFunction,
|
||||
nameBindingsWithData,
|
||||
);
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.SAVE_JS_EXECUTION_RECORD,
|
||||
payload: {
|
||||
[jsAction.jsFunctionId]: result,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function* executeAPIQueryActionSaga(apiAction: ActionPayload) {
|
||||
try {
|
||||
const api: PageAction = yield select(getAction, apiAction.actionId);
|
||||
|
|
@ -193,6 +217,11 @@ export function* executeActionSaga(actionPayloads: ActionPayload[]): any {
|
|||
return call(executeAPIQueryActionSaga, actionPayload);
|
||||
case "QUERY":
|
||||
return call(executeAPIQueryActionSaga, actionPayload);
|
||||
case "JS_FUNCTION":
|
||||
return call(
|
||||
executeJSActionSaga,
|
||||
actionPayload as ExecuteJSActionPayload,
|
||||
);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import { WidgetConfigReducerState } from "reducers/entityReducers/widgetConfigRe
|
|||
import { WidgetCardProps } from "widgets/BaseWidget";
|
||||
import { WidgetSidebarReduxState } from "reducers/uiReducers/widgetSidebarReducer";
|
||||
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
|
||||
import { enhanceWithDynamicValuesAndValidations } from "utils/DynamicBindingUtils";
|
||||
import {
|
||||
enhanceWithDynamicValuesAndValidations,
|
||||
NameBindingsWithData,
|
||||
} from "utils/DynamicBindingUtils";
|
||||
import { getDataTree } from "./entitiesSelector";
|
||||
import {
|
||||
FlattenedWidgetProps,
|
||||
|
|
@ -17,6 +20,7 @@ import { PageListReduxState } from "reducers/entityReducers/pageListReducer";
|
|||
|
||||
import { OccupiedSpace } from "constants/editorConstants";
|
||||
import { WidgetTypes } from "constants/WidgetConstants";
|
||||
import { getNameBindingsWithData } from "./nameBindingsWithDataSelector";
|
||||
|
||||
const getEditorState = (state: AppState) => state.ui.editor;
|
||||
const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig;
|
||||
|
|
@ -112,12 +116,13 @@ export const getWidgetCards = createSelector(
|
|||
|
||||
export const getValidatedDynamicProps = createSelector(
|
||||
getDataTree,
|
||||
(entities: DataTree) => {
|
||||
getNameBindingsWithData,
|
||||
(entities: DataTree, nameBindingsWithData: NameBindingsWithData) => {
|
||||
const widgets = { ...entities.canvasWidgets };
|
||||
Object.keys(widgets).forEach(widgetKey => {
|
||||
widgets[widgetKey] = enhanceWithDynamicValuesAndValidations(
|
||||
widgets[widgetKey],
|
||||
entities,
|
||||
nameBindingsWithData,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
24
app/client/src/selectors/nameBindingsWithDataSelector.ts
Normal file
24
app/client/src/selectors/nameBindingsWithDataSelector.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { DataTree } from "reducers";
|
||||
import { NameBindingsWithData } from "utils/DynamicBindingUtils";
|
||||
import { JSONPath } from "jsonpath-plus";
|
||||
import { createSelector } from "reselect";
|
||||
import { getDataTree } from "./entitiesSelector";
|
||||
|
||||
export const getNameBindingsWithData = createSelector(
|
||||
getDataTree,
|
||||
(dataTree: DataTree): NameBindingsWithData => {
|
||||
const nameBindingsWithData: Record<string, object> = {};
|
||||
Object.keys(dataTree.nameBindings).forEach(key => {
|
||||
const nameBindings = dataTree.nameBindings[key];
|
||||
const evaluatedValue = JSONPath({
|
||||
path: nameBindings,
|
||||
json: dataTree,
|
||||
})[0];
|
||||
if (evaluatedValue && key !== "undefined") {
|
||||
nameBindingsWithData[key] = evaluatedValue;
|
||||
}
|
||||
});
|
||||
|
||||
return nameBindingsWithData;
|
||||
},
|
||||
);
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
import { createSelector } from "reselect";
|
||||
import { AppState, DataTree } from "reducers";
|
||||
import { AppState } from "reducers";
|
||||
import { PropertyPaneReduxState } from "reducers/uiReducers/propertyPaneReducer";
|
||||
import { PropertyPaneConfigState } from "reducers/entityReducers/propertyPaneConfigReducer";
|
||||
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { PropertySection } from "reducers/entityReducers/propertyPaneConfigReducer";
|
||||
import { getDataTree } from "./entitiesSelector";
|
||||
import { enhanceWithDynamicValuesAndValidations } from "utils/DynamicBindingUtils";
|
||||
import {
|
||||
enhanceWithDynamicValuesAndValidations,
|
||||
NameBindingsWithData,
|
||||
} from "utils/DynamicBindingUtils";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { getNameBindingsWithData } from "./nameBindingsWithDataSelector";
|
||||
|
||||
const getPropertyPaneState = (state: AppState): PropertyPaneReduxState =>
|
||||
state.ui.propertyPane;
|
||||
|
|
@ -35,10 +38,17 @@ export const getCurrentWidgetProperties = createSelector(
|
|||
|
||||
export const getWidgetPropsWithValidations = createSelector(
|
||||
getCurrentWidgetProperties,
|
||||
getDataTree,
|
||||
(widget: WidgetProps | undefined, dataTree: DataTree) => {
|
||||
getNameBindingsWithData,
|
||||
(
|
||||
widget: WidgetProps | undefined,
|
||||
nameBindigsWithData: NameBindingsWithData,
|
||||
) => {
|
||||
if (!widget) return undefined;
|
||||
return enhanceWithDynamicValuesAndValidations(widget, dataTree, false);
|
||||
return enhanceWithDynamicValuesAndValidations(
|
||||
widget,
|
||||
nameBindigsWithData,
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,67 @@
|
|||
import _ from "lodash";
|
||||
import { DataTree } from "reducers";
|
||||
import { JSONPath } from "jsonpath-plus";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { DATA_BIND_REGEX, DATA_PATH_REGEX } from "constants/BindingsConstants";
|
||||
import { DATA_BIND_JS_REGEX } from "constants/BindingsConstants";
|
||||
import ValidationFactory from "./ValidationFactory";
|
||||
import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton";
|
||||
|
||||
export type NameBindingsWithData = Record<string, object>;
|
||||
export const isDynamicValue = (value: string): boolean =>
|
||||
DATA_BIND_REGEX.test(value);
|
||||
DATA_BIND_JS_REGEX.test(value);
|
||||
|
||||
//{{}}{{}}}
|
||||
function parseDynamicString(dynamicString: string): string[] {
|
||||
let parsedDynamicValues = [];
|
||||
const indexOfDoubleParanStart = dynamicString.indexOf("{{");
|
||||
if (indexOfDoubleParanStart === -1) {
|
||||
return [dynamicString];
|
||||
}
|
||||
//{{}}{{}}}
|
||||
const firstString = dynamicString.substring(0, indexOfDoubleParanStart);
|
||||
firstString && parsedDynamicValues.push(firstString);
|
||||
let rest = dynamicString.substring(
|
||||
indexOfDoubleParanStart,
|
||||
dynamicString.length,
|
||||
);
|
||||
//{{}}{{}}}
|
||||
let sum = 0;
|
||||
for (let i = 0; i <= rest.length - 1; i++) {
|
||||
const char = rest[i];
|
||||
const prevChar = rest[i - 1];
|
||||
|
||||
if (char === "{") {
|
||||
sum++;
|
||||
} else if (char === "}") {
|
||||
sum--;
|
||||
if (prevChar === "}" && sum === 0) {
|
||||
parsedDynamicValues.push(rest.substring(0, i + 1));
|
||||
rest = rest.substring(i + 1, rest.length);
|
||||
if (rest) {
|
||||
parsedDynamicValues = parsedDynamicValues.concat(
|
||||
parseDynamicString(rest),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sum !== 0 && dynamicString !== "") {
|
||||
return [dynamicString];
|
||||
}
|
||||
return parsedDynamicValues;
|
||||
}
|
||||
|
||||
export const getDynamicBindings = (
|
||||
dynamicString: string,
|
||||
): { bindings: string[]; paths: string[] } => {
|
||||
// Get the {{binding}} bound values
|
||||
const bindings = dynamicString.match(DATA_BIND_REGEX) || [];
|
||||
const bindings = parseDynamicString(dynamicString);
|
||||
// Get the "binding" path values
|
||||
const paths = bindings.map(p => {
|
||||
const matches = p.match(DATA_PATH_REGEX);
|
||||
if (matches) return matches[0];
|
||||
const paths = bindings.map(binding => {
|
||||
const length = binding.length;
|
||||
const matches = binding.match(DATA_BIND_JS_REGEX);
|
||||
if (matches) {
|
||||
return binding.substring(2, length - 2);
|
||||
}
|
||||
return "";
|
||||
});
|
||||
return { bindings, paths };
|
||||
|
|
@ -24,17 +69,10 @@ export const getDynamicBindings = (
|
|||
|
||||
// Paths are expected to have "{name}.{path}" signature
|
||||
export const extractDynamicBoundValue = (
|
||||
dataTree: DataTree,
|
||||
data: NameBindingsWithData,
|
||||
path: string,
|
||||
): any => {
|
||||
// Remove the name in the binding
|
||||
const splitPath = path.split(".");
|
||||
// Find the dataTree path of the name
|
||||
const bindingPath = dataTree.nameBindings[splitPath[0]];
|
||||
// Create the full path
|
||||
const fullPath = `${bindingPath}.${splitPath.slice(1).join(".")}`;
|
||||
// Search with JSONPath
|
||||
return JSONPath({ path: fullPath, json: dataTree })[0];
|
||||
return JSExecutionManagerSingleton.evaluateSync(path, data);
|
||||
};
|
||||
|
||||
// For creating a final value where bindings could be in a template format
|
||||
|
|
@ -57,13 +95,16 @@ export const createDynamicValueString = (
|
|||
|
||||
export const getDynamicValue = (
|
||||
dynamicBinding: string,
|
||||
dataTree: DataTree,
|
||||
data: NameBindingsWithData,
|
||||
): any => {
|
||||
// Get the {{binding}} bound values
|
||||
const { bindings, paths } = getDynamicBindings(dynamicBinding);
|
||||
if (bindings.length) {
|
||||
// Get the Data Tree value of those "binding "paths
|
||||
const values = paths.map(p => extractDynamicBoundValue(dataTree, p));
|
||||
const values = paths.map((p, i) => {
|
||||
return p ? extractDynamicBoundValue(data, p) : bindings[i];
|
||||
});
|
||||
|
||||
// if it is just one binding, no need to create template string
|
||||
if (bindings.length === 1) return values[0];
|
||||
// else return a string template with bindings
|
||||
|
|
@ -74,17 +115,19 @@ export const getDynamicValue = (
|
|||
|
||||
export const enhanceWithDynamicValuesAndValidations = (
|
||||
widget: WidgetProps,
|
||||
entities: DataTree,
|
||||
nameBindingsWithData: NameBindingsWithData,
|
||||
replaceWithParsed: boolean,
|
||||
): WidgetProps => {
|
||||
if (!widget) return widget;
|
||||
const properties = { ...widget };
|
||||
const invalidProps: Record<string, boolean> = {};
|
||||
const t0 = performance.now();
|
||||
|
||||
Object.keys(widget).forEach((property: string) => {
|
||||
let value = widget[property];
|
||||
// Check for dynamic bindings
|
||||
if (widget.dynamicBindings && property in widget.dynamicBindings) {
|
||||
value = getDynamicValue(value, entities);
|
||||
value = getDynamicValue(value, nameBindingsWithData);
|
||||
}
|
||||
// Pass it through validation and parse
|
||||
const { isValid, parsed } = ValidationFactory.validateWidgetProperty(
|
||||
|
|
@ -97,5 +140,14 @@ export const enhanceWithDynamicValuesAndValidations = (
|
|||
// Replace if flag is turned on
|
||||
if (replaceWithParsed) properties[property] = parsed;
|
||||
});
|
||||
const t1 = performance.now();
|
||||
console.log(
|
||||
"Evaluations for " +
|
||||
widget.widgetName +
|
||||
" took " +
|
||||
(t1 - t0) +
|
||||
" milliseconds.",
|
||||
);
|
||||
console.trace();
|
||||
return { ...properties, invalidProps };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from "react";
|
|||
import _ from "lodash";
|
||||
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
|
||||
import { WidgetType } from "constants/WidgetConstants";
|
||||
import { ActionPayload } from "constants/ActionConstants";
|
||||
import { ActionPayload, BaseActionPayload } from "constants/ActionConstants";
|
||||
import { AutoResizer } from "react-base-table";
|
||||
import "react-base-table/styles.css";
|
||||
import { forIn } from "lodash";
|
||||
|
|
@ -94,7 +94,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
|
||||
export type PaginationType = "PAGES" | "INFINITE_SCROLL";
|
||||
|
||||
export interface TableAction extends ActionPayload {
|
||||
export interface TableAction extends BaseActionPayload {
|
||||
actionName: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
2
app/client/typings/Realm/index.d.ts
vendored
Normal file
2
app/client/typings/Realm/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// import * as React from "react";
|
||||
declare module "Realm";
|
||||
Loading…
Reference in New Issue
Block a user