Action creator evaluated value fixes

This commit is contained in:
Hetu Nandu 2020-06-10 12:16:50 +00:00
parent 58e75ee603
commit cb2867069f
14 changed files with 190 additions and 142 deletions

View File

@ -38,6 +38,11 @@ Cypress.Commands.add("DeleteApp", appName => {
"response.body.responseMeta.status",
200,
);
cy.wait("@organizations").should(
"have.nested.property",
"response.body.responseMeta.status",
200,
);
cy.get('button span[icon="chevron-down"]').should("be.visible");
cy.get(homePage.searchInput).type(appName, { force: true });
cy.get(homePage.appMoreIcon)

View File

@ -574,7 +574,8 @@ class DynamicAutocompleteInput extends Component<Props, State> {
}
const showEvaluatedValue =
this.state.isFocused &&
("evaluatedValue" in this.props || "dataTreePath" in this.props);
("evaluatedValue" in this.props ||
("dataTreePath" in this.props && !!this.props.dataTreePath));
return (
<Wrapper>

View File

@ -195,6 +195,7 @@ const views = {
props.set(event);
}
}}
dataTreePath={""}
isValid={props.isValid}
errorMessage={props.validationMessage}
/>

View File

@ -37,7 +37,7 @@ export interface ControlData {
expected: string;
evaluatedValue: any;
validationMessage?: string;
dataTreePath: string;
dataTreePath?: string;
}
export interface ControlFunctions {

View File

@ -1,11 +1,7 @@
import React from "react";
import _ from "lodash";
import BaseControl, { ControlProps } from "./BaseControl";
import {
ControlWrapper,
StyledInputGroup,
StyledPropertyPaneButton,
} from "./StyledControls";
import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls";
import styled from "constants/DefaultTheme";
import { FormIcons } from "icons/FormIcons";
import { AnyStyledComponent } from "styled-components";
@ -18,24 +14,6 @@ const StyledOptionControlWrapper = styled(ControlWrapper)`
width: 100%;
`;
const StyledOptionControlInputGroup = styled(StyledInputGroup)`
margin-right: 2px;
width: 100%;
margin-bottom: 0;
&&& {
input {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
&:focus {
border: none;
color: ${props => props.theme.colors.textOnDarkBG};
background: ${props => props.theme.colors.paneInputBG};
}
}
}
`;
const StyledDynamicInput = styled.div`
width: 100%;
&&& {

View File

@ -5,20 +5,23 @@ import { EventOrValueHandler } from "redux-form";
class CodeEditorControl extends BaseControl<ControlProps> {
render() {
const {
errorMessage,
validationMessage,
expected,
propertyValue,
isValid,
dataTreePath,
evaluatedValue,
} = this.props;
return (
<DynamicAutocompleteInput
theme={"DARK"}
input={{ value: propertyValue, onChange: this.onChange }}
dataTreePath={dataTreePath}
expected={expected}
evaluatedValue={evaluatedValue}
meta={{
error: isValid ? "" : errorMessage,
error: isValid ? "" : validationMessage,
touched: true,
}}
singleLine={false}

View File

@ -21,7 +21,7 @@ const FIELD_VALUES: Record<
isRequired: "boolean",
isVisible: "boolean",
isDisabled: "boolean",
onDateSelected: "undefined",
onDateSelected: "Function Call",
},
TABLE_WIDGET: {
tableData: "Array<Object>",
@ -30,8 +30,8 @@ const FIELD_VALUES: Record<
exportPDF: "boolean",
exportExcel: "boolean",
exportCsv: "boolean",
onRowSelected: "undefined",
onPageChange: "undefined",
onRowSelected: "Function Call",
onPageChange: "Function Call",
},
IMAGE_WIDGET: {
image: "string",
@ -43,7 +43,7 @@ const FIELD_VALUES: Record<
defaultOptionValue: "string",
isRequired: "boolean",
isVisible: "boolean",
onSelectionChange: "undefined",
onSelectionChange: "Function Call",
},
TABS_WIDGET: {
tabs: "Array<{ label: string, id: string }>",
@ -53,7 +53,6 @@ const FIELD_VALUES: Record<
CHART_WIDGET: {
chartName: "string",
chartType: "LINE_CHART | BAR_CHART | PIE_CHART | COLUMN_CHART | AREA_CHART",
singleChartData: "Array<{ x: string, y: number }>",
xAxisName: "string",
yAxisName: "string",
isVisible: "boolean",
@ -71,7 +70,7 @@ const FIELD_VALUES: Record<
isRequired: "boolean",
isVisible: "boolean",
isDisabled: "boolean",
onTextChanged: "undefined",
onTextChanged: "Function Call",
},
DROP_DOWN_WIDGET: {
label: "string",
@ -80,7 +79,7 @@ const FIELD_VALUES: Record<
defaultOptionValue: "string",
isRequired: "boolean",
isVisible: "boolean",
onOptionChange: "boolean",
onOptionChange: "Function Call",
},
FORM_BUTTON_WIDGET: {
text: "string",
@ -88,7 +87,7 @@ const FIELD_VALUES: Record<
disabledWhenInvalid: "boolean",
resetFormOnClick: "boolean",
isVisible: "boolean",
onClick: "boolean",
onClick: "Function Call",
},
MAP_WIDGET: {
defaultMarkers: "Array<{ lat: number, long: number }>",
@ -96,20 +95,20 @@ const FIELD_VALUES: Record<
enablePickLocation: "boolean",
enableCreateMarker: "boolean",
isVisible: "boolean",
onMarkerClick: "undefined",
onCreateMarker: "undefined",
onMarkerClick: "Function Call",
onCreateMarker: "Function Call",
},
BUTTON_WIDGET: {
text: "string",
buttonStyle: "PRIMARY_BUTTON | SECONDARY_BUTTON | DANGER_BUTTON",
isVisible: "boolean",
onClick: "boolean",
onClick: "Function Call",
},
RICH_TEXT_EDITOR_WIDGET: {
defaultText: "string",
isVisible: "boolean",
isDisabled: "boolean",
onTextChange: "undefined",
onTextChange: "Function Call",
},
FILE_PICKER_WIDGET: {
label: "string",
@ -120,7 +119,7 @@ const FIELD_VALUES: Record<
isRequired: "boolean",
isVisible: "boolean",
uploadedFileUrls: "string",
onFilesSelected: "undefined",
onFilesSelected: "Function Call",
},
CHECKBOX_WIDGET: {
label: "string",
@ -128,7 +127,7 @@ const FIELD_VALUES: Record<
isRequired: "boolean",
isDisabled: "boolean",
isVisible: "boolean",
onCheckChange: "undefined",
onCheckChange: "Function Call",
},
FORM_WIDGET: {
backgroundColor: "string",

View File

@ -9,7 +9,7 @@ import { ControlIcons } from "icons/ControlIcons";
import PropertyControlFactory from "utils/PropertyControlFactory";
import { WidgetProps } from "widgets/BaseWidget";
import { PropertyControlPropsType } from "components/propertyControls";
import { Tooltip, Position } from "@blueprintjs/core";
import PropertyHelpLabel from "pages/Editor/PropertyPane/PropertyHelpLabel";
import FIELD_EXPECTED_VALUE from "constants/FieldExpectedValue";
type Props = {
@ -19,57 +19,6 @@ type Props = {
onPropertyChange: (propertyName: string, propertyValue: any) => void;
};
function UnderlinedLabel({
tooltip,
label,
}: {
tooltip?: string;
label: string;
}) {
const toolTipDefined = tooltip !== undefined;
return (
<Tooltip
disabled={!toolTipDefined}
content={tooltip}
position={Position.TOP}
hoverOpenDelay={200}
>
<div
style={{
height: "22px",
}}
>
<label
style={
toolTipDefined
? {
cursor: "help",
}
: {}
}
className={`t--property-control-label`}
>
{label}
</label>
<span
className={"underline"}
style={
toolTipDefined
? {
borderBottom: "1px dashed",
width: "100%",
display: "inline-block",
position: "relative",
top: "-15px",
}
: {}
}
/>
</div>
</Tooltip>
);
}
const PropertyControl = (props: Props) => {
const {
widgetProperties,
@ -136,8 +85,10 @@ const PropertyControl = (props: Props) => {
}
>
<ControlPropertyLabelContainer>
<UnderlinedLabel tooltip={propertyConfig.helpText} label={label} />
<PropertyHelpLabel
tooltip={propertyConfig.helpText}
label={label}
/>
{isConvertible && (
<JSToggleButton
active={isDynamic}

View File

@ -0,0 +1,54 @@
import { Position, Tooltip } from "@blueprintjs/core";
import React from "react";
type Props = {
tooltip?: string;
label: string;
};
const PropertyHelpLabel = (props: Props) => {
const toolTipDefined = props.tooltip !== undefined;
return (
<Tooltip
disabled={!toolTipDefined}
content={props.tooltip}
position={Position.TOP}
hoverOpenDelay={200}
>
<div
style={{
height: "22px",
}}
>
<label
style={
toolTipDefined
? {
cursor: "help",
}
: {}
}
className={`t--property-control-label`}
>
{props.label}
</label>
<span
className={"underline"}
style={
toolTipDefined
? {
borderBottom: "1px dashed",
width: "100%",
display: "inline-block",
position: "relative",
top: "-15px",
}
: {}
}
/>
</div>
</Tooltip>
);
};
export default PropertyHelpLabel;

View File

@ -369,8 +369,11 @@ export function* executeActionTriggers(
export 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(evaluateDataTree);
log.debug({ tree });
const { triggers } = getDynamicValue(dynamicString, tree, responseData, true);
log.debug({ triggers });
if (triggers && triggers.length) {
yield all(
triggers.map(trigger => call(executeActionTriggers, trigger, event)),

View File

@ -36,7 +36,6 @@ import { AppState } from "reducers";
import { Property } from "api/ActionAPI";
import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions";
import {
API_PATH_START_WITH_SLASH_ERROR,
FIELD_REQUIRED_ERROR,
UNIQUE_NAME_ERROR,
VALID_FUNCTION_NAME_ERROR,

View File

@ -27,6 +27,7 @@ const generateConfigWithIds = (config: PropertyConfig) => {
return config;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function* getLocalPropertyPaneConfigSaga() {
// FOR DEV WORK ONLY
try {

View File

@ -160,9 +160,7 @@ export const getDynamicValue = (
includeTriggers = false,
): JSExecutorResult => {
// Get the {{binding}} bound values
const { stringSegments: stringSegments, jsSnippets } = getDynamicBindings(
dynamicBinding,
);
const { stringSegments, jsSnippets } = getDynamicBindings(dynamicBinding);
if (stringSegments.length) {
// Get the Data Tree value of those "binding "paths
const values = jsSnippets.map((jsSnippet, index) => {
@ -199,32 +197,55 @@ export const getValidatedTree = (tree: any) => {
if (entity && entity.type) {
const parsedEntity = { ...entity };
Object.keys(entity).forEach((property: string) => {
const value = entity[property];
// Pass it through parse
const {
parsed,
isValid,
message,
} = ValidationFactory.validateWidgetProperty(
entity.type,
property,
value,
entity,
tree,
const hasEvaluatedValue = _.has(
parsedEntity,
`evaluatedValues.${property}`,
);
parsedEntity[property] = parsed;
if (property !== "evaluatedValues") {
if (!("evaluatedValues" in parsedEntity)) {
_.set(parsedEntity, "evaluatedValues", {});
}
if (!(property in parsedEntity.evaluatedValues)) {
_.set(parsedEntity, `evaluatedValues.${property}`, value);
}
}
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 (!isValid) {
_.set(parsedEntity, `invalidProps.${property}`, true);
_.set(parsedEntity, `validationMessages.${property}`, message);
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 = _.isUndefined(transformed)
? value
: transformed;
_.set(parsedEntity, `evaluatedValues.${property}`, evaluatedValue);
}
const hasValidation = _.has(parsedEntity, `invalidProps.${property}`);
if (!hasValidation && !isValid) {
_.set(parsedEntity, `invalidProps.${property}`, true);
_.set(parsedEntity, `validationMessages.${property}`, message);
}
}
});
return { ...tree, [entityKey]: parsedEntity };
@ -317,6 +338,11 @@ export const createDependencyTree = (
);
});
}
if (entity.dynamicTriggers) {
Object.keys(entity.dynamicTriggers).forEach(prop => {
dependencyMap[`${entityKey}.${prop}`] = [];
});
}
}
if (entity.ENTITY_TYPE === ENTITY_TYPE.ACTION) {
if (entity.dynamicBindingPathList.length) {
@ -548,10 +574,21 @@ function validateAndParseWidgetProperty(
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,
@ -560,7 +597,7 @@ function validateAndParseWidgetProperty(
} = ValidationFactory.validateWidgetProperty(
widget.type,
propertyName,
evalPropertyValue,
valueToValidate,
widget,
currentTree,
);
@ -572,18 +609,22 @@ function validateAndParseWidgetProperty(
_.set(widget, `invalidProps.${propertyName}`, true);
_.set(widget, `validationMessages.${propertyName}`, message);
}
const parsedCache = getParsedValueCache(propertyPath);
if (
!equal(parsedCache.value, parsed) ||
(cachedDependencyValues !== undefined &&
!equal(currentDependencyValues, cachedDependencyValues))
) {
parsedValueCache.set(propertyPath, {
value: parsed,
version: Date.now(),
});
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;
}
return parsed;
}
function isWidget(entity: DataTreeEntity): boolean {
@ -641,6 +682,7 @@ export function dependencySortedEvaluateDataTree(
widgetEntity,
currentTree,
evalPropertyValue,
unEvalPropertyValue,
currentDependencyValues,
cachedDependencyValues,
);

View File

@ -8,14 +8,14 @@ import {
import moment from "moment";
import {
WIDGET_TYPE_VALIDATION_ERROR,
NAVIGATE_TO_VALIDATION_ERROR,
// NAVIGATE_TO_VALIDATION_ERROR,
} from "constants/messages";
import { modalGetter } from "components/editorComponents/actioncreator/ActionCreator";
// import { modalGetter } from "components/editorComponents/actioncreator/ActionCreator";
import { WidgetProps } from "widgets/BaseWidget";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import { PageListPayload } from "constants/ReduxActionConstants";
import { isDynamicValue } from "./DynamicBindingUtils";
const URL_REGEX = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/;
// import { PageListPayload } from "constants/ReduxActionConstants";
// import { isDynamicValue } from "./DynamicBindingUtils";
// const URL_REGEX = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/;
export const VALIDATORS: Record<ValidationType, Validator> = {
[VALIDATION_TYPES.TEXT]: (
@ -415,6 +415,14 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
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);
@ -440,9 +448,12 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
}
}
}
*/
return {
isValid: true,
parsed: value,
isValid: false,
parsed: undefined,
transformed: "undefined",
message: "Not a function call",
};
},
[VALIDATION_TYPES.ARRAY_ACTION_SELECTOR]: (