diff --git a/.env.example b/.env.example
index a98011bb3d..6dfdfab187 100644
--- a/.env.example
+++ b/.env.example
@@ -57,4 +57,9 @@ APPSMITH_MAIL_SMTP_TLS_ENABLED=
#APPSMITH_SENTRY_ENVIRONMENT=
# Configure cloud services
-# APPSMITH_CLOUD_SERVICES_BASE_URL="https://release-cs.appsmith.com"
\ No newline at end of file
+# APPSMITH_CLOUD_SERVICES_BASE_URL="https://release-cs.appsmith.com"
+
+# Google Recaptcha Config
+APPSMITH_RECAPTCHA_SITE_KEY=
+APPSMITH_RECAPTCHA_SECRET_KEY=
+APPSMITH_RECAPTCHA_ENABLED=
\ No newline at end of file
diff --git a/app.json b/app.json
index d6bf73c26b..1a1fb89536 100644
--- a/app.json
+++ b/app.json
@@ -98,6 +98,21 @@
"value": "",
"required": false
},
+ "APPSMITH_RECAPTCHA_SITE_KEY": {
+ "description" : "Google reCAPTCHA v3 site key, it is required if you wish to enable protection against spam/abusive users. Read more at: https://developers.google.com/recaptcha/docs/v3",
+ "value": "",
+ "required": false
+ },
+ "APPSMITH_RECAPTCHA_SECRET_KEY": {
+ "description" : "Google reCAPTCHA v3 verification secret key, it is required if you wish to enable spam protection in your backend server.",
+ "value": "",
+ "required": false
+ },
+ "APPSMITH_RECAPTCHA_ENABLED": {
+ "description" : "Boolean config to enable or disable Google reCAPTCHA v3 verification feature. If set to true, both site key and secret key should be provided.",
+ "value": "",
+ "required": false
+ },
"APPSMITH_DISABLE_TELEMETRY": {
"description" : "We want to be transparent and request that you share anonymous usage data with us. This data is purely statistical in nature and helps us understand your needs & provide better support to your self-hosted instance. You can read more about what information is collected in our documentation https://docs.appsmith.com/v/v1.2.1/setup/telemetry",
"value": "false"
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js
index 06b63c3340..ae64c14c37 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js
@@ -1,5 +1,6 @@
const testdata = require("../../../../fixtures/testdata.json");
const apiwidget = require("../../../../locators/apiWidgetslocator.json");
+const widgetsPage = require("../../../../locators/Widgets.json");
const explorer = require("../../../../locators/explorerlocators.json");
const commonlocators = require("../../../../locators/commonlocators.json");
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
@@ -30,10 +31,13 @@ describe("Entity explorer Drag and Drop widgets testcases", function() {
/**
* @param{Text} Random Colour
*/
- cy.testCodeMirror(this.data.colour);
+ cy.get(widgetsPage.backgroundcolorPicker)
+ .first()
+ .click({ force: true });
+ cy.xpath(widgetsPage.greenColor).click();
cy.get(formWidgetsPage.formD)
.should("have.css", "background-color")
- .and("eq", this.data.rgbValue);
+ .and("eq", "rgb(3, 179, 101)");
/**
* @param{toggleButton Css} Assert to be checked
*/
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js
index 50bef3f6c9..70cef20567 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js
@@ -3,6 +3,7 @@ const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const dsl = require("../../../../fixtures/formdsl.json");
const pages = require("../../../../locators/Pages.json");
+const widgetsPage = require("../../../../locators/Widgets.json");
describe("Form Widget Functionality", function() {
before(() => {
@@ -23,10 +24,13 @@ describe("Form Widget Functionality", function() {
/**
* @param{Text} Random Colour
*/
- cy.testCodeMirror(this.data.colour);
+ cy.get(widgetsPage.backgroundcolorPicker)
+ .first()
+ .click({ force: true });
+ cy.xpath(widgetsPage.greenColor).click();
cy.get(formWidgetsPage.formD)
.should("have.css", "background-color")
- .and("eq", this.data.rgbValue);
+ .and("eq", "rgb(3, 179, 101)");
/**
* @param{toggleButton Css} Assert to be checked
*/
@@ -40,7 +44,7 @@ describe("Form Widget Functionality", function() {
it("Form Widget Functionality To Verify The Colour", function() {
cy.get(formWidgetsPage.formD)
.should("have.css", "background-color")
- .and("eq", this.data.rgbValue);
+ .and("eq", "rgb(3, 179, 101)");
});
it("Form Widget Functionality To Unchecked Visible Widget", function() {
cy.get(publish.backToEditor).click();
diff --git a/app/client/docker/templates/nginx-app.conf.template b/app/client/docker/templates/nginx-app.conf.template
index 4b7c5fdf4e..6b8ed93474 100644
--- a/app/client/docker/templates/nginx-app.conf.template
+++ b/app/client/docker/templates/nginx-app.conf.template
@@ -48,6 +48,7 @@ server {
sub_filter __APPSMITH_MAIL_ENABLED__ '${APPSMITH_MAIL_ENABLED}';
sub_filter __APPSMITH_DISABLE_TELEMETRY__ '${APPSMITH_DISABLE_TELEMETRY}';
sub_filter __APPSMITH_CLOUD_SERVICES_BASE_URL__ '${APPSMITH_CLOUD_SERVICES_BASE_URL}';
+ sub_filter __APPSMITH_RECAPTCHA_SITE_KEY__ '${APPSMITH_RECAPTCHA_SITE_KEY}';
}
location /f {
diff --git a/app/client/netlify.toml b/app/client/netlify.toml
index 3bb6abd265..9c900dc92d 100644
--- a/app/client/netlify.toml
+++ b/app/client/netlify.toml
@@ -8,6 +8,7 @@
REACT_APP_ALGOLIA_SEARCH_INDEX_NAME = "test_appsmith"
REACT_APP_CLIENT_LOG_LEVEL = "debug"
REACT_APP_GOOGLE_MAPS_API_KEY = "AIzaSyBOQFulljufGt3VDhBAwNjZN09KEFufVyg"
+ REACT_APP_GOOGLE_RECAPTCHA_SITE_KEY = ""
REACT_APP_TNC_PP = "true"
REACT_APP_CLOUD_HOSTING = "true"
REACT_APP_INTERCOM_APP_ID = "y10e7138"
diff --git a/app/client/package.json b/app/client/package.json
index 430c1e6f98..afb8a5cc0e 100644
--- a/app/client/package.json
+++ b/app/client/package.json
@@ -152,7 +152,6 @@
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=dev.appsmith.com craco start",
- "start-m1": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=0.0.0.0 craco start",
"build": "./build.sh",
"build-local": "craco --max-old-space-size=4096 build --config craco.build.config.js",
"build-staging": "REACT_APP_ENVIRONMENT=STAGING craco --max-old-space-size=4096 build --config craco.build.config.js",
@@ -164,7 +163,8 @@
"test:unit": "$(npm bin)/jest -b --colors --no-cache --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'",
"test:jest": "$(npm bin)/jest --watch",
"storybook": "start-storybook -p 9009 -s public",
- "build-storybook": "build-storybook -s public"
+ "build-storybook": "build-storybook -s public",
+ "postinstall": "patch-package"
},
"resolution": {
"jest": "24.8.0"
@@ -237,6 +237,8 @@
"mochawesome": "^5.0.0",
"mochawesome-report-generator": "^4.1.0",
"msw": "^0.28.0",
+ "patch-package": "^6.4.7",
+ "postinstall-postinstall": "^2.1.0",
"raw-loader": "^4.0.2",
"react-docgen-typescript-loader": "^3.6.0",
"react-is": "^16.12.0",
diff --git a/app/client/patches/@blueprintjs+core+3.36.0.patch b/app/client/patches/@blueprintjs+core+3.36.0.patch
new file mode 100644
index 0000000000..3342cbfefc
--- /dev/null
+++ b/app/client/patches/@blueprintjs+core+3.36.0.patch
@@ -0,0 +1,22 @@
+diff --git a/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js b/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js
+index 84f03fa..5e5488a 100644
+--- a/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js
++++ b/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js
+@@ -188,7 +188,16 @@ var EditableText = /** @class */ (function (_super) {
+ if (this.state.isEditing && !prevState.isEditing) {
+ (_b = (_a = this.props).onEdit) === null || _b === void 0 ? void 0 : _b.call(_a, this.state.value);
+ }
+- this.updateInputDimensions();
++ // updateInputDimensions is an expensive method. Call it only when the props
++ // it depends on change
++ if (this.state.value !== prevState.value ||
++ this.props.alwaysRenderInput !== prevProps.alwaysRenderInput ||
++ this.props.maxLines !== prevProps.maxLines ||
++ this.props.minLines !== prevProps.minLines ||
++ this.props.minWidth !== prevProps.minWidth ||
++ this.props.multiline !== prevProps.multiline) {
++ this.updateInputDimensions();
++ }
+ };
+ EditableText.prototype.renderInput = function (value) {
+ var _a = this.props, disabled = _a.disabled, maxLength = _a.maxLength, multiline = _a.multiline, type = _a.type, placeholder = _a.placeholder;
diff --git a/app/client/public/index.html b/app/client/public/index.html
index 8079bbba08..df6675bb58 100755
--- a/app/client/public/index.html
+++ b/app/client/public/index.html
@@ -204,7 +204,8 @@
intercomAppID: APP_ID,
mailEnabled: parseConfig("__APPSMITH_MAIL_ENABLED__"),
disableTelemetry: DISABLE_TELEMETRY === "" || DISABLE_TELEMETRY,
- cloudServicesBaseUrl: parseConfig("__APPSMITH_CLOUD_SERVICES_BASE_URL__") || "https://cs.appsmith.com"
+ cloudServicesBaseUrl: parseConfig("__APPSMITH_CLOUD_SERVICES_BASE_URL__") || "https://cs.appsmith.com",
+ googleRecaptchaSiteKey: parseConfig("__APPSMITH_RECAPTCHA_SITE_KEY__"),
};
diff --git a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx
index 44b5338312..daa1459448 100644
--- a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx
+++ b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx
@@ -180,8 +180,25 @@ function RecaptchaComponent(
});
props.onClick && props.onClick(event);
}
+
+ // Check if a string is a valid JSON string
+ const checkValidJson = (inputString: string): boolean => {
+ try {
+ JSON.parse(inputString);
+ return true;
+ } catch (err) {
+ return false;
+ }
+ };
+
+ let validGoogleRecaptchaKey = props.googleRecaptchaKey;
+
+ if (validGoogleRecaptchaKey && checkValidJson(validGoogleRecaptchaKey)) {
+ validGoogleRecaptchaKey = undefined;
+ }
+
const status = useScript(
- `https://www.google.com/recaptcha/api.js?render=${props.googleRecaptchaKey}`,
+ `https://www.google.com/recaptcha/api.js?render=${validGoogleRecaptchaKey}`,
);
return (
void;
+ onModalClose?: () => void;
children: ReactNode;
width?: number;
className?: string;
@@ -76,6 +77,14 @@ export function ModalComponent(props: ModalComponentProps) {
const modalContentRef: RefObject
= useRef(
null,
);
+ useEffect(() => {
+ return () => {
+ // handle modal close events when this component unmounts
+ // will be called in all cases :-
+ // escape key press, click out side, close click from other btn widget
+ if (props.onModalClose) props.onModalClose();
+ };
+ }, []);
useEffect(() => {
if (!props.scrollContents) {
modalContentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
diff --git a/app/client/src/components/editorComponents/EditableText.tsx b/app/client/src/components/editorComponents/EditableText.tsx
index b8c2983a02..baa81e42c0 100644
--- a/app/client/src/components/editorComponents/EditableText.tsx
+++ b/app/client/src/components/editorComponents/EditableText.tsx
@@ -92,10 +92,25 @@ const TextContainer = styled.div<{ isValid: boolean; minimal: boolean }>`
`;
export function EditableText(props: EditableTextProps) {
- const [isEditing, setIsEditing] = useState(!!props.isEditingDefault);
- const [value, setStateValue] = useState(props.defaultValue);
+ const {
+ beforeUnmount,
+ className,
+ defaultValue,
+ editInteractionKind,
+ forceDefault,
+ hideEditIcon,
+ isEditingDefault,
+ isInvalid,
+ minimal,
+ onBlur,
+ onTextChanged,
+ placeholder,
+ updating,
+ valueTransform,
+ } = props;
+ const [isEditing, setIsEditing] = useState(!!isEditingDefault);
+ const [value, setStateValue] = useState(defaultValue);
const inputValRef = useRef("");
- const { beforeUnmount } = props;
const setValue = useCallback((value) => {
inputValRef.current = value;
@@ -103,16 +118,16 @@ export function EditableText(props: EditableTextProps) {
}, []);
useEffect(() => {
- setValue(props.defaultValue);
- }, [props.defaultValue]);
+ setValue(defaultValue);
+ }, [defaultValue]);
useEffect(() => {
- setIsEditing(!!props.isEditingDefault);
- }, [props.defaultValue, props.isEditingDefault]);
+ setIsEditing(!!isEditingDefault);
+ }, [defaultValue, isEditingDefault]);
useEffect(() => {
- if (props.forceDefault === true) setValue(props.defaultValue);
- }, [props.forceDefault, props.defaultValue]);
+ if (forceDefault === true) setValue(defaultValue);
+ }, [forceDefault, defaultValue]);
// at times onTextChange is not fired
// for example when the modal is closed on clicking the overlay
@@ -128,58 +143,63 @@ export function EditableText(props: EditableTextProps) {
e.preventDefault();
e.stopPropagation();
};
- const onChange = (_value: string) => {
- props.onBlur && props.onBlur();
- const isInvalid = props.isInvalid ? props.isInvalid(_value) : false;
- if (!isInvalid) {
- props.onTextChanged(_value);
- setIsEditing(false);
- } else {
- Toaster.show({
- text: "Invalid name",
- variant: Variant.danger,
- });
- }
- };
+ const onChange = useCallback(
+ (_value: string) => {
+ onBlur && onBlur();
+ const _isInvalid = isInvalid ? isInvalid(_value) : false;
+ if (!_isInvalid) {
+ onTextChanged(_value);
+ setIsEditing(false);
+ } else {
+ Toaster.show({
+ text: "Invalid name",
+ variant: Variant.danger,
+ });
+ }
+ },
+ [isInvalid],
+ );
- const onInputchange = (_value: string) => {
- let finalVal: string = _value;
- if (props.valueTransform) {
- finalVal = props.valueTransform(_value);
- }
- setValue(finalVal);
- };
+ const onInputchange = useCallback(
+ (_value: string) => {
+ let finalVal: string = _value;
+ if (valueTransform) {
+ finalVal = valueTransform(_value);
+ }
+ setValue(finalVal);
+ },
+ [valueTransform],
+ );
- const errorMessage = props.isInvalid && props.isInvalid(value);
+ const errorMessage = isInvalid && isInvalid(value);
const error = errorMessage ? errorMessage : undefined;
return (
-
+
- {!props.minimal &&
- !props.hideEditIcon &&
- !props.updating &&
- !isEditing && }
+ {!minimal && !hideEditIcon && !updating && !isEditing && (
+
+ )}
diff --git a/app/client/src/components/editorComponents/GlobalSearch/Footer.tsx b/app/client/src/components/editorComponents/GlobalSearch/Footer.tsx
new file mode 100644
index 0000000000..ad5dfb33b4
--- /dev/null
+++ b/app/client/src/components/editorComponents/GlobalSearch/Footer.tsx
@@ -0,0 +1,49 @@
+import React from "react";
+import styled from "styled-components";
+
+const Wrapper = styled.div`
+ span {
+ color: white;
+ font-variant: all-small-caps;
+ font-size: ${(props) => props.theme.fontSizes[2]}px;
+ margin-right: ${(props) => props.theme.spaces[1]}px;
+ }
+ div {
+ margin-right: ${(props) => props.theme.spaces[7]}px;
+ font-size: ${(props) => props.theme.fontSizes[2]}px;
+ text-align: center;
+ }
+ color: ${(props) => props.theme.colors.globalSearch.searchItemText};
+ padding: ${(props) => props.theme.spaces[1]}px
+ ${(props) => props.theme.spaces[6]}px;
+ display: flex;
+ flex-direction: row;
+`;
+
+const FOOTER_INFO = [
+ {
+ action: "\u2191\u2193",
+ description: "Select",
+ },
+ {
+ action: "ENTER",
+ description: "Open",
+ },
+];
+
+function Footer() {
+ return (
+
+ {FOOTER_INFO.map((info) => {
+ return (
+
+ {info.action}
+ {info.description}
+
+ );
+ })}
+
+ );
+}
+
+export default Footer;
diff --git a/app/client/src/components/editorComponents/GlobalSearch/ResultsNotFound.tsx b/app/client/src/components/editorComponents/GlobalSearch/ResultsNotFound.tsx
index f1af2aa4fc..84766c4720 100644
--- a/app/client/src/components/editorComponents/GlobalSearch/ResultsNotFound.tsx
+++ b/app/client/src/components/editorComponents/GlobalSearch/ResultsNotFound.tsx
@@ -4,6 +4,7 @@ import NoSearchDataImage from "assets/images/no_search_data.png";
import { NO_SEARCH_DATA_TEXT } from "constants/messages";
import { getTypographyByKey } from "constants/DefaultTheme";
import { ReactComponent as DiscordIcon } from "assets/icons/help/discord.svg";
+import AnalyticsUtil from "utils/AnalyticsUtil";
const Container = styled.div`
display: flex;
@@ -49,6 +50,7 @@ function ResultsNotFound() {
className="discord-link"
onClick={() => {
window.open("https://discord.gg/rBTTVJp", "_blank");
+ AnalyticsUtil.logEvent("DISCORD_LINK_CLICK");
}}
>
diff --git a/app/client/src/components/editorComponents/GlobalSearch/SearchResults.tsx b/app/client/src/components/editorComponents/GlobalSearch/SearchResults.tsx
index 5da83a7c9e..42a7cb03e3 100644
--- a/app/client/src/components/editorComponents/GlobalSearch/SearchResults.tsx
+++ b/app/client/src/components/editorComponents/GlobalSearch/SearchResults.tsx
@@ -25,6 +25,7 @@ import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
import { AppState } from "reducers";
import { keyBy, noop } from "lodash";
import { getPageList } from "selectors/editorSelectors";
+import { PluginType } from "entities/Action";
const DocumentIcon = HelpIcons.DOCUMENT;
@@ -158,10 +159,13 @@ function ActionItem(props: {
return state.entities.plugins.list;
});
const pluginGroups = useMemo(() => keyBy(plugins, "id"), [plugins]);
- const icon = getActionConfig(pluginType)?.getIcon(
- item.config,
- pluginGroups[item.config.datasource.pluginId],
- );
+ const icon =
+ pluginType === PluginType.API
+ ? getActionConfig(pluginType)?.icon
+ : getActionConfig(pluginType)?.getIcon(
+ item.config,
+ pluginGroups[item.config.datasource.pluginId],
+ );
let title = getItemTitle(item);
const pageName = usePageName(config.pageId);
@@ -293,9 +297,7 @@ function SearchItemComponent(props: ItemProps) {
itemType !== SEARCH_ITEM_TYPES.placeholder
) {
setActiveItemIndex(index);
- if (itemType !== SEARCH_ITEM_TYPES.document) {
- searchContext?.handleItemLinkClick(item, "SEARCH_ITEM");
- }
+ searchContext?.handleItemLinkClick(item, "SEARCH_ITEM");
}
}}
ref={itemRef}
diff --git a/app/client/src/components/editorComponents/GlobalSearch/index.tsx b/app/client/src/components/editorComponents/GlobalSearch/index.tsx
index 113936eb1b..07577ddb08 100644
--- a/app/client/src/components/editorComponents/GlobalSearch/index.tsx
+++ b/app/client/src/components/editorComponents/GlobalSearch/index.tsx
@@ -46,6 +46,7 @@ import { keyBy, noop } from "lodash";
import EntitiesIcon from "assets/icons/ads/entities.svg";
import DocsIcon from "assets/icons/ads/docs.svg";
import RecentIcon from "assets/icons/ads/recent.svg";
+import Footer from "./Footer";
const StyledContainer = styled.div`
width: 750px;
@@ -204,7 +205,7 @@ function GlobalSearch() {
);
}, [pages, query]);
- const recentsSectionTitle = getSectionTitle("Recents", RecentIcon);
+ const recentsSectionTitle = getSectionTitle("Recent Entities", RecentIcon);
const docsSectionTitle = getSectionTitle("Documentation Links", DocsIcon);
const entitiesSectionTitle = getSectionTitle("Entities", EntitiesIcon);
@@ -397,6 +398,7 @@ function GlobalSearch() {
)}
+ {!query && }
diff --git a/app/client/src/components/formControls/BaseControl.tsx b/app/client/src/components/formControls/BaseControl.tsx
index 9087cb0178..4c9f6d26d3 100644
--- a/app/client/src/components/formControls/BaseControl.tsx
+++ b/app/client/src/components/formControls/BaseControl.tsx
@@ -51,6 +51,7 @@ export interface ControlData {
isRequired?: boolean;
hidden?: HiddenType;
placeholderText?: string;
+ schema?: any;
}
export interface ControlFunctions {
diff --git a/app/client/src/components/formControls/FieldArrayControl.tsx b/app/client/src/components/formControls/FieldArrayControl.tsx
new file mode 100644
index 0000000000..6dba80f6b7
--- /dev/null
+++ b/app/client/src/components/formControls/FieldArrayControl.tsx
@@ -0,0 +1,106 @@
+import React, { useEffect } from "react";
+import FormControl from "pages/Editor/FormControl";
+import Text, { TextType } from "components/ads/Text";
+import Icon, { IconSize } from "components/ads/Icon";
+import { Classes } from "components/ads/common";
+import styled from "styled-components";
+import { FieldArray } from "redux-form";
+import FormLabel from "components/editorComponents/FormLabel";
+import { ControlProps } from "./BaseControl";
+
+const CenteredIcon = styled(Icon)`
+ margin-top: 25px;
+ &.hide {
+ opacity: 0;
+ pointer-events: none;
+ }
+`;
+
+const PrimaryBox = styled.div`
+ display: flex;
+ flex-direction: column;
+ border: 2px solid ${(props) => props.theme.colors.apiPane.dividerBg};
+ padding: 10px;
+ border-radius: 5px;
+`;
+
+const SecondaryBox = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ padding: 5px;
+`;
+
+const AddMoreAction = styled.div`
+ width: fit-content;
+ cursor: pointer;
+ display: flex;
+ margin-top: 16px;
+ .${Classes.TEXT} {
+ margin-left: 8px;
+ color: #03b365;
+ }
+`;
+
+function NestedComponents(props: any) {
+ useEffect(() => {
+ if (props.fields.length < 1) {
+ props.fields.push({});
+ }
+ }, [props.fields.length]);
+ return (
+
+ {props.fields &&
+ props.fields.length > 0 &&
+ props.fields.map((field: string, index: number) => {
+ return (
+
+ {props.schema.map((sch: any, idx: number) => {
+ sch = {
+ ...sch,
+ configProperty: `${field}.${sch.key}`,
+ };
+ return (
+
+ );
+ })}
+ {
+ e.stopPropagation();
+ props.fields.remove(index);
+ }}
+ size={IconSize.XXL}
+ />
+
+ );
+ })}
+ props.fields.push({})}>
+ {/*Hardcoded label to be removed */}
+ + Add Condition (And)
+
+
+ );
+}
+
+export default function FieldArrayControl(props: FieldArrayControlProps) {
+ const { configProperty, formName, label, schema } = props;
+ return (
+ <>
+ {label}
+
+ >
+ );
+}
+
+export type FieldArrayControlProps = ControlProps;
diff --git a/app/client/src/components/propertyControls/DatePickerControl.tsx b/app/client/src/components/propertyControls/DatePickerControl.tsx
index 96aa68f0d3..0151e46e44 100644
--- a/app/client/src/components/propertyControls/DatePickerControl.tsx
+++ b/app/client/src/components/propertyControls/DatePickerControl.tsx
@@ -46,11 +46,11 @@ class DatePickerControl extends BaseControl<
year = this.now.get("year");
maxDate: Date = this.now
.clone()
- .set({ month: 11, date: 31, year: this.year + 20 })
+ .set({ month: 11, date: 31, year: this.year + 100 })
.toDate();
minDate: Date = this.now
.clone()
- .set({ month: 0, date: 1, year: this.year - 20 })
+ .set({ month: 0, date: 1, year: this.year - 100 })
.toDate();
constructor(props: DatePickerControlProps) {
diff --git a/app/client/src/configs/index.ts b/app/client/src/configs/index.ts
index e4b2b60f9a..a50565f606 100644
--- a/app/client/src/configs/index.ts
+++ b/app/client/src/configs/index.ts
@@ -42,6 +42,7 @@ export type INJECTED_CONFIGS = {
mailEnabled: boolean;
disableTelemetry: boolean;
cloudServicesBaseUrl: string;
+ googleRecaptchaSiteKey: string;
onboardingFormEnabled: boolean;
};
declare global {
@@ -116,6 +117,8 @@ const getConfigsFromEnvVars = (): INJECTED_CONFIGS => {
: false,
disableTelemetry: true,
cloudServicesBaseUrl: process.env.REACT_APP_CLOUD_SERVICES_BASE_URL || "",
+ googleRecaptchaSiteKey:
+ process.env.REACT_APP_GOOGLE_RECAPTCHA_SITE_KEY || "",
onboardingFormEnabled: !!process.env.REACT_APP_SHOW_ONBOARDING_FORM,
};
};
@@ -167,6 +170,11 @@ export const getAppsmithConfigs = (): AppsmithUIConfigs => {
);
const google = getConfig(ENV_CONFIG.google, APPSMITH_FEATURE_CONFIGS.google);
+ const googleRecaptchaSiteKey = getConfig(
+ ENV_CONFIG.googleRecaptchaSiteKey,
+ APPSMITH_FEATURE_CONFIGS.googleRecaptchaSiteKey,
+ );
+
// As the following shows, the config variables can be set using a combination
// of env variables and injected configs
const smartLook = getConfig(
@@ -245,6 +253,10 @@ export const getAppsmithConfigs = (): AppsmithUIConfigs => {
enabled: google.enabled,
apiKey: google.value,
},
+ googleRecaptchaSiteKey: {
+ enabled: googleRecaptchaSiteKey.enabled,
+ apiKey: googleRecaptchaSiteKey.value,
+ },
enableRapidAPI:
ENV_CONFIG.enableRapidAPI || APPSMITH_FEATURE_CONFIGS.enableRapidAPI,
enableGithubOAuth:
diff --git a/app/client/src/configs/types.ts b/app/client/src/configs/types.ts
index fde3769c7c..41fde2417f 100644
--- a/app/client/src/configs/types.ts
+++ b/app/client/src/configs/types.ts
@@ -73,5 +73,9 @@ export type AppsmithUIConfigs = {
cloudServicesBaseUrl: string;
+ googleRecaptchaSiteKey: {
+ enabled: boolean;
+ apiKey: string;
+ };
onboardingFormEnabled: boolean;
};
diff --git a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx
index 7e63eb2edc..6f8dc9c1dc 100644
--- a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx
+++ b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx
@@ -56,6 +56,7 @@ export enum EventType {
ON_HOVER = "ON_HOVER",
ON_TOGGLE = "ON_TOGGLE",
ON_LOAD = "ON_LOAD",
+ ON_MODAL_CLOSE = "ON_MODAL_CLOSE",
ON_TEXT_CHANGE = "ON_TEXT_CHANGE",
ON_SUBMIT = "ON_SUBMIT",
ON_CHECK_CHANGE = "ON_CHECK_CHANGE",
diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx
index b8b1da984e..0542a84b83 100644
--- a/app/client/src/mockResponses/WidgetConfigResponse.tsx
+++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx
@@ -148,7 +148,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
},
TABLE_WIDGET: {
rows: 7 * GRID_DENSITY_MIGRATION_V1,
- columns: 8 * GRID_DENSITY_MIGRATION_V1,
+ columns: 9 * GRID_DENSITY_MIGRATION_V1,
label: "Data",
widgetName: "Table",
searchKey: "",
@@ -541,6 +541,58 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
},
xAxisName: "Last Week",
yAxisName: "Total Order Revenue $",
+ customFusionChartConfig: {
+ type: "column2d",
+ dataSource: {
+ chart: {
+ caption: "Last week's revenue",
+ xAxisName: "Last Week",
+ yAxisName: "Total Order Revenue $",
+ theme: "fusion",
+ },
+ data: [
+ {
+ label: "Mon",
+ value: 10000,
+ },
+ {
+ label: "Tue",
+ value: 12000,
+ },
+ {
+ label: "Wed",
+ value: 32000,
+ },
+ {
+ label: "Thu",
+ value: 28000,
+ },
+ {
+ label: "Fri",
+ value: 14000,
+ },
+ {
+ label: "Sat",
+ value: 19000,
+ },
+ {
+ label: "Sun",
+ value: 36000,
+ },
+ ],
+ trendlines: [
+ {
+ line: [
+ {
+ startvalue: "38000",
+ valueOnRight: "1",
+ displayvalue: "Weekly Target",
+ },
+ ],
+ },
+ ],
+ },
+ },
},
FORM_BUTTON_WIDGET: {
rows: 1 * GRID_DENSITY_MIGRATION_V1,
diff --git a/app/client/src/pages/Applications/ApplicationCard.tsx b/app/client/src/pages/Applications/ApplicationCard.tsx
index e65dc5f501..e7ed55a90e 100644
--- a/app/client/src/pages/Applications/ApplicationCard.tsx
+++ b/app/client/src/pages/Applications/ApplicationCard.tsx
@@ -300,7 +300,7 @@ export function ApplicationCard(props: ApplicationCardProps) {
cypressSelector: "t--fork-app",
});
}
- if (!!props.enableImportExport) {
+ if (!!props.enableImportExport && hasEditPermission) {
moreActionItems.push({
onSelect: exportApplicationAsJSONFile,
text: "Export",
diff --git a/app/client/src/pages/Editor/GlobalHotKeys.tsx b/app/client/src/pages/Editor/GlobalHotKeys.tsx
index b725509506..6940c034a7 100644
--- a/app/client/src/pages/Editor/GlobalHotKeys.tsx
+++ b/app/client/src/pages/Editor/GlobalHotKeys.tsx
@@ -71,6 +71,12 @@ class GlobalHotKeys extends React.Component {
return !!multipleWidgetsSelected;
}
+ public onOnmnibarHotKeyDown(e: KeyboardEvent) {
+ e.preventDefault();
+ this.props.toggleShowGlobalSearchModal();
+ AnalyticsUtil.logEvent("OPEN_OMNIBAR", { source: "HOTKEY_COMBO" });
+ }
+
public renderHotkeys() {
return (
@@ -96,12 +102,14 @@ class GlobalHotKeys extends React.Component {
combo="mod + k"
global
label="Show omnibar"
- onKeyDown={(e: KeyboardEvent) => {
- console.log("toggleShowGlobalSearchModal");
- e.preventDefault();
- this.props.toggleShowGlobalSearchModal();
- AnalyticsUtil.logEvent("OPEN_OMNIBAR", { source: "HOTKEY_COMBO" });
- }}
+ onKeyDown={(e) => this.onOnmnibarHotKeyDown(e)}
+ />
+ this.onOnmnibarHotKeyDown(e)}
/>
(section: any): any => {
return section.children.map((formControlOrSection: ControlProps) => {
if (isHidden(props.formData, section.hidden)) return null;
- if ("children" in formControlOrSection) {
+ if (formControlOrSection.hasOwnProperty("children")) {
return renderEachConfig(formName)(formControlOrSection);
} else {
try {
diff --git a/app/client/src/pages/UserAuth/SignUp.tsx b/app/client/src/pages/UserAuth/SignUp.tsx
index 97604e2671..74c74ebcbc 100644
--- a/app/client/src/pages/UserAuth/SignUp.tsx
+++ b/app/client/src/pages/UserAuth/SignUp.tsx
@@ -35,7 +35,6 @@ import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers";
import { SignupFormValues } from "./helpers";
import AnalyticsUtil from "utils/AnalyticsUtil";
-import { getAppsmithConfigs } from "configs";
import { SIGNUP_SUBMIT_PATH } from "constants/ApiConstants";
import { connect } from "react-redux";
import { AppState } from "reducers";
@@ -45,6 +44,8 @@ import PerformanceTracker, {
import { useIntiateOnboarding } from "components/editorComponents/Onboarding/utils";
import { SIGNUP_FORM_EMAIL_FIELD_NAME } from "constants/forms";
+import { getAppsmithConfigs } from "configs";
+import { useScript, ScriptStatus, AddScriptTo } from "utils/hooks/useScript";
const { enableGithubOAuth, enableGoogleOAuth } = getAppsmithConfigs();
const SocialLoginList: string[] = [];
@@ -54,6 +55,13 @@ if (enableGithubOAuth) SocialLoginList.push(SocialLoginTypes.GITHUB);
import { withTheme } from "styled-components";
import { Theme } from "constants/DefaultTheme";
+declare global {
+ interface Window {
+ grecaptcha: any;
+ }
+}
+const { googleRecaptchaSiteKey } = getAppsmithConfigs();
+
const validate = (values: SignupFormValues) => {
const errors: SignupFormValues = {};
if (!values.password || isEmptyString(values.password)) {
@@ -82,6 +90,11 @@ export function SignUp(props: SignUpFormProps) {
const location = useLocation();
const initiateOnboarding = useIntiateOnboarding();
+ const recaptchaStatus = useScript(
+ `https://www.google.com/recaptcha/api.js?render=${googleRecaptchaSiteKey.apiKey}`,
+ AddScriptTo.HEAD,
+ );
+
let showError = false;
let errorMessage = "";
const queryParams = new URLSearchParams(location.search);
@@ -118,7 +131,37 @@ export function SignUp(props: SignUpFormProps) {
{SocialLoginList.length > 0 && (
)}
-
+ {
+ e.preventDefault();
+ const formElement: HTMLFormElement = document.getElementById(
+ "signup-form",
+ ) as HTMLFormElement;
+ if (
+ googleRecaptchaSiteKey.enabled &&
+ recaptchaStatus === ScriptStatus.READY
+ ) {
+ window.grecaptcha
+ .execute(googleRecaptchaSiteKey.apiKey, {
+ action: "submit",
+ })
+ .then(function(token: any) {
+ formElement &&
+ formElement.setAttribute(
+ "action",
+ `${signupURL}?recaptchaToken=${token}`,
+ );
+ formElement && formElement.submit();
+ });
+ } else {
+ formElement && formElement.submit();
+ }
+ return false;
+ }}
+ >
) {
const { id } = actionPayload.payload;
@@ -114,13 +115,27 @@ function* formValueChangeSaga(
return;
}
- yield put(
- setActionProperty({
- actionId: values.id,
- propertyName: field,
- value: actionPayload.payload,
- }),
- );
+ if (
+ actionPayload.type === ReduxFormActionTypes.ARRAY_REMOVE ||
+ actionPayload.type === ReduxFormActionTypes.ARRAY_PUSH
+ ) {
+ const value = get(values, field);
+ yield put(
+ setActionProperty({
+ actionId: values.id,
+ propertyName: field,
+ value,
+ }),
+ );
+ } else {
+ yield put(
+ setActionProperty({
+ actionId: values.id,
+ propertyName: field,
+ value: actionPayload.payload,
+ }),
+ );
+ }
}
function* handleQueryCreatedSaga(actionPayload: ReduxAction) {
diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx
index 92d6de3e6e..7a5a7dbb21 100644
--- a/app/client/src/utils/AnalyticsUtil.tsx
+++ b/app/client/src/utils/AnalyticsUtil.tsx
@@ -122,7 +122,8 @@ export type EventName =
| "DEBUGGER_ENTITY_NAVIGATION"
| "GSHEET_AUTH_INIT"
| "GSHEET_AUTH_COMPLETE"
- | "CYCLICAL_DEPENDENCY_ERROR";
+ | "CYCLICAL_DEPENDENCY_ERROR"
+ | "DISCORD_LINK_CLICK";
function getApplicationId(location: Location) {
const pathSplit = location.pathname.split("/");
diff --git a/app/client/src/utils/FormControlRegistry.tsx b/app/client/src/utils/FormControlRegistry.tsx
index d66a647f09..c25ae1440d 100644
--- a/app/client/src/utils/FormControlRegistry.tsx
+++ b/app/client/src/utils/FormControlRegistry.tsx
@@ -31,6 +31,9 @@ import DynamicInputTextControl, {
DynamicInputControlProps,
} from "components/formControls/DynamicInputTextControl";
import InputNumberControl from "components/formControls/InputNumberControl";
+import FieldArrayControl, {
+ FieldArrayControlProps,
+} from "components/formControls/FieldArrayControl";
class FormControlRegistry {
static registerFormControlBuilders() {
@@ -93,6 +96,11 @@ class FormControlRegistry {
return ;
},
});
+ FormControlFactory.registerControlBuilder("ARRAY_FIELD", {
+ buildPropertyControl(controlProps: FieldArrayControlProps): JSX.Element {
+ return ;
+ },
+ });
}
}
diff --git a/app/client/src/widgets/ChartWidget/index.tsx b/app/client/src/widgets/ChartWidget/index.tsx
index 84432435a9..b06ec6153c 100644
--- a/app/client/src/widgets/ChartWidget/index.tsx
+++ b/app/client/src/widgets/ChartWidget/index.tsx
@@ -92,7 +92,7 @@ export interface ChartData {
export interface ChartWidgetProps extends WidgetProps, WithMeta {
chartType: ChartType;
chartData: AllChartData;
- customFusionChartConfig: { config: CustomFusionChartConfig };
+ customFusionChartConfig: CustomFusionChartConfig;
xAxisName: string;
yAxisName: string;
chartName: string;
diff --git a/app/client/src/widgets/DropdownWidget.tsx b/app/client/src/widgets/DropdownWidget.tsx
index 89c9452c04..90d6176276 100644
--- a/app/client/src/widgets/DropdownWidget.tsx
+++ b/app/client/src/widgets/DropdownWidget.tsx
@@ -152,7 +152,7 @@ class DropdownWidget extends BaseWidget {
}
getPageView() {
- const options = this.props.options || [];
+ const options = _.isArray(this.props.options) ? this.props.options : [];
const selectedIndex = _.findIndex(this.props.options, {
value: this.props.selectedOptionValue,
});
diff --git a/app/client/src/widgets/FormWidget.tsx b/app/client/src/widgets/FormWidget.tsx
index 40af23180a..585320c45f 100644
--- a/app/client/src/widgets/FormWidget.tsx
+++ b/app/client/src/widgets/FormWidget.tsx
@@ -19,7 +19,7 @@ class FormWidget extends ContainerWidget {
label: "Background Color",
helpText: "Use a html color name, HEX, RGB or RGBA value",
placeholderText: "#FFFFFF / Gray / rgb(255, 99, 71)",
- controlType: "INPUT_TEXT",
+ controlType: "COLOR_PICKER",
isBindProperty: true,
isTriggerProperty: false,
validation: VALIDATION_TYPES.TEXT,
diff --git a/app/client/src/widgets/ModalWidget.tsx b/app/client/src/widgets/ModalWidget.tsx
index a740c9eaec..6fae51bd08 100644
--- a/app/client/src/widgets/ModalWidget.tsx
+++ b/app/client/src/widgets/ModalWidget.tsx
@@ -3,6 +3,7 @@ import React, { ReactNode } from "react";
import { connect } from "react-redux";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
+import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import WidgetFactory from "utils/WidgetFactory";
import ModalComponent from "components/designSystems/blueprint/ModalComponent";
import {
@@ -70,6 +71,20 @@ export class ModalWidget extends BaseWidget {
},
],
},
+ {
+ sectionName: "Actions",
+ children: [
+ {
+ helpText: "Triggers an action when the modal is closed",
+ propertyName: "onClose",
+ label: "onClose",
+ controlType: "ACTION_SELECTOR",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: true,
+ },
+ ],
+ },
];
}
static defaultProps = {
@@ -99,6 +114,18 @@ export class ModalWidget extends BaseWidget {
return WidgetFactory.createWidget(childWidgetData, this.props.renderMode);
};
+ onModalClose = () => {
+ if (this.props.onClose) {
+ super.executeAction({
+ triggerPropertyName: "onClose",
+ dynamicString: this.props.onClose,
+ event: {
+ type: EventType.ON_MODAL_CLOSE,
+ },
+ });
+ }
+ };
+
closeModal = (e: any) => {
this.props.showPropertyPane(undefined);
// TODO(abhinav): Create a static property with is a map of widget properties
@@ -124,6 +151,7 @@ export class ModalWidget extends BaseWidget {
height={MODAL_SIZE[this.props.size].height}
isOpen={!!this.props.isVisible}
onClose={this.closeModal}
+ onModalClose={this.onModalClose}
scrollContents={!!this.props.shouldScrollContents}
width={this.getModalWidth()}
>
@@ -159,6 +187,7 @@ export interface ModalWidgetProps extends WidgetProps, WithMeta {
canEscapeKeyClose?: boolean;
shouldScrollContents?: boolean;
size: string;
+ onClose: string;
mainContainer: WidgetProps;
}
diff --git a/app/client/start-https.sh b/app/client/start-https.sh
index fdf492df30..259c14286b 100755
--- a/app/client/start-https.sh
+++ b/app/client/start-https.sh
@@ -87,14 +87,6 @@ case "${uname_out}" in
"
;;
Darwin*) machine=Mac
- # workaround for apple silicon until host.docker.interal works as expected
- if [[ "$(uname -m)" = "arm64" ]]; then
- # if no server was passed
- if [[ -z $1 ]]; then
- server_proxy_pass="http://"$(ipconfig getifaddr en0)":8080"
- fi
- client_proxy_pass="http://"$(ipconfig getifaddr en0)":3000"
- fi
echo "
Starting nginx for MacOS...
"
diff --git a/app/client/yarn.lock b/app/client/yarn.lock
index 684c6ce834..835785c46c 100644
--- a/app/client/yarn.lock
+++ b/app/client/yarn.lock
@@ -4610,6 +4610,11 @@
version "4.2.2"
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
+"@yarnpkg/lockfile@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
+ integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
abab@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
@@ -6670,7 +6675,7 @@ cross-fetch@^3.0.4:
dependencies:
node-fetch "2.6.1"
-cross-spawn@6.0.5, cross-spawn@^6.0.0:
+cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
dependencies:
@@ -8595,6 +8600,13 @@ find-up@^2.0.0, find-up@^2.1.0:
dependencies:
locate-path "^2.0.0"
+find-yarn-workspace-root@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
+ integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
+ dependencies:
+ micromatch "^4.0.2"
+
flat-cache@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
@@ -8738,7 +8750,7 @@ fs-extra@^0.30.0:
path-is-absolute "^1.0.0"
rimraf "^2.2.8"
-fs-extra@^7.0.0:
+fs-extra@^7.0.0, fs-extra@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
dependencies:
@@ -11053,6 +11065,13 @@ kind-of@^6.0.0, kind-of@^6.0.2:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
+klaw-sync@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
+ integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+
klaw@^1.0.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
@@ -12525,6 +12544,14 @@ open@^7.0.0, open@^7.0.2, open@^7.1.0:
is-docker "^2.0.0"
is-wsl "^2.1.1"
+open@^7.4.2:
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
+ integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
+ dependencies:
+ is-docker "^2.0.0"
+ is-wsl "^2.1.1"
+
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
@@ -12789,6 +12816,25 @@ pascalcase@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
+patch-package@^6.4.7:
+ version "6.4.7"
+ resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148"
+ integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ==
+ dependencies:
+ "@yarnpkg/lockfile" "^1.1.0"
+ chalk "^2.4.2"
+ cross-spawn "^6.0.5"
+ find-yarn-workspace-root "^2.0.0"
+ fs-extra "^7.0.1"
+ is-ci "^2.0.0"
+ klaw-sync "^6.0.0"
+ minimist "^1.2.0"
+ open "^7.4.2"
+ rimraf "^2.6.3"
+ semver "^5.6.0"
+ slash "^2.0.0"
+ tmp "^0.0.33"
+
path-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
@@ -13606,6 +13652,11 @@ postcss@^8.1.0:
nanoid "^3.1.15"
source-map "^0.6.1"
+postinstall-postinstall@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
+ integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
+
preact@8.2.9:
version "8.2.9"
resolved "https://registry.yarnpkg.com/preact/-/preact-8.2.9.tgz#813ba9dd45e5d97c5ea0d6c86d375b3be711cc40"
diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java
index 29301e269c..dd5e34540b 100644
--- a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java
+++ b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java
@@ -57,6 +57,7 @@ import java.util.stream.StreamSupport;
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY;
import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_PATH;
import static com.appsmith.external.helpers.PluginUtils.getActionConfigurationPropertyPath;
+import static com.external.utils.WhereConditionUtils.applyWhereConditional;
/**
* Datasource properties:
@@ -71,9 +72,7 @@ public class FirestorePlugin extends BasePlugin {
private static final int ORDER_PROPERTY_INDEX = 1;
private static final int LIMIT_PROPERTY_INDEX = 2;
- private static final int QUERY_PROPERTY_INDEX = 3;
- private static final int OPERATOR_PROPERTY_INDEX = 4;
- private static final int QUERY_VALUE_PROPERTY_INDEX = 5;
+ private static final int WHERE_CONDITIONAL_PROPERTY_INDEX = 3;
private static final int START_AFTER_PROPERTY_INDEX = 6;
private static final int END_BEFORE_PROPERTY_INDEX = 7;
private static final int FIELDVALUE_TIMESTAMP_PROPERTY_INDEX = 8;
@@ -543,14 +542,6 @@ public class FirestorePlugin extends BasePlugin {
List requestParams) {
final String limitString = getPropertyAt(properties, LIMIT_PROPERTY_INDEX, "10");
final int limit = StringUtils.isEmpty(limitString) ? 10 : Integer.parseInt(limitString);
-
- final String queryFieldPath = getPropertyAt(properties, QUERY_PROPERTY_INDEX, null);
-
- final String operatorString = getPropertyAt(properties, OPERATOR_PROPERTY_INDEX, null);
- final Op operator = StringUtils.isEmpty(operatorString) ? null : Op.valueOf(operatorString);
-
- final String queryValue = getPropertyAt(properties, QUERY_VALUE_PROPERTY_INDEX, null);
-
final String orderByString = getPropertyAt(properties, ORDER_PROPERTY_INDEX, "");
requestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(ORDER_PROPERTY_INDEX),
orderByString, null, null, null));
@@ -589,12 +580,6 @@ public class FirestorePlugin extends BasePlugin {
requestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(LIMIT_PROPERTY_INDEX),
limitString == null ? "" : limitString, null, null, null));
- requestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(QUERY_PROPERTY_INDEX),
- queryFieldPath == null ? "" : queryFieldPath, null, null, null));
- requestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(OPERATOR_PROPERTY_INDEX),
- operatorString == null ? "" : operatorString, null, null, null));
- requestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(QUERY_VALUE_PROPERTY_INDEX),
- queryValue == null ? "" : queryValue, null, null, null));
final Map startAfter = startAfterTemp;
final Map endBefore = endBeforeTemp;
@@ -633,53 +618,42 @@ public class FirestorePlugin extends BasePlugin {
})
// Apply where condition, if provided.
.flatMap(query1 -> {
- if (StringUtils.isEmpty(queryFieldPath) || operator == null || queryValue == null) {
+ if (!isWhereMethodUsed(properties)) {
return Mono.just(query1);
}
- switch (operator) {
- case LT:
- return Mono.just(query1.whereLessThan(queryFieldPath, queryValue));
- case LTE:
- return Mono.just(query1.whereLessThanOrEqualTo(queryFieldPath, queryValue));
- case EQ:
- return Mono.just(query1.whereEqualTo(queryFieldPath, queryValue));
- // TODO: NOT_EQ operator support is awaited in the next version of Firestore driver.
- // case NOT_EQ:
- // return Mono.just(query1.whereNotEqualTo(queryFieldPath, queryValue));
- case GT:
- return Mono.just(query1.whereGreaterThan(queryFieldPath, queryValue));
- case GTE:
- return Mono.just(query1.whereGreaterThanOrEqualTo(queryFieldPath, queryValue));
- case ARRAY_CONTAINS:
- return Mono.just(query1.whereArrayContains(queryFieldPath, queryValue));
- case ARRAY_CONTAINS_ANY:
- try {
- return Mono.just(query1.whereArrayContainsAny(queryFieldPath, parseList(queryValue)));
- } catch (IOException e) {
- return Mono.error(new AppsmithPluginException(
- AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
- "Unable to parse condition value as a JSON list."
- ));
- }
- case IN:
- try {
- return Mono.just(query1.whereIn(queryFieldPath, parseList(queryValue)));
- } catch (IOException e) {
- return Mono.error(new AppsmithPluginException(
- AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
- "Unable to parse condition value as a JSON list."
- ));
- }
- // TODO: NOT_IN operator support is awaited in the next version of Firestore driver.
- // case NOT_IN:
- // return Mono.just(query1.whereNotIn(queryFieldPath, queryValue));
- default:
- return Mono.error(new AppsmithPluginException(
- AppsmithPluginError.PLUGIN_ERROR,
- "Unsupported operator for `where` condition " + operator.toString() + "."
- ));
+ List