Merge branch 'release' into perf/ui/optimize-popper-component/4837
This commit is contained in:
commit
d2fafaa7f6
|
|
@ -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=
|
||||
15
app.json
15
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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
22
app/client/patches/@blueprintjs+core+3.36.0.patch
Normal file
22
app/client/patches/@blueprintjs+core+3.36.0.patch
Normal 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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export interface ControlData {
|
|||
isRequired?: boolean;
|
||||
hidden?: HiddenType;
|
||||
placeholderText?: string;
|
||||
schema?: any;
|
||||
}
|
||||
|
||||
export interface ControlFunctions {
|
||||
|
|
|
|||
106
app/client/src/components/formControls/FieldArrayControl.tsx
Normal file
106
app/client/src/components/formControls/FieldArrayControl.tsx
Normal 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;
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -73,5 +73,9 @@ export type AppsmithUIConfigs = {
|
|||
|
||||
cloudServicesBaseUrl: string;
|
||||
|
||||
googleRecaptchaSiteKey: {
|
||||
enabled: boolean;
|
||||
apiKey: string;
|
||||
};
|
||||
onboardingFormEnabled: boolean;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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("/");
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
# ********************************
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user