Merge branch 'release' into fix/form

This commit is contained in:
Nikhil Nandagopal 2020-06-10 17:50:39 +05:30
commit 05bb520479
37 changed files with 440 additions and 319 deletions

View File

@ -113,7 +113,11 @@ cypress-test:
- cp $APPSMITH_SSL_CERTIFICATE /etc/certificate/dev.appsmith.com.pem
- cp $APPSMITH_SSL_KEY /etc/certificate/dev.appsmith.com-key.pem
- nginx
- yarn test:ci
# This command configures the cypress suite to point to our custom installation of sorry-cypress that will help us parallelize our tests
- |
DEBUG=cypress:* $(npm bin)/cypress version
sed -i -e 's|api_url:.*$|api_url: "https://appsmith-cypress.herokuapp.com/"|g' /builds/theappsmith/internal-tools-client/cache/Cypress/4.1.0/Cypress/resources/app/packages/server/config/app.yml
- BUILD_ID=$CI_COMMIT_SHORT_SHA yarn test:ci
artifacts:
when: always
expire_in: 1 week

View File

@ -56,7 +56,7 @@
"isVisible": true,
"isDisabled": false,
"datePickerType": "DATE_PICKER",
"dateFormat": "DD/MM/YYYY",
"dateFormat": "YYYY-MM-DD",
"label": "Date",
"widgetName": "DatePicker1",
"defaultDate": "2020-05-29T12:02:04.074+05:30",
@ -75,7 +75,7 @@
"isVisible": true,
"isDisabled": false,
"datePickerType": "DATE_PICKER",
"dateFormat": "DD/MM/YYYY",
"dateFormat": "YYYY-MM-DD",
"label": "Date",
"widgetName": "DatePicker2",
"defaultDate": "2020-05-29T12:02:04.074+05:30",

View File

@ -1,5 +1,3 @@
const testdata = require("../../../fixtures/testdata.json");
describe("API Panel Test Functionality ", function() {
it("Test API copy/Move/delete feature", function() {
cy.log("Login Successful");
@ -8,7 +6,6 @@ describe("API Panel Test Functionality ", function() {
cy.CreateAPI("FirstAPI");
cy.log("Creation of FirstAPI Action successful");
cy.CopyAPIToHome("FirstAPI");
cy.DeleteAPI("FirstAPI");
cy.MoveAPIToPage();
cy.DeleteAPI("FirstAPI");
cy.CreateAPI("FirstAPI");

View File

@ -49,7 +49,7 @@ describe("DatePicker Widget Functionality", function() {
);
});
it("Datepicker-Claer date validation", function() {
it("Datepicker-Clear date validation", function() {
const today = Cypress.moment()
.add(0, "days")
.format("DD/MM/YYYY");
@ -58,37 +58,37 @@ describe("DatePicker Widget Functionality", function() {
cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-input").should(
"contain.value",
today + " 00:00",
"",
);
});
it("DatePicker-check Required field validation", function() {
// Check the required checkbox
cy.CheckWidgetProperties(commonlocators.requiredCheckbox);
cy.get(formWidgetsPage.datepickerWidget + " .bp3-label").should(
"contain.text",
"From Date",
);
cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-label").should(
"contain.text",
"From Date",
);
});
it("DatePicker-uncheck Required field validation", function() {
// Uncheck the required checkbox
cy.UncheckWidgetProperties(commonlocators.requiredCheckbox);
cy.get(formWidgetsPage.datepickerWidget + " .bp3-label").should(
"contain.text",
"From Date",
);
cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-label").should(
"contain.text",
"From Date",
);
});
// it("DatePicker-check Required field validation", function() {
// // Check the required checkbox
// cy.CheckWidgetProperties(commonlocators.requiredCheckbox);
// cy.get(formWidgetsPage.datepickerWidget + " .bp3-label").should(
// "contain.text",
// "From Date",
// );
// cy.PublishtheApp();
// cy.get(publishPage.datepickerWidget + " .bp3-label").should(
// "contain.text",
// "From Date",
// );
// });
//
// it("DatePicker-uncheck Required field validation", function() {
// // Uncheck the required checkbox
// cy.UncheckWidgetProperties(commonlocators.requiredCheckbox);
// cy.get(formWidgetsPage.datepickerWidget + " .bp3-label").should(
// "contain.text",
// "From Date",
// );
// cy.PublishtheApp();
// cy.get(publishPage.datepickerWidget + " .bp3-label").should(
// "contain.text",
// "From Date",
// );
// });
it("DatePicker-check Visible field validation", function() {
// Check the visible checkbox

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)
@ -352,9 +357,7 @@ Cypress.Commands.add("MoveAPIToPage", () => {
.first()
.click({ force: true });
cy.get(apiwidget.moveTo).click({ force: true });
cy.get(
".single-select >div:contains('".concat(pageidcopy).concat("')"),
).click({ force: true });
cy.get(apiwidget.home).click({ force: true });
cy.wait("@createNewApi").should(
"have.nested.property",
"response.body.responseMeta.status",

View File

@ -26,7 +26,11 @@ echo "Got the target: $target"
if [ "$target" == "ci" ]; then
# On the CI server run the tests in parallel
# This requires the projectId and the record_key to be configured in your environment variables. By default this is defined on the CI server
$(npm bin)/cypress run --headless --browser chrome --record --parallel --group "Electrons on Gitlab CI" --spec "cypress/integration/Smoke_TestSuite/*/*"
echo "Got the Build ID: $BUILD_ID"
CYPRESS_PROJECT_ID=appsmith-project $(npm bin)/cypress run --headless --browser chrome \
--record --key "random-key" --ci-build-id $BUILD_ID \
--parallel --group "Electrons on Gitlab CI" \
--spec "cypress/integration/Smoke_TestSuite/*/*"
else
$(npm bin)/cypress run --headless --browser chrome --spec "cypress/integration/Smoke_TestSuite/*/*"
fi

View File

@ -69,8 +69,8 @@ class DatePickerComponent extends React.Component<
componentDidUpdate(prevProps: DatePickerComponentProps) {
if (
this.props.selectedDate !== this.state.selectedDate &&
!moment(this.props.selectedDate).isSame(
moment(prevProps.selectedDate),
!moment(this.props.selectedDate, this.props.dateFormat).isSame(
moment(prevProps.selectedDate, this.props.dateFormat),
"seconds",
)
) {
@ -106,7 +106,7 @@ class DatePickerComponent extends React.Component<
className={this.props.isLoading ? "bp3-skeleton" : ""}
formatDate={this.formatDate}
parseDate={this.parseDate}
placeholder={this.props.dateFormat}
placeholder={"Select Date"}
disabled={this.props.isDisabled}
showActionsBar={true}
timePrecision={TimePrecision.MINUTE}
@ -114,7 +114,7 @@ class DatePickerComponent extends React.Component<
onChange={this.onDateSelected}
value={
this.state.selectedDate
? moment(this.state.selectedDate).toDate()
? this.parseDate(this.state.selectedDate)
: null
}
maxDate={maxDate.toDate()}
@ -137,16 +137,15 @@ class DatePickerComponent extends React.Component<
}
formatDate = (date: Date): string => {
const dateFormat = "DD/MM/YYYY HH:mm";
return moment(date).format(dateFormat);
return moment(date).format(this.props.dateFormat);
};
parseDate = (dateStr: string): Date => {
return moment(dateStr, "DD/MM/YYYY HH:mm").toDate();
return moment(dateStr, this.props.dateFormat).toDate();
};
onDateSelected = (selectedDate: Date) => {
const date = selectedDate ? moment(selectedDate).toISOString(true) : "";
const date = selectedDate ? this.formatDate(selectedDate) : "";
this.setState({ selectedDate: date });
this.props.onDateSelected(date);
};
@ -156,7 +155,7 @@ interface DatePickerComponentProps extends ComponentProps {
label: string;
dateFormat: string;
enableTimePicker?: boolean;
selectedDate: string;
selectedDate?: string;
minDate?: Date;
maxDate?: Date;
timezone?: string;
@ -167,7 +166,7 @@ interface DatePickerComponentProps extends ComponentProps {
}
interface DatePickerComponentState {
selectedDate: string;
selectedDate?: string;
}
export default DatePickerComponent;

View File

@ -367,6 +367,11 @@ class DynamicAutocompleteInput extends Component<Props, State> {
let inputValue = this.props.input.value || "";
if (typeof inputValue === "object") {
inputValue = JSON.stringify(inputValue, null, 2);
} else if (
typeof inputValue === "number" ||
typeof inputValue === "string"
) {
inputValue += "";
}
this.editor.setValue(inputValue);
this.startAutocomplete();
@ -383,6 +388,11 @@ class DynamicAutocompleteInput extends Component<Props, State> {
// Safe update of value of the editor when value updated outside the editor
if (typeof inputValue === "object") {
inputValue = JSON.stringify(inputValue, null, 2);
} else if (
typeof inputValue === "number" ||
typeof inputValue === "string"
) {
inputValue += "";
}
if ((!!inputValue || inputValue === "") && inputValue !== editorValue) {
this.editor.setValue(inputValue);
@ -574,7 +584,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

@ -3,7 +3,7 @@ import { WIDGET_PADDING } from "constants/WidgetConstants";
import styled, { css } from "styled-components";
const EDGE_RESIZE_HANDLE_WIDTH = 10;
const CORNER_RESIZE_HANDLE_WIDTH = 40;
const CORNER_RESIZE_HANDLE_WIDTH = 10;
export const VisibilityContainer = styled.div<{
visible: boolean;

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

@ -70,7 +70,7 @@ class DatePickerControl extends BaseControl<
}
onDateSelected = (date: Date): void => {
const selectedDate = date ? moment(date).toISOString(true) : "";
const selectedDate = date ? moment(date).toISOString(true) : undefined;
this.setState({ selectedDate: selectedDate });
this.updateProperty(this.props.propertyName, selectedDate);
};
@ -94,7 +94,7 @@ export interface DatePickerControlProps extends ControlProps {
}
interface DatePickerControlState {
selectedDate: string;
selectedDate?: string;
}
export default DatePickerControl;

View File

@ -28,13 +28,23 @@ export const DEFAULT_API_ACTION: Partial<RestAction> = {
export const API_CONSTANT = "API";
export const DEFAULT_PROVIDER_OPTION = "Business Software";
export const CONTENT_TYPE = "content-type";
export const POST_BODY_FORMATS = [
"application/json",
"application/x-www-form-urlencoded",
"raw",
export const POST_BODY_FORMAT_OPTIONS = [
{ label: "json", value: "application/json" },
{
label: "x-www-form-urlencoded",
value: "application/x-www-form-urlencoded",
},
{ label: "form-data", value: "multipart/form-data" },
{ label: "raw", value: "raw" },
];
export const POST_BODY_FORMAT_OPTIONS = POST_BODY_FORMATS.map(method => ({
label: method,
value: method,
}));
export const POST_BODY_FORMAT_OPTIONS_NO_MULTI_PART = POST_BODY_FORMAT_OPTIONS.filter(
option => {
return option.value !== "multipart/form-data";
},
);
export const POST_BODY_FORMATS = POST_BODY_FORMAT_OPTIONS.map(option => {
return option.value;
});

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

@ -1,7 +1,7 @@
export const PLUGIN_NAME_POSTGRES = "PostgresDbPlugin";
export const PLUGIN_NAME_MONGODB = " MongoDBPlugin";
export const PLUGIN_NAME_DBS = [PLUGIN_NAME_POSTGRES, PLUGIN_NAME_MONGODB];
export const QUERY_BODY_FIELD = "actionConfiguration.body";
export const PLUGIN_PACKAGE_POSTGRES = "postgres-plugin";
export const PLUGIN_PACKAGE_MONGO = "mongo-plugin";
export const PLUGIN_PACKAGE_DBS = [

View File

@ -48,7 +48,7 @@ export interface ApiActionConfig extends ActionConfig {
}
export interface QueryActionConfig extends ActionConfig {
queryString: string;
body: string;
}
export interface Action {

View File

@ -86,10 +86,10 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
datePickerType: "DATE_PICKER",
rows: 1,
label: "",
dateFormat: "DD/MM/YYYY",
dateFormat: "DD/MM/YYYY HH:mm",
columns: 5,
widgetName: "DatePicker",
defaultDate: moment().toISOString(true),
defaultDate: moment().format("DD/MM/YYYY HH:mm"),
},
TABLE_WIDGET: {
rows: 7,
@ -131,6 +131,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
{ label: "Vegan", value: "VEGAN" },
],
widgetName: "Dropdown",
defaultOptionValue: "VEG",
},
CHECKBOX_WIDGET: {
rows: 1,
@ -147,7 +148,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
{ id: "1", label: "Male", value: "M" },
{ id: "2", label: "Female", value: "F" },
],
defaultOptionValue: "1",
defaultOptionValue: "M",
widgetName: "RadioGroup",
},
ALERT_WIDGET: {
@ -164,6 +165,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
files: [],
label: "Select Files",
columns: 4,
maxNumFiles: 1,
maxFileSize: 5,
widgetName: "FilePicker",
isDefaultClickDisabled: true,
},
@ -233,8 +236,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
view: [
{
type: "ICON_WIDGET",
position: { left: 15, top: 0 },
size: { rows: 1, cols: 1 },
position: { left: 14, top: 0 },
size: { rows: 1, cols: 2 },
props: {
iconName: "cross",
iconSize: 24,
@ -244,7 +247,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
{
type: "TEXT_WIDGET",
position: { left: 0, top: 0 },
size: { rows: 1, cols: 15 },
size: { rows: 1, cols: 10 },
props: {
text: "Modal Title",
textStyle: "HEADING",

View File

@ -264,7 +264,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
actionConfigurationHeaders={actionConfigurationHeaders}
actionConfiguration={actionConfigurationBody}
change={props.change}
dataTreePath={`${actionName}.config.actionConfiguration.body`}
dataTreePath={`${actionName}.config.actionConfiguration`}
/>
)}
</RequestParamsWrapper>

View File

@ -127,12 +127,30 @@ export default function Pagination(props: PaginationProps) {
marginBottom: "6px",
}}
>
Configure the Table pageNo in the API.
1. Configure the Table pageNo in the API.
</p>
<ExampleApi>
http://api.example.com/users?pageNo={"{{Table1.pageNo}}"}
</ExampleApi>
</CalloutComponent>
<CalloutComponent>
<p
style={{
marginBottom: "6px",
}}
>
2. Enable server side pagination in Table1
</p>
</CalloutComponent>
<CalloutComponent>
<p
style={{
marginBottom: "6px",
}}
>
3. Call this API onPageChange in Table1.
</p>
</CalloutComponent>
</PaginationTypeView>
</React.Fragment>
);

View File

@ -7,6 +7,7 @@ import {
POST_BODY_FORMAT_OPTIONS,
POST_BODY_FORMATS,
CONTENT_TYPE,
POST_BODY_FORMAT_OPTIONS_NO_MULTI_PART,
} from "constants/ApiEditorConstants";
import { API_EDITOR_FORM_NAME } from "constants/forms";
import FormLabel from "components/editorComponents/FormLabel";
@ -62,7 +63,7 @@ const PostBodyData = (props: Props) => {
} = props;
return (
<PostbodyContainer>
<FormLabel>{"Post Body"}</FormLabel>
<FormLabel>{"Body"}</FormLabel>
<DropDownContainer>
<Select
className={"t--apiFormPostBodyType"}
@ -72,19 +73,15 @@ const PostBodyData = (props: Props) => {
onChange={(displayFormatObject: any) => {
if (
displayFormatObject &&
displayFormatObject.value === POST_BODY_FORMATS[2]
displayFormatObject.value === POST_BODY_FORMATS[3]
) {
setDisplayFormat(apiId, {
label: POST_BODY_FORMATS[2],
value: POST_BODY_FORMATS[2],
});
setDisplayFormat(apiId, POST_BODY_FORMAT_OPTIONS[3]);
return;
}
const elementsIndex = actionConfigurationHeaders.findIndex(
(element: { key: string; value: string }) =>
element.key.toLowerCase() === CONTENT_TYPE,
element.key.trim().toLowerCase() === CONTENT_TYPE,
);
if (elementsIndex >= 0 && displayFormatObject) {
@ -92,20 +89,18 @@ const PostBodyData = (props: Props) => {
updatedHeaders[elementsIndex] = {
...updatedHeaders[elementsIndex],
key: CONTENT_TYPE,
value: displayFormatObject.value,
};
onDisplayFormatChange(updatedHeaders);
} else {
setDisplayFormat(apiId, {
label: POST_BODY_FORMATS[2],
value: POST_BODY_FORMATS[2],
});
setDisplayFormat(apiId, POST_BODY_FORMAT_OPTIONS[3]);
}
}}
value={displayFormat}
width={300}
options={POST_BODY_FORMAT_OPTIONS}
options={POST_BODY_FORMAT_OPTIONS_NO_MULTI_PART}
/>
</DropDownContainer>
@ -113,13 +108,16 @@ const PostBodyData = (props: Props) => {
<React.Fragment>
<JSONEditorFieldWrapper className={"t--apiFormPostBody"}>
<DynamicTextField
name="actionConfiguration.body"
expected={FIELD_VALUES.API_ACTION.body}
name="actionConfiguration.body[0]"
height={300}
showLineNumbers
allowTabIndent
singleLine={false}
dataTreePath={`${dataTreePath}[0]`}
placeholder={
'{\n "name":"{{ inputName.property }}",\n "preference":"{{ dropdownName.property }}"\n}\n\n\\\\Take widget inputs using {{ }}'
}
dataTreePath={`${dataTreePath}.body`}
/>
</JSONEditorFieldWrapper>
</React.Fragment>
@ -128,22 +126,29 @@ const PostBodyData = (props: Props) => {
{displayFormat?.value === POST_BODY_FORMAT_OPTIONS[1].value && (
<React.Fragment>
<KeyValueFieldArray
name="actionConfiguration.body[1]"
dataTreePath={`${dataTreePath}[1]`}
name="actionConfiguration.bodyFormData"
dataTreePath={`${dataTreePath}.bodyFormData`}
label=""
/>
</React.Fragment>
)}
{/* Commenting this till we figure the code to create a multipart request
{displayFormat?.value === POST_BODY_FORMAT_OPTIONS[2].value && (
<React.Fragment>
<KeyValueFieldArray name="actionConfiguration.bodyFormData" label="" />
</React.Fragment>
)} */}
{displayFormat?.value === POST_BODY_FORMAT_OPTIONS[3].value && (
<React.Fragment>
<JSONEditorFieldWrapper>
<DynamicTextField
name="actionConfiguration.body[2]"
name="actionConfiguration.body"
height={300}
allowTabIndent
singleLine={false}
dataTreePath={`${dataTreePath}[2]`}
dataTreePath={`${dataTreePath}.body`}
/>
</JSONEditorFieldWrapper>
</React.Fragment>
@ -185,7 +190,7 @@ export default connect((state: AppState) => {
return {
displayFormat:
extraFormData["displayFormat"] || POST_BODY_FORMAT_OPTIONS[2],
extraFormData["displayFormat"] || POST_BODY_FORMAT_OPTIONS[3],
contentType,
apiId,
};

View File

@ -257,7 +257,7 @@ const RapidApiEditorForm: React.FC<Props> = (props: Props) => {
/>
{postbodyResponsePresent && (
<PostbodyContainer>
<FormLabel>{"Post Body"}</FormLabel>
<FormLabel>{"Body"}</FormLabel>
{typeof actionConfigurationBodyFormData ===
"object" && (
<React.Fragment>

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

@ -4,6 +4,7 @@ import {
InjectedFormProps,
Field,
FormSubmitHandler,
formValueSelector,
} from "redux-form";
import {
GridComponent,
@ -31,6 +32,8 @@ import "@syncfusion/ej2-react-grids/styles/material.css";
import { Colors } from "constants/Colors";
import JSONViewer from "./JSONViewer";
import { RestAction } from "entities/Action";
import { connect } from "react-redux";
import { AppState } from "reducers";
const QueryFormContainer = styled.div`
font-size: 20px;
@ -205,7 +208,7 @@ type QueryFormProps = {
onDeleteClick: () => void;
onSaveClick: () => void;
onRunClick: () => void;
createTemplate: (template: any, name: string) => void;
createTemplate: (template: any) => void;
onSubmit: FormSubmitHandler<RestAction>;
isDeleting: boolean;
allowSave: boolean;
@ -223,7 +226,11 @@ type QueryFormProps = {
};
};
export type StateAndRouteProps = QueryFormProps;
type ReduxProps = {
actionName: string;
};
export type StateAndRouteProps = QueryFormProps & ReduxProps;
type Props = StateAndRouteProps &
InjectedFormProps<RestAction, StateAndRouteProps>;
@ -400,18 +407,15 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
{isNewQuery && showTemplateMenu ? (
<TemplateMenu
createTemplate={templateString => {
const name = isSQL
? "actionConfiguration.query.cmd"
: "actionConfiguration.query";
setMenuVisibility(false);
createTemplate(templateString, name);
createTemplate(templateString);
}}
selectedPluginPackage={selectedPluginPackage}
/>
) : isSQL ? (
<Field
name="actionConfiguration.query.cmd"
name="actionConfiguration.body"
dataTreePath={`${props.actionName}.config.actionConfiguration.body`}
component={DynamicAutocompleteInput}
className="textAreaStyles"
mode="sql-js"
@ -419,17 +423,11 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
/>
) : (
<Field
name="actionConfiguration.query"
name="actionConfiguration.body"
dataTreePath={`${props.actionName}.config.actionConfiguration.body`}
component={DynamicAutocompleteInput}
className="textAreaStyles"
mode="js-js"
normalize={(value: any) => {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}}
/>
)}
</form>
@ -437,7 +435,7 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
{dataSources.length === 0 && (
<NoDataSourceContainer>
<p className="font18">
Seems like you dont have any Datasouces to create a query
Seems like you dont have any Datasources to create a query
</p>
<Button
onClick={() =>
@ -492,7 +490,17 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
);
};
export default reduxForm<RestAction, StateAndRouteProps>({
form: QUERY_EDITOR_FORM_NAME,
enableReinitialize: true,
})(QueryEditorForm);
const valueSelector = formValueSelector(QUERY_EDITOR_FORM_NAME);
const mapStateToProps = (state: AppState) => {
const actionName = valueSelector(state, "name");
return {
actionName,
};
};
export default connect(mapStateToProps)(
reduxForm<RestAction, StateAndRouteProps>({
form: QUERY_EDITOR_FORM_NAME,
enableReinitialize: true,
})(QueryEditorForm),
);

View File

@ -23,10 +23,13 @@ import {
getPluginIdsOfPackageNames,
getPluginPackageFromDatasourceId,
} from "selectors/entitiesSelector";
import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants";
import {
PLUGIN_PACKAGE_DBS,
QUERY_BODY_FIELD,
} from "constants/QueryEditorConstants";
import { getCurrentApplication } from "selectors/applicationSelectors";
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
import { RestAction } from "entities/Action";
import { QueryAction, RestAction } from "entities/Action";
const EmptyStateContainer = styled.div`
display: flex;
@ -156,7 +159,7 @@ class QueryEditor extends React.Component<Props> {
const mapStateToProps = (state: AppState): any => {
const { runErrorMessage } = state.ui.queryPane;
const formData = getFormValues(QUERY_EDITOR_FORM_NAME)(state) as RestAction;
const formData = getFormValues(QUERY_EDITOR_FORM_NAME)(state) as QueryAction;
const initialValues = getFormInitialValues(QUERY_EDITOR_FORM_NAME)(
state,
) as RestAction;
@ -187,8 +190,8 @@ const mapDispatchToProps = (dispatch: any): any => ({
deleteAction: (id: string) => dispatch(deleteQuery({ id })),
runAction: (action: RestAction, actionId: string) =>
dispatch(executeQuery({ action, actionId })),
createTemplate: (template: any, name: string) => {
dispatch(change(QUERY_EDITOR_FORM_NAME, name, template));
createTemplate: (template: any) => {
dispatch(change(QUERY_EDITOR_FORM_NAME, QUERY_BODY_FIELD, template));
},
});

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,
@ -227,37 +226,6 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
(header: any) => header.key.toLowerCase() === CONTENT_TYPE,
);
}
const actionConfigurationBody = data.actionConfiguration.body;
data.actionConfiguration.body = [];
if (contentType) {
if (
contentType.value === POST_BODY_FORMAT_OPTIONS[0].value &&
data.actionConfiguration.body
) {
data.actionConfiguration.body[0] = actionConfigurationBody;
} else if (
contentType.value === POST_BODY_FORMAT_OPTIONS[1].value &&
data.actionConfiguration.body
) {
if (typeof actionConfigurationBody !== "object") {
try {
data.actionConfiguration.body[1] = JSON.parse(
actionConfigurationBody,
);
} catch (e) {
data.actionConfiguration.body[2] = actionConfigurationBody;
}
} else {
data.actionConfiguration.body[1] = actionConfigurationBody;
}
} else {
data.actionConfiguration.body[2] = actionConfigurationBody;
}
} else if (!contentType && data.actionConfiguration.body) {
data.actionConfiguration.body[2] = actionConfigurationBody;
}
}
yield put(initialize(API_EDITOR_FORM_NAME, data));
@ -383,10 +351,7 @@ function* updateFormFields(
value: contentType.value,
};
} else {
displayFormat = {
label: POST_BODY_FORMATS[2],
value: POST_BODY_FORMATS[2],
};
displayFormat = POST_BODY_FORMAT_OPTIONS[3];
}
}

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

@ -26,13 +26,14 @@ import {
getCurrentApplicationId,
getCurrentPageId,
} from "selectors/editorSelectors";
import { initialize } from "redux-form";
import { change, initialize } from "redux-form";
import { getAction, getActionParams, getActionTimeout } from "./ActionSagas";
import { AppState } from "reducers";
import ActionAPI, {
PaginationField,
ExecuteActionRequest,
ActionApiResponse,
Property,
} from "api/ActionAPI";
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
import { changeQuery, deleteQuerySuccess } from "actions/queryPaneActions";
@ -128,12 +129,40 @@ function* updateDraftsSaga() {
}
}
function* updateDynamicBindingsSaga(
actionPayload: ReduxActionWithMeta<string, { field: string }>,
) {
const field = actionPayload.meta.field;
if (field === "dynamicBindingPathList") return;
const value = actionPayload.payload;
const { values } = yield select(getFormData, QUERY_EDITOR_FORM_NAME);
if (!values.id) return;
const isDynamic = isDynamicValue(value);
let dynamicBindings: Property[] = values.dynamicBindingPathList || [];
console.log({ field, value, isDynamic, dynamicBindings });
const fieldExists = _.some(dynamicBindings, { key: field });
if (!isDynamic && fieldExists) {
dynamicBindings = dynamicBindings.filter(d => d.key !== field);
}
if (isDynamic && !fieldExists) {
dynamicBindings.push({ key: field });
}
yield put(
change(QUERY_EDITOR_FORM_NAME, "dynamicBindingPathList", dynamicBindings),
);
}
function* formValueChangeSaga(
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
) {
const { form } = actionPayload.meta;
if (form !== QUERY_EDITOR_FORM_NAME) return;
yield all([call(updateDraftsSaga)]);
yield all([
call(updateDynamicBindingsSaga, actionPayload),
call(updateDraftsSaga),
]);
}
function* handleQueryCreatedSaga(actionPayload: ReduxAction<RestAction>) {

View File

@ -2,6 +2,7 @@ import {
CONTENT_TYPE,
HTTP_METHODS,
POST_BODY_FORMATS,
POST_BODY_FORMAT_OPTIONS,
} from "constants/ApiEditorConstants";
import _ from "lodash";
@ -44,15 +45,17 @@ export const transformRestAction = (data: any): any => {
contentType = contentTypeHeader.value;
}
}
let formatIndex = 2;
if (POST_BODY_FORMATS.includes(contentType)) {
formatIndex = POST_BODY_FORMATS.indexOf(contentType);
}
let body = "";
let body: any = "";
if (action.actionConfiguration.body) {
body = action.actionConfiguration.body[formatIndex] || undefined;
if (
contentType !== POST_BODY_FORMAT_OPTIONS[1].value &&
contentType !== POST_BODY_FORMAT_OPTIONS[2].value
) {
action.actionConfiguration.bodyFormData = undefined;
if (action.actionConfiguration.body)
body = action.actionConfiguration.body || undefined;
}
if (!_.isString(body)) body = JSON.stringify(body);
action = {
...action,

View File

@ -1,5 +1,6 @@
import { transformRestAction } from "transformers/RestActionTransformer";
import { PluginType, RestAction } from "entities/Action";
import { POST_BODY_FORMAT_OPTIONS } from "constants/ApiEditorConstants";
// jest.mock("POST_");
@ -85,7 +86,7 @@ describe("Api action transformer", () => {
...BASE_ACTION.actionConfiguration,
httpMethod: "POST",
headers: [{ key: "content-type", value: "application/json" }],
body: ["{ name: 'test' }", null],
body: "{ name: 'test' }",
},
};
const output = {
@ -108,9 +109,9 @@ describe("Api action transformer", () => {
...BASE_ACTION.actionConfiguration,
httpMethod: "POST",
headers: [
{ key: "content-type", value: "application/x-www-form-urlencoded" },
{ key: "content-type", value: POST_BODY_FORMAT_OPTIONS[1].value },
],
body: [{ name: "test" }, [{ key: "hey", value: "ho" }]],
bodyFormData: [{ key: "hey", value: "ho" }],
},
};
const output = {
@ -119,9 +120,10 @@ describe("Api action transformer", () => {
...BASE_ACTION.actionConfiguration,
httpMethod: "POST",
headers: [
{ key: "content-type", value: "application/x-www-form-urlencoded" },
{ key: "content-type", value: POST_BODY_FORMAT_OPTIONS[1].value },
],
body: '[{"key":"hey","value":"ho"}]',
body: "",
bodyFormData: [{ key: "hey", value: "ho" }],
},
};
const result = transformRestAction(input);
@ -135,7 +137,7 @@ describe("Api action transformer", () => {
...BASE_ACTION.actionConfiguration,
headers: [{ key: "content-type", value: "text/html" }],
httpMethod: "POST",
body: [{ name: "test" }, [{ key: "hey", value: "ho" }], "raw body"],
body: "raw body",
},
};
const output = {

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]: (
@ -393,17 +393,21 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
.hour(0)
.minute(0)
.second(0)
.millisecond(0)
.toISOString(true);
.millisecond(0);
if (value === undefined) {
return {
isValid: false,
parsed: today,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
};
}
const isValid = moment(value).isValid();
const parsed = isValid ? moment(value).toISOString(true) : today;
const parsed = isValid
? props.dateFormat
? moment(value).format(props.dateFormat)
: moment(value).toISOString(true)
: today;
return {
isValid,
parsed,
@ -415,6 +419,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 +452,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]: (

View File

@ -60,7 +60,7 @@ class DatePickerWidget extends BaseWidget<DatePickerWidgetProps, WidgetState> {
return (
<DatePickerComponent
label={`${this.props.label}`}
dateFormat={"DD/MM/YYYY HH:mm"}
dateFormat={this.props.dateFormat}
widgetId={this.props.widgetId}
isDisabled={this.props.isDisabled}
datePickerType={"DATE_PICKER"}

View File

@ -21,12 +21,17 @@ import Dashboard from "@uppy/dashboard";
import shallowequal from "shallowequal";
import _ from "lodash";
class FilePickerWidget extends BaseWidget<FilePickerWidgetProps, WidgetState> {
class FilePickerWidget extends BaseWidget<
FilePickerWidgetProps,
FilePickerWidgetState
> {
uppy: any;
constructor(props: FilePickerWidgetProps) {
super(props);
this.refreshUppy(props);
this.state = {
version: 0,
};
}
static getPropertyValidationMap(): WidgetPropertyValidationType {
@ -135,6 +140,7 @@ class FilePickerWidget extends BaseWidget<FilePickerWidgetProps, WidgetState> {
this.uppy.on("upload", () => {
this.onFilesSelected();
});
this.setState({ version: this.state.version + 1 });
};
static getTriggerPropertyMap(): TriggerPropertiesMap {
@ -158,8 +164,8 @@ class FilePickerWidget extends BaseWidget<FilePickerWidgetProps, WidgetState> {
handleFileUploaded = (result: ExecutionResult) => {
if (result.success) {
this.updateWidgetMetaProperty(
"uploadedFileData",
this.props.uploadedFileUrls,
"uploadedFileUrls",
this.props.uploadedFileUrlPaths,
);
}
};
@ -208,6 +214,10 @@ class FilePickerWidget extends BaseWidget<FilePickerWidgetProps, WidgetState> {
}
}
export interface FilePickerWidgetState extends WidgetState {
version: number;
}
export interface FilePickerWidgetProps extends WidgetProps {
label: string;
maxNumFiles?: number;
@ -216,7 +226,7 @@ export interface FilePickerWidgetProps extends WidgetProps {
allowedFileTypes: string[];
onFilesSelected?: string;
isRequired?: boolean;
uploadedFileUrls?: string;
uploadedFileUrlPaths?: string;
}
export default FilePickerWidget;