Merge branch 'release'

This commit is contained in:
Shrikant Sharat Kandula 2021-02-10 14:35:02 +05:30
commit 95d1717f57
43 changed files with 1030 additions and 328 deletions

19
.deepsource.toml Normal file
View File

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

View File

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

View File

@ -1 +1 @@
lts/dubnium
lts/fermium

View File

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

View File

@ -381,7 +381,7 @@ const TextLoadingState = ({ text }: { text?: string }) => (
);
const IconLoadingState = ({ size, icon }: { size?: Size; icon?: IconName }) => (
<Icon name={icon} size={IconSizeProp(size)} invisible={true} />
<Icon name={icon} size={IconSizeProp(size)} invisible />
);
const getIconContent = (props: ButtonProps) =>

View File

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

View File

@ -35,7 +35,7 @@ const PopoverVideo = (props: VideoComponentProps) => {
minimal
usePortal
enforceFocus={false}
lazy={true}
lazy
modifiers={{
flip: {
behavior: ["right", "left", "bottom", "top"],

View File

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

View File

@ -114,7 +114,7 @@ class HelpModal extends React.Component<Props> {
<>
{isHelpModalOpen && (
<ModalComponent
canOutsideClickClose={true}
canOutsideClickClose
canEscapeKeyClose
scrollContents
height={MODAL_HEIGHT}

View File

@ -13,7 +13,9 @@ export type ComparisonOperations =
| "EQUALS"
| "NOT_EQUALS"
| "LESSER"
| "GREATER";
| "GREATER"
| "IN"
| "NOT_IN";
export type HiddenType =
| boolean

View File

@ -54,6 +54,22 @@ describe("isHidden test", () => {
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,

View File

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

View File

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

View File

@ -122,6 +122,8 @@ export const PageTabsContainer = (props: AppViewerHeaderProps) => {
onMouseDown={() => startScrolling(true)}
onMouseUp={stopScrolling}
onMouseLeave={stopScrolling}
onTouchStart={() => startScrolling(true)}
onTouchEnd={stopScrolling}
visible={shouldShowLeftArrow}
>
<Icon name="chevron-left" />
@ -137,6 +139,8 @@ export const PageTabsContainer = (props: AppViewerHeaderProps) => {
onMouseDown={() => startScrolling(false)}
onMouseUp={stopScrolling}
onMouseLeave={stopScrolling}
onTouchStart={() => startScrolling(false)}
onTouchEnd={stopScrolling}
visible={shouldShowRightArrow}
>
<Icon name="chevron-right" />

View File

@ -1346,10 +1346,10 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction<string>) {
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<string>) {
topRow,
rightColumn,
bottomRow,
} = yield calculateNewWidgetPosition(newWidget, "0", widgets);
} = yield calculateNewWidgetPosition(
newWidget,
MAIN_CONTAINER_WIDGET_ID,
widgets,
);
newWidget = {
...newWidget,

View File

@ -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 (
<FilePickerComponent
uppy={this.uppy}
uppy={this.state.uppy}
widgetId={this.props.widgetId}
key={this.props.widgetId}
label={this.props.label}
@ -247,8 +300,8 @@ class FilePickerWidget extends BaseWidget<
}
export interface FilePickerWidgetState extends WidgetState {
version: number;
isLoading: boolean;
uppy: any;
}
export interface FilePickerWidgetProps extends WidgetProps, WithMeta {

View File

@ -154,10 +154,11 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
| React.KeyboardEvent<HTMLTextAreaElement>
| React.KeyboardEvent<HTMLInputElement>,
) => {
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,

View File

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

View File

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

View File

@ -8,6 +8,7 @@ import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DatasourceStructure {
List<Table> tables;

View File

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

View File

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

View File

@ -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<ActionExecutionResult>) 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<DynamoDbClient> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
return (Mono<DynamoDbClient>) 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<DatasourceStructure> getStructure(DynamoDbClient ddb, DatasourceConfiguration datasourceConfiguration) {
return Mono.fromCallable(() -> {
final ListTablesResponse listTablesResponse = ddb.listTables();
List<DatasourceStructure.Table> 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<String, Object> sdkToPlain(SdkPojo response) {
final Map<String, Object> plain = new HashMap<>();
private static Object sdkToPlain(Object valueObj) {
if (valueObj instanceof SdkPojo) {
final SdkPojo response = (SdkPojo) valueObj;
final Map<String, Object> 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<String, Object> valueAsMap = (Map) value;
final Map<String, Object> plainMap = new HashMap<>();
for (final Map.Entry<String, Object> 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<Object> valueAsList = (List) value;
final List<Object> 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<Object, Object> 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<Object> plainList = new ArrayList<>();
for (Object item : valueAsList) {
plainList.add(sdkToPlain(item));
}
return plainList;
}
return plain;
return valueObj;
}
private static boolean isUpperCase(String s) {

View File

@ -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<Object> items = (List<Object>) ((Map<String, Object>) result.getBody()).get("Items");
assertEquals(2, items.size());
})
.verifyComplete();
}
@Test
public void testStructure() {
final Mono<DatasourceStructure> 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();
}
}

View File

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

View File

@ -347,13 +347,15 @@ public class MySqlPlugin extends BasePlugin {
@Override
public Mono<DatasourceTestResult> 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);
}

View File

@ -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<ResponseDTO<Layout>> refactorActionName(@RequestBody RefactorNameDTO refactorNameDTO) {
public Mono<ResponseDTO<LayoutDTO>> refactorActionName(@RequestBody RefactorNameDTO refactorNameDTO) {
return layoutActionService.refactorActionName(refactorNameDTO)
.map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null));
}

View File

@ -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<ResponseDTO<Layout>> updateLayout(@PathVariable String pageId, @PathVariable String layoutId, @RequestBody Layout layout) {
public Mono<ResponseDTO<LayoutDTO>> 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<ResponseDTO<Layout>> refactorWidgetName(@RequestBody RefactorNameDTO refactorNameDTO) {
public Mono<ResponseDTO<LayoutDTO>> refactorWidgetName(@RequestBody RefactorNameDTO refactorNameDTO) {
return layoutActionService.refactorWidgetName(refactorNameDTO)
.map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null));
}

View File

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

View File

@ -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<HashSet<DslActionDTO>> layoutOnLoadActions;
// All the actions which have been updated as part of updateLayout function call
List<LayoutActionUpdateDTO> actionUpdates;
// All the toast messages that the developer user should be displayed to inform about the consequences of update layout.
List<String> messages;
public Set<String> userPermissions = new HashSet<>();
}

View File

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

View File

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

View File

@ -37,8 +37,10 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl<Ne
public Mono<NewAction> 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<Ne
@Override
public Flux<NewAction> findByPageIdAndViewMode(String pageId, Boolean viewMode, AclPermission aclPermission) {
Criteria pageCriteria;
List<Criteria> 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<Ne
Criteria pageCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).in(pageIds);
criteriaList.add(pageCriteria);
}
// 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);
criteriaList.add(deletedCriteria);
}
return queryAll(criteriaList, aclPermission, sort);
@ -183,12 +198,17 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl<Ne
Criteria executeOnLoadCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.executeOnLoad)).is(Boolean.TRUE);
criteriaList.add(executeOnLoadCriteria);
// 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);
criteriaList.add(deletedCriteria);
return queryAll(criteriaList, permission);
}
@Override
public Flux<NewAction> findUnpublishedActionsByNameInAndPageId(Set<String> names, String pageId, AclPermission permission) {
List<Criteria> 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<Ne
Criteria pageCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).is(pageId);
criteriaList.add(pageCriteria);
// 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);
criteriaList.add(deletedCriteria);
return queryAll(criteriaList, permission);
}
@ -212,6 +236,10 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl<Ne
Criteria pageCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).is(pageId);
criteriaList.add(pageCriteria);
// 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);
criteriaList.add(deletedCriteria);
return queryAll(criteriaList, permission);
}
@ -228,9 +256,18 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl<Ne
Boolean viewMode,
AclPermission aclPermission) {
Criteria applicationCriteria = where(fieldName(QNewAction.newAction.applicationId)).is(applicationId);
List<Criteria> 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

View File

@ -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<NewP
@Override
public Mono<NewPage> findByIdAndLayoutsIdAndViewMode(String id, String layoutId, AclPermission aclPermission, Boolean viewMode) {
Criteria idCriterion = getIdCriteria(id);
String layoutsIdKey;
String layoutsKey;
List<Criteria> 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> criteria = List.of(idCriterion, layoutCriterion);
return queryOne(criteria, aclPermission);
}
@Override
public Mono<NewPage> findByNameAndViewMode(String name, AclPermission aclPermission, Boolean viewMode) {
Criteria nameCriterion = getNameCriterion(name, viewMode);
return queryOne(List.of(nameCriterion), aclPermission);
List<Criteria> 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<NewPage> findByNameAndApplicationIdAndViewMode(String name, String applicationId, AclPermission aclPermission, Boolean viewMode) {
List<Criteria> 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

View File

@ -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<Layout> updateLayout(String pageId, String layoutId, Layout layout);
Mono<LayoutDTO> updateLayout(String pageId, String layoutId, Layout layout);
Mono<ActionDTO> moveAction(ActionMoveDTO actionMoveDTO);
Mono<Layout> refactorWidgetName(RefactorNameDTO refactorNameDTO);
Mono<LayoutDTO> refactorWidgetName(RefactorNameDTO refactorNameDTO);
Mono<Layout> refactorActionName(RefactorNameDTO refactorNameDTO);
Mono<LayoutDTO> refactorActionName(RefactorNameDTO refactorNameDTO);
Mono<ActionDTO> updateAction(String id, ActionDTO action);

View File

@ -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<Layout> refactorWidgetName(RefactorNameDTO refactorNameDTO) {
public Mono<LayoutDTO> 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<Layout> refactorActionName(RefactorNameDTO refactorNameDTO) {
public Mono<LayoutDTO> 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<Layout> refactorName(String pageId, String layoutId, String oldName, String newName) {
private Mono<LayoutDTO> 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<Layout> updateLayout(String pageId, String layoutId, Layout layout) {
public Mono<LayoutDTO> 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<String> widgetNames = new HashSet<>();
@ -496,6 +498,8 @@ public class LayoutActionServiceImpl implements LayoutActionService {
Set<ActionDependencyEdge> edges = new HashSet<>();
Set<String> actionsUsedInDSL = new HashSet<>();
List<ActionDTO> flatmapPageLoadActions = new ArrayList<>();
List<LayoutActionUpdateDTO> actionUpdates = new ArrayList<>();
List<String> messages = new ArrayList<>();
Mono<List<HashSet<DslActionDTO>>> 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;
}
}

View File

@ -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<NewAction, String> {
Flux<NewAction> findByPageId(String pageId);
Mono<Boolean> setOnLoad(List<ActionDTO> actions);
Mono<Boolean> updateActionsExecuteOnLoad(List<ActionDTO> actions, String pageId, List<LayoutActionUpdateDTO> actionUpdates, List<String> messages);
}

View File

@ -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<NewActionRepository, NewAc
return repository.findByPageId(pageId);
}
/**
* !!!WARNING!!! This function edits the parameters actionUpdates and messages which are eventually returned back to
* the caller with the updates values.
* @param onLoadActions : All the actions which have been found to be on page load
* @param pageId
* @param actionUpdates : Empty array list which would be set in this function with all the page actions whose
* execute on load setting has changed (whether flipped from true to false, or vice versa)
* @param messages : Empty array list which would be set in this function with all the messages that should be
* displayed to the developer user communicating the action executeOnLoad changes.
* @return
*/
@Override
public Mono<Boolean> setOnLoad(List<ActionDTO> actions) {
if (actions == null) {
return Mono.just(FALSE);
}
public Mono<Boolean> updateActionsExecuteOnLoad(List<ActionDTO> onLoadActions,
String pageId,
List<LayoutActionUpdateDTO> actionUpdates,
List<String> messages) {
List<ActionDTO> 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<String, String> params = CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH));
params.add(FieldName.PAGE_ID, pageId);
// Fetch all the actions which exist in this page.
Flux<ActionDTO> pageActionsFlux = this.getUnpublishedActions(params).cache();
// Before we update the actions, fetch all the actions which are currently set to execute on load.
Mono<List<ActionDTO>> 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<ActionDTO> existingOnPageLoadActions = tuple.getT1();
List<ActionDTO> 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<String> existingOnPageLoadActionNames = existingOnPageLoadActions
.stream()
.map(action -> action.getName())
.collect(Collectors.toSet());
Set<String> 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<String> 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<String> 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<LayoutActionUpdateDTO> addActionUpdatesForActionNames(List<ActionDTO> pageActions,
Set<String> 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

View File

@ -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<String> expected) {
assertThat(tokenize(template)).isEqualTo(expected);
}
@ -406,7 +397,14 @@ public class MustacheHelperTest {
new Property("param2", "{{ queryParam2 }}")
));
final Map<String, String> context = Map.of(
configuration.setPluginSpecifiedTemplates(Arrays.asList(
null,
new Property("prop1", "{{ pluginSpecifiedProp1 }}"),
null,
new Property("prop2", "{{ pluginSpecifiedProp2 }}")
));
final Map<String, String> 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

View File

@ -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<NewAction> 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<NewAction> 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<LayoutDTO> 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<LayoutActionUpdateDTO> 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<LayoutActionUpdateDTO> actionUpdates = updatedLayout.getActionUpdates();
assertThat(actionUpdates.size()).isEqualTo(2);
Optional<LayoutActionUpdateDTO> firstActionUpdateOptional = actionUpdates.stream().filter(actionUpdate -> actionUpdate.getName().equals("firstAction")).findFirst();
LayoutActionUpdateDTO firstActionUpdate = firstActionUpdateOptional.get();
assertThat(firstActionUpdate).isNotNull();
assertThat(firstActionUpdate.getExecuteOnLoad()).isFalse();
Optional<LayoutActionUpdateDTO> 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());
}
}

View File

@ -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<Layout> updatedLayoutMono = layoutActionService.updateLayout("random-impossible-id-page", startLayout.getId(), updateLayout);
Mono<LayoutDTO> updatedLayoutMono = layoutActionService.updateLayout("random-impossible-id-page", startLayout.getId(), updateLayout);
StepVerifier
.create(updatedLayoutMono)
@ -238,7 +237,7 @@ public class LayoutServiceTest {
Mono<Layout> startLayoutMono = pageMono.flatMap(page -> layoutService.createLayout(page.getId(), testLayout));
Mono<Layout> updatedLayoutMono = Mono.zip(pageMono, startLayoutMono)
Mono<LayoutDTO> updatedLayoutMono = Mono.zip(pageMono, startLayoutMono)
.flatMap(tuple -> {
PageDTO page = tuple.getT1();
Layout startLayout = tuple.getT2();
@ -276,7 +275,7 @@ public class LayoutServiceTest {
Mono<PageDTO> pageMono = createPage(app, testPage).cache();
Mono<Layout> testMono = pageMono
Mono<LayoutDTO> testMono = pageMono
.flatMap(page1 -> {
List<Mono<ActionDTO>> monos = new ArrayList<>();
@ -464,7 +463,7 @@ public class LayoutServiceTest {
Mono<PageDTO> pageMono = createPage(app, testPage).cache();
Mono<Layout> testMono = pageMono
Mono<LayoutDTO> testMono = pageMono
.flatMap(page1 -> {
List<Mono<ActionDTO>> monos = new ArrayList<>();

View File

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

View File

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