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_CERTIFICATE /etc/certificate/dev.appsmith.com.pem
- cp $APPSMITH_SSL_KEY /etc/certificate/dev.appsmith.com-key.pem - cp $APPSMITH_SSL_KEY /etc/certificate/dev.appsmith.com-key.pem
- nginx - 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: artifacts:
when: always when: always
expire_in: 1 week expire_in: 1 week

View File

@ -56,7 +56,7 @@
"isVisible": true, "isVisible": true,
"isDisabled": false, "isDisabled": false,
"datePickerType": "DATE_PICKER", "datePickerType": "DATE_PICKER",
"dateFormat": "DD/MM/YYYY", "dateFormat": "YYYY-MM-DD",
"label": "Date", "label": "Date",
"widgetName": "DatePicker1", "widgetName": "DatePicker1",
"defaultDate": "2020-05-29T12:02:04.074+05:30", "defaultDate": "2020-05-29T12:02:04.074+05:30",
@ -75,7 +75,7 @@
"isVisible": true, "isVisible": true,
"isDisabled": false, "isDisabled": false,
"datePickerType": "DATE_PICKER", "datePickerType": "DATE_PICKER",
"dateFormat": "DD/MM/YYYY", "dateFormat": "YYYY-MM-DD",
"label": "Date", "label": "Date",
"widgetName": "DatePicker2", "widgetName": "DatePicker2",
"defaultDate": "2020-05-29T12:02:04.074+05:30", "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() { describe("API Panel Test Functionality ", function() {
it("Test API copy/Move/delete feature", function() { it("Test API copy/Move/delete feature", function() {
cy.log("Login Successful"); cy.log("Login Successful");
@ -8,7 +6,6 @@ describe("API Panel Test Functionality ", function() {
cy.CreateAPI("FirstAPI"); cy.CreateAPI("FirstAPI");
cy.log("Creation of FirstAPI Action successful"); cy.log("Creation of FirstAPI Action successful");
cy.CopyAPIToHome("FirstAPI"); cy.CopyAPIToHome("FirstAPI");
cy.DeleteAPI("FirstAPI");
cy.MoveAPIToPage(); cy.MoveAPIToPage();
cy.DeleteAPI("FirstAPI"); cy.DeleteAPI("FirstAPI");
cy.CreateAPI("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() const today = Cypress.moment()
.add(0, "days") .add(0, "days")
.format("DD/MM/YYYY"); .format("DD/MM/YYYY");
@ -58,37 +58,37 @@ describe("DatePicker Widget Functionality", function() {
cy.PublishtheApp(); cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-input").should( cy.get(publishPage.datepickerWidget + " .bp3-input").should(
"contain.value", "contain.value",
today + " 00:00", "",
); );
}); });
it("DatePicker-check Required field validation", function() { // it("DatePicker-check Required field validation", function() {
// Check the required checkbox // // Check the required checkbox
cy.CheckWidgetProperties(commonlocators.requiredCheckbox); // cy.CheckWidgetProperties(commonlocators.requiredCheckbox);
cy.get(formWidgetsPage.datepickerWidget + " .bp3-label").should( // cy.get(formWidgetsPage.datepickerWidget + " .bp3-label").should(
"contain.text", // "contain.text",
"From Date", // "From Date",
); // );
cy.PublishtheApp(); // cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-label").should( // cy.get(publishPage.datepickerWidget + " .bp3-label").should(
"contain.text", // "contain.text",
"From Date", // "From Date",
); // );
}); // });
//
it("DatePicker-uncheck Required field validation", function() { // it("DatePicker-uncheck Required field validation", function() {
// Uncheck the required checkbox // // Uncheck the required checkbox
cy.UncheckWidgetProperties(commonlocators.requiredCheckbox); // cy.UncheckWidgetProperties(commonlocators.requiredCheckbox);
cy.get(formWidgetsPage.datepickerWidget + " .bp3-label").should( // cy.get(formWidgetsPage.datepickerWidget + " .bp3-label").should(
"contain.text", // "contain.text",
"From Date", // "From Date",
); // );
cy.PublishtheApp(); // cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-label").should( // cy.get(publishPage.datepickerWidget + " .bp3-label").should(
"contain.text", // "contain.text",
"From Date", // "From Date",
); // );
}); // });
it("DatePicker-check Visible field validation", function() { it("DatePicker-check Visible field validation", function() {
// Check the visible checkbox // Check the visible checkbox

View File

@ -38,6 +38,11 @@ Cypress.Commands.add("DeleteApp", appName => {
"response.body.responseMeta.status", "response.body.responseMeta.status",
200, 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('button span[icon="chevron-down"]').should("be.visible");
cy.get(homePage.searchInput).type(appName, { force: true }); cy.get(homePage.searchInput).type(appName, { force: true });
cy.get(homePage.appMoreIcon) cy.get(homePage.appMoreIcon)
@ -352,9 +357,7 @@ Cypress.Commands.add("MoveAPIToPage", () => {
.first() .first()
.click({ force: true }); .click({ force: true });
cy.get(apiwidget.moveTo).click({ force: true }); cy.get(apiwidget.moveTo).click({ force: true });
cy.get( cy.get(apiwidget.home).click({ force: true });
".single-select >div:contains('".concat(pageidcopy).concat("')"),
).click({ force: true });
cy.wait("@createNewApi").should( cy.wait("@createNewApi").should(
"have.nested.property", "have.nested.property",
"response.body.responseMeta.status", "response.body.responseMeta.status",

View File

@ -26,7 +26,11 @@ echo "Got the target: $target"
if [ "$target" == "ci" ]; then if [ "$target" == "ci" ]; then
# On the CI server run the tests in parallel # 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 # 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 else
$(npm bin)/cypress run --headless --browser chrome --spec "cypress/integration/Smoke_TestSuite/*/*" $(npm bin)/cypress run --headless --browser chrome --spec "cypress/integration/Smoke_TestSuite/*/*"
fi fi

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,7 @@
import React from "react"; import React from "react";
import _ from "lodash"; import _ from "lodash";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls";
ControlWrapper,
StyledInputGroup,
StyledPropertyPaneButton,
} from "./StyledControls";
import styled from "constants/DefaultTheme"; import styled from "constants/DefaultTheme";
import { FormIcons } from "icons/FormIcons"; import { FormIcons } from "icons/FormIcons";
import { AnyStyledComponent } from "styled-components"; import { AnyStyledComponent } from "styled-components";
@ -18,24 +14,6 @@ const StyledOptionControlWrapper = styled(ControlWrapper)`
width: 100%; 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` const StyledDynamicInput = styled.div`
width: 100%; width: 100%;
&&& { &&& {

View File

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

View File

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

View File

@ -28,13 +28,23 @@ export const DEFAULT_API_ACTION: Partial<RestAction> = {
export const API_CONSTANT = "API"; export const API_CONSTANT = "API";
export const DEFAULT_PROVIDER_OPTION = "Business Software"; export const DEFAULT_PROVIDER_OPTION = "Business Software";
export const CONTENT_TYPE = "content-type"; export const CONTENT_TYPE = "content-type";
export const POST_BODY_FORMATS = [
"application/json", export const POST_BODY_FORMAT_OPTIONS = [
"application/x-www-form-urlencoded", { label: "json", value: "application/json" },
"raw", {
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 => ({ export const POST_BODY_FORMAT_OPTIONS_NO_MULTI_PART = POST_BODY_FORMAT_OPTIONS.filter(
label: method, option => {
value: method, 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", isRequired: "boolean",
isVisible: "boolean", isVisible: "boolean",
isDisabled: "boolean", isDisabled: "boolean",
onDateSelected: "undefined", onDateSelected: "Function Call",
}, },
TABLE_WIDGET: { TABLE_WIDGET: {
tableData: "Array<Object>", tableData: "Array<Object>",
@ -30,8 +30,8 @@ const FIELD_VALUES: Record<
exportPDF: "boolean", exportPDF: "boolean",
exportExcel: "boolean", exportExcel: "boolean",
exportCsv: "boolean", exportCsv: "boolean",
onRowSelected: "undefined", onRowSelected: "Function Call",
onPageChange: "undefined", onPageChange: "Function Call",
}, },
IMAGE_WIDGET: { IMAGE_WIDGET: {
image: "string", image: "string",
@ -43,7 +43,7 @@ const FIELD_VALUES: Record<
defaultOptionValue: "string", defaultOptionValue: "string",
isRequired: "boolean", isRequired: "boolean",
isVisible: "boolean", isVisible: "boolean",
onSelectionChange: "undefined", onSelectionChange: "Function Call",
}, },
TABS_WIDGET: { TABS_WIDGET: {
tabs: "Array<{ label: string, id: string }>", tabs: "Array<{ label: string, id: string }>",
@ -53,7 +53,6 @@ const FIELD_VALUES: Record<
CHART_WIDGET: { CHART_WIDGET: {
chartName: "string", chartName: "string",
chartType: "LINE_CHART | BAR_CHART | PIE_CHART | COLUMN_CHART | AREA_CHART", chartType: "LINE_CHART | BAR_CHART | PIE_CHART | COLUMN_CHART | AREA_CHART",
singleChartData: "Array<{ x: string, y: number }>",
xAxisName: "string", xAxisName: "string",
yAxisName: "string", yAxisName: "string",
isVisible: "boolean", isVisible: "boolean",
@ -71,7 +70,7 @@ const FIELD_VALUES: Record<
isRequired: "boolean", isRequired: "boolean",
isVisible: "boolean", isVisible: "boolean",
isDisabled: "boolean", isDisabled: "boolean",
onTextChanged: "undefined", onTextChanged: "Function Call",
}, },
DROP_DOWN_WIDGET: { DROP_DOWN_WIDGET: {
label: "string", label: "string",
@ -80,7 +79,7 @@ const FIELD_VALUES: Record<
defaultOptionValue: "string", defaultOptionValue: "string",
isRequired: "boolean", isRequired: "boolean",
isVisible: "boolean", isVisible: "boolean",
onOptionChange: "boolean", onOptionChange: "Function Call",
}, },
FORM_BUTTON_WIDGET: { FORM_BUTTON_WIDGET: {
text: "string", text: "string",
@ -88,7 +87,7 @@ const FIELD_VALUES: Record<
disabledWhenInvalid: "boolean", disabledWhenInvalid: "boolean",
resetFormOnClick: "boolean", resetFormOnClick: "boolean",
isVisible: "boolean", isVisible: "boolean",
onClick: "boolean", onClick: "Function Call",
}, },
MAP_WIDGET: { MAP_WIDGET: {
defaultMarkers: "Array<{ lat: number, long: number }>", defaultMarkers: "Array<{ lat: number, long: number }>",
@ -96,20 +95,20 @@ const FIELD_VALUES: Record<
enablePickLocation: "boolean", enablePickLocation: "boolean",
enableCreateMarker: "boolean", enableCreateMarker: "boolean",
isVisible: "boolean", isVisible: "boolean",
onMarkerClick: "undefined", onMarkerClick: "Function Call",
onCreateMarker: "undefined", onCreateMarker: "Function Call",
}, },
BUTTON_WIDGET: { BUTTON_WIDGET: {
text: "string", text: "string",
buttonStyle: "PRIMARY_BUTTON | SECONDARY_BUTTON | DANGER_BUTTON", buttonStyle: "PRIMARY_BUTTON | SECONDARY_BUTTON | DANGER_BUTTON",
isVisible: "boolean", isVisible: "boolean",
onClick: "boolean", onClick: "Function Call",
}, },
RICH_TEXT_EDITOR_WIDGET: { RICH_TEXT_EDITOR_WIDGET: {
defaultText: "string", defaultText: "string",
isVisible: "boolean", isVisible: "boolean",
isDisabled: "boolean", isDisabled: "boolean",
onTextChange: "undefined", onTextChange: "Function Call",
}, },
FILE_PICKER_WIDGET: { FILE_PICKER_WIDGET: {
label: "string", label: "string",
@ -120,7 +119,7 @@ const FIELD_VALUES: Record<
isRequired: "boolean", isRequired: "boolean",
isVisible: "boolean", isVisible: "boolean",
uploadedFileUrls: "string", uploadedFileUrls: "string",
onFilesSelected: "undefined", onFilesSelected: "Function Call",
}, },
CHECKBOX_WIDGET: { CHECKBOX_WIDGET: {
label: "string", label: "string",
@ -128,7 +127,7 @@ const FIELD_VALUES: Record<
isRequired: "boolean", isRequired: "boolean",
isDisabled: "boolean", isDisabled: "boolean",
isVisible: "boolean", isVisible: "boolean",
onCheckChange: "undefined", onCheckChange: "Function Call",
}, },
FORM_WIDGET: { FORM_WIDGET: {
backgroundColor: "string", backgroundColor: "string",

View File

@ -1,7 +1,7 @@
export const PLUGIN_NAME_POSTGRES = "PostgresDbPlugin"; export const PLUGIN_NAME_POSTGRES = "PostgresDbPlugin";
export const PLUGIN_NAME_MONGODB = " MongoDBPlugin"; export const PLUGIN_NAME_MONGODB = " MongoDBPlugin";
export const PLUGIN_NAME_DBS = [PLUGIN_NAME_POSTGRES, PLUGIN_NAME_MONGODB]; 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_POSTGRES = "postgres-plugin";
export const PLUGIN_PACKAGE_MONGO = "mongo-plugin"; export const PLUGIN_PACKAGE_MONGO = "mongo-plugin";
export const PLUGIN_PACKAGE_DBS = [ export const PLUGIN_PACKAGE_DBS = [

View File

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

View File

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

View File

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

View File

@ -127,12 +127,30 @@ export default function Pagination(props: PaginationProps) {
marginBottom: "6px", marginBottom: "6px",
}} }}
> >
Configure the Table pageNo in the API. 1. Configure the Table pageNo in the API.
</p> </p>
<ExampleApi> <ExampleApi>
http://api.example.com/users?pageNo={"{{Table1.pageNo}}"} http://api.example.com/users?pageNo={"{{Table1.pageNo}}"}
</ExampleApi> </ExampleApi>
</CalloutComponent> </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> </PaginationTypeView>
</React.Fragment> </React.Fragment>
); );

View File

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

View File

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

View File

@ -9,7 +9,7 @@ import { ControlIcons } from "icons/ControlIcons";
import PropertyControlFactory from "utils/PropertyControlFactory"; import PropertyControlFactory from "utils/PropertyControlFactory";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import { PropertyControlPropsType } from "components/propertyControls"; 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"; import FIELD_EXPECTED_VALUE from "constants/FieldExpectedValue";
type Props = { type Props = {
@ -19,57 +19,6 @@ type Props = {
onPropertyChange: (propertyName: string, propertyValue: any) => void; 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 PropertyControl = (props: Props) => {
const { const {
widgetProperties, widgetProperties,
@ -136,8 +85,10 @@ const PropertyControl = (props: Props) => {
} }
> >
<ControlPropertyLabelContainer> <ControlPropertyLabelContainer>
<UnderlinedLabel tooltip={propertyConfig.helpText} label={label} /> <PropertyHelpLabel
tooltip={propertyConfig.helpText}
label={label}
/>
{isConvertible && ( {isConvertible && (
<JSToggleButton <JSToggleButton
active={isDynamic} 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, InjectedFormProps,
Field, Field,
FormSubmitHandler, FormSubmitHandler,
formValueSelector,
} from "redux-form"; } from "redux-form";
import { import {
GridComponent, GridComponent,
@ -31,6 +32,8 @@ import "@syncfusion/ej2-react-grids/styles/material.css";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import JSONViewer from "./JSONViewer"; import JSONViewer from "./JSONViewer";
import { RestAction } from "entities/Action"; import { RestAction } from "entities/Action";
import { connect } from "react-redux";
import { AppState } from "reducers";
const QueryFormContainer = styled.div` const QueryFormContainer = styled.div`
font-size: 20px; font-size: 20px;
@ -205,7 +208,7 @@ type QueryFormProps = {
onDeleteClick: () => void; onDeleteClick: () => void;
onSaveClick: () => void; onSaveClick: () => void;
onRunClick: () => void; onRunClick: () => void;
createTemplate: (template: any, name: string) => void; createTemplate: (template: any) => void;
onSubmit: FormSubmitHandler<RestAction>; onSubmit: FormSubmitHandler<RestAction>;
isDeleting: boolean; isDeleting: boolean;
allowSave: 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 & type Props = StateAndRouteProps &
InjectedFormProps<RestAction, StateAndRouteProps>; InjectedFormProps<RestAction, StateAndRouteProps>;
@ -400,18 +407,15 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
{isNewQuery && showTemplateMenu ? ( {isNewQuery && showTemplateMenu ? (
<TemplateMenu <TemplateMenu
createTemplate={templateString => { createTemplate={templateString => {
const name = isSQL
? "actionConfiguration.query.cmd"
: "actionConfiguration.query";
setMenuVisibility(false); setMenuVisibility(false);
createTemplate(templateString, name); createTemplate(templateString);
}} }}
selectedPluginPackage={selectedPluginPackage} selectedPluginPackage={selectedPluginPackage}
/> />
) : isSQL ? ( ) : isSQL ? (
<Field <Field
name="actionConfiguration.query.cmd" name="actionConfiguration.body"
dataTreePath={`${props.actionName}.config.actionConfiguration.body`}
component={DynamicAutocompleteInput} component={DynamicAutocompleteInput}
className="textAreaStyles" className="textAreaStyles"
mode="sql-js" mode="sql-js"
@ -419,17 +423,11 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
/> />
) : ( ) : (
<Field <Field
name="actionConfiguration.query" name="actionConfiguration.body"
dataTreePath={`${props.actionName}.config.actionConfiguration.body`}
component={DynamicAutocompleteInput} component={DynamicAutocompleteInput}
className="textAreaStyles" className="textAreaStyles"
mode="js-js" mode="js-js"
normalize={(value: any) => {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}}
/> />
)} )}
</form> </form>
@ -437,7 +435,7 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
{dataSources.length === 0 && ( {dataSources.length === 0 && (
<NoDataSourceContainer> <NoDataSourceContainer>
<p className="font18"> <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> </p>
<Button <Button
onClick={() => onClick={() =>
@ -492,7 +490,17 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
); );
}; };
export default reduxForm<RestAction, StateAndRouteProps>({ 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, form: QUERY_EDITOR_FORM_NAME,
enableReinitialize: true, enableReinitialize: true,
})(QueryEditorForm); })(QueryEditorForm),
);

View File

@ -23,10 +23,13 @@ import {
getPluginIdsOfPackageNames, getPluginIdsOfPackageNames,
getPluginPackageFromDatasourceId, getPluginPackageFromDatasourceId,
} from "selectors/entitiesSelector"; } 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 { getCurrentApplication } from "selectors/applicationSelectors";
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer"; import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
import { RestAction } from "entities/Action"; import { QueryAction, RestAction } from "entities/Action";
const EmptyStateContainer = styled.div` const EmptyStateContainer = styled.div`
display: flex; display: flex;
@ -156,7 +159,7 @@ class QueryEditor extends React.Component<Props> {
const mapStateToProps = (state: AppState): any => { const mapStateToProps = (state: AppState): any => {
const { runErrorMessage } = state.ui.queryPane; 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)( const initialValues = getFormInitialValues(QUERY_EDITOR_FORM_NAME)(
state, state,
) as RestAction; ) as RestAction;
@ -187,8 +190,8 @@ const mapDispatchToProps = (dispatch: any): any => ({
deleteAction: (id: string) => dispatch(deleteQuery({ id })), deleteAction: (id: string) => dispatch(deleteQuery({ id })),
runAction: (action: RestAction, actionId: string) => runAction: (action: RestAction, actionId: string) =>
dispatch(executeQuery({ action, actionId })), dispatch(executeQuery({ action, actionId })),
createTemplate: (template: any, name: string) => { createTemplate: (template: any) => {
dispatch(change(QUERY_EDITOR_FORM_NAME, name, template)); 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>) { export function* executeAppAction(action: ReduxAction<ExecuteActionPayload>) {
const { dynamicString, event, responseData } = action.payload; const { dynamicString, event, responseData } = action.payload;
log.debug("Evaluating data tree to get action trigger"); log.debug("Evaluating data tree to get action trigger");
log.debug({ dynamicString });
const tree = yield select(evaluateDataTree); const tree = yield select(evaluateDataTree);
log.debug({ tree });
const { triggers } = getDynamicValue(dynamicString, tree, responseData, true); const { triggers } = getDynamicValue(dynamicString, tree, responseData, true);
log.debug({ triggers });
if (triggers && triggers.length) { if (triggers && triggers.length) {
yield all( yield all(
triggers.map(trigger => call(executeActionTriggers, trigger, event)), triggers.map(trigger => call(executeActionTriggers, trigger, event)),

View File

@ -36,7 +36,6 @@ import { AppState } from "reducers";
import { Property } from "api/ActionAPI"; import { Property } from "api/ActionAPI";
import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions"; import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions";
import { import {
API_PATH_START_WITH_SLASH_ERROR,
FIELD_REQUIRED_ERROR, FIELD_REQUIRED_ERROR,
UNIQUE_NAME_ERROR, UNIQUE_NAME_ERROR,
VALID_FUNCTION_NAME_ERROR, VALID_FUNCTION_NAME_ERROR,
@ -227,37 +226,6 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
(header: any) => header.key.toLowerCase() === CONTENT_TYPE, (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)); yield put(initialize(API_EDITOR_FORM_NAME, data));
@ -383,10 +351,7 @@ function* updateFormFields(
value: contentType.value, value: contentType.value,
}; };
} else { } else {
displayFormat = { displayFormat = POST_BODY_FORMAT_OPTIONS[3];
label: POST_BODY_FORMATS[2],
value: POST_BODY_FORMATS[2],
};
} }
} }

View File

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

View File

@ -26,13 +26,14 @@ import {
getCurrentApplicationId, getCurrentApplicationId,
getCurrentPageId, getCurrentPageId,
} from "selectors/editorSelectors"; } from "selectors/editorSelectors";
import { initialize } from "redux-form"; import { change, initialize } from "redux-form";
import { getAction, getActionParams, getActionTimeout } from "./ActionSagas"; import { getAction, getActionParams, getActionTimeout } from "./ActionSagas";
import { AppState } from "reducers"; import { AppState } from "reducers";
import ActionAPI, { import ActionAPI, {
PaginationField, PaginationField,
ExecuteActionRequest, ExecuteActionRequest,
ActionApiResponse, ActionApiResponse,
Property,
} from "api/ActionAPI"; } from "api/ActionAPI";
import { QUERY_CONSTANT } from "constants/QueryEditorConstants"; import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
import { changeQuery, deleteQuerySuccess } from "actions/queryPaneActions"; 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( function* formValueChangeSaga(
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>, actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
) { ) {
const { form } = actionPayload.meta; const { form } = actionPayload.meta;
if (form !== QUERY_EDITOR_FORM_NAME) return; if (form !== QUERY_EDITOR_FORM_NAME) return;
yield all([call(updateDraftsSaga)]); yield all([
call(updateDynamicBindingsSaga, actionPayload),
call(updateDraftsSaga),
]);
} }
function* handleQueryCreatedSaga(actionPayload: ReduxAction<RestAction>) { function* handleQueryCreatedSaga(actionPayload: ReduxAction<RestAction>) {

View File

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

View File

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

View File

@ -160,9 +160,7 @@ export const getDynamicValue = (
includeTriggers = false, includeTriggers = false,
): JSExecutorResult => { ): JSExecutorResult => {
// Get the {{binding}} bound values // Get the {{binding}} bound values
const { stringSegments: stringSegments, jsSnippets } = getDynamicBindings( const { stringSegments, jsSnippets } = getDynamicBindings(dynamicBinding);
dynamicBinding,
);
if (stringSegments.length) { if (stringSegments.length) {
// Get the Data Tree value of those "binding "paths // Get the Data Tree value of those "binding "paths
const values = jsSnippets.map((jsSnippet, index) => { const values = jsSnippets.map((jsSnippet, index) => {
@ -199,12 +197,35 @@ export const getValidatedTree = (tree: any) => {
if (entity && entity.type) { if (entity && entity.type) {
const parsedEntity = { ...entity }; const parsedEntity = { ...entity };
Object.keys(entity).forEach((property: string) => { Object.keys(entity).forEach((property: string) => {
const hasEvaluatedValue = _.has(
parsedEntity,
`evaluatedValues.${property}`,
);
const hasValidation = _.has(parsedEntity, `invalidProps.${property}`);
const isSpecialField = [
"dynamicBindings",
"dynamicTriggers",
"dynamicProperties",
"evaluatedValues",
"invalidProps",
"validationMessages",
].includes(property);
const isDynamicField =
_.has(parsedEntity, `dynamicBindings.${property}`) ||
_.has(parsedEntity, `dynamicTriggers.${property}`);
if (
!isSpecialField &&
!isDynamicField &&
(!hasValidation || !hasEvaluatedValue)
) {
const value = entity[property]; const value = entity[property];
// Pass it through parse // Pass it through parse
const { const {
parsed, parsed,
isValid, isValid,
message, message,
transformed,
} = ValidationFactory.validateWidgetProperty( } = ValidationFactory.validateWidgetProperty(
entity.type, entity.type,
property, property,
@ -213,19 +234,19 @@ export const getValidatedTree = (tree: any) => {
tree, tree,
); );
parsedEntity[property] = parsed; parsedEntity[property] = parsed;
if (property !== "evaluatedValues") { if (!hasEvaluatedValue) {
if (!("evaluatedValues" in parsedEntity)) { const evaluatedValue = _.isUndefined(transformed)
_.set(parsedEntity, "evaluatedValues", {}); ? value
} : transformed;
if (!(property in parsedEntity.evaluatedValues)) { _.set(parsedEntity, `evaluatedValues.${property}`, evaluatedValue);
_.set(parsedEntity, `evaluatedValues.${property}`, value);
}
} }
if (!isValid) { const hasValidation = _.has(parsedEntity, `invalidProps.${property}`);
if (!hasValidation && !isValid) {
_.set(parsedEntity, `invalidProps.${property}`, true); _.set(parsedEntity, `invalidProps.${property}`, true);
_.set(parsedEntity, `validationMessages.${property}`, message); _.set(parsedEntity, `validationMessages.${property}`, message);
} }
}
}); });
return { ...tree, [entityKey]: parsedEntity }; 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.ENTITY_TYPE === ENTITY_TYPE.ACTION) {
if (entity.dynamicBindingPathList.length) { if (entity.dynamicBindingPathList.length) {
@ -548,10 +574,21 @@ function validateAndParseWidgetProperty(
widget: DataTreeWidget, widget: DataTreeWidget,
currentTree: DataTree, currentTree: DataTree,
evalPropertyValue: any, evalPropertyValue: any,
unEvalPropertyValue: string,
currentDependencyValues: Array<string>, currentDependencyValues: Array<string>,
cachedDependencyValues?: Array<string>, cachedDependencyValues?: Array<string>,
): any { ): any {
const propertyName = propertyPath.split(".")[1]; 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 { const {
parsed, parsed,
isValid, isValid,
@ -560,7 +597,7 @@ function validateAndParseWidgetProperty(
} = ValidationFactory.validateWidgetProperty( } = ValidationFactory.validateWidgetProperty(
widget.type, widget.type,
propertyName, propertyName,
evalPropertyValue, valueToValidate,
widget, widget,
currentTree, currentTree,
); );
@ -572,6 +609,9 @@ function validateAndParseWidgetProperty(
_.set(widget, `invalidProps.${propertyName}`, true); _.set(widget, `invalidProps.${propertyName}`, true);
_.set(widget, `validationMessages.${propertyName}`, message); _.set(widget, `validationMessages.${propertyName}`, message);
} }
if (widget.dynamicTriggers && propertyName in widget.dynamicTriggers) {
return unEvalPropertyValue;
} else {
const parsedCache = getParsedValueCache(propertyPath); const parsedCache = getParsedValueCache(propertyPath);
if ( if (
!equal(parsedCache.value, parsed) || !equal(parsedCache.value, parsed) ||
@ -584,6 +624,7 @@ function validateAndParseWidgetProperty(
}); });
} }
return parsed; return parsed;
}
} }
function isWidget(entity: DataTreeEntity): boolean { function isWidget(entity: DataTreeEntity): boolean {
@ -641,6 +682,7 @@ export function dependencySortedEvaluateDataTree(
widgetEntity, widgetEntity,
currentTree, currentTree,
evalPropertyValue, evalPropertyValue,
unEvalPropertyValue,
currentDependencyValues, currentDependencyValues,
cachedDependencyValues, cachedDependencyValues,
); );

View File

@ -8,14 +8,14 @@ import {
import moment from "moment"; import moment from "moment";
import { import {
WIDGET_TYPE_VALIDATION_ERROR, WIDGET_TYPE_VALIDATION_ERROR,
NAVIGATE_TO_VALIDATION_ERROR, // NAVIGATE_TO_VALIDATION_ERROR,
} from "constants/messages"; } from "constants/messages";
import { modalGetter } from "components/editorComponents/actioncreator/ActionCreator"; // import { modalGetter } from "components/editorComponents/actioncreator/ActionCreator";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import { DataTree } from "entities/DataTree/dataTreeFactory"; import { DataTree } from "entities/DataTree/dataTreeFactory";
import { PageListPayload } from "constants/ReduxActionConstants"; // import { PageListPayload } from "constants/ReduxActionConstants";
import { isDynamicValue } from "./DynamicBindingUtils"; // 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})?(\/.*)?$/; // 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> = { export const VALIDATORS: Record<ValidationType, Validator> = {
[VALIDATION_TYPES.TEXT]: ( [VALIDATION_TYPES.TEXT]: (
@ -393,17 +393,21 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
.hour(0) .hour(0)
.minute(0) .minute(0)
.second(0) .second(0)
.millisecond(0) .millisecond(0);
.toISOString(true);
if (value === undefined) { if (value === undefined) {
return { return {
isValid: false, isValid: false,
parsed: today, parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date`, message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
}; };
} }
const isValid = moment(value).isValid(); 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 { return {
isValid, isValid,
parsed, parsed,
@ -415,6 +419,14 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
props: WidgetProps, props: WidgetProps,
dataTree?: DataTree, dataTree?: DataTree,
): ValidationResponse => { ): ValidationResponse => {
if (Array.isArray(value) && value.length) {
return {
isValid: true,
parsed: undefined,
transformed: "Function Call",
};
}
/*
if (_.isString(value)) { if (_.isString(value)) {
if (value.indexOf("navigateTo") !== -1) { if (value.indexOf("navigateTo") !== -1) {
const pageNameOrUrl = modalGetter(value); const pageNameOrUrl = modalGetter(value);
@ -440,9 +452,12 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
} }
} }
} }
*/
return { return {
isValid: true, isValid: false,
parsed: value, parsed: undefined,
transformed: "undefined",
message: "Not a function call",
}; };
}, },
[VALIDATION_TYPES.ARRAY_ACTION_SELECTOR]: ( [VALIDATION_TYPES.ARRAY_ACTION_SELECTOR]: (

View File

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

View File

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