Merge branch 'release' into perf/ui/optimize-popper-component/4837

This commit is contained in:
Satish Gandham 2021-06-08 14:39:36 +05:30
commit d2fafaa7f6
53 changed files with 948 additions and 214 deletions

View File

@ -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"
# APPSMITH_CLOUD_SERVICES_BASE_URL="https://release-cs.appsmith.com"
# Google Recaptcha Config
APPSMITH_RECAPTCHA_SITE_KEY=
APPSMITH_RECAPTCHA_SECRET_KEY=
APPSMITH_RECAPTCHA_ENABLED=

View File

@ -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"

View File

@ -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
*/

View File

@ -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();

View File

@ -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 {

View File

@ -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"

View File

@ -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",

View File

@ -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;

View File

@ -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__"),
};
</script>

View File

@ -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 (
<div

View File

@ -107,7 +107,7 @@ class DatePickerComponent extends React.Component<
? new Date(this.props.maxDate)
: now
.clone()
.set({ month: 11, date: 31, year: year + 20 })
.set({ month: 11, date: 31, year: year + 100 })
.toDate();
const isValid = this.state.selectedDate
? this.isValidDate(new Date(this.state.selectedDate))

View File

@ -56,6 +56,7 @@ const Content = styled.div<{
export type ModalComponentProps = {
isOpen: boolean;
onClose: (e: any) => void;
onModalClose?: () => void;
children: ReactNode;
width?: number;
className?: string;
@ -76,6 +77,14 @@ export function ModalComponent(props: ModalComponentProps) {
const modalContentRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
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" });

View File

@ -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 (
<EditableTextWrapper
isEditing={isEditing}
minimal={!!props.minimal}
minimal={!!minimal}
onClick={
props.editInteractionKind === EditInteractionKind.SINGLE ? edit : _.noop
editInteractionKind === EditInteractionKind.SINGLE ? edit : _.noop
}
onDoubleClick={
props.editInteractionKind === EditInteractionKind.DOUBLE ? edit : _.noop
editInteractionKind === EditInteractionKind.DOUBLE ? edit : _.noop
}
>
<ErrorTooltip isOpen={!!error} message={errorMessage as string}>
<TextContainer isValid={!error} minimal={!!props.minimal}>
<TextContainer isValid={!error} minimal={!!minimal}>
<BlueprintEditableText
className={props.className}
className={className}
disabled={!isEditing}
isEditing={isEditing}
onCancel={props.onBlur}
onCancel={onBlur}
onChange={onInputchange}
onConfirm={onChange}
placeholder={props.placeholder}
placeholder={placeholder}
selectAllOnFocus
value={value}
/>
{!props.minimal &&
!props.hideEditIcon &&
!props.updating &&
!isEditing && <EditPen alt="Edit pen" src={Edit} />}
{!minimal && !hideEditIcon && !updating && !isEditing && (
<EditPen alt="Edit pen" src={Edit} />
)}
</TextContainer>
</ErrorTooltip>
</EditableTextWrapper>

View File

@ -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 (
<Wrapper>
{FOOTER_INFO.map((info) => {
return (
<div key={info.action}>
<span>{info.action}</span>
{info.description}
</div>
);
})}
</Wrapper>
);
}
export default Footer;

View File

@ -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");
}}
>
<StyledDiscordIcon color="red" height={22} width={24} />

View File

@ -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}

View File

@ -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() {
<ResultsNotFound />
)}
</div>
{!query && <Footer />}
</StyledContainer>
</AlgoliaSearchWrapper>
</SearchModal>

View File

@ -51,6 +51,7 @@ export interface ControlData {
isRequired?: boolean;
hidden?: HiddenType;
placeholderText?: string;
schema?: any;
}
export interface ControlFunctions {

View File

@ -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 (
<PrimaryBox>
{props.fields &&
props.fields.length > 0 &&
props.fields.map((field: string, index: number) => {
return (
<SecondaryBox key={index}>
{props.schema.map((sch: any, idx: number) => {
sch = {
...sch,
configProperty: `${field}.${sch.key}`,
};
return (
<FormControl
config={sch}
formName={props.formName}
key={idx}
/>
);
})}
<CenteredIcon
name="delete"
onClick={(e) => {
e.stopPropagation();
props.fields.remove(index);
}}
size={IconSize.XXL}
/>
</SecondaryBox>
);
})}
<AddMoreAction onClick={() => props.fields.push({})}>
{/*Hardcoded label to be removed */}
<Text type={TextType.H5}>+ Add Condition (And)</Text>
</AddMoreAction>
</PrimaryBox>
);
}
export default function FieldArrayControl(props: FieldArrayControlProps) {
const { configProperty, formName, label, schema } = props;
return (
<>
<FormLabel>{label}</FormLabel>
<FieldArray
component={NestedComponents}
name={configProperty}
props={{ formName, schema }}
rerenderOnEveryChange={false}
/>
</>
);
}
export type FieldArrayControlProps = ControlProps;

View File

@ -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) {

View File

@ -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:

View File

@ -73,5 +73,9 @@ export type AppsmithUIConfigs = {
cloudServicesBaseUrl: string;
googleRecaptchaSiteKey: {
enabled: boolean;
apiKey: string;
};
onboardingFormEnabled: boolean;
};

View File

@ -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",

View File

@ -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,

View File

@ -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",

View File

@ -71,6 +71,12 @@ class GlobalHotKeys extends React.Component<Props> {
return !!multipleWidgetsSelected;
}
public onOnmnibarHotKeyDown(e: KeyboardEvent) {
e.preventDefault();
this.props.toggleShowGlobalSearchModal();
AnalyticsUtil.logEvent("OPEN_OMNIBAR", { source: "HOTKEY_COMBO" });
}
public renderHotkeys() {
return (
<Hotkeys>
@ -96,12 +102,14 @@ class GlobalHotKeys extends React.Component<Props> {
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)}
/>
<Hotkey
allowInInput={false}
combo="mod + p"
global
label="Show omnibar"
onKeyDown={(e) => this.onOnmnibarHotKeyDown(e)}
/>
<Hotkey
combo="mod + d"

View File

@ -461,7 +461,7 @@ export function EditorJSONtoForm(props: Props) {
const renderEachConfig = (formName: string) => (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 {

View File

@ -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 && (
<ThirdPartyAuth logins={SocialLoginList} type={"SIGNUP"} />
)}
<SpacedSubmitForm action={signupURL} method="POST">
<SpacedSubmitForm
action={signupURL}
id="signup-form"
method="POST"
onSubmit={(e) => {
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;
}}
>
<FormGroup
intent={error ? "danger" : "none"}
label={createMessage(SIGNUP_PAGE_EMAIL_INPUT_LABEL)}

View File

@ -37,6 +37,7 @@ import { Toaster } from "components/ads/Toast";
import { Datasource } from "entities/Datasource";
import _ from "lodash";
import { createMessage, ERROR_ACTION_RENAME_FAIL } from "constants/messages";
import get from "lodash/get";
function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
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<QueryAction>) {

View File

@ -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("/");

View File

@ -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 <InputNumberControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("ARRAY_FIELD", {
buildPropertyControl(controlProps: FieldArrayControlProps): JSX.Element {
return <FieldArrayControl {...controlProps} />;
},
});
}
}

View File

@ -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;

View File

@ -152,7 +152,7 @@ class DropdownWidget extends BaseWidget<DropdownWidgetProps, WidgetState> {
}
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,
});

View File

@ -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,

View File

@ -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<ModalWidgetProps, WidgetState> {
},
],
},
{
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<ModalWidgetProps, WidgetState> {
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<ModalWidgetProps, WidgetState> {
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;
}

View File

@ -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...
"

View File

@ -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"

View File

@ -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<RequestParamDTO> 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<String, Object> startAfter = startAfterTemp;
final Map<String, Object> 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<Object> conditionList = (List) properties.get(WHERE_CONDITIONAL_PROPERTY_INDEX).getValue();
requestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(WHERE_CONDITIONAL_PROPERTY_INDEX),
conditionList, null, null, null));
for(Object condition : conditionList) {
String path = ((Map<String, String>)condition).get("path");
String operatorString = ((Map<String, String>)condition).get("operator");
String value = ((Map<String, String>)condition).get("value");
/**
* - If all values in all where condition tuples are null, then isWhereMethodUsed(...)
* function will indicate that where conditions are not used effectively and program
* execution would return without reaching here.
*/
if (StringUtils.isEmpty(path) || StringUtils.isEmpty(operatorString) || StringUtils.isEmpty(value)) {
return Mono.error(
new AppsmithPluginException(
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
"One of the where condition fields has been found empty. Please fill " +
"all the where condition fields and try again."
)
);
}
try {
query1 = applyWhereConditional(query1, path, operatorString, value);
} catch (AppsmithPluginException e) {
return Mono.error(e);
}
}
return Mono.just(query1);
})
// Apply limit, always provided, since without it we can inadvertently end up processing too much data.
.map(query1 -> query1.limit(limit))
@ -710,6 +684,25 @@ public class FirestorePlugin extends BasePlugin {
});
}
private boolean isWhereMethodUsed(List<Property> properties) {
// Check if the where property list does not exist or is null
if(properties.size() <= WHERE_CONDITIONAL_PROPERTY_INDEX || properties.get(WHERE_CONDITIONAL_PROPERTY_INDEX) == null
|| CollectionUtils.isEmpty((List) properties.get(WHERE_CONDITIONAL_PROPERTY_INDEX).getValue())) {
return false;
}
// Check if all values in the where property list are null.
boolean allValuesNull = ((List) properties.get(WHERE_CONDITIONAL_PROPERTY_INDEX).getValue()).stream()
.allMatch(valueMap -> valueMap == null ||
((Map) valueMap).entrySet().stream().allMatch(e -> ((Map.Entry) e).getValue() == null));
if (allValuesNull) {
return false;
}
return true;
}
private Mono<ActionExecutionResult> methodAddToCollection(CollectionReference collection, Map<String, Object> mapBody) {
return Mono.justOrEmpty(collection.add(mapBody))
.flatMap(future -> {
@ -878,10 +871,6 @@ public class FirestorePlugin extends BasePlugin {
return invalids;
}
private <T> List<T> parseList(String arrayJson) throws IOException {
return (List<T>) objectMapper.readValue(arrayJson, ArrayList.class);
}
@Override
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
return datasourceCreate(datasourceConfiguration)

View File

@ -0,0 +1,90 @@
package com.external.utils;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.external.plugins.Op;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.cloud.firestore.FieldPath;
import com.google.cloud.firestore.Query;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class WhereConditionUtils {
protected static final ObjectMapper objectMapper = new ObjectMapper();
public static Query applyWhereConditional(Query query, String path, String operatorString, String value) throws AppsmithPluginException {
if (query == null) {
throw new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Appsmith server has found null query object when applying where conditional on Firestore " +
"query. Please contact Appsmith's customer support to resolve this."
);
}
Op operator;
try {
operator = StringUtils.isEmpty(operatorString) ? null : Op.valueOf(operatorString);
} catch (IllegalArgumentException e) {
throw new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Appsmith server has encountered an invalid operator for Firestore query's where conditional." +
" Please contact Appsmith's customer support to resolve this."
);
}
FieldPath fieldPath = FieldPath.of(path.split("\\."));
switch (operator) {
case LT:
return query.whereLessThan(fieldPath, value);
case LTE:
return query.whereLessThanOrEqualTo(fieldPath, value);
case EQ:
return query.whereEqualTo(fieldPath, value);
// TODO: NOT_EQ operator support is awaited in the next version of Firestore driver.
// case NOT_EQ:
// return Mono.just(query.whereNotEqualTo(path, value));
case GT:
return query.whereGreaterThan(fieldPath, value);
case GTE:
return query.whereGreaterThanOrEqualTo(fieldPath, value);
case ARRAY_CONTAINS:
return query.whereArrayContains(fieldPath, value);
case ARRAY_CONTAINS_ANY:
try {
return query.whereArrayContainsAny(fieldPath, parseList(value));
} catch (IOException e) {
throw new AppsmithPluginException(
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
"Unable to parse condition value as a JSON list."
);
}
case IN:
try {
return query.whereIn(fieldPath, parseList(value));
} catch (IOException e) {
throw 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(query.whereNotIn(fieldPath, value));
default:
throw new AppsmithPluginException(
AppsmithPluginError.PLUGIN_ERROR,
"Appsmith server has encountered an invalid operator for Firestore query's where conditional." +
" Please contact Appsmith's customer support to resolve this."
);
}
}
private static <T> List<T> parseList(String arrayJson) throws IOException {
return (List<T>) objectMapper.readValue(arrayJson, ArrayList.class);
}
}

View File

@ -167,43 +167,32 @@
"initialValue": "10"
},
{
"sectionName": "Query",
"id": 2,
"children": [
"label": "Where Conditions Key",
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[3].key",
"controlType": "INPUT_TEXT",
"hidden": true,
"initialValue": "whereConditionTuples"
},
{
"label": "Where Conditions",
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[3].value",
"controlType": "ARRAY_FIELD",
"hidden": {
"path": "actionConfiguration.pluginSpecifiedTemplates[0].value",
"comparison": "NOT_EQUALS",
"value": "GET_COLLECTION"
},
"schema": [
{
"label": "Field Path Key",
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[3].key",
"controlType": "INPUT_TEXT",
"hidden": true,
"initialValue": "fieldPath"
},
{
"label": "Where Condition: Field Path (leave empty to not apply any conditions)",
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[3].value",
"label": "Path",
"key": "path",
"controlType": "QUERY_DYNAMIC_INPUT_TEXT",
"hidden": {
"path": "actionConfiguration.pluginSpecifiedTemplates[0].value",
"comparison": "NOT_EQUALS",
"value": "GET_COLLECTION"
},
"initialValue": ""
"placeholderText": "key1/nestedKey2"
},
{
"label": "Operator Key",
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[4].key",
"controlType": "INPUT_TEXT",
"hidden": true,
"initialValue": "operator"
},
{
"label": "Where Condition: Operator",
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[4].value",
"label": "Operator",
"key": "operator",
"controlType": "DROP_DOWN",
"hidden": {
"path": "actionConfiguration.pluginSpecifiedTemplates[0].value",
"comparison": "NOT_EQUALS",
"value": "GET_COLLECTION"
},
"initialValue": "EQ",
"options": [
{
@ -241,22 +230,10 @@
]
},
{
"label": "Value Key",
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[5].key",
"controlType": "INPUT_TEXT",
"hidden": true,
"initialValue": "fieldValue"
},
{
"label": "Where Condition: Value",
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[5].value",
"label": "Value",
"key": "value",
"controlType": "QUERY_DYNAMIC_INPUT_TEXT",
"hidden": {
"path": "actionConfiguration.pluginSpecifiedTemplates[0].value",
"comparison": "NOT_EQUALS",
"value": "GET_COLLECTION"
},
"initialValue": ""
"placeholderText": "value"
}
]
},

View File

@ -77,7 +77,8 @@ public class FirestorePluginTest {
.build()
.getService();
firestoreConnection.document("initial/one").set(Map.of("value", 1, "name", "one", "isPlural", false)).get();
firestoreConnection.document("initial/one").set(Map.of("value", 1, "name", "one", "isPlural", false,
"category", "test")).get();
final Map<String, Object> twoData = new HashMap<>(Map.of(
"value", 2,
"name", "two",
@ -85,7 +86,8 @@ public class FirestorePluginTest {
"geo", new GeoPoint(-90, 90),
"dt", FieldValue.serverTimestamp(),
"ref", firestoreConnection.document("initial/one"),
"bytes", Blob.fromBytes("abc def".getBytes(StandardCharsets.UTF_8))
"bytes", Blob.fromBytes("abc def".getBytes(StandardCharsets.UTF_8)),
"category", "test"
));
twoData.put("null-ref", null);
firestoreConnection.document("initial/two").set(twoData).get();
@ -146,6 +148,7 @@ public class FirestorePluginTest {
assertFalse((Boolean) first.remove("isPlural"));
assertEquals(1L, first.remove("value"));
assertEquals(Map.of("id", "one", "path", "initial/one"), first.remove("_ref"));
assertEquals("test", first.remove("category"));
assertEquals(Collections.emptyMap(), first);
/*
@ -184,6 +187,7 @@ public class FirestorePluginTest {
assertEquals("abc def", ((Blob) doc.remove("bytes")).toByteString().toStringUtf8());
assertNull(doc.remove("null-ref"));
assertEquals(Map.of("id", "two", "path", "initial/two"), doc.remove("_ref"));
assertEquals("test", doc.remove("category"));
assertEquals(Collections.emptyMap(), doc);
})
.verifyComplete();
@ -240,6 +244,7 @@ public class FirestorePluginTest {
assertFalse((Boolean) first.remove("isPlural"));
assertEquals(1L, first.remove("value"));
assertEquals(Map.of("id", "one", "path", "initial/one"), first.remove("_ref"));
assertEquals("test", first.remove("category"));
assertEquals(Collections.emptyMap(), first);
final Map<String, Object> second = results.stream().filter(d -> "two".equals(d.get("name"))).findFirst().orElse(null);
@ -253,6 +258,7 @@ public class FirestorePluginTest {
assertEquals("abc def", ((Blob) second.remove("bytes")).toByteString().toStringUtf8());
assertNull(second.remove("null-ref"));
assertEquals(Map.of("id", "two", "path", "initial/two"), second.remove("_ref"));
assertEquals("test", second.remove("category"));
assertEquals(Collections.emptyMap(), second);
final Map<String, Object> third = results.stream().filter(d -> "third".equals(d.get("name"))).findFirst().orElse(null);
@ -637,12 +643,6 @@ public class FirestorePluginTest {
null, null)); // End before
expectedRequestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(2), "15", null,
null, null)); // Limit
expectedRequestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(3), "", null,
null, null)); // Field Path
expectedRequestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(4), "", null,
null, null)); // Operator
expectedRequestParams.add(new RequestParamDTO(getActionConfigurationPropertyPath(5), "", null,
null, null)); // Value
assertEquals(result.getRequest().getRequestParams().toString(), expectedRequestParams.toString());
})
@ -696,6 +696,65 @@ public class FirestorePluginTest {
}
@Test
public void testWhereConditional() {
ActionConfiguration actionConfiguration = new ActionConfiguration();
actionConfiguration.setPath("initial");
List<Property> pluginSpecifiedTemplates = new ArrayList<>();
pluginSpecifiedTemplates.add(new Property("method", "GET_COLLECTION"));
pluginSpecifiedTemplates.add(new Property("order", null));
pluginSpecifiedTemplates.add(new Property("limit", null));
Property whereProperty = new Property("where", null);
whereProperty.setValue(new ArrayList<>());
/*
* - get all documents where category == test.
* - this returns 2 documents.
*/
((List)whereProperty.getValue()).add(new HashMap<String, Object>() {{
put("path", "category");
put("operator", "EQ");
put("value", "test");
}});
/*
* - get all documents where name == two.
* - Of the two documents returned by above condition, this will narrow it down to one.
*/
((List)whereProperty.getValue()).add(new HashMap<String, Object>() {{
put("path", "name");
put("operator", "EQ");
put("value", "two");
}});
pluginSpecifiedTemplates.add(whereProperty);
actionConfiguration.setPluginSpecifiedTemplates(pluginSpecifiedTemplates);
Mono<ActionExecutionResult> resultMono = pluginExecutor
.executeParameterized(firestoreConnection, null, dsConfig, actionConfiguration);
StepVerifier.create(resultMono)
.assertNext(result -> {
assertTrue(result.getIsExecutionSuccess());
List<Map<String, Object>> results = (List) result.getBody();
assertEquals(1, results.size());
final Map<String, Object> second = results.stream().findFirst().orElse(null);
assertNotNull(second);
assertEquals("two", second.remove("name"));
assertTrue((Boolean) second.remove("isPlural"));
assertEquals(2L, second.remove("value"));
assertEquals(Map.of("path", "initial/one", "id", "one"), second.remove("ref"));
assertEquals(new GeoPoint(-90, 90), second.remove("geo"));
assertNotNull(second.remove("dt"));
assertEquals("abc def", ((Blob) second.remove("bytes")).toByteString().toStringUtf8());
assertNull(second.remove("null-ref"));
assertEquals(Map.of("id", "two", "path", "initial/two"), second.remove("_ref"));
assertEquals("test", second.remove("category"));
assertEquals(Collections.emptyMap(), second);
})
.verifyComplete();
}
public void testUpdateDocumentWithFieldValueTimestamp() {
List<Property> properties = new ArrayList<>();
properties.add(new Property("method", "UPDATE_DOCUMENT")); // index 0

View File

@ -2291,4 +2291,99 @@ public class DatabaseChangelog {
mongockTemplate.save(action);
}
}
/**
* - Older firestore action form had support for only on where condition, which mapped path, operator and value to
* three different indexes on the pluginSpecifiedTemplates list.
* - In the newer form, the three properties are treated as a tuple, and a list of tuples is mapped to only one
* index in pluginSpecifiedTemplates list.
* - [... path, operator, value, ...] --> [... [ {"path":path, "operator":operator, "value":value} ] ...]
*/
@ChangeSet(order = "070", id = "update-firestore-where-conditions-data", author = "")
public void updateFirestoreWhereConditionsData(MongoTemplate mongoTemplate) {
Plugin firestorePlugin = mongoTemplate
.findOne(query(where("packageName").is("firestore-plugin")), Plugin.class);
Query query = query(new Criteria().andOperator(
where("pluginId").is(firestorePlugin.getId()),
new Criteria().orOperator(
where("unpublishedAction.actionConfiguration.pluginSpecifiedTemplates.3").exists(true),
where("unpublishedAction.actionConfiguration.pluginSpecifiedTemplates.4").exists(true),
where("unpublishedAction.actionConfiguration.pluginSpecifiedTemplates.5").exists(true),
where("publishedAction.actionConfiguration.pluginSpecifiedTemplates.3").exists(true),
where("publishedAction.actionConfiguration.pluginSpecifiedTemplates.4").exists(true),
where("publishedAction.actionConfiguration.pluginSpecifiedTemplates.5").exists(true)
)));
List<NewAction> firestoreActionQueries = mongoTemplate.find(query, NewAction.class);
firestoreActionQueries.stream()
.forEach(action -> {
// For unpublished action
if (action.getUnpublishedAction() != null
&& action.getUnpublishedAction().getActionConfiguration() != null
&& action.getUnpublishedAction().getActionConfiguration().getPluginSpecifiedTemplates() != null
&& action.getUnpublishedAction().getActionConfiguration().getPluginSpecifiedTemplates().size() > 3) {
String path = null;
String op = null;
String value = null;
List<Property> properties = action.getUnpublishedAction().getActionConfiguration().getPluginSpecifiedTemplates();
if (properties.size() > 3 && properties.get(3) != null) {
path = (String) properties.get(3).getValue();
}
if (properties.size() > 4 && properties.get(4) != null) {
op = (String) properties.get(4).getValue();
properties.set(4, null); // Index 4 does not map to any value in the new query format
}
if (properties.size() > 5 && properties.get(5) != null) {
value = (String) properties.get(5).getValue();
properties.set(5, null); // Index 5 does not map to any value in the new query format
}
Map newFormat = new HashMap();
newFormat.put("path", path);
newFormat.put("operator", op);
newFormat.put("value", value);
properties.set(3, new Property("whereConditionTuples", List.of(newFormat)));
}
// For published action
if (action.getPublishedAction() != null
&& action.getPublishedAction().getActionConfiguration() != null
&& action.getPublishedAction().getActionConfiguration().getPluginSpecifiedTemplates() != null
&& action.getPublishedAction().getActionConfiguration().getPluginSpecifiedTemplates().size() > 3) {
String path = null;
String op = null;
String value = null;
List<Property> properties = action.getPublishedAction().getActionConfiguration().getPluginSpecifiedTemplates();
if (properties.size() > 3 && properties.get(3) != null) {
path = (String) properties.get(3).getValue();
}
if (properties.size() > 4 && properties.get(4) != null) {
op = (String) properties.get(4).getValue();
properties.set(4, null); // Index 4 does not map to any value in the new query format
}
if (properties.size() > 5 && properties.get(5) != null) {
value = (String) properties.get(5).getValue();
properties.set(5, null); // Index 5 does not map to any value in the new query format
}
HashMap newFormat = new HashMap();
newFormat.put("path", path);
newFormat.put("operator", op);
newFormat.put("value", value);
properties.set(3, new Property("whereConditionTuples", List.of(newFormat)));
}
});
/**
* - Save changes only after all the processing is done so that in case any data manipulation fails, no data
* write occurs.
* - Write data back to db only if all data manipulations done above have succeeded.
*/
firestoreActionQueries.stream()
.forEach(action -> mongoTemplate.save(action));
}
}

View File

@ -71,7 +71,6 @@ On your development machine, please ensure that:
- By default your client app points to the local api server - `http://host.docker.internal:8080` for MacOS or `http://localhost:8080` for Linux. Your page will load with errors if you don't have the api server running on your local system. To setup the api server on your local system please follow the instructions [here](https://github.com/appsmithorg/appsmith/blob/release/contributions/ServerSetup.md)
- In case you are unable to setup the api server on your local system, you can also [use Appsmith's staging API server](#if-you-would-like-to-hit-a-different-appsmith-server).
- In case you are using a M1 chip Macbook please run the client with `yarn start-m1`.
#### If yarn start throws mismatch node version error

View File

@ -39,4 +39,7 @@ tnc_pp: ''
version_id: ''
version_release_date: ''
intercom_app_id: ''
google_recaptcha_site_key: ''
google_recaptcha_secret_key: ''
google_recaptcha_enabled: 'false'

View File

@ -45,3 +45,9 @@ APPSMITH_MONGODB_URI=mongodb://{{ mongo_root_user }}:{{ mongo_root_password }}@{
# *******************************
APPSMITH_DISABLE_TELEMETRY={{ disable_telemetry }}
# ******** Google Recaptcha Keys ***********
APPSMITH_RECAPTCHA_SITE_KEY= {{ google_recaptcha_site_key }}
APPSMITH_RECAPTCHA_SECRET_KEY= {{ google_recaptcha_secrete_key }}
APPSMITH_RECAPTCHA_ENABLED= {{ google_recaptcha_enabled }}
# ********************************

View File

@ -37,6 +37,9 @@
sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '{{ version_release_date }}';
sub_filter __APPSMITH_INTERCOM_APP_ID__ '{{ intercom_app_id }}';
sub_filter __APPSMITH_MAIL_ENABLED__ '{{ mail_enabled }}';
sub_filter __APPSMITH_RECAPTCHA_SITE_KEY__ '{{ google_recaptcha_site_key }}';
sub_filter __APPSMITH_RECAPTCHA_SECRET_KEY__ '{{ google_recaptcha_secrete_key }}';
sub_filter __APPSMITH_RECAPTCHA_ENABLED__ '{{ google_recaptcha_enabled }}';
}
location /f {
@ -93,6 +96,9 @@
{{ ssl_cmt }} sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '{{ version_release_date }}';
{{ ssl_cmt }} sub_filter __APPSMITH_INTERCOM_APP_ID__ '{{ intercom_app_id }}';
{{ ssl_cmt }} sub_filter __APPSMITH_MAIL_ENABLED__ '{{ mail_enabled }}';
{{ ssl_cmt }} sub_filter __APPSMITH_RECAPTCHA_SITE_KEY__ '{{ google_recaptcha_site_key }}';
{{ ssl_cmt }} sub_filter __APPSMITH_RECAPTCHA_SECRET_KEY__ '{{ google_recaptcha_secrete_key }}';
{{ ssl_cmt }} sub_filter __APPSMITH_RECAPTCHA_ENABLED__ '{{ google_recaptcha_enabled }}';
{{ ssl_cmt }} }
{{ ssl_cmt }} location /f {

View File

@ -33,6 +33,10 @@ Quickly set up Appsmith to explore product functionality using Heroku.
- `APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET`: Client secret provided by Github for OAuth2 login
- `APPSMITH_GOOGLE_MAPS_API_KEY`: Google Maps API key which is required if you wish to leverage Google Maps widget. Read more at: https://docs.appsmith.com/v/v1.2.1/setup/docker/google-maps
- `APPSMITH_DISABLE_TELEMETRY`: 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
- Google reCAPTCHA v3 Configuration:
- `APPSMITH_RECAPTCHA_SITE_KEY`: 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
- `APPSMITH_RECAPTCHA_SECRET_KEY`: Google reCAPTCHA v3 verification secret key, it is required if you wish to enable spam protection in your backend server.
- `APPSMITH_RECAPTCHA_ENABLED`: Boolean config to enable or disable Google reCAPTCHA v3 verification feature. If set to true, both site key and secret key should be provided.
After Heroku finishes setting up the app, click “View” and your Appsmith should be up and running. You will be taken to the account creation page, where you can enter credentials to create an account and get started.

View File

@ -42,6 +42,12 @@ if [[ -z "${APPSMITH_GOOGLE_MAPS_API_KEY}" ]]; then
unset APPSMITH_GOOGLE_MAPS_API_KEY
fi
if [[ -z "${APPSMITH_RECAPTCHA_SITE_KEY}" ]] || [[ -z "${APPSMITH_RECAPTCHA_SECRET_KEY}" ]] || [[ -z "${APPSMITH_RECAPTCHA_ENABLED}" ]]; then
unset APPSMITH_RECAPTCHA_SITE_KEY # If this field is empty is might cause application crash
unset APPSMITH_RECAPTCHA_SECRET_KEY
unset APPSMITH_RECAPTCHA_ENABLED
fi
cat /etc/nginx/conf.d/default.conf.template | envsubst "$(printf '$%s,' $(env | grep -Eo '^APPSMITH_[A-Z0-9_]+'))" | sed -e 's|\${\(APPSMITH_[A-Z0-9_]*\)}||g' > /etc/nginx/conf.d/default.conf.template.1
envsubst "\$PORT" < /etc/nginx/conf.d/default.conf.template.1 > /etc/nginx/conf.d/default.conf

View File

@ -32,6 +32,9 @@ server {
sub_filter __APPSMITH_INTERCOM_APP_ID__ '${APPSMITH_INTERCOM_APP_ID}';
sub_filter __APPSMITH_MAIL_ENABLED__ '${APPSMITH_MAIL_ENABLED}';
sub_filter __APPSMITH_DISABLE_TELEMETRY__ '${APPSMITH_DISABLE_TELEMETRY}';
sub_filter __APPSMITH_RECAPTCHA_SITE_KEY__ '${APPSMITH_RECAPTCHA_SITE_KEY}';
sub_filter __APPSMITH_RECAPTCHA_SECRET_KEY__ '${APPSMITH_RECAPTCHA_SECRET_KEY}';
sub_filter __APPSMITH_RECAPTCHA_ENABLED__ '${APPSMITH_RECAPTCHA_ENABLED}';
}
location /f {

View File

@ -30,4 +30,7 @@ data:
APPSMITH_REDIS_URL: redis://redis-service:6379
APPSMITH_MONGODB_URI: $mongo_protocol$encoded_mongo_root_user:$encoded_mongo_root_password@$mongo_host/$mongo_db?retryWrites=true&authSource=admin
APPSMITH_DISABLE_TELEMETRY: "$disable_telemetry"
APPSMITH_RECAPTCHA_SITE_KEY= ""
APPSMITH_RECAPTCHA_SECRET_KEY= ""
APPSMITH_RECAPTCHA_ENABLED= "false"
EOF

View File

@ -37,6 +37,9 @@ data:
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}';
sub_filter __APPSMITH_RECAPTCHA_SECRET_KEY__ '${APPSMITH_RECAPTCHA_SECRET_KEY}';
sub_filter __APPSMITH_RECAPTCHA_ENABLED__ '${APPSMITH_RECAPTCHA_ENABLED}';
}
location /f {

View File

@ -59,4 +59,10 @@ APPSMITH_DISABLE_TELEMETRY=$disable_telemetry
# APPSMITH_CODEC_SIZE=
# *******************************
# ***** Google Recaptcha Config ******
# APPSMITH_RECAPTCHA_SITE_KEY=
# APPSMITH_RECAPTCHA_SECRET_KEY=
# APPSMITH_RECAPTCHA_ENABLED=
# ************************************
EOF

View File

@ -49,6 +49,9 @@ $NGINX_SSL_CMNT server_name $custom_domain ;
sub_filter __APPSMITH_INTERCOM_APP_ID__ '\${APPSMITH_INTERCOM_APP_ID}';
sub_filter __APPSMITH_MAIL_ENABLED__ '\${APPSMITH_MAIL_ENABLED}';
sub_filter __APPSMITH_DISABLE_TELEMETRY__ '\${APPSMITH_DISABLE_TELEMETRY}';
sub_filter __APPSMITH_RECAPTCHA_SITE_KEY__ '\${APPSMITH_RECAPTCHA_SITE_KEY}';
sub_filter __APPSMITH_RECAPTCHA_SECRET_KEY__ '\${APPSMITH_RECAPTCHA_SECRET_KEY}';
sub_filter __APPSMITH_RECAPTCHA_ENABLED__ '\${APPSMITH_RECAPTCHA_ENABLED}';
}
location /f {
@ -106,6 +109,9 @@ $NGINX_SSL_CMNT sub_filter __APPSMITH_VERSION_RELEASE_DATE__ '\${APPSMITH
$NGINX_SSL_CMNT sub_filter __APPSMITH_INTERCOM_APP_ID__ '\${APPSMITH_INTERCOM_APP_ID}';
$NGINX_SSL_CMNT sub_filter __APPSMITH_MAIL_ENABLED__ '\${APPSMITH_MAIL_ENABLED}';
$NGINX_SSL_CMNT sub_filter __APPSMITH_DISABLE_TELEMETRY__ '\${APPSMITH_DISABLE_TELEMETRY}';
$NGINX_SSL_CMNT sub_filter __APPSMITH_RECAPTCHA_SITE_KEY__ '\${APPSMITH_RECAPTCHA_SITE_KEY};
$NGINX_SSL_CMNT sub_filter __APPSMITH_RECAPTCHA_SECRET_KEY__ '\${APPSMITH_RECAPTCHA_SECRET_KEY};
$NGINX_SSL_CMNT sub_filter __APPSMITH_RECAPTCHA_ENABLED__ '\${APPSMITH_RECAPTCHA_ENABLED}';
$NGINX_SSL_CMNT }
$NGINX_SSL_CMNT
$NGINX_SSL_CMNT location /f {