diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000000..dfbccd88ae --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,19 @@ +version = 1 + +exclude_patterns = [ + "static/**", + "deploy/**", + "contributions/**" +] + +[[analyzers]] +name = "javascript" +enabled = true + + [analyzers.meta] + plugins = ["react"] + environment = [ + "nodejs", + "browser" + ] + dialect = "typescript" diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index b67ebd449a..566b38f060 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -48,10 +48,10 @@ jobs: - name: Figure out the PR number run: echo ${{ github.event.pull_request.number }} - - name: Use Node.js 10.16.3 + - name: Use Node.js 14.15.4 uses: actions/setup-node@v1 with: - node-version: "10.16.3" + node-version: "14.15.4" # Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again - name: Cache npm dependencies @@ -151,10 +151,10 @@ jobs: if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' uses: actions/checkout@v2 - - name: Use Node.js 10.16.3 + - name: Use Node.js 14.15.4 uses: actions/setup-node@v1 with: - node-version: "10.16.3" + node-version: "14.15.4" # Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again - name: Cache npm dependencies diff --git a/app/client/.nvmrc b/app/client/.nvmrc index 03128968bf..518633e168 100644 --- a/app/client/.nvmrc +++ b/app/client/.nvmrc @@ -1 +1 @@ -lts/dubnium +lts/fermium diff --git a/app/client/package.json b/app/client/package.json index db2892f260..effa27e4af 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "engines": { - "node": "^10.16.3", - "npm": "^6.9.0" + "node": "^14.15.4", + "npm": "^6.14.10" }, "cracoConfig": "craco.dev.config.js", "dependencies": { @@ -35,14 +35,14 @@ "@types/react-table": "^7.0.13", "@types/styled-components": "^5.1.3", "@types/tinycolor2": "^1.4.2", - "@uppy/core": "^1.8.2", - "@uppy/dashboard": "^1.6.2", - "@uppy/file-input": "^1.3.1", - "@uppy/google-drive": "^1.3.2", - "@uppy/onedrive": "^0.1.1", - "@uppy/react": "^1.4.5", - "@uppy/url": "^1.3.2", - "@uppy/webcam": "^1.3.1", + "@uppy/core": "^1.16.0", + "@uppy/dashboard": "^1.16.0", + "@uppy/file-input": "^1.4.22", + "@uppy/google-drive": "^1.5.22", + "@uppy/onedrive": "^1.1.22", + "@uppy/react": "^1.11.2", + "@uppy/url": "^1.5.16", + "@uppy/webcam": "^1.8.4", "@welldone-software/why-did-you-render": "^4.2.5", "algoliasearch": "^4.2.0", "axios": "^0.21.1", diff --git a/app/client/src/components/ads/Button.tsx b/app/client/src/components/ads/Button.tsx index 11c877be92..5afc28c04a 100644 --- a/app/client/src/components/ads/Button.tsx +++ b/app/client/src/components/ads/Button.tsx @@ -381,7 +381,7 @@ const TextLoadingState = ({ text }: { text?: string }) => ( ); const IconLoadingState = ({ size, icon }: { size?: Size; icon?: IconName }) => ( - + ); const getIconContent = (props: ButtonProps) => diff --git a/app/client/src/components/ads/EditableText.tsx b/app/client/src/components/ads/EditableText.tsx index 6346f67c16..3656267c22 100644 --- a/app/client/src/components/ads/EditableText.tsx +++ b/app/client/src/components/ads/EditableText.tsx @@ -263,7 +263,7 @@ export const EditableText = (props: EditableTextProps) => { onChange={onInputchange} onConfirm={onConfirm} value={value} - selectAllOnFocus={true} + selectAllOnFocus placeholder={props.placeholder || defaultValue} className={props.className} onCancel={onConfirm} diff --git a/app/client/src/components/designSystems/appsmith/PopoverVideo.tsx b/app/client/src/components/designSystems/appsmith/PopoverVideo.tsx index b08c3191dd..dc4d490fa0 100644 --- a/app/client/src/components/designSystems/appsmith/PopoverVideo.tsx +++ b/app/client/src/components/designSystems/appsmith/PopoverVideo.tsx @@ -35,7 +35,7 @@ const PopoverVideo = (props: VideoComponentProps) => { minimal usePortal enforceFocus={false} - lazy={true} + lazy modifiers={{ flip: { behavior: ["right", "left", "bottom", "top"], diff --git a/app/client/src/components/designSystems/appsmith/TableHeader.tsx b/app/client/src/components/designSystems/appsmith/TableHeader.tsx index 8e94ede01d..eb96bcc07b 100644 --- a/app/client/src/components/designSystems/appsmith/TableHeader.tsx +++ b/app/client/src/components/designSystems/appsmith/TableHeader.tsx @@ -55,7 +55,7 @@ const PageNumberInput = (props: { min={1} max={props.pageCount || 1} buttonPosition="none" - clampValueOnBlur={true} + clampValueOnBlur onBlur={(e: any) => { const oldPageNo = Number(props.pageNo || 0); const value = e.target.value; diff --git a/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx b/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx index d455d75cd3..87efe89db2 100644 --- a/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx +++ b/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx @@ -114,7 +114,7 @@ class HelpModal extends React.Component { <> {isHelpModalOpen && ( { comparison: "NOT_EQUALS", }, }, + { + values: { name: "Name", config: { type: "Different BODY" } }, + hidden: { + path: "config.type", + value: ["EMAIL", "BODY"], + comparison: "IN", + }, + }, + { + values: { name: "Name", config: { type: "BODY" } }, + hidden: { + path: "config.type", + value: ["EMAIL", "BODY"], + comparison: "NOT_IN", + }, + }, { values: undefined, hidden: false, diff --git a/app/client/src/components/formControls/utils.ts b/app/client/src/components/formControls/utils.ts index 2e2616d803..0afa6604b0 100644 --- a/app/client/src/components/formControls/utils.ts +++ b/app/client/src/components/formControls/utils.ts @@ -15,6 +15,10 @@ export const isHidden = (values: any, hiddenConfig?: HiddenType) => { return valueAtPath > value; case "LESSER": return valueAtPath < value; + case "IN": + return Array.isArray(value) && value.includes(valueAtPath); + case "NOT_IN": + return Array.isArray(value) && !value.includes(valueAtPath); default: return true; } diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index 01964f6296..b0bf40aefd 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -8,12 +8,12 @@ export const FIELD_REQUIRED_ERROR = "This field is required"; export const VALID_FUNCTION_NAME_ERROR = "Must be a valid variable name (camelCase)"; export const UNIQUE_NAME_ERROR = "Name must be unique"; -export const NAME_SPACE_ERROR = "Name must not have spaces"; +export const NAME_SPACE_ERROR = "Name cannot have spaces"; export const FORM_VALIDATION_EMPTY_EMAIL = "Please enter an email"; export const FORM_VALIDATION_INVALID_EMAIL = "Please provide a valid email address"; -export const ENTER_VIDEO_URL = "Please provide a valid url"; +export const ENTER_VIDEO_URL = "Please provide a valid URL"; export const FORM_VALIDATION_EMPTY_PASSWORD = "Please enter the password"; export const FORM_VALIDATION_PASSWORD_RULE = @@ -27,11 +27,11 @@ export const LOGIN_PAGE_PASSWORD_INPUT_LABEL = "Password"; export const LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER = "Email"; export const LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password"; export const LOGIN_PAGE_INVALID_CREDS_ERROR = - "It looks like you may have entered incorrect/invalid credentials. Please try again or reset password using the button below."; + "You may have entered incorrect/invalid credentials. Please try again or reset your password."; export const LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK = "Reset Password"; export const NEW_TO_APPSMITH = "New to Appsmith?"; -export const LOGIN_PAGE_LOGIN_BUTTON_TEXT = "sign in"; +export const LOGIN_PAGE_LOGIN_BUTTON_TEXT = "Sign in"; export const LOGIN_PAGE_FORGOT_PASSWORD_TEXT = "Forgot Password"; export const LOGIN_PAGE_REMEMBER_ME_LABEL = "Remember"; export const LOGIN_PAGE_SIGN_UP_LINK_TEXT = "Sign up"; @@ -44,16 +44,16 @@ export const SIGNUP_PAGE_NAME_INPUT_LABEL = "Name"; export const SIGNUP_PAGE_PASSWORD_INPUT_LABEL = "Password"; export const SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password"; export const SIGNUP_PAGE_LOGIN_LINK_TEXT = "Sign In"; -export const SIGNUP_PAGE_NAME_INPUT_SUBTEXT = "How should we call you?"; +export const SIGNUP_PAGE_NAME_INPUT_SUBTEXT = "What should we call you?"; export const SIGNUP_PAGE_SUBMIT_BUTTON_TEXT = "Sign Up"; -export const ALREADY_HAVE_AN_ACCOUNT = "Already have an account?"; +export const ALREADY_HAVE_AN_ACCOUNT = "You may already have an account."; export const SIGNUP_PAGE_SUCCESS = "Awesome! You have successfully registered."; export const SIGNUP_PAGE_SUCCESS_LOGIN_BUTTON_TEXT = "Login"; export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL = "New Password"; export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER = "New Password"; -export const RESET_PASSWORD_LOGIN_LINK_TEXT = "Back to Sign In"; +export const RESET_PASSWORD_LOGIN_LINK_TEXT = "Back to sign in page"; export const RESET_PASSWORD_PAGE_TITLE = "Reset Password"; export const RESET_PASSWORD_SUBMIT_BUTTON_TEXT = "Reset"; export const RESET_PASSWORD_PAGE_SUBTITLE = @@ -63,9 +63,9 @@ export const RESET_PASSWORD_RESET_SUCCESS = "Your password has been reset"; //"Y export const RESET_PASSWORD_RESET_SUCCESS_LOGIN_LINK = "Login"; export const RESET_PASSWORD_EXPIRED_TOKEN = - "The password reset link has expired. Please try generating a new link"; + "This password reset link has expired. Please try generating a new link"; export const RESET_PASSWORD_INVALID_TOKEN = - "The password reset link is invalid. Please try generating a new link"; + "This password reset link is invalid. Please try generating a new link"; export const RESET_PASSWORD_FORGOT_PASSWORD_LINK = "Forgot Password"; export const FORGOT_PASSWORD_PAGE_EMAIL_INPUT_LABEL = "Email"; @@ -81,7 +81,7 @@ export const PRIVACY_POLICY_LINK = "Privacy Policy"; export const TERMS_AND_CONDITIONS_LINK = "Terms and Conditions"; export const ERROR_500 = - "We apologize, Something went wrong. We're working to fix things."; + "Whoops, something went wrong. This is unexpected, and we'll look into this."; export const ERROR_0 = "We could not connect to our servers. Please check your network connection"; export const ERROR_401 = @@ -159,7 +159,7 @@ export const LIGHTNING_MENU_API_CREATE_NEW = "Create new API"; export const LIGHTNING_MENU_OPTION_TEXT = "Plain Text"; export const LIGHTNING_MENU_OPTION_JS = "Write JS"; export const LIGHTNING_MENU_OPTION_HTML = "Write HTML"; -export const CHECK_REQUEST_BODY = "Check Request body to debug?"; +export const CHECK_REQUEST_BODY = "Please check request body to debug?"; export const DONT_SHOW_THIS_AGAIN = "Don't show this again"; export const SHOW_REQUEST = "Show Request"; @@ -168,7 +168,7 @@ export const TABLE_FILTER_COLUMN_TYPE_CALLOUT = export const WIDGET_SIDEBAR_TITLE = "Widgets"; export const WIDGET_SIDEBAR_CAPTION = - "To add a widget, please drag and drop a widget on the canvas to the right"; + "To add a widget, please click + and add widget to the canvas."; export const GOOGLE_RECAPTCHA_KEY_ERROR = "Google Re-Captcha Token Generation failed! Please check the Re-captcha Site Key."; export const GOOGLE_RECAPTCHA_DOMAIN_ERROR = diff --git a/app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx b/app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx index b3e4d17239..f3922758af 100644 --- a/app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx +++ b/app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx @@ -122,6 +122,8 @@ export const PageTabsContainer = (props: AppViewerHeaderProps) => { onMouseDown={() => startScrolling(true)} onMouseUp={stopScrolling} onMouseLeave={stopScrolling} + onTouchStart={() => startScrolling(true)} + onTouchEnd={stopScrolling} visible={shouldShowLeftArrow} > @@ -137,6 +139,8 @@ export const PageTabsContainer = (props: AppViewerHeaderProps) => { onMouseDown={() => startScrolling(false)} onMouseUp={stopScrolling} onMouseLeave={stopScrolling} + onTouchStart={() => startScrolling(false)} + onTouchEnd={stopScrolling} visible={shouldShowRightArrow} > diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index 4334f3de20..c00400bba5 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -1346,10 +1346,10 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction) { rightColumn: columns, columns, rows, - parentId: "0", + parentId: MAIN_CONTAINER_WIDGET_ID, widgetName, renderMode: RenderModes.CANVAS, - parentRowSpace: 1, + parentRowSpace: GridDefaults.DEFAULT_GRID_ROW_HEIGHT, parentColumnSpace: 1, isLoading: false, props: { @@ -1362,7 +1362,11 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction) { topRow, rightColumn, bottomRow, - } = yield calculateNewWidgetPosition(newWidget, "0", widgets); + } = yield calculateNewWidgetPosition( + newWidget, + MAIN_CONTAINER_WIDGET_ID, + widgets, + ); newWidget = { ...newWidget, diff --git a/app/client/src/widgets/FilepickerWidget.tsx b/app/client/src/widgets/FilepickerWidget.tsx index 67e0371412..ed05de6c88 100644 --- a/app/client/src/widgets/FilepickerWidget.tsx +++ b/app/client/src/widgets/FilepickerWidget.tsx @@ -27,13 +27,11 @@ class FilePickerWidget extends BaseWidget< FilePickerWidgetProps, FilePickerWidgetState > { - uppy: any; - constructor(props: FilePickerWidgetProps) { super(props); this.state = { - version: 0, isLoading: false, + uppy: this.initializeUppy(), }; } @@ -63,24 +61,70 @@ class FilePickerWidget extends BaseWidget< }; } - refreshUppy = (props: FilePickerWidgetProps) => { - this.uppy = Uppy({ + static getTriggerPropertyMap(): TriggerPropertiesMap { + return { + onFilesSelected: true, + }; + } + + /** + * if uppy is not initialized before, initialize it + * else setState of uppy instance + */ + initializeUppy = () => { + const uppyState = { id: this.props.widgetId, autoProceed: false, allowMultipleUploads: true, debug: false, + restrictions: { + maxFileSize: this.props.maxFileSize + ? this.props.maxFileSize * 1024 * 1024 + : null, + maxNumberOfFiles: this.props.maxNumFiles, + minNumberOfFiles: null, + allowedFileTypes: + this.props.allowedFileTypes && + (this.props.allowedFileTypes.includes("*") || + _.isEmpty(this.props.allowedFileTypes)) + ? null + : this.props.allowedFileTypes, + }, + }; + + return Uppy(uppyState); + }; + + /** + * set states on the uppy instance with new values + */ + reinitializeUppy = (props: FilePickerWidgetProps) => { + const uppyState = { + id: props.widgetId, + autoProceed: false, + allowMultipleUploads: true, + debug: false, restrictions: { maxFileSize: props.maxFileSize ? props.maxFileSize * 1024 * 1024 : null, maxNumberOfFiles: props.maxNumFiles, minNumberOfFiles: null, allowedFileTypes: props.allowedFileTypes && - (props.allowedFileTypes.includes("*") || + (this.props.allowedFileTypes.includes("*") || _.isEmpty(props.allowedFileTypes)) ? null : props.allowedFileTypes, }, - }) + }; + + this.state.uppy.setOptions(uppyState); + }; + + /** + * add all uppy events listeners needed + */ + initializeUppyEventListeners = () => { + this.state.uppy .use(Dashboard, { target: "body", metaFields: [], @@ -101,7 +145,11 @@ class FilePickerWidget extends BaseWidget< disablePageScrollWhenModalOpen: true, proudlyDisplayPoweredByUppy: false, onRequestCloseModal: () => { - this.uppy.getPlugin("Dashboard").closeModal(); + const plugin = this.state.uppy.getPlugin("Dashboard"); + + if (plugin) { + plugin.closeModal(); + } }, locale: {}, }) @@ -117,7 +165,8 @@ class FilePickerWidget extends BaseWidget< facingMode: "user", locale: {}, }); - this.uppy.on("file-removed", (file: any) => { + + this.state.uppy.on("file-removed", (file: any) => { const updatedFiles = this.props.files ? this.props.files.filter((dslFile) => { return file.id !== dslFile.id; @@ -125,47 +174,50 @@ class FilePickerWidget extends BaseWidget< : []; this.props.updateWidgetMetaProperty("files", updatedFiles); }); - this.uppy.on("file-added", (file: any) => { - const dslFiles = this.props.files ? [...this.props.files] : []; - const reader = new FileReader(); - reader.readAsDataURL(file.data); - reader.onloadend = () => { - const base64data = reader.result; - const binaryReader = new FileReader(); - binaryReader.readAsBinaryString(file.data); - binaryReader.onloadend = () => { - const rawData = binaryReader.result; - const textReader = new FileReader(); - textReader.readAsText(file.data); - textReader.onloadend = () => { - const text = textReader.result; - const newFile = { - id: file.id, - base64: base64data, - blob: file.data, - raw: rawData, - text: text, - name: file.meta ? file.meta.name : undefined, + this.state.uppy.on("files-added", (files: any[]) => { + const dslFiles = this.props.files ? [...this.props.files] : []; + + const fileReaderPromises = files.map((file) => { + const reader = new FileReader(); + return new Promise((resolve) => { + reader.readAsDataURL(file.data); + reader.onloadend = () => { + const base64data = reader.result; + const binaryReader = new FileReader(); + binaryReader.readAsBinaryString(file.data); + binaryReader.onloadend = () => { + const rawData = binaryReader.result; + const textReader = new FileReader(); + textReader.readAsText(file.data); + textReader.onloadend = () => { + const text = textReader.result; + const newFile = { + id: file.id, + base64: base64data, + blob: file.data, + raw: rawData, + text: text, + name: file.meta ? file.meta.name : undefined, + }; + + resolve(newFile); + }; }; - dslFiles.push(newFile); - this.props.updateWidgetMetaProperty("files", dslFiles); }; - }; - }; + }); + }); + + Promise.all(fileReaderPromises).then((files) => { + this.props.updateWidgetMetaProperty("files", dslFiles.concat(files)); + }); }); - this.uppy.on("upload", () => { + + this.state.uppy.on("upload", () => { this.onFilesSelected(); }); - this.setState({ version: this.state.version + 1 }); }; - static getTriggerPropertyMap(): TriggerPropertiesMap { - return { - onFilesSelected: true, - }; - } - /** * this function is called when user selects the files and it do two things: * 1. calls the action if any @@ -208,29 +260,30 @@ class FilePickerWidget extends BaseWidget< prevProps.files.length > 0 && this.props.files === undefined ) { - this.uppy.reset(); + this.state.uppy.reset(); } else if ( !shallowequal(prevProps.allowedFileTypes, this.props.allowedFileTypes) || prevProps.maxNumFiles !== this.props.maxNumFiles || prevProps.maxFileSize !== this.props.maxFileSize ) { - this.refreshUppy(this.props); + this.reinitializeUppy(this.props); } } componentDidMount() { super.componentDidMount(); - this.refreshUppy(this.props); + + this.initializeUppyEventListeners(); } componentWillUnmount() { - this.uppy.close(); + this.state.uppy.close(); } getPageView() { return ( { | React.KeyboardEvent | React.KeyboardEvent, ) => { + const { isValid, onSubmit } = this.props; const isEnterKey = e.key === "Enter" || e.keyCode === 13; - if (isEnterKey && this.props.onSubmit) { + if (isEnterKey && onSubmit && isValid) { super.executeAction({ - dynamicString: this.props.onSubmit, + dynamicString: onSubmit, event: { type: EventType.ON_SUBMIT, callback: this.onSubmitSuccess, diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 101fdc9df9..b54e2bef39 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -4501,39 +4501,40 @@ "@typescript-eslint/types" "4.6.0" eslint-visitor-keys "^2.0.0" -"@uppy/companion-client@^1.4.1", "@uppy/companion-client@^1.5.4": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@uppy/companion-client/-/companion-client-1.5.4.tgz#11a9d2ee91a13789ca083f88a3ac7378b3fb20ed" - integrity sha512-AmrKamjHraqm/eV00ps8brw5ajI9NqjzBotTtPH9QaC+D3wIQ/EoWZNTGx4OlGYRahEoVr1K6hhlyUoHmEQc4w== +"@uppy/companion-client@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@uppy/companion-client/-/companion-client-1.8.1.tgz#e8796ac5d2219663da1ef46367efd9cbcf629b17" + integrity sha512-me07SraLbf+Q7z5i6IVTrKc6tiIe84ztqrifx8ftL/XKfOf2niYrS04PU4sppOCtkHiSPG/jP1TDFYd9HVL3aA== dependencies: - "@uppy/utils" "^3.2.3" + "@uppy/utils" "^3.4.0" namespace-emitter "^2.0.1" + qs-stringify "^1.1.0" -"@uppy/core@^1.8.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@uppy/core/-/core-1.13.2.tgz#78c03019ca78ebaa2920c1fa4888ee76531d2e59" - integrity sha512-h0fbwlntm51lJ5i9k16csAsGNuWSzAZe69gLX/5DOISWqziRdF3g2EtSG17p/p/tK/MwX1dzyNU8JI6krDYIwA== +"@uppy/core@^1.16.0": + version "1.16.0" + resolved "https://registry.yarnpkg.com/@uppy/core/-/core-1.16.0.tgz#a5058e4adb1c7e3d7b52d260eef48fe7283294d7" + integrity sha512-qzd/G23KVTLOVmfmszPj8qaTuKyt6ZfLNpp5UnjrZkdvs9b+tY/uKEjUI3Dw8ScSa7roaizQU064z3eik8G84Q== dependencies: "@transloadit/prettier-bytes" "0.0.7" - "@uppy/store-default" "^1.2.4" - "@uppy/utils" "^3.2.3" + "@uppy/store-default" "^1.2.5" + "@uppy/utils" "^3.4.0" cuid "^2.1.1" lodash.throttle "^4.1.1" mime-match "^1.0.2" namespace-emitter "^2.0.1" preact "8.2.9" -"@uppy/dashboard@^1.12.8", "@uppy/dashboard@^1.6.2": - version "1.12.8" - resolved "https://registry.yarnpkg.com/@uppy/dashboard/-/dashboard-1.12.8.tgz#1ec26020753fb58ac6182f5adad7d018e6157097" - integrity sha512-ryz2x2i/jwVJCDGcdiQqD6Az92WPY6ft/r5iu2TTxOIzUFdou3T1rKF/6Dbh3qVCbzzFviPMLdTVfaSsEoQdNw== +"@uppy/dashboard@^1.16.0": + version "1.16.0" + resolved "https://registry.yarnpkg.com/@uppy/dashboard/-/dashboard-1.16.0.tgz#5489dedae26cf4980894de3dd5147a5f62073343" + integrity sha512-Kc4OyIvR7b9rVmuJkDG1hWgCYNcE+x2u5a2tB73CAa6BzXFRUTizd3sx3OAVZELWFBJ6GR0gERcjjeXZItXBSw== dependencies: "@transloadit/prettier-bytes" "0.0.7" - "@uppy/informer" "^1.5.11" - "@uppy/provider-views" "^1.7.7" - "@uppy/status-bar" "^1.7.6" - "@uppy/thumbnail-generator" "^1.6.7" - "@uppy/utils" "^3.2.3" + "@uppy/informer" "^1.6.0" + "@uppy/provider-views" "^1.11.0" + "@uppy/status-bar" "^1.9.0" + "@uppy/thumbnail-generator" "^1.7.5" + "@uppy/utils" "^3.4.0" classnames "^2.2.6" cuid "^2.1.1" is-shallow-equal "^1.0.1" @@ -4543,134 +4544,128 @@ preact "8.2.9" resize-observer-polyfill "^1.5.0" -"@uppy/drag-drop@^1.4.19": - version "1.4.19" - resolved "https://registry.yarnpkg.com/@uppy/drag-drop/-/drag-drop-1.4.19.tgz#b8e2748c6384cddaf42b43d55c801288d59f5e60" - integrity sha512-gY1zDOcAqw1tNNCIhjjJm63IujZyNpAUA6yMVWuSri9Sisy7D2GjJUuoRhUUBZlg37AJthFSku7NVERMBxqSTw== +"@uppy/drag-drop@^1.4.24": + version "1.4.24" + resolved "https://registry.yarnpkg.com/@uppy/drag-drop/-/drag-drop-1.4.24.tgz#02ee9df001a1074f45ddaeb93fc84f7f7c05d82f" + integrity sha512-uiAGu2GlLH/AE3B6H1ooyDGuRmVPcycpVXupzavlXMMTd4NDnove9DK24svvzTYYm83WgRIF3UDqkDn12wYn1w== dependencies: - "@uppy/utils" "^3.2.3" + "@uppy/utils" "^3.4.0" preact "8.2.9" -"@uppy/file-input@^1.3.1": - version "1.4.17" - resolved "https://registry.yarnpkg.com/@uppy/file-input/-/file-input-1.4.17.tgz#60cd804b57be35dcfb4a6edfd1a7b6c15f1095ed" - integrity sha512-Z29AYWLwd2pOCEeEFdrhkEvzKOCYRm60H7j5heZ6HIovU0bdz7znpY5s9pVg6R4JDwtUux05+zMexFKKMxuVmA== +"@uppy/file-input@^1.4.22": + version "1.4.22" + resolved "https://registry.yarnpkg.com/@uppy/file-input/-/file-input-1.4.22.tgz#e8642542ceedce71a11494f08d41a5144605f814" + integrity sha512-GvH3B3fQqJdeKUxRIK1RMAd3BizlCigXjDXO7ndEfXlc/iK7YQi7P8UUZadfdgOK7bne2JPzKydvkSP1EbVAwQ== dependencies: - "@uppy/utils" "^3.2.3" + "@uppy/utils" "^3.4.0" preact "8.2.9" -"@uppy/google-drive@^1.3.2": - version "1.5.16" - resolved "https://registry.yarnpkg.com/@uppy/google-drive/-/google-drive-1.5.16.tgz#12f37a0c8320980edda901b335d1fd5bb8a84ecb" - integrity sha512-es6rKyY3iUnpX6i5LNt/okzz5GIQKdnE8GS/GzbIlywsenRFw9hpRo5oqIDFqkKCQ4t0PKOoB5kb1d18VfJxuw== +"@uppy/google-drive@^1.5.22": + version "1.5.22" + resolved "https://registry.yarnpkg.com/@uppy/google-drive/-/google-drive-1.5.22.tgz#d185e8da86ba2a42d1198544401a60c9092e4025" + integrity sha512-yzlT4J3FDh2By1ogjmFWRFxuVybLCVuZYg55vTUKnIrQWCbd8clena2YZuuJofJOAYjd4HlCaaRKZDFKekS89w== dependencies: - "@uppy/companion-client" "^1.5.4" - "@uppy/provider-views" "^1.7.7" - "@uppy/utils" "^3.2.3" + "@uppy/companion-client" "^1.8.1" + "@uppy/provider-views" "^1.11.0" + "@uppy/utils" "^3.4.0" preact "8.2.9" -"@uppy/informer@^1.5.11": - version "1.5.11" - resolved "https://registry.yarnpkg.com/@uppy/informer/-/informer-1.5.11.tgz#ebd5b8960838e47ec2088a2ce0e6b914b664a283" - integrity sha512-sZIWKvV3UO5Epk6V2e5girlzgsqukDAKnLkZh6nOqjSGRkMudGUrEZwuSOB496lBVqo38BTavI/zf+eLvrdBlA== +"@uppy/informer@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@uppy/informer/-/informer-1.6.0.tgz#65e90c1f258c8fc54c71dcbf4a8d0c485400fc8e" + integrity sha512-599eQol5XlsVk+rIRctMKGbUHldyeDOHxzSHgvzsTOSBLL3vj6bN+OblBX1kF4DlHgEHhanLrjo+UfjvpNvy+A== dependencies: - "@uppy/utils" "^3.2.3" + "@uppy/utils" "^3.4.0" preact "8.2.9" -"@uppy/onedrive@^0.1.1": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@uppy/onedrive/-/onedrive-0.1.4.tgz#18e4459019070ec7f2430629cdeb16fddeb343c4" - integrity sha512-3qOhqjh7kv7I7S7t8qSFYNQf3Jr6g+DrNgsf/jcht/mp7zm1F+UlJR/nmxZC5BEtUbA91GYPXwcowxdeX+oWvQ== +"@uppy/onedrive@^1.1.22": + version "1.1.22" + resolved "https://registry.yarnpkg.com/@uppy/onedrive/-/onedrive-1.1.22.tgz#16e983f299cd1afc12c5c8b7e6551417ebff20f8" + integrity sha512-nCqj+l6+YsYASt/bHIzuvSDuFzweESD/gzBi+/1XE6CaQytzqqDbEfxhxkTcaoyj6iu+GGsKHxTJ8h8YSPEr4g== dependencies: - "@uppy/companion-client" "^1.4.1" - "@uppy/provider-views" "^1.5.2" - "@uppy/utils" "^2.1.2" + "@uppy/companion-client" "^1.8.1" + "@uppy/provider-views" "^1.11.0" + "@uppy/utils" "^3.4.0" preact "8.2.9" -"@uppy/progress-bar@^1.3.19": - version "1.3.19" - resolved "https://registry.yarnpkg.com/@uppy/progress-bar/-/progress-bar-1.3.19.tgz#6abf87a2c05f1d91c2d1fb2955ce0d5ca2199408" - integrity sha512-o0sCpN7J/CcW17IbOoQ/0kbUGHoVPgwfHgyrO77I0J5aMn+qFqEs094xvVnU7gp92NMBsllvT1Vyr8ZNtPq+Dg== +"@uppy/progress-bar@^1.3.24": + version "1.3.24" + resolved "https://registry.yarnpkg.com/@uppy/progress-bar/-/progress-bar-1.3.24.tgz#a4fa3ca88931b6333a1583ee9a2670f591206dca" + integrity sha512-FLfGGaswnr+Glsk+1WIxd5tT0DfxxZJiLhjmBHVZZGHy2qpBpXbPTeFxcPdlOUPokYKeBGqMKyAYz/LBVpjXAQ== dependencies: - "@uppy/utils" "^3.2.3" + "@uppy/utils" "^3.4.0" preact "8.2.9" -"@uppy/provider-views@^1.5.2", "@uppy/provider-views@^1.7.7": - version "1.7.7" - resolved "https://registry.yarnpkg.com/@uppy/provider-views/-/provider-views-1.7.7.tgz#fe85617d86b72ce376f9efa0c369ab7b2eb3af2e" - integrity sha512-PLIAeUJKNOU+Yfxr4iX6oFoHS15SZZhnDj+n2JWyg3l6iNOG7gvLq85OMARFOUA0DlP5srRq4NlR/nlMWo+0xw== +"@uppy/provider-views@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@uppy/provider-views/-/provider-views-1.11.0.tgz#efd6db4d77a539ad092f4b178a3480701bbc9f7e" + integrity sha512-aFGGzByDAR3w8QgMRiuE2XEkDsr51bN6kPiE2p6zplwKZ5FGPVx+j5/+pUNa0Qsb8w0dYJBqlhCCnFcRBDkgjw== dependencies: - "@uppy/utils" "^3.2.3" + "@uppy/utils" "^3.4.0" classnames "^2.2.6" preact "8.2.9" -"@uppy/react@^1.4.5": - version "1.10.8" - resolved "https://registry.yarnpkg.com/@uppy/react/-/react-1.10.8.tgz#260bc37693c8a1aa6f719cada22b67414913466d" - integrity sha512-FO6PThrYZaEGZd7G3YooHHKsxhZqF6/Euy+jI8CLhROf78Kg0Gr3dURhwaH902NgPrA5Yfa6xRSC1d4etwMktg== +"@uppy/react@^1.11.2": + version "1.11.2" + resolved "https://registry.yarnpkg.com/@uppy/react/-/react-1.11.2.tgz#9d1a1a4c0710e456654cdb4dc17735501036a54a" + integrity sha512-Pz4ghEn+9I8XiAkqJGKMLXbVvcnvTAID/o7aqXq7yMQGi61sy3SUgeagX0yaCuKOZ6l4IXItPsZnBMe/Km3MJQ== dependencies: - "@uppy/dashboard" "^1.12.8" - "@uppy/drag-drop" "^1.4.19" - "@uppy/progress-bar" "^1.3.19" - "@uppy/status-bar" "^1.7.6" - "@uppy/utils" "^3.2.3" + "@uppy/dashboard" "^1.16.0" + "@uppy/drag-drop" "^1.4.24" + "@uppy/file-input" "^1.4.22" + "@uppy/progress-bar" "^1.3.24" + "@uppy/status-bar" "^1.9.0" + "@uppy/utils" "^3.4.0" prop-types "^15.6.1" -"@uppy/status-bar@^1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@uppy/status-bar/-/status-bar-1.7.6.tgz#f25b50466398b1952c8b60b16d2c491ea342992f" - integrity sha512-nlZztGjDNEmWZQfWfV/DajeNjtdhqeGO1mRQVpmjWvrCABFaTvWN9jb1t7VEZjuaegSujZot00RP7uU4au/Q+w== +"@uppy/status-bar@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@uppy/status-bar/-/status-bar-1.9.0.tgz#1db2859fd10b1dc7eb6045dbeb56bb3b4cbcb518" + integrity sha512-5Hmx3iRRDfP04xmopDXAzEYz7GM4SNAXs2ayRPYSQZ5OC2Bvqqb2IYVLj09PHCRsH695VBGz9+3kIEvn/OFqbQ== dependencies: "@transloadit/prettier-bytes" "0.0.7" - "@uppy/utils" "^3.2.3" + "@uppy/utils" "^3.4.0" classnames "^2.2.6" lodash.throttle "^4.1.1" preact "8.2.9" -"@uppy/store-default@^1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@uppy/store-default/-/store-default-1.2.4.tgz#480f50cbf23f36158557a42b7699da0cf629d368" - integrity sha512-d4YTq6SeH792+vG9OXGCfmCIoo4RteLJKU2mZCzMYNjCZ1yNfU/FVw1r7heTc7f2wNls56ABor2xSybk/X+wFA== +"@uppy/store-default@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@uppy/store-default/-/store-default-1.2.5.tgz#52936331d6ac4911197d256d13974d5bae93b3a6" + integrity sha512-jnf0U8cfb8Bhgt6yh86YRJO9EEnCyG9BgXZ8dPWWLybgC9Expw3Ah/s3T21tcdChgv4zzdhSACd0JKxCQowyYg== -"@uppy/thumbnail-generator@^1.6.7": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@uppy/thumbnail-generator/-/thumbnail-generator-1.6.7.tgz#65375106dae2cfd1f4cddb073e9d8ff278ca94e5" - integrity sha512-8FRO042ulfK3qRDE1tEETxw/iDIrLzlHxqhrHG45kxZ2QfqbByg5bJAgvlwg7Zyd4202rkq75J8KeFowS/Ntjg== +"@uppy/thumbnail-generator@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@uppy/thumbnail-generator/-/thumbnail-generator-1.7.5.tgz#ae2fa68c684463b41fa7b4a878c4e29974680c6f" + integrity sha512-YUbFLpkfOudbdJMQPqWmVmq/qHh40+0ZHkJHrStpZmTq/OSjHLxyXWSZ1RDgJnAcsh3lUhR4yuembNVmtSrtjg== dependencies: - "@uppy/utils" "^3.2.3" - exifr "^5.0.2" + "@uppy/utils" "^3.4.0" + exifr "^6.0.0" math-log2 "^1.0.1" -"@uppy/url@^1.3.2": - version "1.5.11" - resolved "https://registry.yarnpkg.com/@uppy/url/-/url-1.5.11.tgz#a664b6a386c913ce33af51c8f497c3e036a0389b" - integrity sha512-EYAK/9m++O1HApKCty3Vd6T5qcdCMC3GL0IfN1kO0cE6J7fmhL7nEnud5eWqq6752ydNmhvR9lFYrPv/8AHljQ== +"@uppy/url@^1.5.16": + version "1.5.16" + resolved "https://registry.yarnpkg.com/@uppy/url/-/url-1.5.16.tgz#636830aa01b13a3726abfe87c2e66006412ba867" + integrity sha512-IEsfhVwTUoyvgZcr/uHEwxBMX8SDQQBXFkd3eW0T+jSNVzyP7jQo7bqVyUEjrALOp9xH8X21aXTPvnWc4k1iww== dependencies: - "@uppy/companion-client" "^1.5.4" - "@uppy/utils" "^3.2.3" + "@uppy/companion-client" "^1.8.1" + "@uppy/utils" "^3.4.0" preact "8.2.9" -"@uppy/utils@^2.1.2": - version "2.4.4" - resolved "https://registry.yarnpkg.com/@uppy/utils/-/utils-2.4.4.tgz#29520cdfaf926af58828a9c141cb7b0fa43f8bc4" - integrity sha512-7A0uwK5Rf8XcKqlpNUZ5L5LmkHT5c0/UWjDJGwmzeCxp2lECgzsMC+4vgA6kT4sFzPFbLtUtxHi7ecFwow3NQQ== - dependencies: - lodash.throttle "^4.1.1" - -"@uppy/utils@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@uppy/utils/-/utils-3.2.3.tgz#91cb4a133bd610b861fe3069be1f0e85036dcc20" - integrity sha512-ue14v4yKK6bYBPGjt31wvLnYMThKCVOsy3R7y++eFuPsxfidIGmOGiv+Qmv0895kMJtt7gk0MddXjbCdBX4Ksg== +"@uppy/utils@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@uppy/utils/-/utils-3.4.0.tgz#4afcf8c26dec13ee54676bd1f7958fc1063b68df" + integrity sha512-XcT4UgUm1NhWAeQjMrr5LCE7Uookg12FTwQw/QwXBuGABoVilfk56i6h8ecf1bVX0D74zdpSnUiB1h+8a7ollw== dependencies: abortcontroller-polyfill "^1.4.0" lodash.throttle "^4.1.1" -"@uppy/webcam@^1.3.1": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@uppy/webcam/-/webcam-1.7.0.tgz#e770733b0679454ed1aa78075d55f49a95b1cc6c" - integrity sha512-j5OAAAHWAPDLThdnHS/a6K9O28FQbsi7ZOX7FJzA+u14iOBEmAbuWy99ziETITNdE6vIjiRrak1ixeDSdPFRQQ== +"@uppy/webcam@^1.8.4": + version "1.8.4" + resolved "https://registry.yarnpkg.com/@uppy/webcam/-/webcam-1.8.4.tgz#7a4f7f9dc4def955e701560a59c22fdb08e5efe4" + integrity sha512-WhJrOgH0N2V5QhQZc10N1kIunxOF3/SQ/KTssTEudn7tkVNhkTLRldAlmcA4vZ0HB3Zxg9HaJPy4twBvUgFUbw== dependencies: - "@uppy/utils" "^3.2.3" + "@uppy/utils" "^3.4.0" preact "8.2.9" "@vue/compiler-core@3.0.0": @@ -4912,9 +4907,9 @@ abbrev@1: integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== abortcontroller-polyfill@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.5.0.tgz#2c562f530869abbcf88d949a2b60d1d402e87a7c" - integrity sha512-O6Xk757Jb4o0LMzMOMdWvxpHWrQzruYBaUruFaIOfAQRnWFxfdXYobw12jrVHGtoXk6WiiyYzc0QWN9aL62HQA== + version "1.7.1" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.1.tgz#27084bac87d78a7224c8ee78135d05df430c2d2f" + integrity sha512-yml9NiDEH4M4p0G4AcPkg8AAa4mF3nfYF28VQxaokpO67j9H7gWgmsVWJ/f1Rn+PzsnDYvzJzWIQzCqDKRvWlA== accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" @@ -8905,10 +8900,10 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -exifr@^5.0.2: - version "5.0.6" - resolved "https://registry.yarnpkg.com/exifr/-/exifr-5.0.6.tgz#7c8e5aad1083fa4ec172103dca24215203001209" - integrity sha512-iDB4IhKoKVF+uDDrHRlyNxWqGaTxYluVWqvBWVG54HkQZe8qkFYl9eQrjEP3d8Q4UMBZ9rWu3Pa+mfC+o4CZuw== +exifr@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/exifr/-/exifr-6.0.0.tgz#e82af10e158852a1c7e19aea45bceb4cdd486727" + integrity sha512-a8n3SVIyuI5NP5VJCb/rJHsqXnofgYL1ZXcJdKBXOmCNIrj+pSExaBFHcbdEF5xp5GQrK4kpOabLJ+wBfUGYuA== exit-hook@^1.0.0: version "1.1.1" @@ -15374,6 +15369,11 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= +qs-stringify@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/qs-stringify/-/qs-stringify-1.2.1.tgz#9b39ef6b816bd83309628fc9dad435fc0eccc28b" + integrity sha512-2N5xGLGZUxpgAYq1fD1LmBSCbxQVsXYt5JU0nU3FuPWO8PlCnKNFQwXkZgyB6mrTdg7IbexX4wxIR403dJw9pw== + qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java index 2d6aabd1c7..b5fbe1d8bb 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java @@ -21,6 +21,8 @@ public enum AppsmithPluginError { AppsmithErrorAction.DEFAULT), PLUGIN_JSON_PARSE_ERROR(500, 5006, "Plugin failed to parse JSON \"{0}\" with error: {1}", AppsmithErrorAction.DEFAULT), + PLUGIN_DATASOURCE_TEST_GENERIC_ERROR(500, 5007, "Plugin failed to test with the given configuration. Please reach out to Appsmith customer support to report this", + AppsmithErrorAction.LOG_EXTERNALLY), ; private final Integer httpErrorCode; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceStructure.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceStructure.java index f2c5144060..99eb13633b 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceStructure.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceStructure.java @@ -8,6 +8,7 @@ import java.util.List; @Data @NoArgsConstructor +@AllArgsConstructor public class DatasourceStructure { List tables; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceTestResult.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceTestResult.java index df69c176e6..09caf20151 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceTestResult.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceTestResult.java @@ -1,11 +1,14 @@ package com.appsmith.external.models; +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.springframework.util.CollectionUtils; +import java.util.Arrays; import java.util.Set; +import java.util.stream.Collectors; @Getter @Setter @@ -21,6 +24,15 @@ public class DatasourceTestResult { * @param invalids String messages that explain why the test failed. */ public DatasourceTestResult(String... invalids) { + if (invalids == null) { + invalids = new String[]{AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage()}; + } else { + invalids = Arrays + .stream(invalids) + .map(x -> x == null ? AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage() : x) + .toArray(String[]::new); + } + this.invalids = Set.of(invalids); } diff --git a/app/server/appsmith-interfaces/src/test/java/com.appsmith.external.models/DatasourceTestResultTest.java b/app/server/appsmith-interfaces/src/test/java/com.appsmith.external.models/DatasourceTestResultTest.java new file mode 100644 index 0000000000..d31b18ba0c --- /dev/null +++ b/app/server/appsmith-interfaces/src/test/java/com.appsmith.external.models/DatasourceTestResultTest.java @@ -0,0 +1,21 @@ +package com.appsmith.external.models; + +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class DatasourceTestResultTest { + + @Test + public void testNewDatasourceTestResult_NullInvalidArray() { + DatasourceTestResult nullInvalidsResult = new DatasourceTestResult((String) null); + assertNotNull(nullInvalidsResult); + assertTrue(nullInvalidsResult.getInvalids().contains(AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage())); + + nullInvalidsResult = new DatasourceTestResult(new String[]{null}); + assertNotNull(nullInvalidsResult); + assertTrue(nullInvalidsResult.getInvalids().contains(AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage())); + } +} \ No newline at end of file diff --git a/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java b/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java index a75a070bba..883db8e1f2 100644 --- a/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java +++ b/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java @@ -1,13 +1,14 @@ package com.external.plugins; +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.Endpoint; -import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; -import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; import lombok.NonNull; @@ -29,6 +30,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder; import software.amazon.awssdk.services.dynamodb.model.DynamoDbResponse; +import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -38,6 +40,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -73,15 +76,15 @@ public class DynamoPlugin extends BasePlugin { DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { - return (Mono) Mono.fromCallable(() -> { + return Mono.fromCallable(() -> { ActionExecutionResult result = new ActionExecutionResult(); final String action = actionConfiguration.getPath(); if (StringUtils.isEmpty(action)) { - return Mono.error(new AppsmithPluginException( + throw new AppsmithPluginException( AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Missing action name (like `ListTables`, `GetItem` etc.)." - )); + ); } final String body = actionConfiguration.getBody(); @@ -93,17 +96,17 @@ public class DynamoPlugin extends BasePlugin { } catch (IOException e) { final String message = "Error parsing the JSON body: " + e.getMessage(); log.warn(message, e); - return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, message)); + throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, message); } final Class requestClass; try { requestClass = Class.forName("software.amazon.awssdk.services.dynamodb.model." + action + "Request"); } catch (ClassNotFoundException e) { - return Mono.error(new AppsmithPluginException( + throw new AppsmithPluginException( AppsmithPluginError.PLUGIN_ERROR, "Unknown action: `" + action + "`. Note that action names are case-sensitive." - )); + ); } try { @@ -117,21 +120,20 @@ public class DynamoPlugin extends BasePlugin { } catch (AppsmithPluginException | InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) { final String message = "Error executing the DynamoDB Action: " + (e.getCause() == null ? e : e.getCause()).getMessage(); log.warn(message, e); - return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message)); + throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message); } result.setIsExecutionSuccess(true); System.out.println(Thread.currentThread().getName() + ": In the DynamoPlugin, got action execution result"); - return Mono.just(result); + return result; }) - .flatMap(obj -> obj) .subscribeOn(scheduler); } @Override public Mono datasourceCreate(DatasourceConfiguration datasourceConfiguration) { - return (Mono) Mono.fromCallable(() -> { + return Mono.fromCallable(() -> { final DynamoDbClientBuilder builder = DynamoDbClient.builder(); if (!CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { @@ -141,10 +143,10 @@ public class DynamoPlugin extends BasePlugin { final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); if (authentication == null || StringUtils.isEmpty(authentication.getDatabaseName())) { - return Mono.error(new AppsmithPluginException( + throw new AppsmithPluginException( AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, "Missing region in datasource." - )); + ); } builder.region(Region.of(authentication.getDatabaseName())); @@ -153,9 +155,8 @@ public class DynamoPlugin extends BasePlugin { AwsBasicCredentials.create(authentication.getUsername(), authentication.getPassword()) )); - return Mono.justOrEmpty(builder.build()); + return builder.build(); }) - .flatMap(obj -> obj) .subscribeOn(scheduler); } @@ -204,6 +205,28 @@ public class DynamoPlugin extends BasePlugin { ) .subscribeOn(scheduler); } + + @Override + public Mono getStructure(DynamoDbClient ddb, DatasourceConfiguration datasourceConfiguration) { + return Mono.fromCallable(() -> { + final ListTablesResponse listTablesResponse = ddb.listTables(); + + List tables = new ArrayList<>(); + for (final String tableName : listTablesResponse.tableNames()) { + tables.add(new DatasourceStructure.Table( + DatasourceStructure.TableType.TABLE, + tableName, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList() + )); + } + + return new DatasourceStructure(tables); + + }).subscribeOn(scheduler); + } + } private static String toLowerCamelCase(String action) { @@ -259,7 +282,8 @@ public class DynamoPlugin extends BasePlugin { || value instanceof Integer || value instanceof Float || value instanceof Double) { - // These data types have a setter method that takes a the value as is. Nothing fancy here. + // This will *never* be successful. DynamoDB takes in numeric values as strings, which means the + // control should never flow here for numeric types. builderType.getMethod(setterName, value.getClass()).invoke(builder, value); } else if (value instanceof Map) { @@ -337,44 +361,41 @@ public class DynamoPlugin extends BasePlugin { } } - private static Map sdkToPlain(SdkPojo response) { - final Map plain = new HashMap<>(); + private static Object sdkToPlain(Object valueObj) { + if (valueObj instanceof SdkPojo) { + final SdkPojo response = (SdkPojo) valueObj; + final Map plain = new HashMap<>(); - for (final SdkField field : response.sdkFields()) { - Object value = field.getValueOrDefault(response); - - if (value instanceof SdkPojo) { - value = sdkToPlain((SdkPojo) value); - - } else if (value instanceof Map) { - final Map valueAsMap = (Map) value; - final Map plainMap = new HashMap<>(); - for (final Map.Entry entry : valueAsMap.entrySet()) { - final var key = entry.getKey(); - Object innerValue = entry.getValue(); - if (innerValue instanceof SdkPojo) { - innerValue = sdkToPlain((SdkPojo) innerValue); - } - plainMap.put(key, innerValue); - } - value = plainMap; - - } else if (value instanceof List) { - final List valueAsList = (List) value; - final List plainList = new ArrayList<>(); - for (Object item : valueAsList) { - if (item instanceof SdkPojo) { - item = sdkToPlain((SdkPojo) item); - } - plainList.add(item); - } - value = plainList; + for (final SdkField field : response.sdkFields()) { + Object value = field.getValueOrDefault(response); + plain.put(field.memberName(), sdkToPlain(value)); } - plain.put(field.memberName(), value); + return plain; + + } else if (valueObj instanceof Map) { + final Map valueAsMap = (Map) valueObj; + final Map plainMap = new HashMap<>(); + + for (final Map.Entry entry : valueAsMap.entrySet()) { + plainMap.put(entry.getKey(), sdkToPlain(entry.getValue())); + } + + return plainMap; + + } else if (valueObj instanceof Collection) { + final List valueAsList = (List) valueObj; + final List plainList = new ArrayList<>(); + + for (Object item : valueAsList) { + plainList.add(sdkToPlain(item)); + } + + return plainList; + } - return plain; + return valueObj; } private static boolean isUpperCase(String s) { diff --git a/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java b/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java index 7bfcb0a9c9..39b6c03dae 100644 --- a/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java +++ b/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java @@ -4,6 +4,7 @@ import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.Endpoint; import lombok.extern.log4j.Log4j; import org.junit.BeforeClass; @@ -29,6 +30,7 @@ import java.net.URI; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -91,8 +93,6 @@ public class DynamoPluginTest { )) .build()); - System.out.println(ddb.listTables()); - Endpoint endpoint = new Endpoint(); endpoint.setHost(host); endpoint.setPort(port.longValue()); @@ -221,4 +221,41 @@ public class DynamoPluginTest { .verifyComplete(); } + @Test + public void testScan() { + final String body = "{\n" + + " \"TableName\": \"cities\"\n" + + "}\n"; + + StepVerifier.create(execute("Scan", body)) + .assertNext(result -> { + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + final List items = (List) ((Map) result.getBody()).get("Items"); + assertEquals(2, items.size()); + }) + .verifyComplete(); + } + + @Test + public void testStructure() { + final Mono structureMono = pluginExecutor + .datasourceCreate(dsConfig) + .flatMap(conn -> pluginExecutor.getStructure(conn, dsConfig)); + + StepVerifier.create(structureMono) + .assertNext(structure -> { + assertNotNull(structure); + assertNotNull(structure.getTables()); + assertEquals( + List.of("cities"), + structure.getTables().stream() + .map(DatasourceStructure.Table::getName) + .collect(Collectors.toList()) + ); + }) + .verifyComplete(); + } + } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json index 97f2e828ee..e592ec6a3a 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json @@ -188,7 +188,12 @@ { "label": "Body", "configProperty": "actionConfiguration.body", - "controlType": "QUERY_DYNAMIC_TEXT" + "controlType": "QUERY_DYNAMIC_TEXT", + "hidden": { + "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", + "comparison": "IN", + "value": ["GET_DOCUMENT", "GET_COLLECTION", "DELETE_DOCUMENT"] + } } ] } diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java index a4d0ba4fe3..3f95085eec 100644 --- a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java @@ -347,13 +347,15 @@ public class MySqlPlugin extends BasePlugin { @Override public Mono testDatasource(DatasourceConfiguration datasourceConfiguration) { return datasourceCreate(datasourceConfiguration) - .flatMap(connection -> { - return Mono.from(connection.close()); - }) + .flatMap(connection -> Mono.from(connection.close())) .then(Mono.just(new DatasourceTestResult())) .onErrorResume(error -> { - log.error("Error when testing MySQL datasource.", error); - return Mono.just(new DatasourceTestResult(error.getMessage())); + // We always expect to have an error object, but the error object may not be well formed + final String errorMessage = error.getMessage() == null + ? AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage() + : error.getMessage(); + System.out.println("Error when testing MySQL datasource. " + errorMessage); + return Mono.just(new DatasourceTestResult(errorMessage)); }) .subscribeOn(scheduler); @@ -541,14 +543,14 @@ public class MySqlPlugin extends BasePlugin { return structure; }) .onErrorMap(e -> { - if (!(e instanceof AppsmithPluginException) && !(e instanceof StaleConnectionException)) { - return new AppsmithPluginException( - AppsmithPluginError.PLUGIN_ERROR, - e.getMessage() - ); - } + if (!(e instanceof AppsmithPluginException) && !(e instanceof StaleConnectionException)) { + return new AppsmithPluginException( + AppsmithPluginError.PLUGIN_ERROR, + e.getMessage() + ); + } - return e; + return e; }) .subscribeOn(scheduler); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java index 7f34fb6d82..11062409f0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java @@ -2,11 +2,11 @@ package com.appsmith.server.controllers; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.server.constants.Url; -import com.appsmith.server.domains.Layout; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; import com.appsmith.server.dtos.ActionViewDTO; import com.appsmith.server.dtos.ExecuteActionDTO; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.RefactorNameDTO; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.services.ActionCollectionService; @@ -82,7 +82,7 @@ public class ActionController { } @PutMapping("/refactor") - public Mono> refactorActionName(@RequestBody RefactorNameDTO refactorNameDTO) { + public Mono> refactorActionName(@RequestBody RefactorNameDTO refactorNameDTO) { return layoutActionService.refactorActionName(refactorNameDTO) .map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/LayoutController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/LayoutController.java index 18996edb58..5cb2d43be2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/LayoutController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/LayoutController.java @@ -2,6 +2,7 @@ package com.appsmith.server.controllers; import com.appsmith.server.constants.Url; import com.appsmith.server.domains.Layout; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.RefactorNameDTO; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.services.LayoutActionService; @@ -46,7 +47,7 @@ public class LayoutController { } @PutMapping("/{layoutId}/pages/{pageId}") - public Mono> updateLayout(@PathVariable String pageId, @PathVariable String layoutId, @RequestBody Layout layout) { + public Mono> updateLayout(@PathVariable String pageId, @PathVariable String layoutId, @RequestBody Layout layout) { return layoutActionService.updateLayout(pageId, layoutId, layout) .map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null)); } @@ -58,7 +59,7 @@ public class LayoutController { } @PutMapping("/refactor") - public Mono> refactorWidgetName(@RequestBody RefactorNameDTO refactorNameDTO) { + public Mono> refactorWidgetName(@RequestBody RefactorNameDTO refactorNameDTO) { return layoutActionService.refactorWidgetName(refactorNameDTO) .map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutActionUpdateDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutActionUpdateDTO.java new file mode 100644 index 0000000000..0a3775fcca --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutActionUpdateDTO.java @@ -0,0 +1,16 @@ +package com.appsmith.server.dtos; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +/** + * This class would be used to send any action updates that have happened as part of update layout. The client should + * consume this structure to update the actions in its local storage (instead of fetching all the page actions afresh). + */ +public class LayoutActionUpdateDTO { + String id; + String name; + Boolean executeOnLoad; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutDTO.java new file mode 100644 index 0000000000..97ddeed54a --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutDTO.java @@ -0,0 +1,31 @@ +package com.appsmith.server.dtos; + +import com.appsmith.server.domains.ScreenType; +import lombok.Getter; +import lombok.Setter; +import net.minidev.json.JSONObject; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Getter +@Setter +public class LayoutDTO { + + private String id; + + ScreenType screen; + + JSONObject dsl; + + List> layoutOnLoadActions; + + // All the actions which have been updated as part of updateLayout function call + List actionUpdates; + + // All the toast messages that the developer user should be displayed to inform about the consequences of update layout. + List messages; + + public Set userPermissions = new HashSet<>(); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/MustacheHelper.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/MustacheHelper.java index 349ff4e16c..a68bd66e68 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/MustacheHelper.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/MustacheHelper.java @@ -251,14 +251,14 @@ public class MustacheHelper { } else if (value instanceof List) { for (Object childValue : (List) value) { - if (isDomainModel(childValue.getClass())) { + if (childValue != null && isDomainModel(childValue.getClass())) { renderFieldValues(childValue, context); } } } else if (value instanceof Map) { for (Object childValue : ((Map) value).values()) { - if (isDomainModel(childValue.getClass())) { + if (childValue != null && isDomainModel(childValue.getClass())) { renderFieldValues(childValue, context); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java index 97f912197a..bae4585458 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java @@ -1578,4 +1578,43 @@ public class DatabaseChangelog { public void clearUserDataCollection(MongoTemplate mongoTemplate) { mongoTemplate.dropCollection(UserData.class); } + + @ChangeSet(order = "050", id = "update-database-documentation-links-v1-2-1", author = "") + public void updateDatabaseDocumentationLinks_v1_2_1(MongoTemplate mongoTemplate) { + for (Plugin plugin : mongoTemplate.findAll(Plugin.class)) { + switch (plugin.getPackageName()) { + case "postgres-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-postgres"); + break; + case "mongo-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-mongodb"); + break; + case "elasticsearch-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-elasticsearch"); + break; + case "dynamo-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-dynamodb"); + break; + case "redis-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-redis"); + break; + case "mssql-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-mssql"); + break; + case "firestore-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-firestore"); + break; + case "redshift-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-redshift"); + break; + case "mysql-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-mysql"); + break; + default: + continue; + } + + mongoTemplate.save(plugin); + } + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewActionRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewActionRepositoryImpl.java index ee484ccee3..fb7582bef1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewActionRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewActionRepositoryImpl.java @@ -37,8 +37,10 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl findByUnpublishedNameAndPageId(String name, String pageId, AclPermission aclPermission) { Criteria nameCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.name)).is(name); Criteria pageCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).is(pageId); + // In case an action has been deleted in edit mode, but still exists in deployed mode, NewAction object would exist. To handle this, only fetch non-deleted actions + Criteria deletedCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.deletedAt)).is(null); - return queryOne(List.of(nameCriteria, pageCriteria), aclPermission); + return queryOne(List.of(nameCriteria, pageCriteria, deletedCriteria), aclPermission); } @Override @@ -77,17 +79,26 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl findByPageIdAndViewMode(String pageId, Boolean viewMode, AclPermission aclPermission) { - Criteria pageCriteria; + + List criteria = new ArrayList<>(); + + Criteria pageCriterion; // Fetch published actions if (Boolean.TRUE.equals(viewMode)) { - pageCriteria = where(fieldName(QNewAction.newAction.publishedAction) + "." + fieldName(QNewAction.newAction.publishedAction.pageId)).is(pageId); + pageCriterion = where(fieldName(QNewAction.newAction.publishedAction) + "." + fieldName(QNewAction.newAction.publishedAction.pageId)).is(pageId); + criteria.add(pageCriterion); } // Fetch unpublished actions else { - pageCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).is(pageId); + pageCriterion = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).is(pageId); + criteria.add(pageCriterion); + + // In case an action has been deleted in edit mode, but still exists in deployed mode, NewAction object would exist. To handle this, only fetch non-deleted actions + Criteria deletedCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.deletedAt)).is(null); + criteria.add(deletedCriteria); } - return queryAll(List.of(pageCriteria), aclPermission); + return queryAll(criteria, aclPermission); } @Override @@ -163,6 +174,10 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl findUnpublishedActionsByNameInAndPageId(Set names, String pageId, AclPermission permission) { List criteriaList = new ArrayList<>(); + if (names != null) { Criteria namesCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.name)).in(names); criteriaList.add(namesCriteria); @@ -196,6 +216,10 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl criteria = new ArrayList<>(); - return queryAll(List.of(applicationCriteria), aclPermission); + Criteria applicationCriterion = where(fieldName(QNewAction.newAction.applicationId)).is(applicationId); + criteria.add(applicationCriterion); + + if (Boolean.FALSE.equals(viewMode)) { + // In case an action has been deleted in edit mode, but still exists in deployed mode, NewAction object would exist. To handle this, only fetch non-deleted actions + Criteria deletedCriterion = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.deletedAt)).is(null); + criteria.add(deletedCriterion); + } + + return queryAll(criteria, aclPermission); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewPageRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewPageRepositoryImpl.java index 426e9c263b..2f6852ebbb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewPageRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewPageRepositoryImpl.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.ArrayList; import java.util.List; import static org.springframework.data.mongodb.core.query.Criteria.where; @@ -33,37 +34,65 @@ public class CustomNewPageRepositoryImpl extends BaseAppsmithRepositoryImpl findByIdAndLayoutsIdAndViewMode(String id, String layoutId, AclPermission aclPermission, Boolean viewMode) { - Criteria idCriterion = getIdCriteria(id); String layoutsIdKey; String layoutsKey; + List criteria = new ArrayList<>(); + Criteria idCriterion = getIdCriteria(id); + criteria.add(idCriterion); + if (Boolean.TRUE.equals(viewMode)) { layoutsKey = fieldName(QNewPage.newPage.publishedPage) + "." + fieldName(QNewPage.newPage.publishedPage.layouts); } else { layoutsKey = fieldName(QNewPage.newPage.unpublishedPage) + "." + fieldName(QNewPage.newPage.unpublishedPage.layouts); + + // In case a page has been deleted in edit mode, but still exists in deployed mode, NewPage object would exist. To handle this, only fetch non-deleted pages + Criteria deletedCriterion = where (fieldName(QNewPage.newPage.unpublishedPage) + "." + fieldName(QNewPage.newPage.unpublishedPage.deletedAt)).is(null); + criteria.add(deletedCriterion); } layoutsIdKey = layoutsKey + "." + fieldName(QLayout.layout.id); Criteria layoutCriterion = where(layoutsIdKey).is(layoutId); + criteria.add(layoutCriterion); - List criteria = List.of(idCriterion, layoutCriterion); return queryOne(criteria, aclPermission); } @Override public Mono findByNameAndViewMode(String name, AclPermission aclPermission, Boolean viewMode) { - Criteria nameCriterion = getNameCriterion(name, viewMode); - return queryOne(List.of(nameCriterion), aclPermission); + List criteria = new ArrayList<>(); + + Criteria nameCriterion = getNameCriterion(name, viewMode); + criteria.add(nameCriterion); + + if (Boolean.FALSE.equals(viewMode)) { + // In case a page has been deleted in edit mode, but still exists in deployed mode, NewPage object would exist. To handle this, only fetch non-deleted pages + Criteria deletedCriterion = where (fieldName(QNewPage.newPage.unpublishedPage) + "." + fieldName(QNewPage.newPage.unpublishedPage.deletedAt)).is(null); + criteria.add(deletedCriterion); + } + + return queryOne(criteria, aclPermission); } @Override public Mono findByNameAndApplicationIdAndViewMode(String name, String applicationId, AclPermission aclPermission, Boolean viewMode) { + + List criteria = new ArrayList<>(); + Criteria nameCriterion = getNameCriterion(name, viewMode); + criteria.add(nameCriterion); Criteria applicationIdCriterion = where(fieldName(QNewPage.newPage.applicationId)).is(applicationId); + criteria.add(applicationIdCriterion); - return queryOne(List.of(nameCriterion, applicationIdCriterion), aclPermission); + if (Boolean.FALSE.equals(viewMode)) { + // In case a page has been deleted in edit mode, but still exists in deployed mode, NewPage object would exist. To handle this, only fetch non-deleted pages + Criteria deletedCriteria = where (fieldName(QNewPage.newPage.unpublishedPage) + "." + fieldName(QNewPage.newPage.unpublishedPage.deletedAt)).is(null); + criteria.add(deletedCriteria); + } + + return queryOne(criteria, aclPermission); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java index 3929aadeba..cb43f494ac 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java @@ -4,16 +4,17 @@ import com.appsmith.server.domains.Layout; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; import com.appsmith.server.dtos.RefactorNameDTO; +import com.appsmith.server.dtos.LayoutDTO; import reactor.core.publisher.Mono; public interface LayoutActionService { - Mono updateLayout(String pageId, String layoutId, Layout layout); + Mono updateLayout(String pageId, String layoutId, Layout layout); Mono moveAction(ActionMoveDTO actionMoveDTO); - Mono refactorWidgetName(RefactorNameDTO refactorNameDTO); + Mono refactorWidgetName(RefactorNameDTO refactorNameDTO); - Mono refactorActionName(RefactorNameDTO refactorNameDTO); + Mono refactorActionName(RefactorNameDTO refactorNameDTO); Mono updateAction(String id, ActionDTO action); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java index c4f4ac3462..d9ad17dda7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java @@ -7,8 +7,10 @@ import com.appsmith.server.domains.Layout; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; import com.appsmith.server.dtos.DslActionDTO; +import com.appsmith.server.dtos.LayoutActionUpdateDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.RefactorNameDTO; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.MustacheHelper; @@ -121,7 +123,7 @@ public class LayoutActionServiceImpl implements LayoutActionService { } @Override - public Mono refactorWidgetName(RefactorNameDTO refactorNameDTO) { + public Mono refactorWidgetName(RefactorNameDTO refactorNameDTO) { String pageId = refactorNameDTO.getPageId(); String layoutId = refactorNameDTO.getLayoutId(); String oldName = refactorNameDTO.getOldName(); @@ -136,7 +138,7 @@ public class LayoutActionServiceImpl implements LayoutActionService { } @Override - public Mono refactorActionName(RefactorNameDTO refactorNameDTO) { + public Mono refactorActionName(RefactorNameDTO refactorNameDTO) { String pageId = refactorNameDTO.getPageId(); String layoutId = refactorNameDTO.getLayoutId(); String oldName = refactorNameDTO.getOldName(); @@ -167,7 +169,7 @@ public class LayoutActionServiceImpl implements LayoutActionService { * @param newName * @return */ - private Mono refactorName(String pageId, String layoutId, String oldName, String newName) { + private Mono refactorName(String pageId, String layoutId, String oldName, String newName) { String regexPattern = preWord + oldName + postWord; Pattern oldNamePattern = Pattern.compile(regexPattern); @@ -466,11 +468,11 @@ public class LayoutActionServiceImpl implements LayoutActionService { } @Override - public Mono updateLayout(String pageId, String layoutId, Layout layout) { + public Mono updateLayout(String pageId, String layoutId, Layout layout) { JSONObject dsl = layout.getDsl(); if (dsl == null) { // There is no DSL here. No need to process anything. Return as is. - return Mono.just(layout); + return Mono.just(generateResponseDTO(layout)); } Set widgetNames = new HashSet<>(); @@ -496,6 +498,8 @@ public class LayoutActionServiceImpl implements LayoutActionService { Set edges = new HashSet<>(); Set actionsUsedInDSL = new HashSet<>(); List flatmapPageLoadActions = new ArrayList<>(); + List actionUpdates = new ArrayList<>(); + List messages = new ArrayList<>(); Mono>> allOnLoadActionsMono = pageLoadActionsUtil .findAllOnLoadActions(dynamicBindingNames, actionNames, pageId, edges, actionsUsedInDSL, flatmapPageLoadActions); @@ -504,7 +508,9 @@ public class LayoutActionServiceImpl implements LayoutActionService { return allOnLoadActionsMono .flatMap(allOnLoadActions -> { // Update these actions to be executed on load, unless the user has touched the executeOnLoad setting for this - return newActionService.setOnLoad((flatmapPageLoadActions)).thenReturn(allOnLoadActions); + return newActionService + .updateActionsExecuteOnLoad(flatmapPageLoadActions, pageId, actionUpdates, messages) + .thenReturn(allOnLoadActions); }) .zipWith(newPageService.findByIdAndLayoutsId(pageId, layoutId, MANAGE_PAGES, false) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, @@ -542,7 +548,26 @@ public class LayoutActionServiceImpl implements LayoutActionService { } } return Mono.empty(); + }) + .map(savedLayout -> { + LayoutDTO layoutDTO = generateResponseDTO(savedLayout); + layoutDTO.setActionUpdates(actionUpdates); + layoutDTO.setMessages(messages); + return layoutDTO; }); } + private LayoutDTO generateResponseDTO(Layout layout) { + + LayoutDTO layoutDTO = new LayoutDTO(); + + layoutDTO.setId(layout.getId()); + layoutDTO.setDsl(layout.getDsl()); + layoutDTO.setScreen(layout.getScreen()); + layoutDTO.setLayoutOnLoadActions(layout.getLayoutOnLoadActions()); + layoutDTO.setUserPermissions(layout.getUserPermissions()); + + return layoutDTO; + } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java index 64a6204d1d..d34efe9756 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java @@ -6,6 +6,7 @@ import com.appsmith.server.domains.NewAction; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionViewDTO; import com.appsmith.server.dtos.ExecuteActionDTO; +import com.appsmith.server.dtos.LayoutActionUpdateDTO; import org.springframework.data.domain.Sort; import org.springframework.util.MultiValueMap; import reactor.core.publisher.Flux; @@ -57,5 +58,5 @@ public interface NewActionService extends CrudService { Flux findByPageId(String pageId); - Mono setOnLoad(List actions); + Mono updateActionsExecuteOnLoad(List actions, String pageId, List actionUpdates, List messages); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java index 4c91518940..afd677a35a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java @@ -30,6 +30,7 @@ import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionViewDTO; import com.appsmith.server.dtos.ExecuteActionDTO; +import com.appsmith.server.dtos.LayoutActionUpdateDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.MustacheHelper; @@ -42,6 +43,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; @@ -57,6 +59,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeoutException; @@ -916,25 +919,147 @@ public class NewActionServiceImpl extends BaseService setOnLoad(List actions) { - if (actions == null) { - return Mono.just(FALSE); - } + public Mono updateActionsExecuteOnLoad(List onLoadActions, + String pageId, + List actionUpdates, + List messages) { List toUpdateActions = new ArrayList<>(); - for (ActionDTO action : actions) { - // If a user has ever set execute on load, this field can not be changed automatically. It has to be - // explicitly changed by the user again. Add the action to update only if this condition is false. - if (FALSE.equals(action.getUserSetOnLoad())) { - action.setExecuteOnLoad(TRUE); - toUpdateActions.add(action); - } - } - return Flux.fromIterable(toUpdateActions) - .flatMap(actionDTO -> updateUnpublishedAction(actionDTO.getId(), actionDTO)) - .then(Mono.just(TRUE)); + MultiValueMap params = CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)); + params.add(FieldName.PAGE_ID, pageId); + + // Fetch all the actions which exist in this page. + Flux pageActionsFlux = this.getUnpublishedActions(params).cache(); + + // Before we update the actions, fetch all the actions which are currently set to execute on load. + Mono> existingOnPageLoadActionsMono = pageActionsFlux + .flatMap(action -> { + if (TRUE.equals(action.getExecuteOnLoad())) { + return Mono.just(action); + } + return Mono.empty(); + }) + .collectList(); + + return existingOnPageLoadActionsMono + .zipWith(pageActionsFlux.collectList()) + .flatMap( tuple -> { + List existingOnPageLoadActions = tuple.getT1(); + List pageActions = tuple.getT2(); + + // There are no actions in this page. No need to proceed further since no actions would get updated + if (pageActions.isEmpty()) { + return Mono.just(FALSE); + } + + // No actions require an update if no actions have been found as page load actions as well as + // existing on load page actions are empty + if (existingOnPageLoadActions.isEmpty() && (onLoadActions == null || onLoadActions.isEmpty())) { + return Mono.just(FALSE); + } + + // Extract names of existing pageload actions and new page load actions for quick lookup. + Set existingOnPageLoadActionNames = existingOnPageLoadActions + .stream() + .map(action -> action.getName()) + .collect(Collectors.toSet()); + + Set newOnLoadActionNames = onLoadActions + .stream() + .map(action -> action.getName()) + .collect(Collectors.toSet()); + + + // Calculate the actions which would need to be updated from execute on load TRUE to FALSE. + Set turnedOffActionNames = new HashSet<>(); + turnedOffActionNames.addAll(existingOnPageLoadActionNames); + turnedOffActionNames.removeAll(newOnLoadActionNames); + + // Calculate the actions which would need to be updated from execute on load FALSE to TRUE + Set turnedOnActionNames = new HashSet<>(); + turnedOnActionNames.addAll(newOnLoadActionNames); + turnedOnActionNames.removeAll(existingOnPageLoadActionNames); + + for (ActionDTO action : pageActions) { + + String actionName = action.getName(); + // If a user has ever set execute on load, this field can not be changed automatically. It has to be + // explicitly changed by the user again. Add the action to update only if this condition is false. + if (FALSE.equals(action.getUserSetOnLoad())) { + + // If this action is no longer an onload action, turn the execute on load to false + if (turnedOffActionNames.contains(actionName)) { + action.setExecuteOnLoad(FALSE); + toUpdateActions.add(action); + } + + // If this action is newly found to be on load, turn execute on load to true + if (turnedOnActionNames.contains(actionName)) { + action.setExecuteOnLoad(TRUE); + toUpdateActions.add(action); + } + } else { + // Remove the action name from either of the lists (if present) because this action should + // not be updated + turnedOnActionNames.remove(actionName); + turnedOffActionNames.remove(actionName); + } + } + + // Add newly turned on page actions to report back to the caller + actionUpdates.addAll( + addActionUpdatesForActionNames(pageActions, turnedOnActionNames) + ); + + // Add newly turned off page actions to report back to the caller + actionUpdates.addAll( + addActionUpdatesForActionNames(pageActions, turnedOffActionNames) + ); + + // Now add messages that would eventually be displayed to the developer user informing them + // about the action setting change. + if (!turnedOffActionNames.isEmpty()) { + messages.add(turnedOffActionNames.toString() + " will no longer be executed on page load"); + } + + if (!turnedOnActionNames.isEmpty()) { + messages.add(turnedOnActionNames.toString() + " will be executed automatically on page load"); + } + + // Finally update the actions which require an update + return Flux.fromIterable(toUpdateActions) + .flatMap(actionDTO -> updateUnpublishedAction(actionDTO.getId(), actionDTO)) + .then(Mono.just(TRUE)); + }); + } + + private List addActionUpdatesForActionNames(List pageActions, + Set actionNames) { + + return pageActions + .stream() + .filter(pageAction -> actionNames.contains(pageAction.getName())) + .map(pageAction -> { + LayoutActionUpdateDTO layoutActionUpdateDTO = new LayoutActionUpdateDTO(); + layoutActionUpdateDTO.setId(pageAction.getId()); + layoutActionUpdateDTO.setName(pageAction.getName()); + layoutActionUpdateDTO.setExecuteOnLoad(pageAction.getExecuteOnLoad()); + return layoutActionUpdateDTO; + }) + .collect(Collectors.toList()); } @Override diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/MustacheHelperTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/MustacheHelperTest.java index dffc93b718..d9b550a883 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/MustacheHelperTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/MustacheHelperTest.java @@ -5,15 +5,11 @@ import com.appsmith.external.models.Connection; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Endpoint; import com.appsmith.external.models.Property; -import com.fasterxml.jackson.databind.ObjectMapper; import org.assertj.core.api.IterableAssert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -29,13 +25,8 @@ import static org.assertj.core.api.Assertions.assertThat; // Disabling this so we may use `Arrays.asList` with single argument, which is easier to refactor, just for tests. "ArraysAsListWithZeroOrOneArgument" ) -@RunWith(SpringRunner.class) -@SpringBootTest public class MustacheHelperTest { - @Autowired - private ObjectMapper objectMapper; - private void checkTokens(String template, List expected) { assertThat(tokenize(template)).isEqualTo(expected); } @@ -406,7 +397,14 @@ public class MustacheHelperTest { new Property("param2", "{{ queryParam2 }}") )); - final Map context = Map.of( + configuration.setPluginSpecifiedTemplates(Arrays.asList( + null, + new Property("prop1", "{{ pluginSpecifiedProp1 }}"), + null, + new Property("prop2", "{{ pluginSpecifiedProp2 }}") + )); + + final Map context = new HashMap<>(Map.of( "body", "rendered body", "path", "rendered path", "next", "rendered next", @@ -416,7 +414,12 @@ public class MustacheHelperTest { "bodyParam2", "rendered bodyParam2", "queryParam1", "rendered queryParam1", "queryParam2", "rendered queryParam2" - ); + )); + + context.putAll(Map.of( + "pluginSpecifiedProp1", "rendered pluginSpecifiedProp1", + "pluginSpecifiedProp2", "rendered pluginSpecifiedProp2" + )); assertKeys(configuration).hasSameElementsAs(context.keySet()); @@ -440,6 +443,13 @@ public class MustacheHelperTest { new Property("param1", "rendered queryParam1"), new Property("param2", "rendered queryParam2") ); + + assertThat(configuration.getPluginSpecifiedTemplates()).containsExactly( + null, + new Property("prop1", "rendered pluginSpecifiedProp1"), + null, + new Property("prop2", "rendered pluginSpecifiedProp2") + ); } @Test diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java index f850509da5..f776a86215 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java @@ -11,6 +11,8 @@ import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.DslActionDTO; +import com.appsmith.server.dtos.LayoutActionUpdateDTO; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.RefactorNameDTO; import com.appsmith.server.helpers.MockPluginExecutor; @@ -37,6 +39,7 @@ import reactor.test.StepVerifier; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -216,7 +219,7 @@ public class LayoutActionServiceTest { ActionDTO createdAction = newActionService.createAction(action).block(); - Layout firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); + LayoutDTO firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); RefactorNameDTO refactorNameDTO = new RefactorNameDTO(); @@ -225,7 +228,7 @@ public class LayoutActionServiceTest { refactorNameDTO.setOldName("beforeNameChange"); refactorNameDTO.setNewName("PostNameChange"); - Layout postNameChangeLayout = layoutActionService.refactorActionName(refactorNameDTO).block(); + LayoutDTO postNameChangeLayout = layoutActionService.refactorActionName(refactorNameDTO).block(); Mono postNameChangeActionMono = newActionService.findById(createdAction.getId(), READ_ACTIONS); @@ -238,11 +241,152 @@ public class LayoutActionServiceTest { DslActionDTO actionDTO = postNameChangeLayout.getLayoutOnLoadActions().get(0).iterator().next(); assertThat(actionDTO.getName()).isEqualTo("PostNameChange"); -// JSONObject newDsl = new JSONObject(Map.of("widgetName", "firstWidget", "mustacheProp", "{{ PostNameChange.data }}")); dsl.put("testField", "{{ PostNameChange.data }}"); assertThat(postNameChangeLayout.getDsl()).isEqualTo(dsl); }) .verifyComplete(); } + @Test + @WithUserDetails(value = "api_user") + public void refactorActionNameToDeletedName() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor())); + + ActionDTO action = new ActionDTO(); + action.setName("Query1"); + action.setPageId(testPage.getId()); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); + + Layout layout = testPage.getLayouts().get(0); + + ActionDTO firstAction = newActionService.createAction(action).block(); + + LayoutDTO firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); + + applicationPageService.publish(testPage.getApplicationId()).block(); + + newActionService.deleteUnpublishedAction(firstAction.getId()).block(); + + // Create another action with the same name as the erstwhile deleted action + action.setId(null); + ActionDTO secondAction = newActionService.createAction(action).block(); + + RefactorNameDTO refactorNameDTO = new RefactorNameDTO(); + refactorNameDTO.setPageId(testPage.getId()); + refactorNameDTO.setLayoutId(firstLayout.getId()); + refactorNameDTO.setOldName("Query1"); + refactorNameDTO.setNewName("NewActionName"); + + layoutActionService.refactorActionName(refactorNameDTO).block(); + + Mono postNameChangeActionMono = newActionService.findById(secondAction.getId(), READ_ACTIONS); + + StepVerifier + .create(postNameChangeActionMono) + .assertNext(updatedAction -> { + + assertThat(updatedAction.getUnpublishedAction().getName()).isEqualTo("NewActionName"); + + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void actionExecuteOnLoadChangeOnUpdateLayout() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor())); + + ActionDTO action1 = new ActionDTO(); + action1.setName("firstAction"); + action1.setPageId(testPage.getId()); + ActionConfiguration actionConfiguration1 = new ActionConfiguration(); + actionConfiguration1.setHttpMethod(HttpMethod.GET); + action1.setActionConfiguration(actionConfiguration1); + action1.setDatasource(datasource); + + ActionDTO action2 = new ActionDTO(); + action2.setName("secondAction"); + action2.setPageId(testPage.getId()); + ActionConfiguration actionConfiguration2 = new ActionConfiguration(); + actionConfiguration2.setHttpMethod(HttpMethod.GET); + action2.setActionConfiguration(actionConfiguration2); + action2.setDatasource(datasource); + + JSONObject dsl = new JSONObject(); + dsl.put("widgetName", "firstWidget"); + JSONArray temp = new JSONArray(); + temp.addAll(List.of(new JSONObject(Map.of("key", "testField")))); + dsl.put("dynamicBindingPathList", temp); + dsl.put("testField", "{{ firstAction.data }}"); + + Layout layout = testPage.getLayouts().get(0); + layout.setDsl(dsl); + + ActionDTO createdAction1 = newActionService.createAction(action1).block(); + ActionDTO createdAction2 = newActionService.createAction(action2).block(); + + Mono updateLayoutMono = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout); + + StepVerifier.create(updateLayoutMono) + .assertNext(updatedLayout -> { + log.debug("{}", updatedLayout.getMessages()); + DslActionDTO actionDTO = updatedLayout.getLayoutOnLoadActions().get(0).iterator().next(); + assertThat(actionDTO.getName()).isEqualTo("firstAction"); + + List actionUpdates = updatedLayout.getActionUpdates(); + assertThat(actionUpdates.size()).isEqualTo(1); + assertThat(actionUpdates.get(0).getName()).isEqualTo("firstAction"); + assertThat(actionUpdates.get(0).getExecuteOnLoad()).isTrue(); + }) + .verifyComplete(); + + StepVerifier.create(newActionService.findById(createdAction1.getId())) + .assertNext(newAction -> assertThat(newAction.getUnpublishedAction().getExecuteOnLoad()).isTrue()); + + StepVerifier.create(newActionService.findById(createdAction2.getId())) + .assertNext(newAction -> assertThat(newAction.getUnpublishedAction().getExecuteOnLoad()).isFalse()); + + dsl = new JSONObject(); + dsl.put("widgetName", "firstWidget"); + temp = new JSONArray(); + temp.addAll(List.of(new JSONObject(Map.of("key", "testField")))); + dsl.put("dynamicBindingPathList", temp); + dsl.put("testField", "{{ secondAction.data }}"); + + layout.setDsl(dsl); + + updateLayoutMono = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout); + + StepVerifier.create(updateLayoutMono) + .assertNext(updatedLayout -> { + log.debug("{}", updatedLayout.getMessages()); + DslActionDTO actionDTO = updatedLayout.getLayoutOnLoadActions().get(0).iterator().next(); + assertThat(actionDTO.getName()).isEqualTo("secondAction"); + + List actionUpdates = updatedLayout.getActionUpdates(); + assertThat(actionUpdates.size()).isEqualTo(2); + + Optional firstActionUpdateOptional = actionUpdates.stream().filter(actionUpdate -> actionUpdate.getName().equals("firstAction")).findFirst(); + LayoutActionUpdateDTO firstActionUpdate = firstActionUpdateOptional.get(); + assertThat(firstActionUpdate).isNotNull(); + assertThat(firstActionUpdate.getExecuteOnLoad()).isFalse(); + + Optional secondActionUpdateOptional = actionUpdates.stream().filter(actionUpdate -> actionUpdate.getName().equals("secondAction")).findFirst(); + LayoutActionUpdateDTO secondActionUpdate = secondActionUpdateOptional.get(); + assertThat(secondActionUpdate).isNotNull(); + assertThat(secondActionUpdate.getExecuteOnLoad()).isTrue(); + }) + .verifyComplete(); + + StepVerifier.create(newActionService.findById(createdAction1.getId())) + .assertNext(newAction -> assertThat(newAction.getUnpublishedAction().getExecuteOnLoad()).isFalse()); + + StepVerifier.create(newActionService.findById(createdAction2.getId())) + .assertNext(newAction -> assertThat(newAction.getUnpublishedAction().getExecuteOnLoad()).isTrue()); + + } + } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java index 401edcf19f..078f6d498e 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java @@ -7,13 +7,12 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Layout; -import com.appsmith.server.domains.NewAction; -import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.PluginType; import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.DslActionDTO; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; @@ -205,7 +204,7 @@ public class LayoutServiceTest { Layout startLayout = layoutService.createLayout(page.getId(), testLayout).block(); - Mono updatedLayoutMono = layoutActionService.updateLayout("random-impossible-id-page", startLayout.getId(), updateLayout); + Mono updatedLayoutMono = layoutActionService.updateLayout("random-impossible-id-page", startLayout.getId(), updateLayout); StepVerifier .create(updatedLayoutMono) @@ -238,7 +237,7 @@ public class LayoutServiceTest { Mono startLayoutMono = pageMono.flatMap(page -> layoutService.createLayout(page.getId(), testLayout)); - Mono updatedLayoutMono = Mono.zip(pageMono, startLayoutMono) + Mono updatedLayoutMono = Mono.zip(pageMono, startLayoutMono) .flatMap(tuple -> { PageDTO page = tuple.getT1(); Layout startLayout = tuple.getT2(); @@ -276,7 +275,7 @@ public class LayoutServiceTest { Mono pageMono = createPage(app, testPage).cache(); - Mono testMono = pageMono + Mono testMono = pageMono .flatMap(page1 -> { List> monos = new ArrayList<>(); @@ -464,7 +463,7 @@ public class LayoutServiceTest { Mono pageMono = createPage(app, testPage).cache(); - Mono testMono = pageMono + Mono testMono = pageMono .flatMap(page1 -> { List> monos = new ArrayList<>(); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java index c8ecef61c7..c8cff36efa 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java @@ -266,6 +266,46 @@ public class PageServiceTest { .verifyComplete(); } + @Test + @WithUserDetails(value = "api_user") + public void reuseDeletedPageName() { + + PageDTO testPage = new PageDTO(); + testPage.setName("reuseDeletedPageName"); + setupTestApplication(); + testPage.setApplicationId(application.getId()); + + // Create Page + PageDTO firstPage = applicationPageService.createPage(testPage).block(); + + // Publish the application + applicationPageService.publish(application.getId()); + + //Delete Page in edit mode + applicationPageService.deleteUnpublishedPage(firstPage.getId()).block(); + + testPage.setId(null); + testPage.setName("New Page Name"); + // Create Second Page + PageDTO secondPage = applicationPageService.createPage(testPage).block(); + + //Update the name of the new page + PageDTO newPage = new PageDTO(); + newPage.setId(secondPage.getId()); + newPage.setName("reuseDeletedPageName"); + Mono updatePageNameMono = newPageService.updatePage(secondPage.getId(), newPage); + + StepVerifier + .create(updatePageNameMono) + .assertNext(page -> { + assertThat(page).isNotNull(); + assertThat(page.getId()).isNotNull(); + assertThat("reuseDeletedPageName".equals(page.getName())); + + }) + .verifyComplete(); + } + @After public void purgeAllPages() { diff --git a/contributions/ServerSetup.md b/contributions/ServerSetup.md index ca9b4111e1..69d117a400 100644 --- a/contributions/ServerSetup.md +++ b/contributions/ServerSetup.md @@ -100,5 +100,5 @@ example: ![381611580157_ pic_hd](https://user-images.githubusercontent.com/4025839/105710505-2ead5300-5f52-11eb-9549-531e459e86ea.jpg) ## Need Assistance -- If you are unable to resolve any issue while doing the setup, please initiate a Github discussion or send an email to support@appsmith.com. We'll be happy to help you. +- If you are unable to resolve any issue while doing the setup, please feel free to ask questions on our [Discord channel](https://discord.com/invite/rBTTVJp) or initiate a [Github discussion](https://github.com/appsmithorg/appsmith/discussions) or send an email to `support@appsmith.com`. We'll be happy to help you. - In case you notice any discrepancy, please raise an issue on Github and/or send an email to support@appsmith.com.