commit
0a16e29a80
|
|
@ -23,7 +23,7 @@ describe("Test Create Api and Bind to Table widget", function() {
|
|||
/**Bind Table with Textwidget with selected row */
|
||||
cy.SearchEntityandOpen("Text1");
|
||||
cy.testJsontext("text", "{{Table1.selectedRow.url}}");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
cy.SearchEntityandOpen("Table1");
|
||||
cy.readTabledata("0", "0").then(tabData => {
|
||||
const tableData = tabData;
|
||||
localStorage.setItem("tableDataPage1", tableData);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
|
||||
it("Duplicating an application", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Click on any application action icon (Three dots)
|
||||
// Click on "Duplicate" option
|
||||
// Ensure the application gets copied
|
||||
// Click on "Appsmith" to navigate to homepage
|
||||
// Click on action icon
|
||||
// Click on Delete option
|
||||
// Click on "Are You Sure?" option
|
||||
// Ensure the App gets deleted
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
15
app/client/cypress/manual_TestSuite/Duplicate_App.js
Normal file
15
app/client/cypress/manual_TestSuite/Duplicate_App.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
|
||||
it("Duplicating an application", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Click on any application action icon (Three dots)
|
||||
// Click on "Duplicate" option
|
||||
// Ensure the application gets copied
|
||||
// Ensure the name is appended with the word "Copy"
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
28
app/client/cypress/manual_TestSuite/Duplicate_App_Spec.js
Normal file
28
app/client/cypress/manual_TestSuite/Duplicate_App_Spec.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
|
||||
it("Duplicating an application", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Click on any application action icon (Three dots)
|
||||
// Click on "Duplicate" option
|
||||
// Ensure the application gets copied
|
||||
// Ensure the name is appended with the word "Copy"
|
||||
}
|
||||
)
|
||||
it("Deleting the duplicated Application ", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Click on any application action icon (Three dots)
|
||||
// Click on "Duplicate" option
|
||||
// Ensure the application gets copied
|
||||
// Click on "Appsmith" to navigate to homepage
|
||||
// Click on action icon
|
||||
// Click on Delete option
|
||||
// Click on "Are You Sure?" option
|
||||
// Ensure the App gets deleted
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
19
app/client/cypress/manual_TestSuite/Org_Logo_Del.js
Normal file
19
app/client/cypress/manual_TestSuite/Org_Logo_Del.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Deletion of organisational Logo ", function() {
|
||||
it(" org logo upload ", function()
|
||||
{
|
||||
//Click on the dropdown next to organisational Name
|
||||
// Navigate between tabs
|
||||
// Naviagte to General Tab
|
||||
// Add an Organisational Logo
|
||||
// Wait until it loads
|
||||
// Switch between Tabs
|
||||
// Click on the remove Icon
|
||||
//Ensure the organisational Logo is deleted
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
18
app/client/cypress/manual_TestSuite/Org_Logo_Set.js
Normal file
18
app/client/cypress/manual_TestSuite/Org_Logo_Set.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("insert organisational Logo ", function() {
|
||||
it(" org logo upload ", function()
|
||||
{
|
||||
//Click on the dropdown next to organisational Name
|
||||
// Navigate between tabs
|
||||
// Naviagte to General Tab
|
||||
// Add an Organisational Logo
|
||||
//Wait until it loads
|
||||
// Switch between Tabs
|
||||
// Navigate to General Tab and ensure the logo exsits
|
||||
//navigate back to Homepage
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
15
app/client/cypress/manual_TestSuite/Organisation_Name.js
Normal file
15
app/client/cypress/manual_TestSuite/Organisation_Name.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Checking for error message on Organisation Name ", function() {
|
||||
it("Ensure of Inactive Submit button ", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Click on Create Organisation
|
||||
// Type "Space" as first character
|
||||
// Ensure "Submit" button does not get Active
|
||||
// Now click on "X" (Close icon) ensure the pop up closes
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Checking for error message on Organisation Name ", function() {
|
||||
it("Ensure of Inactive Submit button ", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Click on Create Organisation
|
||||
// Type "Space" as first character
|
||||
// Ensure "Submit" button does not get Active
|
||||
// Now click on "X" (Close icon) ensure the pop up closes
|
||||
}
|
||||
)
|
||||
it("Reuse the name of the deleted application name ", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Create an Application by name "XYZ"
|
||||
// Add some widgets
|
||||
// Navigate back to the application
|
||||
// Delete the Application
|
||||
// Click on "Create New" option under samee organisation
|
||||
// Enter the name "XYZ"
|
||||
// Ensure the application can be created with the same name
|
||||
}
|
||||
)
|
||||
it("Adding Special Character ", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Click on Create Organisation
|
||||
// Add special as first character
|
||||
// Ensure "Submit" get Active
|
||||
// Now click outside and ensure the pop up closes
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Reuse the name of the deleted application name inside the same organisation", function() {
|
||||
it("Reuse the name of the deleted application name ", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Create an Application by name "XYZ"
|
||||
// Add some widgets
|
||||
// Navigate back to the application
|
||||
// Delete the Application
|
||||
// Click on "Create New" option under samee organisation
|
||||
// Enter the name "XYZ"
|
||||
// Ensure the application can be created with the same name
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
17
app/client/cypress/manual_TestSuite/Share_User_Icon.js
Normal file
17
app/client/cypress/manual_TestSuite/Share_User_Icon.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Shared user icon ", function() {
|
||||
it(" User Icon is disaplyed to user ", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
//Click on Share Icon
|
||||
// Click on Field to add an Email Id
|
||||
// Click on the Roles field
|
||||
// Add an role from the Dropdown
|
||||
// CLick on Invite
|
||||
//Now observe the icon next to the Share Icon
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
15
app/client/cypress/manual_TestSuite/Spl_Chracter_Org_Name.js
Normal file
15
app/client/cypress/manual_TestSuite/Spl_Chracter_Org_Name.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const homePage = require("../../../locators/HomePage.json");
|
||||
|
||||
|
||||
describe("Adding Special Character ", function() {
|
||||
it("Adding Special Character ", function()
|
||||
{
|
||||
// Navigate to home Page
|
||||
// Click on Create Organisation
|
||||
// Add special as first character
|
||||
// Ensure "Submit" get Active
|
||||
// Now click outside and ensure the pop up closes
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactNode } from "react";
|
||||
import React, { forwardRef, ReactNode, Ref } from "react";
|
||||
import { CommonComponentProps, Classes } from "./common";
|
||||
import styled from "styled-components";
|
||||
import Icon, { IconName, IconSize } from "./Icon";
|
||||
|
|
@ -13,22 +13,31 @@ export type MenuItemProps = CommonComponentProps & {
|
|||
href?: string;
|
||||
type?: "warning";
|
||||
ellipsize?: number;
|
||||
selected?: boolean;
|
||||
onSelect?: () => void;
|
||||
};
|
||||
|
||||
const ItemRow = styled.a<{ disabled?: boolean }>`
|
||||
const ItemRow = styled.a<{ disabled?: boolean; selected?: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text-decoration: none;
|
||||
padding: 0px ${props => props.theme.spaces[6]}px;
|
||||
background-color: ${props =>
|
||||
props.selected ? props.theme.colors.menuItem.hoverBg : "transparent"};
|
||||
.${Classes.TEXT} {
|
||||
color: ${props => props.theme.colors.menuItem.normalText};
|
||||
color: ${props =>
|
||||
props.selected
|
||||
? props.theme.colors.menuItem.hoverText
|
||||
: props.theme.colors.menuItem.normalText};
|
||||
}
|
||||
.${Classes.ICON} {
|
||||
svg {
|
||||
path {
|
||||
fill: ${props => props.theme.colors.menuItem.normalIcon};
|
||||
fill: ${props =>
|
||||
props.selected
|
||||
? props.theme.colors.menuItem.hoverIcon
|
||||
: props.theme.colors.menuItem.normalIcon};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,40 +87,46 @@ const IconContainer = styled.span`
|
|||
margin-right: ${props => props.theme.spaces[5]}px;
|
||||
}
|
||||
`;
|
||||
|
||||
function MenuItem(props: MenuItemProps) {
|
||||
return props.ellipsize && props.text.length > props.ellipsize ? (
|
||||
<TooltipComponent position={Position.BOTTOM} content={props.text}>
|
||||
<MenuItemContent {...props} />
|
||||
</TooltipComponent>
|
||||
) : (
|
||||
<MenuItemContent {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function MenuItemContent(props: MenuItemProps) {
|
||||
return (
|
||||
<ItemRow
|
||||
href={props.href}
|
||||
onClick={props.onSelect}
|
||||
disabled={props.disabled}
|
||||
data-cy={props.cypressSelector}
|
||||
type={props.type}
|
||||
>
|
||||
<IconContainer className={props.className}>
|
||||
{props.icon ? <Icon name={props.icon} size={IconSize.LARGE} /> : null}
|
||||
{props.text ? (
|
||||
<Text type={TextType.H5} weight={FontWeight.NORMAL}>
|
||||
{props.ellipsize
|
||||
? ellipsize(props.ellipsize, props.text)
|
||||
: props.text}
|
||||
</Text>
|
||||
) : null}
|
||||
</IconContainer>
|
||||
{props.label ? props.label : null}
|
||||
</ItemRow>
|
||||
);
|
||||
}
|
||||
const MenuItem = forwardRef(
|
||||
(props: MenuItemProps, ref: Ref<HTMLAnchorElement>) => {
|
||||
return props.ellipsize && props.text.length > props.ellipsize ? (
|
||||
<TooltipComponent position={Position.BOTTOM} content={props.text}>
|
||||
<MenuItemContent ref={ref} {...props} />
|
||||
</TooltipComponent>
|
||||
) : (
|
||||
<MenuItemContent ref={ref} {...props} />
|
||||
);
|
||||
},
|
||||
);
|
||||
const MenuItemContent = forwardRef(
|
||||
(props: MenuItemProps, ref: Ref<HTMLAnchorElement>) => {
|
||||
return (
|
||||
<ItemRow
|
||||
href={props.href}
|
||||
onClick={props.onSelect}
|
||||
disabled={props.disabled}
|
||||
data-cy={props.cypressSelector}
|
||||
type={props.type}
|
||||
ref={ref}
|
||||
selected={props.selected}
|
||||
>
|
||||
<IconContainer className={props.className}>
|
||||
{props.icon ? <Icon name={props.icon} size={IconSize.LARGE} /> : null}
|
||||
{props.text ? (
|
||||
<Text type={TextType.H5} weight={FontWeight.NORMAL}>
|
||||
{props.ellipsize
|
||||
? ellipsize(props.ellipsize, props.text)
|
||||
: props.text}
|
||||
</Text>
|
||||
) : null}
|
||||
</IconContainer>
|
||||
{props.label ? props.label : null}
|
||||
</ItemRow>
|
||||
);
|
||||
},
|
||||
);
|
||||
MenuItemContent.displayName = "MenuItemContent";
|
||||
MenuItem.displayName = "MenuItem";
|
||||
|
||||
function ellipsize(length: number, text: string) {
|
||||
return text.length > length ? text.slice(0, length).concat(" ...") : text;
|
||||
|
|
|
|||
|
|
@ -88,11 +88,12 @@ export const DeployLinkButton = (props: Props) => {
|
|||
content={
|
||||
<DeployLinkDialog>
|
||||
<Tooltip
|
||||
content={isCopied ? "Copied!" : "Copy app publish link"}
|
||||
content={isCopied ? "Copied!" : "Copy link to published app"}
|
||||
autoFocus={false}
|
||||
interactionKind={PopoverInteractionKind.HOVER_TARGET_ONLY}
|
||||
lazy
|
||||
position={PopoverPosition.BOTTOM}
|
||||
openOnTargetFocus={false}
|
||||
>
|
||||
<IconContainer onClick={copyToClipboard}>
|
||||
<Icon icon="link" color="#BCCCD9" />
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import React, { ReactNode } from "react";
|
|||
import styled from "styled-components";
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
type Props = { isValid: boolean; children: ReactNode };
|
||||
type Props = { children: ReactNode };
|
||||
type State = { hasError: boolean };
|
||||
|
||||
const ErrorBoundaryContainer = styled.div<{ isValid: boolean }>`
|
||||
const ErrorBoundaryContainer = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ class ErrorBoundary extends React.Component<Props, State> {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<ErrorBoundaryContainer isValid={this.props.isValid}>
|
||||
<ErrorBoundaryContainer>
|
||||
{this.state.hasError ? (
|
||||
<p>
|
||||
Oops, Something went wrong.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Component, Fragment, useState } from "react";
|
||||
import React, { Component, Fragment, useEffect, useRef, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { connect, useSelector, useDispatch } from "react-redux";
|
||||
import { AppState } from "reducers";
|
||||
|
|
@ -340,6 +340,8 @@ const ApplicationAddCardWrapper = styled(Card)`
|
|||
`;
|
||||
|
||||
function LeftPane() {
|
||||
const menuRef = useRef<HTMLAnchorElement>(null);
|
||||
const [selectedOrg, setSelectedOrg] = useState<string>("");
|
||||
const fetchedUserOrgs = useSelector(getUserApplicationsOrgs);
|
||||
const isFetchingApplications = useSelector(getIsFetchingApplications);
|
||||
const NewWorkspaceTrigger = (
|
||||
|
|
@ -359,6 +361,20 @@ function LeftPane() {
|
|||
userOrgs = loadingUserOrgs as any;
|
||||
}
|
||||
|
||||
const urlHash = decodeURI(
|
||||
window.location.hash.substring(1, window.location.hash.length),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (menuRef && menuRef.current) {
|
||||
menuRef.current.scrollIntoView({ behavior: "smooth" });
|
||||
menuRef.current.click();
|
||||
}
|
||||
}, 0);
|
||||
return () => clearTimeout(timer);
|
||||
}, [fetchedUserOrgs]);
|
||||
|
||||
return (
|
||||
<LeftPaneWrapper>
|
||||
<LeftPaneSection
|
||||
|
|
@ -375,6 +391,7 @@ function LeftPane() {
|
|||
{userOrgs &&
|
||||
userOrgs.map((org: any) => (
|
||||
<MenuItem
|
||||
{...(urlHash === org.organization.name ? { ref: menuRef } : {})}
|
||||
className={
|
||||
isFetchingApplications ? BlueprintClasses.SKELETON : ""
|
||||
}
|
||||
|
|
@ -383,6 +400,11 @@ function LeftPane() {
|
|||
href={`${window.location.pathname}#${org.organization.name}`}
|
||||
text={org.organization.name}
|
||||
ellipsize={20}
|
||||
onSelect={() => setSelectedOrg(org.organization.id)}
|
||||
selected={
|
||||
selectedOrg === org.organization.id &&
|
||||
urlHash === org.organization.name
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</WorkpsacesNavigator>
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ const PropertyPaneTitle = memo((props: PropertyPaneTitleProps) => {
|
|||
}
|
||||
position={Position.TOP}
|
||||
hoverOpenDelay={200}
|
||||
boundary="window"
|
||||
>
|
||||
<Icon color={theme.colors.paneSectionLabel} icon="help" iconSize={16} />
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import styled from "styled-components";
|
|||
import AutoToolTipComponent from "components/designSystems/appsmith/AutoToolTipComponent";
|
||||
import { getType, Types } from "utils/TypeHelpers";
|
||||
import { Colors } from "constants/Colors";
|
||||
import ErrorBoundary from "components/editorComponents/ErrorBoundry";
|
||||
|
||||
interface TableProps {
|
||||
data: Record<string, any>[];
|
||||
|
|
@ -214,59 +215,61 @@ const Table = (props: TableProps) => {
|
|||
if (rows.length === 0 || headerGroups.length === 0) return null;
|
||||
|
||||
return (
|
||||
<TableWrapper>
|
||||
<div className="tableWrap">
|
||||
<div {...getTableProps()} className="table">
|
||||
{headerGroups.map((headerGroup: any, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
{...headerGroup.getHeaderGroupProps()}
|
||||
className="tr"
|
||||
>
|
||||
{headerGroup.headers.map((column: any, columnIndex: number) => (
|
||||
<div
|
||||
key={columnIndex}
|
||||
{...column.getHeaderProps()}
|
||||
className="th header-reorder"
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<TableWrapper>
|
||||
<div className="tableWrap">
|
||||
<div {...getTableProps()} className="table">
|
||||
{headerGroups.map((headerGroup: any, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
{...headerGroup.getHeaderGroupProps()}
|
||||
className="tr"
|
||||
>
|
||||
{headerGroup.headers.map((column: any, columnIndex: number) => (
|
||||
<div
|
||||
className={
|
||||
!column.isHidden ? "draggable-header" : "hidden-header"
|
||||
}
|
||||
key={columnIndex}
|
||||
{...column.getHeaderProps()}
|
||||
className="th header-reorder"
|
||||
>
|
||||
{column.render("Header")}
|
||||
<div
|
||||
className={
|
||||
!column.isHidden ? "draggable-header" : "hidden-header"
|
||||
}
|
||||
>
|
||||
{column.render("Header")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
{rows.map((row: any, index: number) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<div key={index} {...row.getRowProps()} className={"tr"}>
|
||||
{row.cells.map((cell: any, cellIndex: number) => {
|
||||
return (
|
||||
<div
|
||||
key={cellIndex}
|
||||
{...cell.getCellProps()}
|
||||
className="td"
|
||||
data-rowindex={index}
|
||||
data-colindex={cellIndex}
|
||||
>
|
||||
<CellWrapper isHidden={false}>
|
||||
{cell.render("Cell")}
|
||||
</CellWrapper>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
{rows.map((row: any, index: number) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<div key={index} {...row.getRowProps()} className={"tr"}>
|
||||
{row.cells.map((cell: any, cellIndex: number) => {
|
||||
return (
|
||||
<div
|
||||
key={cellIndex}
|
||||
{...cell.getCellProps()}
|
||||
className="td"
|
||||
data-rowindex={index}
|
||||
data-colindex={cellIndex}
|
||||
>
|
||||
<CellWrapper isHidden={false}>
|
||||
{cell.render("Cell")}
|
||||
</CellWrapper>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableWrapper>
|
||||
</TableWrapper>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createReducer } from "utils/AppsmithUtils";
|
||||
import { createImmerReducer } from "utils/AppsmithUtils";
|
||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ export type EvaluatedTreeState = DataTree;
|
|||
|
||||
const initialState: EvaluatedTreeState = {};
|
||||
|
||||
const evaluatedTreeReducer = createReducer(initialState, {
|
||||
const evaluatedTreeReducer = createImmerReducer(initialState, {
|
||||
[ReduxActionTypes.SET_EVALUATED_TREE]: (
|
||||
state: EvaluatedTreeState,
|
||||
action: ReduxAction<DataTree>,
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { combineReducers } from "redux";
|
||||
import entityReducer from "./entityReducers";
|
||||
import uiReducer from "./uiReducers";
|
||||
import evaluationsReducer from "./evalutationReducers";
|
||||
import evaluationsReducer from "./evaluationReducers";
|
||||
import { reducer as formReducer } from "redux-form";
|
||||
import { CanvasWidgetsReduxState } from "./entityReducers/canvasWidgetsReducer";
|
||||
import { EditorReduxState } from "./uiReducers/editorReducer";
|
||||
|
|
@ -35,8 +35,8 @@ import { PageCanvasStructureReduxState } from "./uiReducers/pageCanvasStructure"
|
|||
import { ConfirmRunActionReduxState } from "./uiReducers/confirmRunActionReducer";
|
||||
import { AppDataState } from "reducers/entityReducers/appReducer";
|
||||
import { DatasourceNameReduxState } from "./uiReducers/datasourceNameReducer";
|
||||
import { EvaluatedTreeState } from "./evalutationReducers/treeReducer";
|
||||
import { EvaluationDependencyState } from "./evalutationReducers/dependencyReducer";
|
||||
import { EvaluatedTreeState } from "./evaluationReducers/treeReducer";
|
||||
import { EvaluationDependencyState } from "./evaluationReducers/dependencyReducer";
|
||||
import { PageWidgetsReduxState } from "./uiReducers/pageWidgetsReducer";
|
||||
|
||||
const appReducer = combineReducers({
|
||||
|
|
|
|||
|
|
@ -249,11 +249,42 @@ export function* evaluateDynamicBoundValueSaga(
|
|||
|
||||
const EXECUTION_PARAM_REFERENCE_REGEX = /this.params/g;
|
||||
|
||||
/**
|
||||
* Api1
|
||||
* URL: https://example.com/{{Text1.text}}
|
||||
* Body: {
|
||||
* "name": "{{this.params.name}}",
|
||||
* "age": {{this.params.age}},
|
||||
* "gender": {{Dropdown1.selectedOptionValue}}
|
||||
* }
|
||||
*
|
||||
* If you call
|
||||
* Api1.run(undefined, undefined, { name: "Hetu", age: Input1.text });
|
||||
*
|
||||
* executionParams is { name: "Hetu", age: Input1.text }
|
||||
* bindings is [
|
||||
* "Text1.text",
|
||||
* "Dropdown1.selectedOptionValue",
|
||||
* "this.params.name",
|
||||
* "this.params.age",
|
||||
* ]
|
||||
*
|
||||
* Return will be [
|
||||
* { key: "Text1.text", value: "updateUser" },
|
||||
* { key: "Dropdown1.selectedOptionValue", value: "M" },
|
||||
* { key: "this.params.name", value: "Hetu" },
|
||||
* { key: "this.params.age", value: 26 },
|
||||
* ]
|
||||
* @param bindings
|
||||
* @param executionParams
|
||||
*/
|
||||
export function* getActionParams(
|
||||
bindings: string[] | undefined,
|
||||
executionParams?: Record<string, any>,
|
||||
) {
|
||||
if (_.isNil(bindings)) return [];
|
||||
// This might look like a bug, but isn't.
|
||||
// We send in stringified executionParams, but get back an object
|
||||
const evaluatedExecutionParams = yield evaluateDynamicBoundValueSaga(
|
||||
JSON.stringify(executionParams),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -117,10 +117,11 @@ export function* evaluateSingleValue(
|
|||
) {
|
||||
if (evaluationWorker) {
|
||||
const dataTree = yield select(getDataTree);
|
||||
dataTree[EXECUTION_PARAM_KEY] = executionParams;
|
||||
evaluationWorker.postMessage({
|
||||
action: EVAL_WORKER_ACTIONS.EVAL_SINGLE,
|
||||
dataTree,
|
||||
dataTree: Object.assign({}, dataTree, {
|
||||
[EXECUTION_PARAM_KEY]: executionParams,
|
||||
}),
|
||||
binding,
|
||||
});
|
||||
const workerResponse = yield take(workerChannel);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const dataTreeTypeDefCreator = (dataTree: DataTree) => {
|
|||
};
|
||||
Object.keys(dataTree).forEach(entityName => {
|
||||
const entity = dataTree[entityName];
|
||||
if ("ENTITY_TYPE" in entity) {
|
||||
if (entity && "ENTITY_TYPE" in entity) {
|
||||
if (entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET) {
|
||||
const widgetType = entity.type;
|
||||
if (widgetType in entityDefinitions) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {
|
|||
CSSUnit,
|
||||
CONTAINER_GRID_PADDING,
|
||||
} from "constants/WidgetConstants";
|
||||
import _ from "lodash";
|
||||
import DraggableComponent from "components/editorComponents/DraggableComponent";
|
||||
import ResizableComponent from "components/editorComponents/ResizableComponent";
|
||||
import { ExecuteActionPayload } from "constants/ActionConstants";
|
||||
|
|
@ -205,8 +204,8 @@ abstract class BaseWidget<
|
|||
);
|
||||
}
|
||||
|
||||
addErrorBoundary(content: ReactNode, isValid: boolean) {
|
||||
return <ErrorBoundary isValid={isValid}>{content}</ErrorBoundary>;
|
||||
addErrorBoundary(content: ReactNode) {
|
||||
return <ErrorBoundary>{content}</ErrorBoundary>;
|
||||
}
|
||||
|
||||
private getWidgetView(): ReactNode {
|
||||
|
|
@ -226,7 +225,7 @@ abstract class BaseWidget<
|
|||
case RenderModes.PAGE:
|
||||
content = this.getPageView();
|
||||
if (this.props.isVisible) {
|
||||
content = this.addErrorBoundary(content, true);
|
||||
content = this.addErrorBoundary(content);
|
||||
if (!this.props.detachFromLayout) {
|
||||
content = this.makePositioned(content);
|
||||
}
|
||||
|
|
@ -241,13 +240,8 @@ abstract class BaseWidget<
|
|||
abstract getPageView(): ReactNode;
|
||||
|
||||
getCanvasView(): ReactNode {
|
||||
let isValid = true;
|
||||
if (this.props.invalidProps) {
|
||||
isValid = _.keys(this.props.invalidProps).length === 0;
|
||||
}
|
||||
if (this.props.isLoading) isValid = true;
|
||||
const content = this.getPageView();
|
||||
return this.addErrorBoundary(content, isValid);
|
||||
return this.addErrorBoundary(content);
|
||||
}
|
||||
|
||||
// TODO(abhinav): Maybe make this a pure component to bailout from updating altogether.
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
let inputFormat;
|
||||
try {
|
||||
const type = column.metaProperties.inputFormat;
|
||||
if (type !== "EPOCH" && type !== "Milliseconds") {
|
||||
if (type !== "Epoch" && type !== "Milliseconds") {
|
||||
inputFormat = type;
|
||||
moment(value, inputFormat);
|
||||
} else if (!isNumber(value)) {
|
||||
|
|
@ -253,6 +253,8 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
outputFormat = inputFormat;
|
||||
}
|
||||
if (column.metaProperties.inputFormat === "Milliseconds") {
|
||||
value = Number(value);
|
||||
} else if (column.metaProperties.inputFormat === "Epoch") {
|
||||
value = 1000 * Number(value);
|
||||
}
|
||||
tableRow[accessor] = moment(value, inputFormat).format(
|
||||
|
|
@ -656,8 +658,10 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
};
|
||||
|
||||
handleRowClick = (rowData: Record<string, unknown>, index: number) => {
|
||||
const { selectedRowIndices } = this.props;
|
||||
if (this.props.multiRowSelection) {
|
||||
const selectedRowIndices = this.props.selectedRowIndices
|
||||
? [...this.props.selectedRowIndices]
|
||||
: [];
|
||||
if (selectedRowIndices.includes(index)) {
|
||||
const rowIndex = selectedRowIndices.indexOf(index);
|
||||
selectedRowIndices.splice(rowIndex, 1);
|
||||
|
|
|
|||
21
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/DocumentType.java
vendored
Normal file
21
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/DocumentType.java
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package com.appsmith.external.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* This annotation is meant to introduce polymorphic behaviour in persistent objects. Since we do not expect Spring to
|
||||
* be able to automatically detect such objects, objects marked with this annotation are specifically registered in the
|
||||
* type mapper for {@link org.springframework.data.mongodb.core.MongoTemplate}
|
||||
*
|
||||
* The value associated to this annotation functions as an alias for the entity.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface DocumentType {
|
||||
|
||||
public String value() default "";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.appsmith.external.annotations;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.convert.TypeInformationMapper;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This {@link TypeInformationMapper} implementation makes use of the {@link DocumentType} annotation to register all
|
||||
* such entities as possible candidates for domain mapping.
|
||||
*/
|
||||
public class DocumentTypeMapper implements TypeInformationMapper {
|
||||
|
||||
private final Map<String, ClassTypeInformation<?>> aliasToTypeMap;
|
||||
private final Map<ClassTypeInformation<?>, String> typeToAliasMap;
|
||||
|
||||
private DocumentTypeMapper(List<String> basePackagesToScan) {
|
||||
aliasToTypeMap = new HashMap<>();
|
||||
typeToAliasMap = new HashMap<>();
|
||||
|
||||
// Upon initialization, read all aliases from annotated entities
|
||||
populateTypeMap(basePackagesToScan);
|
||||
}
|
||||
|
||||
private void populateTypeMap(List<String> basePackagesToScan) {
|
||||
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(DocumentType.class));
|
||||
|
||||
for (String basePackage : basePackagesToScan) {
|
||||
for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(bd.getBeanClassName());
|
||||
DocumentType documentTypeAnnotation = clazz.getAnnotation(DocumentType.class);
|
||||
|
||||
ClassTypeInformation<?> type = ClassTypeInformation.from(clazz);
|
||||
String alias = documentTypeAnnotation.value();
|
||||
|
||||
aliasToTypeMap.put(alias, type);
|
||||
typeToAliasMap.put(type, alias);
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(String.format("Class [%s] could not be loaded.", bd.getBeanClassName()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInformation<?> resolveTypeFrom(Alias alias) {
|
||||
if (aliasToTypeMap.containsKey((String) alias.getValue())) {
|
||||
return aliasToTypeMap.get(alias.getValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias createAliasFor(TypeInformation<?> typeInformation) {
|
||||
if (typeToAliasMap.containsKey(typeInformation)) {
|
||||
return Alias.of(typeToAliasMap.get(typeInformation));
|
||||
}
|
||||
return Alias.NONE;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
List<String> basePackagesToScan;
|
||||
|
||||
public Builder() {
|
||||
basePackagesToScan = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Builder withBasePackage(String basePackage) {
|
||||
basePackagesToScan.add(basePackage);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withBasePackages(String[] basePackages) {
|
||||
basePackagesToScan.addAll(Arrays.asList(basePackages));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withBasePackages(Collection< ? extends String> basePackages) {
|
||||
basePackagesToScan.addAll(basePackages);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DocumentTypeMapper build() {
|
||||
return new DocumentTypeMapper(basePackagesToScan);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/AuthType.java
vendored
Normal file
6
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/AuthType.java
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package com.appsmith.external.constants;
|
||||
|
||||
public class AuthType {
|
||||
public static final String DB_AUTH = "dbAuth";
|
||||
public static final String OAUTH2 = "oAuth2";
|
||||
}
|
||||
8
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/FieldName.java
vendored
Normal file
8
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/FieldName.java
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package com.appsmith.external.constants;
|
||||
|
||||
public class FieldName {
|
||||
public static final String CLIENT_SECRET = "clientSecret";
|
||||
public static final String TOKEN = "token";
|
||||
|
||||
public static final String PASSWORD = "password";
|
||||
}
|
||||
|
|
@ -1,30 +1,53 @@
|
|||
package com.appsmith.external.models;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import com.appsmith.external.constants.AuthType;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.EXISTING_PROPERTY,
|
||||
visible = true,
|
||||
property = "type",
|
||||
defaultImpl = DBAuth.class)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = DBAuth.class, name = AuthType.DB_AUTH),
|
||||
@JsonSubTypes.Type(value = OAuth2.class, name = AuthType.OAUTH2)
|
||||
})
|
||||
public class AuthenticationDTO {
|
||||
// In principle, this class should've been abstract. However, when this class is abstract, Spring's deserialization
|
||||
// routines choke on identifying the correct class to instantiate and ends up trying to instantiate this abstract
|
||||
// class and fails.
|
||||
|
||||
public enum Type {
|
||||
SCRAM_SHA_1, SCRAM_SHA_256, MONGODB_CR, USERNAME_PASSWORD
|
||||
@JsonIgnore
|
||||
private Boolean isEncrypted;
|
||||
|
||||
@JsonIgnore
|
||||
public Map<String, String> getEncryptionFields() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Type authType;
|
||||
public void setEncryptionFields(Map<String, String> encryptedFields) {
|
||||
// This is supposed to be overridden by implementations.
|
||||
}
|
||||
|
||||
String username;
|
||||
@JsonIgnore
|
||||
public Set<String> getEmptyEncryptionFields() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
String password;
|
||||
|
||||
String databaseName;
|
||||
@JsonIgnore
|
||||
public boolean isEncrypted() {
|
||||
return Boolean.TRUE.equals(isEncrypted);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
59
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DBAuth.java
vendored
Normal file
59
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DBAuth.java
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package com.appsmith.external.models;
|
||||
|
||||
import com.appsmith.external.annotations.DocumentType;
|
||||
import com.appsmith.external.constants.AuthType;
|
||||
import com.appsmith.external.constants.FieldName;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DocumentType(AuthType.DB_AUTH)
|
||||
public class DBAuth extends AuthenticationDTO {
|
||||
|
||||
public enum Type {
|
||||
SCRAM_SHA_1, SCRAM_SHA_256, MONGODB_CR, USERNAME_PASSWORD
|
||||
}
|
||||
|
||||
Type authType;
|
||||
|
||||
String username;
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
String password;
|
||||
|
||||
String databaseName;
|
||||
|
||||
@Override
|
||||
public Map<String, String> getEncryptionFields() {
|
||||
if (this.password != null && !this.password.isEmpty()) {
|
||||
return Map.of(FieldName.PASSWORD, this.password);
|
||||
}
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionFields(Map<String, String> encryptedFields) {
|
||||
if (encryptedFields != null && encryptedFields.containsKey(FieldName.PASSWORD)) {
|
||||
this.password = encryptedFields.get(FieldName.PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getEmptyEncryptionFields() {
|
||||
if (this.password == null || this.password.isEmpty()) {
|
||||
return Set.of(FieldName.PASSWORD);
|
||||
}
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
84
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/OAuth2.java
vendored
Normal file
84
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/OAuth2.java
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package com.appsmith.external.models;
|
||||
|
||||
import com.appsmith.external.annotations.DocumentType;
|
||||
import com.appsmith.external.constants.AuthType;
|
||||
import com.appsmith.external.constants.FieldName;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DocumentType(AuthType.OAUTH2)
|
||||
public class OAuth2 extends AuthenticationDTO {
|
||||
public enum Type {
|
||||
CLIENT_CREDENTIALS,
|
||||
}
|
||||
|
||||
Type authType;
|
||||
|
||||
String clientId;
|
||||
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
String clientSecret;
|
||||
|
||||
String accessTokenUrl;
|
||||
|
||||
String scope;
|
||||
|
||||
@JsonIgnore
|
||||
String token;
|
||||
|
||||
@JsonIgnore
|
||||
Instant expiresAt;
|
||||
|
||||
@Override
|
||||
public Map<String, String> getEncryptionFields() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
if (this.clientSecret != null) {
|
||||
map.put(FieldName.CLIENT_SECRET, this.clientSecret);
|
||||
}
|
||||
if (this.token != null) {
|
||||
map.put(FieldName.TOKEN, this.token);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionFields(Map<String, String> encryptedFields) {
|
||||
if (encryptedFields != null) {
|
||||
if (encryptedFields.containsKey(FieldName.CLIENT_SECRET)) {
|
||||
this.clientSecret = encryptedFields.get(FieldName.CLIENT_SECRET);
|
||||
}
|
||||
if (encryptedFields.containsKey(FieldName.TOKEN)) {
|
||||
this.token = encryptedFields.get(FieldName.TOKEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getEmptyEncryptionFields() {
|
||||
Set<String> set = new HashSet<>();
|
||||
if (this.clientSecret == null || this.clientSecret.isEmpty()) {
|
||||
set.add(FieldName.CLIENT_SECRET);
|
||||
}
|
||||
if (this.token == null || this.token.isEmpty()) {
|
||||
set.add(FieldName.TOKEN);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.appsmith.external.models;
|
||||
|
||||
public interface UpdatableConnection {
|
||||
void updateDatasource(DatasourceConfiguration datasourceConfiguration);
|
||||
|
||||
default boolean isUpdated() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
|
|
@ -138,7 +138,7 @@ public class DynamoPlugin extends BasePlugin {
|
|||
builder.endpointOverride(URI.create("http://" + endpoint.getHost() + ":" + endpoint.getPort()));
|
||||
}
|
||||
|
||||
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (authentication == null || StringUtils.isEmpty(authentication.getDatabaseName())) {
|
||||
return Mono.error(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
|
|
@ -169,7 +169,7 @@ public class DynamoPlugin extends BasePlugin {
|
|||
public Set<String> validateDatasource(@NonNull DatasourceConfiguration datasourceConfiguration) {
|
||||
Set<String> invalids = new HashSet<>();
|
||||
|
||||
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (authentication == null) {
|
||||
invalids.add("Missing AWS Access Key ID and Secret Access Key.");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import lombok.extern.log4j.Log4j;
|
||||
|
|
@ -96,10 +96,11 @@ public class DynamoPluginTest {
|
|||
Endpoint endpoint = new Endpoint();
|
||||
endpoint.setHost(host);
|
||||
endpoint.setPort(port.longValue());
|
||||
dsConfig.setAuthentication(new AuthenticationDTO());
|
||||
dsConfig.getAuthentication().setUsername("dummy");
|
||||
dsConfig.getAuthentication().setPassword("dummy");
|
||||
dsConfig.getAuthentication().setDatabaseName(Region.AP_SOUTH_1.toString());
|
||||
DBAuth auth = new DBAuth();
|
||||
auth.setUsername("dummy");
|
||||
auth.setPassword("dummy");
|
||||
auth.setDatabaseName(Region.AP_SOUTH_1.toString());
|
||||
dsConfig.setAuthentication(auth);
|
||||
dsConfig.setEndpoints(List.of(endpoint));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
|
|
@ -137,7 +137,7 @@ public class ElasticSearchPlugin extends BasePlugin {
|
|||
|
||||
final RestClientBuilder clientBuilder = RestClient.builder(hosts.toArray(new HttpHost[]{}));
|
||||
|
||||
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (authentication != null
|
||||
&& !StringUtils.isEmpty(authentication.getUsername())
|
||||
&& !StringUtils.isEmpty(authentication.getPassword())) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceStructure;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
|
|
@ -17,6 +17,7 @@ import com.google.cloud.firestore.CollectionReference;
|
|||
import com.google.cloud.firestore.DocumentReference;
|
||||
import com.google.cloud.firestore.DocumentSnapshot;
|
||||
import com.google.cloud.firestore.Firestore;
|
||||
import com.google.cloud.firestore.Query;
|
||||
import com.google.cloud.firestore.QueryDocumentSnapshot;
|
||||
import com.google.cloud.firestore.QuerySnapshot;
|
||||
import com.google.cloud.firestore.WriteResult;
|
||||
|
|
@ -132,7 +133,7 @@ public class FirestorePlugin extends BasePlugin {
|
|||
if (method.isDocumentLevel()) {
|
||||
return handleDocumentLevelMethod(connection, path, method, mapBody);
|
||||
} else {
|
||||
return handleCollectionLevelMethod(connection, path, method, properties);
|
||||
return handleCollectionLevelMethod(connection, path, method, properties, mapBody);
|
||||
}
|
||||
})
|
||||
.subscribeOn(scheduler);
|
||||
|
|
@ -221,15 +222,33 @@ public class FirestorePlugin extends BasePlugin {
|
|||
Firestore connection,
|
||||
String path,
|
||||
com.external.plugins.Method method,
|
||||
List<Property> properties
|
||||
List<Property> properties,
|
||||
Map<String, Object> mapBody
|
||||
) {
|
||||
final CollectionReference collection = connection.collection(path);
|
||||
|
||||
if (method == Method.GET_COLLECTION) {
|
||||
return methodGetCollection(collection, properties);
|
||||
|
||||
} else if (method == Method.ADD_TO_COLLECTION) {
|
||||
return methodAddToCollection(collection, mapBody);
|
||||
|
||||
}
|
||||
|
||||
return Mono.error(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
"Unsupported collection-level command: " + method
|
||||
));
|
||||
}
|
||||
|
||||
private Mono<ActionExecutionResult> methodGetCollection(CollectionReference query, List<Property> properties) {
|
||||
final String orderBy = properties.size() > 1 && properties.get(1) != null ? properties.get(1).getValue() : null;
|
||||
final int limit = properties.size() > 2 && properties.get(2) != null ? Integer.parseInt(properties.get(2).getValue()) : 10;
|
||||
final String queryFieldPath = properties.size() > 3 && properties.get(3) != null ? properties.get(3).getValue() : null;
|
||||
final Op operator = properties.size() > 4 && properties.get(4) != null ? Op.valueOf(properties.get(4).getValue()) : null;
|
||||
final String queryValue = properties.size() > 5 && properties.get(5) != null ? properties.get(5).getValue() : null;
|
||||
|
||||
return Mono.just(connection.collection(path))
|
||||
return Mono.just(query)
|
||||
// Apply ordering, if provided.
|
||||
.map(query1 -> StringUtils.isEmpty(orderBy) ? query1 : query1.orderBy(orderBy))
|
||||
// Apply where condition, if provided.
|
||||
|
|
@ -285,17 +304,7 @@ public class FirestorePlugin extends BasePlugin {
|
|||
// Apply limit, always provided, since without it we can inadvertently end up processing too much data.
|
||||
.map(query1 -> query1.limit(limit))
|
||||
// Run the Firestore query to get a Future of the results.
|
||||
.flatMap(query1 -> {
|
||||
switch (method) {
|
||||
case GET_COLLECTION:
|
||||
return Mono.just(query1.get());
|
||||
default:
|
||||
return Mono.error(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
"Unknown collection method: " + method.toString() + "."
|
||||
));
|
||||
}
|
||||
})
|
||||
.map(Query::get)
|
||||
// Consume the future to get the actual results.
|
||||
.flatMap(resultFuture -> {
|
||||
try {
|
||||
|
|
@ -315,7 +324,35 @@ public class FirestorePlugin extends BasePlugin {
|
|||
result.setIsExecutionSuccess(true);
|
||||
System.out.println(
|
||||
Thread.currentThread().getName()
|
||||
+ ": In the Firestore Plugin, got action execution result"
|
||||
+ ": In the Firestore Plugin, got action execution result for get collection"
|
||||
);
|
||||
return Mono.just(result);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<ActionExecutionResult> methodAddToCollection(CollectionReference collection, Map<String, Object> mapBody) {
|
||||
return Mono.justOrEmpty(collection.add(mapBody))
|
||||
.flatMap(future -> {
|
||||
try {
|
||||
return Mono.just(future.get());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
return Mono.error(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
e.getMessage()
|
||||
));
|
||||
}
|
||||
})
|
||||
.flatMap(opResult -> {
|
||||
ActionExecutionResult result = new ActionExecutionResult();
|
||||
try {
|
||||
result.setBody(resultToMap(opResult));
|
||||
} catch (AppsmithPluginException e) {
|
||||
return Mono.error(e);
|
||||
}
|
||||
result.setIsExecutionSuccess(true);
|
||||
System.out.println(
|
||||
Thread.currentThread().getName()
|
||||
+ ": In the Firestore Plugin, got action execution result for add to collection"
|
||||
);
|
||||
return Mono.just(result);
|
||||
});
|
||||
|
|
@ -344,6 +381,13 @@ public class FirestorePlugin extends BasePlugin {
|
|||
}
|
||||
return documents;
|
||||
|
||||
} else if (objResult instanceof DocumentReference) {
|
||||
DocumentReference documentReference = (DocumentReference) objResult;
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("id", documentReference.getId());
|
||||
resultMap.put("path", documentReference.getPath());
|
||||
return resultMap;
|
||||
|
||||
} else {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
|
|
@ -355,7 +399,7 @@ public class FirestorePlugin extends BasePlugin {
|
|||
|
||||
@Override
|
||||
public Mono<Firestore> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
|
||||
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
|
||||
final Set<String> errors = validateDatasource(datasourceConfiguration);
|
||||
if (!CollectionUtils.isEmpty(errors)) {
|
||||
|
|
@ -405,7 +449,7 @@ public class FirestorePlugin extends BasePlugin {
|
|||
|
||||
@Override
|
||||
public Set<String> validateDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
|
||||
Set<String> invalids = new HashSet<>();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ public enum Method {
|
|||
GET_COLLECTION(false, false),
|
||||
SET_DOCUMENT(true, true),
|
||||
CREATE_DOCUMENT(true, true),
|
||||
ADD_TO_COLLECTION(false, true),
|
||||
UPDATE_DOCUMENT(true, true),
|
||||
DELETE_DOCUMENT(true, false),
|
||||
;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@
|
|||
"label": "Create Document",
|
||||
"value": "CREATE_DOCUMENT"
|
||||
},
|
||||
{
|
||||
"label": "Add Document to Collection",
|
||||
"value": "ADD_TO_COLLECTION"
|
||||
},
|
||||
{
|
||||
"label": "Update Document",
|
||||
"value": "UPDATE_DOCUMENT"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Property;
|
||||
import com.google.cloud.NoCredentials;
|
||||
|
|
@ -25,6 +25,7 @@ import java.util.concurrent.ExecutionException;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
|
|
@ -60,9 +61,10 @@ public class FirestorePluginTest {
|
|||
firestoreConnection.document("changing/to-delete").set(Map.of("value", 1)).get();
|
||||
|
||||
dsConfig.setUrl(emulator.getEmulatorEndpoint());
|
||||
dsConfig.setAuthentication(new AuthenticationDTO());
|
||||
dsConfig.getAuthentication().setUsername("test-project");
|
||||
dsConfig.getAuthentication().setPassword("");
|
||||
DBAuth auth = new DBAuth();
|
||||
auth.setUsername("test-project");
|
||||
auth.setPassword("");
|
||||
dsConfig.setAuthentication(auth);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -203,4 +205,27 @@ public class FirestorePluginTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddToCollection() {
|
||||
ActionConfiguration actionConfiguration = new ActionConfiguration();
|
||||
actionConfiguration.setPath("changing");
|
||||
|
||||
actionConfiguration.setPluginSpecifiedTemplates(List.of(new Property("method", "ADD_TO_COLLECTION")));
|
||||
|
||||
actionConfiguration.setBody("{\n" +
|
||||
" \"question\": \"What is the answer to life, universe and everything else?\",\n" +
|
||||
" \"answer\": 42\n" +
|
||||
"}");
|
||||
|
||||
Mono<ActionExecutionResult> resultMono = pluginExecutor
|
||||
.execute(firestoreConnection, dsConfig, actionConfiguration);
|
||||
|
||||
StepVerifier.create(resultMono)
|
||||
.assertNext(result -> {
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(firestoreConnection.document("changing/" + ((Map) result.getBody()).get("id")));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.Connection;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceStructure;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
|
|
@ -53,10 +53,10 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class MongoPlugin extends BasePlugin {
|
||||
|
||||
private static final Set<AuthenticationDTO.Type> VALID_AUTH_TYPES = Set.of(
|
||||
AuthenticationDTO.Type.SCRAM_SHA_1,
|
||||
AuthenticationDTO.Type.SCRAM_SHA_256,
|
||||
AuthenticationDTO.Type.MONGODB_CR // NOTE: Deprecated in the driver.
|
||||
private static final Set<DBAuth.Type> VALID_AUTH_TYPES = Set.of(
|
||||
DBAuth.Type.SCRAM_SHA_1,
|
||||
DBAuth.Type.SCRAM_SHA_256,
|
||||
DBAuth.Type.MONGODB_CR // NOTE: Deprecated in the driver.
|
||||
);
|
||||
|
||||
private static final String VALID_AUTH_TYPES_STR = VALID_AUTH_TYPES.stream()
|
||||
|
|
@ -180,7 +180,7 @@ public class MongoPlugin extends BasePlugin {
|
|||
String databaseName = datasourceConfiguration.getConnection().getDefaultDatabaseName();
|
||||
|
||||
// If that's not available, pick the authentication database.
|
||||
final AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (StringUtils.isEmpty(databaseName) && authentication != null) {
|
||||
databaseName = authentication.getDatabaseName();
|
||||
}
|
||||
|
|
@ -221,7 +221,7 @@ public class MongoPlugin extends BasePlugin {
|
|||
builder.append("mongodb://");
|
||||
}
|
||||
|
||||
AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (authentication != null) {
|
||||
builder
|
||||
.append(urlEncode(authentication.getUsername()))
|
||||
|
|
@ -293,12 +293,12 @@ public class MongoPlugin extends BasePlugin {
|
|||
|
||||
}
|
||||
|
||||
AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (authentication == null) {
|
||||
invalids.add("Missing authentication details.");
|
||||
|
||||
} else {
|
||||
AuthenticationDTO.Type authType = authentication.getAuthType();
|
||||
DBAuth.Type authType = authentication.getAuthType();
|
||||
|
||||
if (authType != null && VALID_AUTH_TYPES.contains(authType)) {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"templates": [
|
||||
{
|
||||
"file": "CREATE.json"
|
||||
},
|
||||
{
|
||||
"file": "READ.json"
|
||||
},
|
||||
{
|
||||
"file": "UPDATE.json"
|
||||
},
|
||||
{
|
||||
"file": "DELETE.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
|
|
@ -196,7 +196,7 @@ public class MssqlPlugin extends BasePlugin {
|
|||
));
|
||||
}
|
||||
|
||||
AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
|
||||
com.appsmith.external.models.Connection configurationConnection = datasourceConfiguration.getConnection();
|
||||
|
||||
|
|
@ -282,15 +282,16 @@ public class MssqlPlugin extends BasePlugin {
|
|||
invalids.add("Missing Connection Mode.");
|
||||
}
|
||||
|
||||
if (datasourceConfiguration.getAuthentication() == null) {
|
||||
DBAuth auth = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (auth == null) {
|
||||
invalids.add("Missing authentication details.");
|
||||
|
||||
} else {
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getUsername())) {
|
||||
if (StringUtils.isEmpty(auth.getUsername())) {
|
||||
invalids.add("Missing username for authentication.");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getPassword())) {
|
||||
if (StringUtils.isEmpty(auth.getPassword())) {
|
||||
invalids.add("Missing password for authentication.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"templates": [
|
||||
{
|
||||
"file": "CREATE.sql"
|
||||
},
|
||||
{
|
||||
"file": "SELECT.sql"
|
||||
},
|
||||
{
|
||||
"file": "UPDATE.sql"
|
||||
},
|
||||
{
|
||||
"file": "DELETE.sql"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import com.appsmith.external.pluginExceptions.AppsmithPluginException;
|
||||
|
|
@ -107,8 +107,8 @@ public class MssqlPluginTest {
|
|||
}
|
||||
|
||||
private DatasourceConfiguration createDatasourceConfiguration() {
|
||||
AuthenticationDTO authDTO = new AuthenticationDTO();
|
||||
authDTO.setAuthType(AuthenticationDTO.Type.USERNAME_PASSWORD);
|
||||
DBAuth authDTO = new DBAuth();
|
||||
authDTO.setAuthType(DBAuth.Type.USERNAME_PASSWORD);
|
||||
authDTO.setUsername(username);
|
||||
authDTO.setPassword(password);
|
||||
|
||||
|
|
@ -208,8 +208,9 @@ public class MssqlPluginTest {
|
|||
|
||||
DatasourceConfiguration dsConfig = createDatasourceConfiguration();
|
||||
// Set up random username and password and try to connect
|
||||
dsConfig.getAuthentication().setUsername(new ObjectId().toString());
|
||||
dsConfig.getAuthentication().setPassword(new ObjectId().toString());
|
||||
DBAuth auth = (DBAuth) dsConfig.getAuthentication();
|
||||
auth.setUsername(new ObjectId().toString());
|
||||
auth.setPassword(new ObjectId().toString());
|
||||
|
||||
Mono<Connection> dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceStructure;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
|
|
@ -55,15 +55,15 @@ public class MySqlPlugin extends BasePlugin {
|
|||
private static final String TIMESTAMP_COLUMN_TYPE_NAME = "timestamp";
|
||||
|
||||
/**
|
||||
Example output for COLUMNS_QUERY:
|
||||
+------------+-----------+-------------+-------------+-------------+------------+----------------+
|
||||
| table_name | column_id | column_name | column_type | is_nullable | COLUMN_KEY | EXTRA |
|
||||
+------------+-----------+-------------+-------------+-------------+------------+----------------+
|
||||
| test | 1 | id | int | 0 | PRI | auto_increment |
|
||||
| test | 2 | firstname | varchar | 1 | | |
|
||||
| test | 3 | middlename | varchar | 1 | | |
|
||||
| test | 4 | lastname | varchar | 1 | | |
|
||||
+------------+-----------+-------------+-------------+-------------+------------+----------------+
|
||||
* Example output for COLUMNS_QUERY:
|
||||
* +------------+-----------+-------------+-------------+-------------+------------+----------------+
|
||||
* | table_name | column_id | column_name | column_type | is_nullable | COLUMN_KEY | EXTRA |
|
||||
* +------------+-----------+-------------+-------------+-------------+------------+----------------+
|
||||
* | test | 1 | id | int | 0 | PRI | auto_increment |
|
||||
* | test | 2 | firstname | varchar | 1 | | |
|
||||
* | test | 3 | middlename | varchar | 1 | | |
|
||||
* | test | 4 | lastname | varchar | 1 | | |
|
||||
* +------------+-----------+-------------+-------------+-------------+------------+----------------+
|
||||
*/
|
||||
private static final String COLUMNS_QUERY = "select tab.table_name as table_name,\n" +
|
||||
" col.ordinal_position as column_id,\n" +
|
||||
|
|
@ -82,12 +82,12 @@ public class MySqlPlugin extends BasePlugin {
|
|||
" col.ordinal_position;";
|
||||
|
||||
/**
|
||||
Example output for KEYS_QUERY:
|
||||
+-----------------+-------------+------------+-----------------+-------------+----------------+---------------+----------------+
|
||||
| CONSTRAINT_NAME | self_schema | self_table | constraint_type | self_column | foreign_schema | foreign_table | foreign_column |
|
||||
+-----------------+-------------+------------+-----------------+-------------+----------------+---------------+----------------+
|
||||
| PRIMARY | mytestdb | test | p | id | NULL | NULL | NULL |
|
||||
+-----------------+-------------+------------+-----------------+-------------+----------------+---------------+----------------+
|
||||
* Example output for KEYS_QUERY:
|
||||
* +-----------------+-------------+------------+-----------------+-------------+----------------+---------------+----------------+
|
||||
* | CONSTRAINT_NAME | self_schema | self_table | constraint_type | self_column | foreign_schema | foreign_table | foreign_column |
|
||||
* +-----------------+-------------+------------+-----------------+-------------+----------------+---------------+----------------+
|
||||
* | PRIMARY | mytestdb | test | p | id | NULL | NULL | NULL |
|
||||
* +-----------------+-------------+------------+-----------------+-------------+----------------+---------------+----------------+
|
||||
*/
|
||||
private static final String KEYS_QUERY = "select i.constraint_name,\n" +
|
||||
" i.TABLE_SCHEMA as self_schema,\n" +
|
||||
|
|
@ -123,18 +123,17 @@ public class MySqlPlugin extends BasePlugin {
|
|||
Iterator<ColumnMetadata> iterator = (Iterator<ColumnMetadata>) meta.getColumnMetadatas().iterator();
|
||||
Map<String, Object> processedRow = new LinkedHashMap<>();
|
||||
|
||||
while(iterator.hasNext()) {
|
||||
while (iterator.hasNext()) {
|
||||
ColumnMetadata metaData = iterator.next();
|
||||
String columnName = metaData.getName();
|
||||
String typeName = metaData.getJavaType().toString();
|
||||
Object columnValue = row.get(columnName);
|
||||
|
||||
if(java.time.LocalDate.class.toString().equalsIgnoreCase(typeName)
|
||||
if (java.time.LocalDate.class.toString().equalsIgnoreCase(typeName)
|
||||
&& columnValue != null) {
|
||||
columnValue = DateTimeFormatter.ISO_DATE.format(row.get(columnName,
|
||||
LocalDate.class));
|
||||
}
|
||||
else if ((java.time.LocalDateTime.class.toString().equalsIgnoreCase(typeName))
|
||||
} else if ((java.time.LocalDateTime.class.toString().equalsIgnoreCase(typeName))
|
||||
&& columnValue != null) {
|
||||
columnValue = DateTimeFormatter.ISO_DATE_TIME.format(
|
||||
LocalDateTime.of(
|
||||
|
|
@ -142,17 +141,14 @@ public class MySqlPlugin extends BasePlugin {
|
|||
row.get(columnName, LocalDateTime.class).toLocalTime()
|
||||
)
|
||||
) + "Z";
|
||||
}
|
||||
else if(java.time.LocalTime.class.toString().equalsIgnoreCase(typeName)
|
||||
} else if (java.time.LocalTime.class.toString().equalsIgnoreCase(typeName)
|
||||
&& columnValue != null) {
|
||||
columnValue = DateTimeFormatter.ISO_TIME.format(row.get(columnName,
|
||||
LocalTime.class));
|
||||
}
|
||||
else if (java.time.Year.class.toString().equalsIgnoreCase(typeName)
|
||||
} else if (java.time.Year.class.toString().equalsIgnoreCase(typeName)
|
||||
&& columnValue != null) {
|
||||
columnValue = row.get(columnName, LocalDate.class).getYear();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
columnValue = row.get(columnName);
|
||||
}
|
||||
|
||||
|
|
@ -165,10 +161,10 @@ public class MySqlPlugin extends BasePlugin {
|
|||
/**
|
||||
* 1. Check the type of sql query - i.e Select ... or Insert/Update/Drop
|
||||
* 2. In case sql queries are chained together, then decide the type based on the last query. i.e In case of
|
||||
* query "select * from test; updated test ..." the type of query will be based on the update statement.
|
||||
* query "select * from test; updated test ..." the type of query will be based on the update statement.
|
||||
* 3. This is used because the output returned to client is based on the type of the query. In case of a
|
||||
* select query rows are returned, whereas, in case of any other query the number of updated rows is
|
||||
* returned.
|
||||
* select query rows are returned, whereas, in case of any other query the number of updated rows is
|
||||
* returned.
|
||||
*/
|
||||
private boolean getIsSelectOrShowQuery(String query) {
|
||||
String[] queries = query.split(";");
|
||||
|
|
@ -189,17 +185,16 @@ public class MySqlPlugin extends BasePlugin {
|
|||
boolean isSelectOrShowQuery = getIsSelectOrShowQuery(query);
|
||||
final List<Map<String, Object>> rowsList = new ArrayList<>(50);
|
||||
Flux<Result> resultFlux = Mono.from(connection.validate(ValidationDepth.REMOTE))
|
||||
.flatMapMany(isValid -> {
|
||||
if(isValid) {
|
||||
return connection.createStatement(query).execute();
|
||||
}
|
||||
else {
|
||||
return Flux.error(new StaleConnectionException());
|
||||
}
|
||||
});
|
||||
.flatMapMany(isValid -> {
|
||||
if (isValid) {
|
||||
return connection.createStatement(query).execute();
|
||||
} else {
|
||||
return Flux.error(new StaleConnectionException());
|
||||
}
|
||||
});
|
||||
Mono<List<Map<String, Object>>> resultMono = null;
|
||||
|
||||
if(isSelectOrShowQuery) {
|
||||
if (isSelectOrShowQuery) {
|
||||
resultMono = resultFlux
|
||||
.flatMap(result -> {
|
||||
return result.map((row, meta) -> {
|
||||
|
|
@ -211,8 +206,7 @@ public class MySqlPlugin extends BasePlugin {
|
|||
.flatMap(execResult -> {
|
||||
return Mono.just(rowsList);
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
resultMono = resultFlux
|
||||
.flatMap(result -> result.getRowsUpdated())
|
||||
.collectList()
|
||||
|
|
@ -243,7 +237,7 @@ public class MySqlPlugin extends BasePlugin {
|
|||
|
||||
@Override
|
||||
public Mono<Connection> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
|
||||
AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
com.appsmith.external.models.Connection configurationConnection = datasourceConfiguration.getConnection();
|
||||
|
||||
StringBuilder urlBuilder = new StringBuilder();
|
||||
|
|
@ -328,16 +322,17 @@ public class MySqlPlugin extends BasePlugin {
|
|||
if (datasourceConfiguration.getAuthentication() == null) {
|
||||
invalids.add("Missing authentication details.");
|
||||
} else {
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getUsername())) {
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (StringUtils.isEmpty(authentication.getUsername())) {
|
||||
invalids.add("Missing username for authentication.");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getPassword())) {
|
||||
if (StringUtils.isEmpty(authentication.getPassword())) {
|
||||
invalids.add("Missing password for authentication.");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getDatabaseName())) {
|
||||
invalids.add("Missing database name");
|
||||
if (StringUtils.isEmpty(authentication.getDatabaseName())) {
|
||||
invalids.add("Missing database name.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -516,11 +511,11 @@ public class MySqlPlugin extends BasePlugin {
|
|||
.collectList()
|
||||
.thenMany(Flux.from(connection.createStatement(KEYS_QUERY).execute()))
|
||||
.flatMap(result -> {
|
||||
return result.map((row, meta) -> {
|
||||
getKeyInfo(row, meta, tablesByName, keyRegistry);
|
||||
return result.map((row, meta) -> {
|
||||
getKeyInfo(row, meta, tablesByName, keyRegistry);
|
||||
|
||||
return result;
|
||||
});
|
||||
return result;
|
||||
});
|
||||
})
|
||||
.collectList()
|
||||
.map(list -> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"templates": [
|
||||
{
|
||||
"file": "CREATE.sql"
|
||||
},
|
||||
{
|
||||
"file": "SELECT.sql"
|
||||
},
|
||||
{
|
||||
"file": "UPDATE.sql"
|
||||
},
|
||||
{
|
||||
"file": "DELETE.sql"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
package com.external.plugins;
|
||||
|
||||
import com.appsmith.external.models.*;
|
||||
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 com.appsmith.external.models.Property;
|
||||
import com.appsmith.external.pluginExceptions.StaleConnectionException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
|
@ -9,7 +15,6 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
|||
import io.r2dbc.spi.ConnectionFactoryOptions;
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactories;
|
||||
import io.r2dbc.spi.Batch;
|
||||
import lombok.extern.log4j.Log4j;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
|
|
@ -119,8 +124,8 @@ public class MySqlPluginTest {
|
|||
}
|
||||
|
||||
private static DatasourceConfiguration createDatasourceConfiguration() {
|
||||
AuthenticationDTO authDTO = new AuthenticationDTO();
|
||||
authDTO.setAuthType(AuthenticationDTO.Type.USERNAME_PASSWORD);
|
||||
DBAuth authDTO = new DBAuth();
|
||||
authDTO.setAuthType(DBAuth.Type.USERNAME_PASSWORD);
|
||||
authDTO.setUsername(username);
|
||||
authDTO.setPassword(password);
|
||||
authDTO.setDatabaseName(database);
|
||||
|
|
@ -147,8 +152,8 @@ public class MySqlPluginTest {
|
|||
|
||||
@Test
|
||||
public void testConnectMySQLContainerWithInvalidTimezone() {
|
||||
AuthenticationDTO authDTO = new AuthenticationDTO();
|
||||
authDTO.setAuthType(AuthenticationDTO.Type.USERNAME_PASSWORD);
|
||||
DBAuth authDTO = new DBAuth();
|
||||
authDTO.setAuthType(DBAuth.Type.USERNAME_PASSWORD);
|
||||
authDTO.setUsername(mySQLContainerWithInvalidTimezone.getUsername());
|
||||
authDTO.setPassword(mySQLContainerWithInvalidTimezone.getPassword());
|
||||
authDTO.setDatabaseName(mySQLContainerWithInvalidTimezone.getDatabaseName());
|
||||
|
|
@ -227,9 +232,10 @@ public class MySqlPluginTest {
|
|||
@Test
|
||||
public void testValidateDatasourceNullCredentials() {
|
||||
dsConfig.setConnection(new com.appsmith.external.models.Connection());
|
||||
dsConfig.getAuthentication().setUsername(null);
|
||||
dsConfig.getAuthentication().setPassword(null);
|
||||
dsConfig.getAuthentication().setDatabaseName("someDbName");
|
||||
DBAuth auth = (DBAuth) dsConfig.getAuthentication();
|
||||
auth.setUsername(null);
|
||||
auth.setPassword(null);
|
||||
auth.setDatabaseName("someDbName");
|
||||
Set<String> output = pluginExecutor.validateDatasource(dsConfig);
|
||||
assertTrue(output.contains("Missing username for authentication."));
|
||||
assertTrue(output.contains("Missing password for authentication."));
|
||||
|
|
@ -237,10 +243,10 @@ public class MySqlPluginTest {
|
|||
|
||||
@Test
|
||||
public void testValidateDatasourceMissingDBName() {
|
||||
dsConfig.getAuthentication().setDatabaseName("");
|
||||
((DBAuth) dsConfig.getAuthentication()).setDatabaseName("");
|
||||
Set<String> output = pluginExecutor.validateDatasource(dsConfig);
|
||||
assertEquals(output.size(), 1);
|
||||
assertTrue(output.contains("Missing database name"));
|
||||
assertTrue(output.contains("Missing database name."));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceStructure;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
|
|
@ -288,15 +288,16 @@ public class PostgresPlugin extends BasePlugin {
|
|||
invalids.add("Missing authentication details.");
|
||||
|
||||
} else {
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getUsername())) {
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (StringUtils.isEmpty(authentication.getUsername())) {
|
||||
invalids.add("Missing username for authentication.");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getPassword())) {
|
||||
if (StringUtils.isEmpty(authentication.getPassword())) {
|
||||
invalids.add("Missing password for authentication.");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(datasourceConfiguration.getAuthentication().getDatabaseName())) {
|
||||
if (StringUtils.isEmpty(authentication.getDatabaseName())) {
|
||||
invalids.add("Missing database name.");
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +510,7 @@ public class PostgresPlugin extends BasePlugin {
|
|||
config.addDataSourceProperty(SSL, isSslEnabled);
|
||||
|
||||
// Set authentication properties
|
||||
AuthenticationDTO authentication = datasourceConfiguration.getAuthentication();
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (authentication.getUsername() != null) {
|
||||
config.setUsername(authentication.getUsername());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"templates": [
|
||||
{
|
||||
"file": "CREATE.sql"
|
||||
},
|
||||
{
|
||||
"file": "SELECT.sql"
|
||||
},
|
||||
{
|
||||
"file": "UPDATE.sql"
|
||||
},
|
||||
{
|
||||
"file": "DELETE.sql"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.external.plugins;
|
|||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceStructure;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
|
|
@ -138,8 +139,8 @@ public class PostgresPluginTest {
|
|||
}
|
||||
|
||||
private DatasourceConfiguration createDatasourceConfiguration() {
|
||||
AuthenticationDTO authDTO = new AuthenticationDTO();
|
||||
authDTO.setAuthType(AuthenticationDTO.Type.USERNAME_PASSWORD);
|
||||
DBAuth authDTO = new DBAuth();
|
||||
authDTO.setAuthType(DBAuth.Type.USERNAME_PASSWORD);
|
||||
authDTO.setUsername(username);
|
||||
authDTO.setPassword(password);
|
||||
authDTO.setDatabaseName("postgres");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
|
|
@ -113,8 +113,8 @@ public class RedisPlugin extends BasePlugin {
|
|||
Integer port = (int) (long) ObjectUtils.defaultIfNull(endpoint.getPort(), DEFAULT_PORT);
|
||||
Jedis jedis = new Jedis(endpoint.getHost(), port);
|
||||
|
||||
AuthenticationDTO auth = datasourceConfiguration.getAuthentication();
|
||||
if (auth != null && AuthenticationDTO.Type.USERNAME_PASSWORD.equals(auth.getAuthType())) {
|
||||
DBAuth auth = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (auth != null && DBAuth.Type.USERNAME_PASSWORD.equals(auth.getAuthType())) {
|
||||
jedis.auth(auth.getUsername(), auth.getPassword());
|
||||
}
|
||||
|
||||
|
|
@ -158,13 +158,13 @@ public class RedisPlugin extends BasePlugin {
|
|||
}
|
||||
}
|
||||
|
||||
AuthenticationDTO auth = datasourceConfiguration.getAuthentication();
|
||||
if (auth != null && AuthenticationDTO.Type.USERNAME_PASSWORD.equals(auth.getAuthType())) {
|
||||
if (StringUtils.isNullOrEmpty(datasourceConfiguration.getAuthentication().getUsername())) {
|
||||
DBAuth auth = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (auth != null && DBAuth.Type.USERNAME_PASSWORD.equals(auth.getAuthType())) {
|
||||
if (StringUtils.isNullOrEmpty(auth.getUsername())) {
|
||||
invalids.add("Missing username for authentication.");
|
||||
}
|
||||
|
||||
if (StringUtils.isNullOrEmpty(datasourceConfiguration.getAuthentication().getPassword())) {
|
||||
if (StringUtils.isNullOrEmpty(auth.getPassword())) {
|
||||
invalids.add("Missing password for authentication.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.external.plugins;
|
|||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
|
|
@ -99,8 +99,8 @@ public class RedisPluginTest {
|
|||
Endpoint endpoint = new Endpoint();
|
||||
endpoint.setHost("test-host");
|
||||
|
||||
AuthenticationDTO invalidAuth = new AuthenticationDTO();
|
||||
invalidAuth.setAuthType(AuthenticationDTO.Type.USERNAME_PASSWORD);
|
||||
DBAuth invalidAuth = new DBAuth();
|
||||
invalidAuth.setAuthType(DBAuth.Type.USERNAME_PASSWORD);
|
||||
|
||||
invalidDatasourceConfiguration.setAuthentication(invalidAuth);
|
||||
invalidDatasourceConfiguration.setEndpoints(Collections.singletonList(endpoint));
|
||||
|
|
@ -115,8 +115,8 @@ public class RedisPluginTest {
|
|||
public void itShouldValidateDatasource() {
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
|
||||
AuthenticationDTO auth = new AuthenticationDTO();
|
||||
auth.setAuthType(AuthenticationDTO.Type.USERNAME_PASSWORD);
|
||||
DBAuth auth = new DBAuth();
|
||||
auth.setAuthType(DBAuth.Type.USERNAME_PASSWORD);
|
||||
auth.setUsername("test-username");
|
||||
auth.setPassword("test-password");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.appsmith.server.configurations;
|
||||
|
||||
import com.appsmith.external.annotations.DocumentTypeMapper;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.server.configurations.mongo.SoftDeleteMongoRepositoryFactoryBean;
|
||||
import com.appsmith.server.repositories.BaseRepositoryImpl;
|
||||
import com.github.cloudyrock.mongock.SpringBootMongock;
|
||||
|
|
@ -8,10 +10,21 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.convert.DefaultTypeMapper;
|
||||
import org.springframework.data.convert.SimpleTypeInformationMapper;
|
||||
import org.springframework.data.convert.TypeInformationMapper;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.config.EnableMongoAuditing;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoTypeMapper;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This configures the JPA Mongo repositories. The default base implementation is defined in {@link BaseRepositoryImpl}.
|
||||
* This is required to add default clauses for default JPA queries defined by Spring Data.
|
||||
|
|
@ -39,4 +52,28 @@ public class MongoConfig {
|
|||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MappingMongoConverter mappingMongoConverter) {
|
||||
return new MongoTemplate(mongoDbFactory, mappingMongoConverter);
|
||||
}
|
||||
|
||||
// Custom type mapper here includes our annotation based mapper that is meant to ensure correct mapping for sub-classes
|
||||
// We have currently only included the package which contains the DTOs that need this mapping
|
||||
@Bean
|
||||
public DefaultTypeMapper typeMapper() {
|
||||
TypeInformationMapper typeInformationMapper = new DocumentTypeMapper
|
||||
.Builder()
|
||||
.withBasePackages(new String[]{AuthenticationDTO.class.getPackageName()})
|
||||
.build();
|
||||
// This is a hack to include the default mapper as a fallback, because Spring seems to override its list instead of appending mappers
|
||||
return new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, Arrays.asList(typeInformationMapper, new SimpleTypeInformationMapper()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MappingMongoConverter mappingMongoConverter(DefaultTypeMapper typeMapper, MongoMappingContext context) {
|
||||
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
|
||||
converter.setTypeMapper((MongoTypeMapper) typeMapper);
|
||||
return converter;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,17 @@ import com.appsmith.server.services.UserOrganizationService;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import lombok.Setter;
|
|||
import lombok.ToString;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ package com.appsmith.server.exceptions;
|
|||
|
||||
import com.appsmith.external.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.server.dtos.ResponseDTO;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.SentryLevel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.FieldError;
|
||||
|
|
@ -13,17 +14,12 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.SentryEvent;
|
||||
import io.sentry.SentryOptions;
|
||||
import io.sentry.SentryLevel;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.io.StringWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -74,7 +74,9 @@ public final class BeanCopyUtils {
|
|||
|
||||
Object targetValue = targetBeanWrapper.getPropertyValue(name);
|
||||
|
||||
if (targetValue != null && isDomainModel(propertyDescriptor.getPropertyType())) {
|
||||
if (targetValue != null
|
||||
&& sourceValue.getClass().isAssignableFrom(targetValue.getClass())
|
||||
&& isDomainModel(propertyDescriptor.getPropertyType())) {
|
||||
// Go deeper *only* if the property belongs to Appsmith's models, and both the source and target values
|
||||
// are not null.
|
||||
copyNestedNonNullProperties(sourceValue, targetValue);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.appsmith.server.migrations;
|
||||
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AppsmithRole;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
|
|
@ -38,14 +38,21 @@ import com.appsmith.server.services.OrganizationService;
|
|||
import com.github.cloudyrock.mongock.ChangeLog;
|
||||
import com.github.cloudyrock.mongock.ChangeSet;
|
||||
import com.google.gson.Gson;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.data.mongodb.core.CollectionCallback;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndexDefinition;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
|
|
@ -556,7 +563,7 @@ public class DatabaseChangelog {
|
|||
);
|
||||
|
||||
for (final Datasource datasource : datasources) {
|
||||
AuthenticationDTO authentication = datasource.getDatasourceConfiguration().getAuthentication();
|
||||
DBAuth authentication = (DBAuth) datasource.getDatasourceConfiguration().getAuthentication();
|
||||
authentication.setPassword(encryptionService.encryptString(authentication.getPassword()));
|
||||
mongoTemplate.save(datasource);
|
||||
}
|
||||
|
|
@ -1408,4 +1415,84 @@ public class DatabaseChangelog {
|
|||
}
|
||||
}
|
||||
|
||||
@ChangeSet(order = "045", id = "update-authentication-type", author = "")
|
||||
public void updateAuthenticationTypes(MongoTemplate mongoTemplate) {
|
||||
mongoTemplate.execute("datasource", new CollectionCallback<String>() {
|
||||
@Override
|
||||
public String doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
// Only update _class for authentication objects that exist
|
||||
MongoCursor cursor = collection.find(Filters.exists("datasourceConfiguration.authentication")).cursor();
|
||||
while (cursor.hasNext()) {
|
||||
Document current = (Document) cursor.next();
|
||||
Document old = Document.parse(current.toJson());
|
||||
|
||||
// Extra precaution to only update _class for authentication objects that don't already have this
|
||||
// Is this condition required? What does production datasource look like?
|
||||
((Document) ((Document) current.get("datasourceConfiguration"))
|
||||
.get("authentication"))
|
||||
.putIfAbsent("_class", "dbAuth");
|
||||
|
||||
// Replace old document with the new one
|
||||
collection.findOneAndReplace(old, current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
mongoTemplate.execute("newAction", new CollectionCallback<String>() {
|
||||
@Override
|
||||
public String doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
// Only update _class for authentication objects that exist
|
||||
MongoCursor cursor = collection
|
||||
.find(Filters.and(
|
||||
Filters.exists("unpublishedAction.datasource"),
|
||||
Filters.exists("unpublishedAction.datasource.datasourceConfiguration"),
|
||||
Filters.exists("unpublishedAction.datasource.datasourceConfiguration.authentication"))).cursor();
|
||||
while (cursor.hasNext()) {
|
||||
Document current = (Document) cursor.next();
|
||||
Document old = Document.parse(current.toJson());
|
||||
|
||||
// Extra precaution to only update _class for authentication objects that don't already have this
|
||||
// Is this condition required? What does production datasource look like?
|
||||
((Document) ((Document) ((Document) ((Document) current.get("unpublishedAction"))
|
||||
.get("datasource"))
|
||||
.get("datasourceConfiguration"))
|
||||
.get("authentication"))
|
||||
.putIfAbsent("_class", "dbAuth");
|
||||
|
||||
// Replace old document with the new one
|
||||
collection.findOneAndReplace(old, current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
mongoTemplate.execute("newAction", new CollectionCallback<String>() {
|
||||
@Override
|
||||
public String doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
// Only update _class for authentication objects that exist
|
||||
MongoCursor cursor = collection
|
||||
.find(Filters.and(
|
||||
Filters.exists("publishedAction.datasource"),
|
||||
Filters.exists("publishedAction.datasource.datasourceConfiguration"),
|
||||
Filters.exists("publishedAction.datasource.datasourceConfiguration.authentication"))).cursor();
|
||||
while (cursor.hasNext()) {
|
||||
Document current = (Document) cursor.next();
|
||||
Document old = Document.parse(current.toJson());
|
||||
|
||||
// Extra precaution to only update _class for authentication objects that don't already have this
|
||||
// Is this condition required? What does production datasource look like?
|
||||
((Document) ((Document) ((Document) ((Document) current.get("publishedAction"))
|
||||
.get("datasource"))
|
||||
.get("datasourceConfiguration"))
|
||||
.get("authentication"))
|
||||
.putIfAbsent("_class", "dbAuth");
|
||||
|
||||
// Replace old document with the new one
|
||||
collection.findOneAndReplace(old, current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import reactor.core.publisher.Mono;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES;
|
||||
|
||||
|
|
@ -170,10 +171,15 @@ public class DatasourceContextServiceImpl implements DatasourceContextService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationDTO decryptSensitiveFields(AuthenticationDTO authenticationDTO) {
|
||||
if (authenticationDTO != null && authenticationDTO.getPassword() != null) {
|
||||
authenticationDTO.setPassword(encryptionService.decryptString(authenticationDTO.getPassword()));
|
||||
public AuthenticationDTO decryptSensitiveFields(AuthenticationDTO authentication) {
|
||||
if (authentication != null && authentication.isEncrypted()) {
|
||||
Map<String, String> decryptedFields = authentication.getEncryptionFields().entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> encryptionService.decryptString(e.getValue())));
|
||||
authentication.setEncryptionFields(decryptedFields);
|
||||
authentication.setIsEncrypted(false);
|
||||
}
|
||||
return authenticationDTO;
|
||||
return authentication;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.Datasource;
|
||||
|
|
@ -28,4 +29,6 @@ public interface DatasourceService extends CrudService<Datasource, String> {
|
|||
Flux<Datasource> findAllByOrganizationId(String organizationId, AclPermission readDatasources);
|
||||
|
||||
Flux<Datasource> saveAll(List<Datasource> datasourceList);
|
||||
|
||||
AuthenticationDTO encryptAuthenticationFields(AuthenticationDTO authentication);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.appsmith.server.services;
|
|||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
|
|
@ -12,7 +11,6 @@ import com.appsmith.server.constants.FieldName;
|
|||
import com.appsmith.server.domains.Datasource;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.Plugin;
|
||||
import com.appsmith.server.domains.PluginType;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
|
|
@ -36,6 +34,7 @@ import javax.validation.Validator;
|
|||
import javax.validation.constraints.NotNull;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -111,23 +110,23 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
|
|||
return datasourceMono
|
||||
.flatMap(datasource1 ->
|
||||
sessionUserService.getCurrentUser()
|
||||
.flatMap(user -> {
|
||||
// Create policies for this datasource -> This datasource should inherit its permissions and policies from
|
||||
// the organization and this datasource should also allow the current user to crud this datasource.
|
||||
return organizationService.findById(datasource1.getOrganizationId(), AclPermission.ORGANIZATION_MANAGE_APPLICATIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, datasource1.getOrganizationId())))
|
||||
.map(org -> {
|
||||
Set<Policy> policySet = org.getPolicies().stream()
|
||||
.filter(policy ->
|
||||
policy.getPermission().equals(ORGANIZATION_MANAGE_APPLICATIONS.getValue()) ||
|
||||
policy.getPermission().equals(ORGANIZATION_READ_APPLICATIONS.getValue())
|
||||
).collect(Collectors.toSet());
|
||||
.flatMap(user -> {
|
||||
// Create policies for this datasource -> This datasource should inherit its permissions and policies from
|
||||
// the organization and this datasource should also allow the current user to crud this datasource.
|
||||
return organizationService.findById(datasource1.getOrganizationId(), AclPermission.ORGANIZATION_MANAGE_APPLICATIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, datasource1.getOrganizationId())))
|
||||
.map(org -> {
|
||||
Set<Policy> policySet = org.getPolicies().stream()
|
||||
.filter(policy ->
|
||||
policy.getPermission().equals(ORGANIZATION_MANAGE_APPLICATIONS.getValue()) ||
|
||||
policy.getPermission().equals(ORGANIZATION_READ_APPLICATIONS.getValue())
|
||||
).collect(Collectors.toSet());
|
||||
|
||||
Set<Policy> documentPolicies = policyGenerator.getAllChildPolicies(policySet, Organization.class, Datasource.class);
|
||||
datasource1.setPolicies(documentPolicies);
|
||||
return datasource1;
|
||||
});
|
||||
})
|
||||
Set<Policy> documentPolicies = policyGenerator.getAllChildPolicies(policySet, Organization.class, Datasource.class);
|
||||
datasource1.setPolicies(documentPolicies);
|
||||
return datasource1;
|
||||
});
|
||||
})
|
||||
)
|
||||
.flatMap(this::validateAndSaveDatasourceToRepository);
|
||||
}
|
||||
|
|
@ -157,10 +156,17 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
|
|||
.flatMap(this::validateAndSaveDatasourceToRepository);
|
||||
}
|
||||
|
||||
private AuthenticationDTO encryptAuthenticationFields(AuthenticationDTO authentication) {
|
||||
// Encrypt password in AuthenticationDTO
|
||||
if (authentication != null && authentication.getPassword() != null) {
|
||||
authentication.setPassword(encryptionService.encryptString(authentication.getPassword()));
|
||||
@Override
|
||||
public AuthenticationDTO encryptAuthenticationFields(AuthenticationDTO authentication) {
|
||||
if (authentication != null
|
||||
&& CollectionUtils.isEmpty(authentication.getEmptyEncryptionFields())
|
||||
&& !authentication.isEncrypted()) {
|
||||
Map<String, String> encryptedFields = authentication.getEncryptionFields().entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> encryptionService.encryptString(e.getValue())));
|
||||
authentication.setEncryptionFields(encryptedFields);
|
||||
authentication.setIsEncrypted(true);
|
||||
}
|
||||
return authentication;
|
||||
}
|
||||
|
|
@ -232,30 +238,36 @@ public class DatasourceServiceImpl extends BaseService<DatasourceRepository, Dat
|
|||
}
|
||||
|
||||
/**
|
||||
* This function can now only be used if you send the entire datasource object and not just id inside the datasource object. We only fetch
|
||||
* the password from the db if its a saved datasource before testing.
|
||||
*/
|
||||
* This function can now only be used if you send the entire datasource object and not just id inside the datasource object. We only fetch
|
||||
* the password from the db if its a saved datasource before testing.
|
||||
*/
|
||||
@Override
|
||||
public Mono<DatasourceTestResult> testDatasource(Datasource datasource) {
|
||||
Mono<Datasource> datasourceMono = null;
|
||||
|
||||
// Fetch the password from the db if the datasource being tested does not have password set.
|
||||
// Fetch any fields that maybe encrypted from the db if the datasource being tested does not have those fields set.
|
||||
// This scenario would happen whenever an existing datasource is being tested and no changes are present in the
|
||||
// password field (because password is not sent over the network after encryption back to the client
|
||||
if (datasource.getId() != null && datasource.getDatasourceConfiguration()!=null &&
|
||||
datasource.getDatasourceConfiguration().getAuthentication()!=null) {
|
||||
String password = datasource.getDatasourceConfiguration().getAuthentication().getPassword();
|
||||
if (password == null || password.isEmpty()) {
|
||||
// encrypted field (because encrypted fields are not sent over the network after encryption back to the client
|
||||
if (datasource.getId() != null && datasource.getDatasourceConfiguration() != null &&
|
||||
datasource.getDatasourceConfiguration().getAuthentication() != null) {
|
||||
Set<String> emptyFields = datasource.getDatasourceConfiguration().getAuthentication().getEmptyEncryptionFields();
|
||||
if (emptyFields != null && !emptyFields.isEmpty()) {
|
||||
|
||||
datasourceMono = getById(datasource.getId())
|
||||
// If datasource has encrypted password, decrypt and set it in the datasource which is being tested
|
||||
.map(datasourceFromRepo-> {
|
||||
if (datasourceFromRepo.getDatasourceConfiguration()!=null && datasourceFromRepo.getDatasourceConfiguration().getAuthentication()!=null) {
|
||||
// If datasource has encrypted fields, decrypt and set it in the datasource which is being tested
|
||||
.map(datasourceFromRepo -> {
|
||||
if (datasourceFromRepo.getDatasourceConfiguration() != null && datasourceFromRepo.getDatasourceConfiguration().getAuthentication() != null) {
|
||||
AuthenticationDTO authentication = datasourceFromRepo.getDatasourceConfiguration().getAuthentication();
|
||||
if (authentication.getPassword() != null) {
|
||||
String decryptedPassword = encryptionService.decryptString(authentication.getPassword());
|
||||
datasource.getDatasourceConfiguration().getAuthentication().setPassword(decryptedPassword);
|
||||
|
||||
if (!authentication.getEncryptionFields().isEmpty()) {
|
||||
Map<String, String> decryptedFields = authentication.getEncryptionFields();
|
||||
decryptedFields = decryptedFields.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> encryptionService.decryptString(e.getValue())));
|
||||
datasource.getDatasourceConfiguration().getAuthentication().setEncryptionFields(decryptedFields);
|
||||
datasource.getDatasourceConfiguration().getAuthentication().setIsEncrypted(false);
|
||||
}
|
||||
|
||||
}
|
||||
return datasource;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import org.springframework.security.crypto.encrypt.Encryptors;
|
|||
import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
@Service
|
||||
public class EncryptionServiceImpl implements EncryptionService {
|
||||
private final EncryptionConfig encryptionConfig;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
|
|
|||
|
|
@ -253,6 +253,17 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
|
||||
Mono<Datasource> datasourceMono;
|
||||
if (action.getDatasource().getId() == null) {
|
||||
if (action.getDatasource().getDatasourceConfiguration() != null &&
|
||||
action.getDatasource().getDatasourceConfiguration().getAuthentication() != null) {
|
||||
action.getDatasource()
|
||||
.getDatasourceConfiguration()
|
||||
.setAuthentication(datasourceService.encryptAuthenticationFields(action
|
||||
.getDatasource()
|
||||
.getDatasourceConfiguration()
|
||||
.getAuthentication()
|
||||
));
|
||||
}
|
||||
|
||||
datasourceMono = Mono.just(action.getDatasource())
|
||||
.flatMap(datasourceService::validateDatasource);
|
||||
} else {
|
||||
|
|
@ -394,9 +405,6 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
// the update doesn't lead to resetting of this field.
|
||||
action.setUserSetOnLoad(null);
|
||||
|
||||
NewAction newAction = new NewAction();
|
||||
newAction.setUnpublishedAction(action);
|
||||
|
||||
Mono<NewAction> updatedActionMono = repository.findById(id, MANAGE_ACTIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ACTION, id)))
|
||||
.map(dbAction -> {
|
||||
|
|
@ -412,7 +420,7 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
Mono<NewAction> analyticsUpdateMono = updatedActionMono
|
||||
.flatMap(analyticsService::sendUpdateEvent);
|
||||
|
||||
// First Update the Action
|
||||
// First Update the Action
|
||||
return savedUpdatedActionMono
|
||||
// Now send the update event to analytics service
|
||||
.then(analyticsUpdateMono)
|
||||
|
|
@ -438,38 +446,38 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
String actionId = executeActionDTO.getActionId();
|
||||
// 2. Fetch the action from the DB and check if it can be executed
|
||||
Mono<ActionDTO> actionMono = repository.findById(actionId, EXECUTE_ACTIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ACTION, actionId)))
|
||||
.flatMap(dbAction -> {
|
||||
ActionDTO action;
|
||||
if (TRUE.equals(executeActionDTO.getViewMode())) {
|
||||
action = dbAction.getPublishedAction();
|
||||
// If the action has not been published, return error
|
||||
if (action == null) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ACTION, actionId));
|
||||
}
|
||||
} else {
|
||||
action = dbAction.getUnpublishedAction();
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ACTION, actionId)))
|
||||
.flatMap(dbAction -> {
|
||||
ActionDTO action;
|
||||
if (TRUE.equals(executeActionDTO.getViewMode())) {
|
||||
action = dbAction.getPublishedAction();
|
||||
// If the action has not been published, return error
|
||||
if (action == null) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ACTION, actionId));
|
||||
}
|
||||
} else {
|
||||
action = dbAction.getUnpublishedAction();
|
||||
}
|
||||
|
||||
// Now check for erroneous situations which would deter the execution of the action :
|
||||
// Now check for erroneous situations which would deter the execution of the action :
|
||||
|
||||
// Error out with in case of an invalid action
|
||||
if (Boolean.FALSE.equals(action.getIsValid())) {
|
||||
return Mono.error(new AppsmithException(
|
||||
AppsmithError.INVALID_ACTION,
|
||||
action.getName(),
|
||||
actionId,
|
||||
ArrayUtils.toString(action.getInvalids().toArray())
|
||||
));
|
||||
}
|
||||
// Error out with in case of an invalid action
|
||||
if (Boolean.FALSE.equals(action.getIsValid())) {
|
||||
return Mono.error(new AppsmithException(
|
||||
AppsmithError.INVALID_ACTION,
|
||||
action.getName(),
|
||||
actionId,
|
||||
ArrayUtils.toString(action.getInvalids().toArray())
|
||||
));
|
||||
}
|
||||
|
||||
// Error out in case of JS Plugin (this is currently client side execution only)
|
||||
if (dbAction.getPluginType() == PluginType.JS) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
|
||||
}
|
||||
return Mono.just(action);
|
||||
})
|
||||
.cache();
|
||||
// Error out in case of JS Plugin (this is currently client side execution only)
|
||||
if (dbAction.getPluginType() == PluginType.JS) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
|
||||
}
|
||||
return Mono.just(action);
|
||||
})
|
||||
.cache();
|
||||
|
||||
// 3. Instantiate the implementation class based on the query type
|
||||
|
||||
|
|
@ -683,7 +691,7 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
/**
|
||||
* Given a list of names of actions and pageId, find all the actions matching this criteria of names and pageId
|
||||
*
|
||||
* @param names Set of Action names. The returned list of actions will be a subset of the actioned named in this set.
|
||||
* @param names Set of Action names. The returned list of actions will be a subset of the actioned named in this set.
|
||||
* @param pageId Id of the Page within which to look for Actions.
|
||||
* @return A Flux of Actions that are identified to be executed on page-load.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.appsmith.server.exceptions.AppsmithException;
|
|||
import com.appsmith.server.repositories.PluginRepository;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.pf4j.PluginManager;
|
||||
|
|
@ -28,6 +29,7 @@ import org.springframework.data.redis.listener.ChannelTopic;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
|
@ -44,6 +46,7 @@ import java.nio.file.Path;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -336,9 +339,13 @@ public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, Str
|
|||
templateCache.remove(pluginId)
|
||||
)
|
||||
// It's okay if the templates folder is not present, we just return empty templates collection.
|
||||
.onErrorMap(throwable -> new AppsmithException(
|
||||
AppsmithError.PLUGIN_LOAD_TEMPLATES_FAIL, Exceptions.unwrap(throwable).getMessage())
|
||||
)
|
||||
.onErrorMap(throwable -> {
|
||||
log.error("Error loading templates for plugin {}.", plugin.getPackageName(), throwable);
|
||||
return new AppsmithException(
|
||||
AppsmithError.PLUGIN_LOAD_TEMPLATES_FAIL,
|
||||
Exceptions.unwrap(throwable).getMessage()
|
||||
);
|
||||
})
|
||||
.cache();
|
||||
|
||||
templateCache.put(pluginId, mono);
|
||||
|
|
@ -354,27 +361,49 @@ public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, Str
|
|||
.getPluginClassLoader()
|
||||
);
|
||||
|
||||
final Map<String, String> templates = new HashMap<>();
|
||||
|
||||
Resource[] resources;
|
||||
final PluginTemplatesMeta pluginTemplatesMeta;
|
||||
try {
|
||||
resources = resolver.getResources("templates/*");
|
||||
pluginTemplatesMeta = objectMapper.readValue(
|
||||
resolver.getResource("templates/meta.json").getInputStream(),
|
||||
PluginTemplatesMeta.class
|
||||
);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Error resolving templates in plugin for id: " + plugin.getId());
|
||||
log.error("Error loading templates metadata in plugin for id: " + plugin.getId());
|
||||
throw Exceptions.propagate(e);
|
||||
|
||||
}
|
||||
|
||||
for (final Resource resource : resources) {
|
||||
final String filename = resource.getFilename();
|
||||
if (pluginTemplatesMeta.getTemplates() == null) {
|
||||
log.warn("Missing templates key in plugin templates meta.");
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
final Map<String, String> templates = new LinkedHashMap<>();
|
||||
|
||||
for (final PluginTemplate template : pluginTemplatesMeta.getTemplates()) {
|
||||
final String filename = template.getFile();
|
||||
|
||||
if (filename == null) {
|
||||
log.warn("Empty or missing file for a template in plugin {}.", plugin.getPackageName());
|
||||
continue;
|
||||
}
|
||||
|
||||
final Resource resource = resolver.getResource("templates/" + filename);
|
||||
final String title = StringUtils.isEmpty(template.getTitle())
|
||||
? filename.replaceFirst("\\.\\w+$", "")
|
||||
: template.getTitle();
|
||||
|
||||
try {
|
||||
final String templateContent = StreamUtils.copyToString(
|
||||
resource.getInputStream(), Charset.defaultCharset());
|
||||
if (filename != null) {
|
||||
templates.put(filename.replaceFirst("\\.\\w+$", ""), templateContent);
|
||||
}
|
||||
templates.put(
|
||||
title,
|
||||
StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset())
|
||||
);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Error loading template {} for plugin {}", filename, plugin.getId());
|
||||
throw Exceptions.propagate(e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -403,4 +432,15 @@ public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, Str
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Data
|
||||
static class PluginTemplatesMeta {
|
||||
List<PluginTemplate> templates;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class PluginTemplate {
|
||||
String file;
|
||||
String title = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ import com.appsmith.server.domains.UserRole;
|
|||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import com.appsmith.server.notifications.EmailSender;
|
||||
import com.appsmith.server.repositories.OrganizationRepository;
|
||||
import com.appsmith.server.repositories.UserRepository;
|
||||
import com.appsmith.server.notifications.EmailSender;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import org.springframework.stereotype.Component;
|
|||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
|
|
@ -39,16 +41,16 @@ public class DatasourceStructureSolution {
|
|||
|
||||
public Mono<DatasourceStructure> getStructure(String datasourceId, boolean ignoreCache) {
|
||||
return datasourceService.getById(datasourceId)
|
||||
.flatMap(datasource -> getStructure(datasource, ignoreCache));
|
||||
.flatMap(datasource -> getStructure(datasource, ignoreCache));
|
||||
}
|
||||
|
||||
public Mono<DatasourceStructure> getStructure(Datasource datasource, boolean ignoreCache) {
|
||||
// This mono, when computed, will yield the cached structure if applicable, or resolve to an empty mono.
|
||||
// If the structure is `null` inside the datasource, this will resolve to empty as well.
|
||||
// If the structure is `null` inside the datasource, this will resolve to empty as well.
|
||||
final Mono<DatasourceStructure> cachedStructureMono =
|
||||
ignoreCache ? Mono.empty() : Mono.justOrEmpty(datasource.getStructure());
|
||||
ignoreCache ? Mono.empty() : Mono.justOrEmpty(datasource.getStructure());
|
||||
|
||||
decryptPasswordInDatasource(datasource);
|
||||
decryptEncryptedFieldsInDatasource(datasource);
|
||||
|
||||
// This mono, when computed, will load the structure of the datasource by calling the plugin method.
|
||||
final Mono<DatasourceStructure> loadStructureMono = pluginExecutorHelper
|
||||
|
|
@ -83,12 +85,17 @@ public class DatasourceStructureSolution {
|
|||
.defaultIfEmpty(new DatasourceStructure());
|
||||
}
|
||||
|
||||
private Datasource decryptPasswordInDatasource(Datasource datasource) {
|
||||
// If datasource has encrypted password, decrypt and set it in the datasource.
|
||||
private Datasource decryptEncryptedFieldsInDatasource(Datasource datasource) {
|
||||
// If datasource has encrypted fields, decrypt and set it in the datasource.
|
||||
if (datasource.getDatasourceConfiguration() != null) {
|
||||
AuthenticationDTO authentication = datasource.getDatasourceConfiguration().getAuthentication();
|
||||
if (authentication != null && authentication.getPassword() != null) {
|
||||
authentication.setPassword(encryptionService.decryptString(authentication.getPassword()));
|
||||
if (authentication != null && authentication.getEmptyEncryptionFields().isEmpty() && authentication.isEncrypted()) {
|
||||
Map<String, String> decryptedFields = authentication.getEncryptionFields().entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> encryptionService.decryptString(e.getValue())));
|
||||
authentication.setEncryptionFields(decryptedFields);
|
||||
authentication.setIsEncrypted(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.domains.Datasource;
|
||||
|
|
@ -64,7 +64,7 @@ public class DatasourceContextServiceTest {
|
|||
datasource.setName("test datasource name for authenticated fields decryption test");
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setUrl("http://test.com");
|
||||
AuthenticationDTO authenticationDTO = new AuthenticationDTO();
|
||||
DBAuth authenticationDTO = new DBAuth();
|
||||
String username = "username";
|
||||
String password = "password";
|
||||
authenticationDTO.setUsername(username);
|
||||
|
|
@ -81,8 +81,8 @@ public class DatasourceContextServiceTest {
|
|||
StepVerifier
|
||||
.create(datasourceMono)
|
||||
.assertNext(savedDatasource -> {
|
||||
AuthenticationDTO authentication = savedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
AuthenticationDTO decryptedAuthentication = datasourceContextService.decryptSensitiveFields(authentication);
|
||||
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
DBAuth decryptedAuthentication = (DBAuth) datasourceContextService.decryptSensitiveFields(authentication);
|
||||
assertThat(decryptedAuthentication.getPassword()).isEqualTo(password);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
|
@ -98,7 +98,7 @@ public class DatasourceContextServiceTest {
|
|||
datasource.setName("test datasource name for authenticated fields decryption test null password");
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setUrl("http://test.com");
|
||||
AuthenticationDTO authenticationDTO = new AuthenticationDTO();
|
||||
DBAuth authenticationDTO = new DBAuth();
|
||||
datasourceConfiguration.setAuthentication(authenticationDTO);
|
||||
datasource.setDatasourceConfiguration(datasourceConfiguration);
|
||||
datasource.setOrganizationId(orgId);
|
||||
|
|
@ -111,8 +111,8 @@ public class DatasourceContextServiceTest {
|
|||
StepVerifier
|
||||
.create(datasourceMono)
|
||||
.assertNext(savedDatasource -> {
|
||||
AuthenticationDTO authentication = savedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
AuthenticationDTO decryptedAuthentication = datasourceContextService.decryptSensitiveFields(authentication);
|
||||
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
DBAuth decryptedAuthentication = (DBAuth) datasourceContextService.decryptSensitiveFields(authentication);
|
||||
assertThat(decryptedAuthentication.getPassword()).isNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.Connection;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.Endpoint;
|
||||
import com.appsmith.external.models.OAuth2;
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.external.models.SSLDetails;
|
||||
import com.appsmith.external.models.UploadedFile;
|
||||
|
|
@ -74,7 +75,7 @@ public class DatasourceServiceTest {
|
|||
@MockBean
|
||||
PluginExecutorHelper pluginExecutorHelper;
|
||||
|
||||
String orgId = "";
|
||||
String orgId = "";
|
||||
|
||||
@Before
|
||||
@WithUserDetails(value = "api_user")
|
||||
|
|
@ -234,9 +235,10 @@ public class DatasourceServiceTest {
|
|||
Connection connection1 = new Connection();
|
||||
SSLDetails ssl = new SSLDetails();
|
||||
ssl.setKeyFile(new UploadedFile());
|
||||
ssl.getKeyFile().setName("ssl_key_file_id");
|
||||
ssl.getKeyFile().setName("ssl_key_file_id2");
|
||||
connection1.setSsl(ssl);
|
||||
datasourceConfiguration1.setConnection(connection1);
|
||||
updates.setDatasourceConfiguration(datasourceConfiguration1);
|
||||
return datasourceService.update(datasource1.getId(), updates);
|
||||
});
|
||||
|
||||
|
|
@ -246,7 +248,72 @@ public class DatasourceServiceTest {
|
|||
assertThat(createdDatasource.getId()).isNotEmpty();
|
||||
assertThat(createdDatasource.getPluginId()).isEqualTo(datasource.getPluginId());
|
||||
assertThat(createdDatasource.getName()).isEqualTo(datasource.getName());
|
||||
assertThat(createdDatasource.getDatasourceConfiguration().getConnection().getSsl().getKeyFile().getName()).isEqualTo("ssl_key_file_id");
|
||||
assertThat(createdDatasource.getDatasourceConfiguration().getConnection().getSsl().getKeyFile().getName()).isEqualTo("ssl_key_file_id2");
|
||||
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void createAndUpdateDatasourceDifferentAuthentication() {
|
||||
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor()));
|
||||
|
||||
Datasource datasource = new Datasource();
|
||||
datasource.setName("test db datasource1");
|
||||
datasource.setOrganizationId(orgId);
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
Connection connection = new Connection();
|
||||
connection.setMode(Connection.Mode.READ_ONLY);
|
||||
connection.setType(Connection.Type.REPLICA_SET);
|
||||
SSLDetails sslDetails = new SSLDetails();
|
||||
sslDetails.setAuthType(SSLDetails.AuthType.CA_CERTIFICATE);
|
||||
sslDetails.setKeyFile(new UploadedFile("ssl_key_file_id", ""));
|
||||
sslDetails.setCertificateFile(new UploadedFile("ssl_cert_file_id", ""));
|
||||
connection.setSsl(sslDetails);
|
||||
datasourceConfiguration.setConnection(connection);
|
||||
DBAuth auth = new DBAuth();
|
||||
auth.setUsername("test");
|
||||
auth.setPassword("test");
|
||||
datasourceConfiguration.setAuthentication(auth);
|
||||
datasource.setDatasourceConfiguration(datasourceConfiguration);
|
||||
|
||||
datasource.setOrganizationId(orgId);
|
||||
|
||||
Mono<Plugin> pluginMono = pluginService.findByName("Installed Plugin Name");
|
||||
|
||||
Mono<Datasource> datasourceMono = pluginMono
|
||||
.map(plugin -> {
|
||||
datasource.setPluginId(plugin.getId());
|
||||
return datasource;
|
||||
})
|
||||
.flatMap(datasourceService::create)
|
||||
.flatMap(datasource1 -> {
|
||||
Datasource updates = new Datasource();
|
||||
DatasourceConfiguration datasourceConfiguration1 = new DatasourceConfiguration();
|
||||
Connection connection1 = new Connection();
|
||||
SSLDetails ssl = new SSLDetails();
|
||||
ssl.setKeyFile(new UploadedFile());
|
||||
ssl.getKeyFile().setName("ssl_key_file_id2");
|
||||
connection1.setSsl(ssl);
|
||||
OAuth2 auth2 = new OAuth2();
|
||||
auth2.setClientId("test");
|
||||
auth2.setClientSecret("test");
|
||||
datasourceConfiguration1.setAuthentication(auth2);
|
||||
datasourceConfiguration1.setConnection(connection1);
|
||||
updates.setDatasourceConfiguration(datasourceConfiguration1);
|
||||
|
||||
return datasourceService.update(datasource1.getId(), updates);
|
||||
});
|
||||
|
||||
StepVerifier
|
||||
.create(datasourceMono)
|
||||
.assertNext(createdDatasource -> {
|
||||
assertThat(createdDatasource.getId()).isNotEmpty();
|
||||
assertThat(createdDatasource.getPluginId()).isEqualTo(datasource.getPluginId());
|
||||
assertThat(createdDatasource.getName()).isEqualTo(datasource.getName());
|
||||
assertThat(createdDatasource.getDatasourceConfiguration().getConnection().getSsl().getKeyFile().getName()).isEqualTo("ssl_key_file_id2");
|
||||
assertThat(createdDatasource.getDatasourceConfiguration().getAuthentication() instanceof OAuth2).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
@ -270,10 +337,10 @@ public class DatasourceServiceTest {
|
|||
|
||||
final Mono<Tuple2<Datasource, Datasource>> datasourcesMono = pluginMono
|
||||
.flatMap(plugin -> {
|
||||
datasource1.setPluginId(plugin.getId());
|
||||
datasource2.setPluginId(plugin.getId());
|
||||
return datasourceService.create(datasource1);
|
||||
})
|
||||
datasource1.setPluginId(plugin.getId());
|
||||
datasource2.setPluginId(plugin.getId());
|
||||
return datasourceService.create(datasource1);
|
||||
})
|
||||
.zipWhen(datasource -> datasourceService.create(datasource2));
|
||||
|
||||
StepVerifier
|
||||
|
|
@ -323,6 +390,54 @@ public class DatasourceServiceTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void testDatasourceEmptyFields() {
|
||||
|
||||
Datasource datasource = new Datasource();
|
||||
datasource.setName("test db datasource empty");
|
||||
datasource.setOrganizationId(orgId);
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
Connection connection = new Connection();
|
||||
connection.setMode(Connection.Mode.READ_ONLY);
|
||||
connection.setType(Connection.Type.REPLICA_SET);
|
||||
SSLDetails sslDetails = new SSLDetails();
|
||||
sslDetails.setAuthType(SSLDetails.AuthType.CA_CERTIFICATE);
|
||||
sslDetails.setKeyFile(new UploadedFile("ssl_key_file_id", ""));
|
||||
sslDetails.setCertificateFile(new UploadedFile("ssl_cert_file_id", ""));
|
||||
connection.setSsl(sslDetails);
|
||||
datasourceConfiguration.setConnection(connection);
|
||||
DBAuth auth = new DBAuth();
|
||||
auth.setUsername("test");
|
||||
auth.setPassword("test");
|
||||
datasourceConfiguration.setAuthentication(auth);
|
||||
datasource.setDatasourceConfiguration(datasourceConfiguration);
|
||||
|
||||
datasource.setOrganizationId(orgId);
|
||||
|
||||
Mono<Plugin> pluginMono = pluginService.findByName("Installed Plugin Name");
|
||||
|
||||
Mono<Datasource> datasourceMono = pluginMono.map(plugin -> {
|
||||
datasource.setPluginId(plugin.getId());
|
||||
return datasource;
|
||||
}).flatMap(datasourceService::create);
|
||||
|
||||
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor()));
|
||||
|
||||
Mono<DatasourceTestResult> testResultMono = datasourceMono.flatMap(datasource1 -> {
|
||||
((DBAuth) datasource1.getDatasourceConfiguration().getAuthentication()).setPassword(null);
|
||||
return datasourceService.testDatasource(datasource1);
|
||||
});
|
||||
|
||||
StepVerifier
|
||||
.create(testResultMono)
|
||||
.assertNext(testResult -> {
|
||||
assertThat(testResult).isNotNull();
|
||||
assertThat(testResult.getInvalids()).isEmpty();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void deleteDatasourceWithoutActions() {
|
||||
|
|
@ -424,13 +539,13 @@ public class DatasourceServiceTest {
|
|||
@WithUserDetails(value = "api_user")
|
||||
public void checkEncryptionOfAuthenticationDTOTest() {
|
||||
Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor()));
|
||||
|
||||
|
||||
Mono<Plugin> pluginMono = pluginService.findByName("Installed Plugin Name");
|
||||
Datasource datasource = new Datasource();
|
||||
datasource.setName("test datasource name for authenticated fields encryption test");
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setUrl("http://test.com");
|
||||
AuthenticationDTO authenticationDTO = new AuthenticationDTO();
|
||||
DBAuth authenticationDTO = new DBAuth();
|
||||
String username = "username";
|
||||
String password = "password";
|
||||
authenticationDTO.setUsername(username);
|
||||
|
|
@ -447,7 +562,7 @@ public class DatasourceServiceTest {
|
|||
StepVerifier
|
||||
.create(datasourceMono)
|
||||
.assertNext(savedDatasource -> {
|
||||
AuthenticationDTO authentication = savedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
assertThat(authentication.getUsername()).isEqualTo(username);
|
||||
assertThat(authentication.getPassword()).isEqualTo(encryptionService.encryptString(password));
|
||||
})
|
||||
|
|
@ -464,7 +579,7 @@ public class DatasourceServiceTest {
|
|||
datasource.setName("test datasource name for authenticated fields encryption test null password.");
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setUrl("http://test.com");
|
||||
AuthenticationDTO authenticationDTO = new AuthenticationDTO();
|
||||
DBAuth authenticationDTO = new DBAuth();
|
||||
authenticationDTO.setDatabaseName("admin");
|
||||
datasourceConfiguration.setAuthentication(authenticationDTO);
|
||||
datasource.setDatasourceConfiguration(datasourceConfiguration);
|
||||
|
|
@ -478,9 +593,10 @@ public class DatasourceServiceTest {
|
|||
StepVerifier
|
||||
.create(datasourceMono)
|
||||
.assertNext(savedDatasource -> {
|
||||
AuthenticationDTO authentication = savedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
DBAuth authentication = (DBAuth) savedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
assertThat(authentication.getUsername()).isNull();
|
||||
assertThat(authentication.getPassword()).isNull();
|
||||
assertThat(authentication.isEncrypted()).isFalse();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
@ -495,7 +611,7 @@ public class DatasourceServiceTest {
|
|||
datasource.setName("test datasource name for authenticated fields encryption test post update");
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setUrl("http://test.com");
|
||||
AuthenticationDTO authenticationDTO = new AuthenticationDTO();
|
||||
DBAuth authenticationDTO = new DBAuth();
|
||||
String username = "username";
|
||||
String password = "password";
|
||||
authenticationDTO.setUsername(username);
|
||||
|
|
@ -519,9 +635,10 @@ public class DatasourceServiceTest {
|
|||
StepVerifier
|
||||
.create(datasourceMono)
|
||||
.assertNext(updatedDatasource -> {
|
||||
AuthenticationDTO authentication = updatedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
DBAuth authentication = (DBAuth) updatedDatasource.getDatasourceConfiguration().getAuthentication();
|
||||
assertThat(authentication.getUsername()).isEqualTo(username);
|
||||
assertThat(authentication.getPassword()).isEqualTo(encryptionService.encryptString(password));
|
||||
assertThat(authentication.isEncrypted()).isTrue();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.AuthenticationDTO;
|
||||
import com.appsmith.external.models.DBAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Property;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
|
|
@ -361,8 +361,9 @@ public class ExamplesOrganizationClonerTests {
|
|||
ds2.setName("datasource 2");
|
||||
ds2.setOrganizationId(organization.getId());
|
||||
ds2.setDatasourceConfiguration(new DatasourceConfiguration());
|
||||
ds2.getDatasourceConfiguration().setAuthentication(new AuthenticationDTO());
|
||||
ds2.getDatasourceConfiguration().getAuthentication().setPassword("answer-to-life");
|
||||
DBAuth auth = new DBAuth();
|
||||
auth.setPassword("answer-to-life");
|
||||
ds2.getDatasourceConfiguration().setAuthentication(auth);
|
||||
|
||||
return Mono.when(
|
||||
datasourceService.create(ds1),
|
||||
|
|
@ -398,7 +399,7 @@ public class ExamplesOrganizationClonerTests {
|
|||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertThat(ds2.getDatasourceConfiguration().getAuthentication()).isNotNull();
|
||||
assertThat(ds2.getDatasourceConfiguration().getAuthentication().getPassword())
|
||||
assertThat(((DBAuth) ds2.getDatasourceConfiguration().getAuthentication()).getPassword())
|
||||
.isEqualTo(encryptionService.encryptString("answer-to-life"));
|
||||
|
||||
assertThat(data.applications).isEmpty();
|
||||
|
|
|
|||
|
|
@ -524,8 +524,8 @@ echo "Installing Appsmith to '$install_dir'."
|
|||
mkdir -p "$install_dir"
|
||||
echo ""
|
||||
|
||||
if confirm y "Would you like to initialize the default database?"; then
|
||||
echo "Appsmith needs to create a MongoDB instance."
|
||||
echo "Appsmith needs a MongoDB instance to run"
|
||||
if confirm y "Initialise a new database? (Recommended)"; then
|
||||
mongo_host="mongo"
|
||||
mongo_database="appsmith"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user