Property validations
This commit is contained in:
parent
fe91dd4441
commit
750d69a6c6
|
|
@ -95,6 +95,7 @@
|
|||
"not op_mini all"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.22",
|
||||
"@types/react-select": "^3.0.5",
|
||||
"@types/react-tabs": "^2.3.1",
|
||||
"@types/redux-form": "^8.1.9",
|
||||
|
|
@ -106,6 +107,7 @@
|
|||
"eslint-config-react": "^1.1.7",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"eslint-plugin-react": "^7.14.3",
|
||||
"react-test-renderer": "^16.11.0",
|
||||
"redux-devtools": "^3.5.0",
|
||||
"redux-devtools-extension": "^2.13.8"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
ReduxAction,
|
||||
} from "../constants/ReduxActionConstants";
|
||||
import { RenderMode } from "../constants/WidgetConstants";
|
||||
import { ErrorCode } from "../constants/validationErrorCodes";
|
||||
|
||||
export const updateWidgetProperty = (
|
||||
widgetId: string,
|
||||
|
|
@ -21,9 +22,28 @@ export const updateWidgetProperty = (
|
|||
};
|
||||
};
|
||||
|
||||
export const updateWidgetPropertyValidation = (
|
||||
widgetId: string,
|
||||
propertyName: string,
|
||||
errorCode: ErrorCode,
|
||||
): ReduxAction<UpdateWidgetPropertyValidation> => ({
|
||||
type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY_VALIDATION,
|
||||
payload: {
|
||||
widgetId,
|
||||
propertyName,
|
||||
errorCode,
|
||||
},
|
||||
});
|
||||
|
||||
export interface UpdateWidgetPropertyPayload {
|
||||
widgetId: string;
|
||||
propertyName: string;
|
||||
propertyValue: any;
|
||||
renderMode: RenderMode;
|
||||
}
|
||||
|
||||
export interface UpdateWidgetPropertyValidation {
|
||||
widgetId: string;
|
||||
propertyName: string;
|
||||
errorCode: ErrorCode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import { Component } from "react";
|
||||
import _ from "lodash";
|
||||
import { ControlType } from "../../constants/PropertyControlConstants";
|
||||
import { ErrorCode } from "../../constants/validationErrorCodes";
|
||||
|
||||
abstract class BaseControl<T extends ControlProps> extends Component<T> {
|
||||
updateProperty(propertyName: string, propertyValue: any) {
|
||||
|
|
@ -29,10 +30,13 @@ export interface ControlData {
|
|||
propertyName: string;
|
||||
controlType: ControlType;
|
||||
propertyValue?: any;
|
||||
propertyError?: ErrorCode;
|
||||
}
|
||||
|
||||
export interface ControlFunctions {
|
||||
onPropertyChange?: (propertyName: string, propertyValue: string) => void;
|
||||
getDynamicValue: (dynamicBinding: string) => any;
|
||||
setPropertyValidation: (propertyName: string, errorCode: ErrorCode) => void;
|
||||
}
|
||||
|
||||
export default BaseControl;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlWrapper, StyledInputGroup } from "./StyledControls";
|
||||
import { InputType } from "zlib";
|
||||
import {
|
||||
ControlWrapper,
|
||||
StyledInputGroup,
|
||||
StyledValidationError,
|
||||
} from "./StyledControls";
|
||||
import { InputType } from "../../widgets/InputWidget";
|
||||
import { ControlType } from "../../constants/PropertyControlConstants";
|
||||
import { isDynamicValue } from "../../utils/DynamicBindingUtils";
|
||||
import { ERROR_CODES } from "../../constants/validationErrorCodes";
|
||||
|
||||
type InputTextControlType = InputType | "OBJECT" | "ARRAY" | "BOOLEAN";
|
||||
|
||||
class InputTextControl extends BaseControl<InputControlProps> {
|
||||
render() {
|
||||
|
|
@ -15,11 +24,16 @@ class InputTextControl extends BaseControl<InputControlProps> {
|
|||
placeholder={this.props.placeholderText}
|
||||
defaultValue={this.props.propertyValue}
|
||||
/>
|
||||
{this.props.propertyError && (
|
||||
<StyledValidationError>
|
||||
{this.props.propertyError}
|
||||
</StyledValidationError>
|
||||
)}
|
||||
</ControlWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
isNumberType(inputType: InputType): boolean {
|
||||
isNumberType(inputType: InputTextControlType): boolean {
|
||||
switch (inputType) {
|
||||
case "CURRENCY":
|
||||
case "INTEGER":
|
||||
|
|
@ -31,18 +45,66 @@ class InputTextControl extends BaseControl<InputControlProps> {
|
|||
}
|
||||
}
|
||||
|
||||
isStringType(inputType: InputTextControlType): boolean {
|
||||
switch (inputType) {
|
||||
case "TEXT":
|
||||
case "EMAIL":
|
||||
case "PASSWORD":
|
||||
case "SEARCH":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.updateProperty(this.props.propertyName, event.target.value);
|
||||
let value: string | number = event.target.value;
|
||||
if (this.isNumberType(this.props.inputType)) {
|
||||
value = _.toNumber(value);
|
||||
}
|
||||
this.validateInput(value);
|
||||
this.updateProperty(this.props.propertyName, value);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
return "INPUT_TEXT";
|
||||
}
|
||||
|
||||
validateInput(inputValue: any): boolean {
|
||||
const {
|
||||
getDynamicValue,
|
||||
inputType,
|
||||
setPropertyValidation,
|
||||
propertyName,
|
||||
} = this.props;
|
||||
let value = inputValue;
|
||||
if (isDynamicValue(inputValue)) {
|
||||
value = getDynamicValue(inputValue);
|
||||
}
|
||||
if (this.isNumberType(inputType) && !_.isNumber(value)) {
|
||||
setPropertyValidation(propertyName, ERROR_CODES.TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (this.isStringType(inputType) && !_.isString(value)) {
|
||||
setPropertyValidation(propertyName, ERROR_CODES.TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (inputType === "ARRAY" && !Array.isArray(value)) {
|
||||
setPropertyValidation(propertyName, ERROR_CODES.TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (inputType === "OBJECT" && !_.isObject(value)) {
|
||||
setPropertyValidation(propertyName, ERROR_CODES.TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
setPropertyValidation(propertyName, ERROR_CODES.NO_ERROR);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface InputControlProps extends ControlProps {
|
||||
placeholderText: string;
|
||||
inputType: InputType;
|
||||
inputType: InputTextControlType;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,3 +76,7 @@ export const StyledTimeZonePicker = styled(TimezonePicker)`
|
|||
box-shadow: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledValidationError = styled.span`
|
||||
color: ${props => props.theme.colors.error};
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
CREATE_APPLICATION_SUCCESS: "CREATE_APPLICATION_SUCCESS",
|
||||
CREATE_UPDATE_BINDINGS_MAP_INIT: "CREATE_UPDATE_BINDINGS_MAP_INIT",
|
||||
CREATE_UPDATE_BINDINGS_MAP_SUCCESS: "CREATE_UPDATE_BINDINGS_MAP_SUCCESS",
|
||||
UPDATE_WIDGET_PROPERTY_VALIDATION: "UPDATE_WIDGET_PROPERTY_VALIDATION",
|
||||
HIDE_PROPERTY_PANE: "HIDE_PROPERTY_PANE",
|
||||
};
|
||||
|
||||
|
|
|
|||
11
app/client/src/constants/validationErrorCodes.ts
Normal file
11
app/client/src/constants/validationErrorCodes.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export const ERROR_CODES = {
|
||||
NO_ERROR: "NO_ERROR",
|
||||
TYPE_ERROR: "TYPE_ERROR",
|
||||
};
|
||||
|
||||
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
||||
|
||||
export const ERROR_CODES_MESSAGES: Record<ErrorCode, string> = {
|
||||
NO_ERROR: "",
|
||||
TYPE_ERROR: "This input is not of a valid type",
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { PropertyPaneConfigState } from "../reducers/entityReducers/propertyPaneConfigReducer";
|
||||
|
||||
const PropertyPaneConfigResponse: PropertyPaneConfigState = {
|
||||
const PropertyPaneConfigResponse = {
|
||||
config: {
|
||||
BUTTON_WIDGET: [
|
||||
{
|
||||
|
|
@ -384,6 +384,7 @@ const PropertyPaneConfigResponse: PropertyPaneConfigState = {
|
|||
propertyName: "tableData",
|
||||
label: "Table Data",
|
||||
controlType: "INPUT_TEXT",
|
||||
inputType: "ARRAY",
|
||||
},
|
||||
{
|
||||
id: "11.3",
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
import React, { Component } from "react";
|
||||
import styled from "styled-components";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "../../reducers";
|
||||
import { AppState, DataTree } from "../../reducers";
|
||||
import PropertyControlFactory from "../../utils/PropertyControlFactory";
|
||||
import _ from "lodash";
|
||||
import { PropertySection } from "../../reducers/entityReducers/propertyPaneConfigReducer";
|
||||
import { updateWidgetProperty } from "../../actions/controlActions";
|
||||
import {
|
||||
updateWidgetProperty,
|
||||
updateWidgetPropertyValidation,
|
||||
} from "../../actions/controlActions";
|
||||
import {
|
||||
getCurrentWidgetId,
|
||||
getCurrentReferenceNode,
|
||||
getPropertyConfig,
|
||||
getIsPropertyPaneVisible,
|
||||
getCurrentWidgetProperties,
|
||||
getPropertyErrors,
|
||||
} from "../../selectors/propertyPaneSelectors";
|
||||
import { Divider } from "@blueprintjs/core";
|
||||
|
||||
|
|
@ -19,6 +23,12 @@ import Popper from "./Popper";
|
|||
import { ControlProps } from "../../components/propertyControls/BaseControl";
|
||||
import { RenderModes } from "../../constants/WidgetConstants";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { getDataTree } from "../../selectors/entitiesSelector";
|
||||
import { getDynamicValue as extractDynamicValue } from "../../utils/DynamicBindingUtils";
|
||||
import {
|
||||
ErrorCode,
|
||||
ERROR_CODES_MESSAGES,
|
||||
} from "../../constants/validationErrorCodes";
|
||||
import { CloseButton } from "components/designSystems/blueprint/CloseButton";
|
||||
import { theme } from "../../constants/DefaultTheme";
|
||||
|
||||
|
|
@ -52,6 +62,8 @@ class PropertyPane extends Component<
|
|||
constructor(props: PropertyPaneProps & PropertyPaneFunctions) {
|
||||
super(props);
|
||||
this.onPropertyChange = this.onPropertyChange.bind(this);
|
||||
this.getDynamicValue = this.getDynamicValue.bind(this);
|
||||
this.setPropertyValidation = this.setPropertyValidation.bind(this);
|
||||
}
|
||||
|
||||
getPropertyValue = (propertyName: string) => {
|
||||
|
|
@ -63,6 +75,17 @@ class PropertyPane extends Component<
|
|||
return widgetProperties[propertyName];
|
||||
};
|
||||
|
||||
getPropertyValidation = (propertyName: string): string | undefined => {
|
||||
const { propertyErrors, widgetId } = this.props;
|
||||
if (!widgetId) return undefined;
|
||||
if (widgetId in propertyErrors) {
|
||||
const errorCode: ErrorCode = propertyErrors[widgetId][propertyName];
|
||||
return ERROR_CODES_MESSAGES[errorCode];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.props.isVisible) {
|
||||
const content = this.renderPropertyPane(this.props.propertySections);
|
||||
|
|
@ -131,9 +154,16 @@ class PropertyPane extends Component<
|
|||
propertyControlOrSection.propertyValue = this.getPropertyValue(
|
||||
propertyControlOrSection.propertyName,
|
||||
);
|
||||
propertyControlOrSection.propertyError = this.getPropertyValidation(
|
||||
propertyControlOrSection.propertyName,
|
||||
);
|
||||
return PropertyControlFactory.createControl(
|
||||
propertyControlOrSection,
|
||||
{ onPropertyChange: this.onPropertyChange },
|
||||
{
|
||||
onPropertyChange: this.onPropertyChange,
|
||||
getDynamicValue: this.getDynamicValue,
|
||||
setPropertyValidation: this.setPropertyValidation,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
|
@ -153,11 +183,28 @@ class PropertyPane extends Component<
|
|||
propertyValue,
|
||||
);
|
||||
}
|
||||
|
||||
getDynamicValue(dynamicBinding: string) {
|
||||
const { dataTree } = this.props;
|
||||
return extractDynamicValue(dynamicBinding, dataTree);
|
||||
}
|
||||
|
||||
setPropertyValidation(propertyName: string, errorCode: ErrorCode) {
|
||||
if (this.props.widgetId) {
|
||||
this.props.setPropertyValidation(
|
||||
this.props.widgetId,
|
||||
propertyName,
|
||||
errorCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState): PropertyPaneProps => {
|
||||
return {
|
||||
propertySections: getPropertyConfig(state),
|
||||
dataTree: getDataTree(state),
|
||||
propertyErrors: getPropertyErrors(state),
|
||||
widgetId: getCurrentWidgetId(state),
|
||||
widgetProperties: getCurrentWidgetProperties(state),
|
||||
isVisible: getIsPropertyPaneVisible(state),
|
||||
|
|
@ -184,11 +231,21 @@ const mapDispatchToProps = (dispatch: any): PropertyPaneFunctions => {
|
|||
dispatch({
|
||||
type: ReduxActionTypes.HIDE_PROPERTY_PANE,
|
||||
}),
|
||||
setPropertyValidation: (
|
||||
widgetId: string,
|
||||
propertyName: string,
|
||||
errorCode: ErrorCode,
|
||||
) =>
|
||||
dispatch(
|
||||
updateWidgetPropertyValidation(widgetId, propertyName, errorCode),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export interface PropertyPaneProps {
|
||||
propertySections?: PropertySection[];
|
||||
propertyErrors: Record<string, Record<string, ErrorCode>>;
|
||||
dataTree: DataTree;
|
||||
widgetId?: string;
|
||||
widgetProperties?: any; //TODO(abhinav): Secure type definition
|
||||
isVisible: boolean;
|
||||
|
|
@ -198,6 +255,11 @@ export interface PropertyPaneProps {
|
|||
export interface PropertyPaneFunctions {
|
||||
updateWidgetProperty: Function;
|
||||
hidePropertyPane: () => void;
|
||||
setPropertyValidation: (
|
||||
widgetId: string,
|
||||
propertyName: string,
|
||||
errorCode: ErrorCode,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default connect(
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ import { ActionDataState } from "./actionsReducer";
|
|||
|
||||
const initialState: APIDataState = {};
|
||||
|
||||
export interface APIDataState {
|
||||
[id: string]: ActionResponse;
|
||||
}
|
||||
export type APIDataState = Record<string, ActionResponse>;
|
||||
|
||||
const apiDataReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.EXECUTE_ACTION_SUCCESS]: (
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import _ from "lodash";
|
||||
import { createReducer } from "../../utils/AppsmithUtils";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxAction,
|
||||
ShowPropertyPanePayload,
|
||||
} from "../../constants/ReduxActionConstants";
|
||||
import { ERROR_CODES, ErrorCode } from "../../constants/validationErrorCodes";
|
||||
import { UpdateWidgetPropertyValidation } from "../../actions/controlActions";
|
||||
|
||||
const initialState: PropertyPaneReduxState = {
|
||||
isVisible: false,
|
||||
widgetId: undefined,
|
||||
node: undefined,
|
||||
errors: {},
|
||||
};
|
||||
|
||||
const propertyPaneReducer = createReducer(initialState, {
|
||||
|
|
@ -24,17 +28,32 @@ const propertyPaneReducer = createReducer(initialState, {
|
|||
if (toggle) {
|
||||
isVisible = !state.isVisible;
|
||||
}
|
||||
return { widgetId, node, isVisible };
|
||||
return { ...state, widgetId, node, isVisible };
|
||||
},
|
||||
[ReduxActionTypes.HIDE_PROPERTY_PANE]: (state: PropertyPaneReduxState) => {
|
||||
return { ...state, isVisible: false };
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_WIDGET_PROPERTY_VALIDATION]: (
|
||||
state: PropertyPaneReduxState,
|
||||
action: ReduxAction<UpdateWidgetPropertyValidation>,
|
||||
) => {
|
||||
const { widgetId, propertyName, errorCode } = action.payload;
|
||||
let widgetErrors = { ...state.errors[widgetId] };
|
||||
if (action.payload.errorCode === ERROR_CODES.NO_ERROR) {
|
||||
widgetErrors = _.omit(widgetErrors, propertyName);
|
||||
} else {
|
||||
widgetErrors[propertyName] = errorCode;
|
||||
}
|
||||
const errors = { ...state.errors, [widgetId]: widgetErrors };
|
||||
return { ...state, errors };
|
||||
},
|
||||
});
|
||||
|
||||
export interface PropertyPaneReduxState {
|
||||
widgetId?: string;
|
||||
isVisible: boolean;
|
||||
node?: HTMLDivElement;
|
||||
errors: Record<string, Record<string, ErrorCode>>;
|
||||
}
|
||||
|
||||
export default propertyPaneReducer;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import ActionAPI, {
|
|||
ExecuteActionRequest,
|
||||
RestAction,
|
||||
} from "../api/ActionAPI";
|
||||
import { AppState, DataTree } from "../reducers";
|
||||
import { AppState } from "../reducers";
|
||||
import _ from "lodash";
|
||||
import { mapToPropList } from "../utils/AppsmithUtils";
|
||||
import AppToaster from "../components/editorComponents/ToastComponent";
|
||||
|
|
@ -31,18 +31,15 @@ import {
|
|||
updateActionSuccess,
|
||||
} from "../actions/actionActions";
|
||||
import { API_EDITOR_ID_URL, API_EDITOR_URL } from "../constants/routes";
|
||||
import { getDynamicBoundValue } from "../utils/DynamicBindingUtils";
|
||||
import { extractDynamicBoundValue } from "../utils/DynamicBindingUtils";
|
||||
import history from "../utils/history";
|
||||
import { validateResponse } from "./ErrorSagas";
|
||||
import { getDataTree } from "../selectors/entitiesSelector";
|
||||
import {
|
||||
ERROR_MESSAGE_SELECT_ACTION,
|
||||
ERROR_MESSAGE_SELECT_ACTION_TYPE,
|
||||
} from "constants/messages";
|
||||
|
||||
const getDataTree = (state: AppState): DataTree => {
|
||||
return state.entities;
|
||||
};
|
||||
|
||||
const getAction = (
|
||||
state: AppState,
|
||||
actionId: string,
|
||||
|
|
@ -69,7 +66,7 @@ const createActionErrorResponse = (
|
|||
|
||||
export function* evaluateJSONPathSaga(path: string): any {
|
||||
const dataTree = yield select(getDataTree);
|
||||
return getDynamicBoundValue(dataTree, path);
|
||||
return extractDynamicBoundValue(dataTree, path);
|
||||
}
|
||||
|
||||
export function* executeAPIQueryActionSaga(apiAction: ActionPayload) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { put, select, takeEvery, takeLatest, all } from "redux-saga/effects";
|
||||
import { getNextWidgetName } from "../utils/AppsmithUtils";
|
||||
import { UpdateWidgetPropertyPayload } from "../actions/controlActions";
|
||||
import { DATA_BIND_REGEX } from "../constants/BindingsConstants";
|
||||
import { isDynamicValue } from "../utils/DynamicBindingUtils";
|
||||
|
||||
export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) {
|
||||
try {
|
||||
|
|
@ -173,8 +173,7 @@ function* updateWidgetPropertySaga(
|
|||
payload: { propertyValue },
|
||||
} = updateAction;
|
||||
|
||||
const isDynamic = DATA_BIND_REGEX.test(propertyValue);
|
||||
if (isDynamic) {
|
||||
if (isDynamicValue(propertyValue)) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.UPDATE_WIDGET_DYNAMIC_PROPERTY,
|
||||
payload: updateAction.payload,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import { AppState, DataTree } from "../reducers";
|
|||
import { AppViewReduxState } from "../reducers/uiReducers/appViewReducer";
|
||||
import { AppViewerProps } from "../pages/AppViewer";
|
||||
import { injectDataTreeIntoDsl } from "../utils/DynamicBindingUtils";
|
||||
import { getDataTree } from "./entitiesSelector";
|
||||
|
||||
const getAppViewState = (state: AppState) => state.ui.appView;
|
||||
const getDataTree = (state: AppState): DataTree => state.entities;
|
||||
|
||||
export const getCurrentLayoutId = (state: AppState, props: AppViewerProps) =>
|
||||
state.ui.appView.currentLayoutId || props.match.params.layoutId;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { WidgetCardProps } from "widgets/BaseWidget";
|
|||
import { WidgetSidebarReduxState } from "reducers/uiReducers/widgetSidebarReducer";
|
||||
import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer";
|
||||
import { injectDataTreeIntoDsl } from "utils/DynamicBindingUtils";
|
||||
import { getDataTree } from "./entitiesSelector";
|
||||
import {
|
||||
FlattenedWidgetProps,
|
||||
CanvasWidgetsReduxState,
|
||||
|
|
@ -18,7 +19,6 @@ import { WidgetTypes } from "constants/WidgetConstants";
|
|||
|
||||
const getEditorState = (state: AppState) => state.ui.editor;
|
||||
const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig;
|
||||
const getEntities = (state: AppState) => state.entities;
|
||||
const getWidgetSideBar = (state: AppState) => state.ui.widgetSidebar;
|
||||
|
||||
const getWidgets = (state: AppState): CanvasWidgetsReduxState =>
|
||||
|
|
@ -102,7 +102,7 @@ export const getWidgetCards = createSelector(
|
|||
|
||||
export const getDenormalizedDSL = createCachedSelector(
|
||||
getPageWidgetId,
|
||||
getEntities,
|
||||
getDataTree,
|
||||
(pageWidgetId: string, entities: DataTree) => {
|
||||
const dsl = CanvasWidgetsNormalizer.denormalize(pageWidgetId, entities);
|
||||
return injectDataTreeIntoDsl(entities, dsl);
|
||||
|
|
|
|||
3
app/client/src/selectors/entitiesSelector.ts
Normal file
3
app/client/src/selectors/entitiesSelector.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { AppState, DataTree } from "../reducers";
|
||||
|
||||
export const getDataTree = (state: AppState): DataTree => state.entities;
|
||||
|
|
@ -61,3 +61,8 @@ export const getIsPropertyPaneVisible = createSelector(
|
|||
(pane: PropertyPaneReduxState, content?: PropertySection[]) =>
|
||||
!!(pane.isVisible && pane.widgetId && pane.node && content),
|
||||
);
|
||||
|
||||
export const getPropertyErrors = createSelector(
|
||||
getPropertyPaneState,
|
||||
(pane: PropertyPaneReduxState) => pane.errors || {},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,28 @@ import {
|
|||
DATA_PATH_REGEX,
|
||||
} from "../constants/BindingsConstants";
|
||||
|
||||
export const isDynamicValue = (value: string): boolean =>
|
||||
DATA_BIND_REGEX.test(value);
|
||||
|
||||
export const getDynamicBindings = (
|
||||
dynamicString: string,
|
||||
): { bindings: string[]; paths: string[] } => {
|
||||
// Get the {{binding}} bound values
|
||||
const bindings = dynamicString.match(DATA_BIND_REGEX) || [];
|
||||
// Get the "binding" path values
|
||||
const paths = bindings.map(p => {
|
||||
const matches = p.match(DATA_PATH_REGEX);
|
||||
if (matches) return matches[0];
|
||||
return "";
|
||||
});
|
||||
return { bindings, paths };
|
||||
};
|
||||
|
||||
// Paths are expected to have "{name}.{path}" signature
|
||||
export const getDynamicBoundValue = (
|
||||
export const extractDynamicBoundValue = (
|
||||
dataTree: DataTree,
|
||||
path: string,
|
||||
): Array<any> => {
|
||||
): any => {
|
||||
// Remove the name in the binding
|
||||
const splitPath = path.split(".");
|
||||
// Find the dataTree path of the name
|
||||
|
|
@ -20,7 +37,42 @@ export const getDynamicBoundValue = (
|
|||
// Create the full path
|
||||
const fullPath = `${bindingPath}.${splitPath.slice(1).join(".")}`;
|
||||
// Search with JSONPath
|
||||
return JSONPath({ path: fullPath, json: dataTree });
|
||||
return JSONPath({ path: fullPath, json: dataTree })[0];
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
finalValue = finalValue.replace(b, value);
|
||||
});
|
||||
return finalValue;
|
||||
};
|
||||
|
||||
export const getDynamicValue = (
|
||||
dynamicBinding: string,
|
||||
dataTree: DataTree,
|
||||
): 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));
|
||||
// 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
|
||||
return createDynamicValueString(dynamicBinding, bindings, values);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const injectDataTreeIntoDsl = (
|
||||
|
|
@ -36,33 +88,7 @@ export const injectDataTreeIntoDsl = (
|
|||
// Check for dynamic bindings
|
||||
if (dynamicBindings && !_.isEmpty(dynamicBindings)) {
|
||||
Object.keys(dynamicBindings).forEach((dKey: string) => {
|
||||
// Get the {{binding}} bound values
|
||||
const bindings = dynamicBindings[dKey].match(DATA_BIND_REGEX);
|
||||
if (bindings && bindings.length) {
|
||||
// Get the "binding" path values
|
||||
const paths = bindings.map(p => {
|
||||
const matches = p.match(DATA_PATH_REGEX);
|
||||
if (matches) return matches[0];
|
||||
return "";
|
||||
});
|
||||
// Get the Data Tree value of those "binding "paths
|
||||
const values = paths.map(p => {
|
||||
const value = getDynamicBoundValue(entities, p)[0];
|
||||
if (value) return value;
|
||||
return "undefined";
|
||||
});
|
||||
// Replace the string with the data tree values
|
||||
let string = dynamicBindings[dKey];
|
||||
bindings.forEach((b, i) => {
|
||||
let value = values[i];
|
||||
if (Array.isArray(value)) {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
string = string.replace(b, value);
|
||||
});
|
||||
// Overwrite the property with the evaluated data tree property
|
||||
widget[dKey] = string;
|
||||
}
|
||||
widget[dKey] = getDynamicValue(dynamicBindings[dKey], entities);
|
||||
});
|
||||
}
|
||||
if (tree.children) {
|
||||
|
|
|
|||
34
app/client/src/utils/DynamicBindingsUtil.test.ts
Normal file
34
app/client/src/utils/DynamicBindingsUtil.test.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { getDynamicValue } from "./DynamicBindingUtils";
|
||||
import { DataTree } from "../reducers";
|
||||
|
||||
it("Gets the value from the data tree", () => {
|
||||
const dynamicBinding = "{{GetUsers.data}}";
|
||||
const dataTree: Partial<DataTree> = {
|
||||
apiData: {
|
||||
id: {
|
||||
body: {
|
||||
data: "correct data",
|
||||
},
|
||||
headers: {},
|
||||
statusCode: "0",
|
||||
duration: "0",
|
||||
size: "0",
|
||||
},
|
||||
someOtherId: {
|
||||
body: {
|
||||
data: "wrong data",
|
||||
},
|
||||
headers: {},
|
||||
statusCode: "0",
|
||||
duration: "0",
|
||||
size: "0",
|
||||
},
|
||||
},
|
||||
nameBindings: {
|
||||
GetUsers: "$.apiData.id.body",
|
||||
},
|
||||
};
|
||||
const actualValue = "correct data";
|
||||
const value = getDynamicValue(dynamicBinding, dataTree);
|
||||
expect(value).toEqual(actualValue);
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import _ from "lodash";
|
||||
|
||||
import ContainerComponent from "components/designSystems/appsmith/ContainerComponent";
|
||||
import ContainerComponent from "../components/designSystems/appsmith/ContainerComponent";
|
||||
import { ContainerOrientation, WidgetType } from "constants/WidgetConstants";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import { Color } from "constants/Colors";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
|
||||
import { WidgetType } from "../constants/WidgetConstants";
|
||||
import { ActionPayload } from "../constants/ActionConstants";
|
||||
|
|
@ -26,27 +27,24 @@ function constructColumns(data: object[]): Column[] {
|
|||
return cols;
|
||||
}
|
||||
|
||||
function parseTableArray(parsable: string): object[] {
|
||||
let data: object[] = [];
|
||||
function getTableArrayData(tableData: string | object[] | undefined): object[] {
|
||||
try {
|
||||
const parsedData = JSON.parse(parsable);
|
||||
if (!Array.isArray(parsedData)) {
|
||||
throw new Error("Parsed Data is an object");
|
||||
if (!tableData) return [];
|
||||
if (_.isString(tableData)) {
|
||||
return JSON.parse(tableData);
|
||||
}
|
||||
data = parsedData;
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
return tableData;
|
||||
} catch (error) {
|
||||
console.error({ error });
|
||||
return [];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||
getPageView() {
|
||||
const tableData = parseTableArray(
|
||||
this.props.tableData ? ((this.props.tableData as any) as string) : "",
|
||||
);
|
||||
|
||||
const columns = constructColumns(tableData);
|
||||
const { tableData } = this.props;
|
||||
const data = getTableArrayData(tableData);
|
||||
const columns = constructColumns(data);
|
||||
return (
|
||||
<AutoResizer>
|
||||
{({ width, height }: { width: number; height: number }) => (
|
||||
|
|
@ -54,7 +52,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
width={width}
|
||||
height={height}
|
||||
columns={columns}
|
||||
data={tableData}
|
||||
data={data}
|
||||
maxHeight={height}
|
||||
selectedRowIndex={
|
||||
this.props.selectedRow && this.props.selectedRow.rowIndex
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"importHelpers": true,
|
||||
"typeRoots" : ["./typings", "./node_modules/@types"],
|
||||
"sourceMap": true,
|
||||
"baseUrl": "./src",
|
||||
"baseUrl": "./src"
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
|
|
|
|||
|
|
@ -1607,6 +1607,18 @@
|
|||
"@types/istanbul-lib-coverage" "*"
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest-diff@*":
|
||||
version "20.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89"
|
||||
integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==
|
||||
|
||||
"@types/jest@^24.0.22":
|
||||
version "24.0.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.22.tgz#08a50be08e78aba850a1185626e71d31e2336145"
|
||||
integrity sha512-t2OvhNZnrNjlzi2i0/cxbLVM59WN15I2r1Qtb7wDv28PnV9IzrPtagFRey/S9ezdLD0zyh1XGMQIEQND2YEfrw==
|
||||
dependencies:
|
||||
"@types/jest-diff" "*"
|
||||
|
||||
"@types/json-schema@^7.0.3":
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
|
||||
|
|
@ -10086,7 +10098,7 @@ react-input-autosize@^2.2.2:
|
|||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.2, react-is@^16.8.4:
|
||||
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.2, react-is@^16.8.4, react-is@^16.8.6:
|
||||
version "16.11.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa"
|
||||
integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==
|
||||
|
|
@ -10249,6 +10261,16 @@ react-select@^3.0.8:
|
|||
react-input-autosize "^2.2.2"
|
||||
react-transition-group "^2.2.1"
|
||||
|
||||
react-test-renderer@^16.11.0:
|
||||
version "16.11.0"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.11.0.tgz#72574566496462c808ac449b0287a4c0a1a7d8f8"
|
||||
integrity sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.8.6"
|
||||
scheduler "^0.17.0"
|
||||
|
||||
react-transition-group@^2.2.1, react-transition-group@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user