Merge branch 'release' of https://github.com/appsmithorg/appsmith into release
This commit is contained in:
commit
940c4b96fd
9
app/client/src/assets/icons/control/compact.svg
Executable file
9
app/client/src/assets/icons/control/compact.svg
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
<svg width="20" height="19" viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="M8.14648 2.72705L9.60103 4.54523H20.0001V2.72705H8.14648Z" fill="#2E3D49"/>
|
||||
<path d="M19.9996 6.36377H6.36328V8.18195H19.9996V6.36377Z" fill="#2E3D49"/>
|
||||
<path d="M19.9996 10H6.36328V11.8182H19.9996V10Z" fill="#2E3D49"/>
|
||||
<path d="M8.14648 15.4544H20.0001V13.6362H9.60103L8.14648 15.4544Z" fill="#2E3D49"/>
|
||||
<path d="M4.54545 4.54545H7.27273L3.63636 0L0 4.54545H2.72727V13.6364H0L3.63636 18.1818L7.27273 13.6364H4.54545V4.54545Z" fill="#2E3D49"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 565 B |
|
|
@ -7,6 +7,7 @@ import {
|
|||
getMenuOptions,
|
||||
getAllTableColumnKeys,
|
||||
} from "components/designSystems/appsmith/TableUtilities";
|
||||
import { CompactMode } from "components/designSystems/appsmith/TableCompactMode";
|
||||
|
||||
export enum ColumnTypes {
|
||||
CURRENCY = "currency",
|
||||
|
|
@ -95,6 +96,8 @@ interface ReactTableComponentProps {
|
|||
handleReorderColumn: Function;
|
||||
searchTableData: (searchKey: any) => void;
|
||||
columns: ReactTableColumnProps[];
|
||||
compactMode?: CompactMode;
|
||||
updateCompactMode: (compactMode: CompactMode) => void;
|
||||
}
|
||||
|
||||
const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||
|
|
@ -322,6 +325,8 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
|||
props.disableDrag(false);
|
||||
}}
|
||||
searchTableData={debounce(props.searchTableData, 500)}
|
||||
compactMode={props.compactMode}
|
||||
updateCompactMode={props.updateCompactMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,12 +15,29 @@ import { TableHeaderCell, renderEmptyRows } from "./TableUtilities";
|
|||
import TableHeader from "./TableHeader";
|
||||
import { Classes } from "@blueprintjs/core";
|
||||
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
|
||||
import {
|
||||
CompactMode,
|
||||
CompactModeTypes,
|
||||
} from "components/designSystems/appsmith/TableCompactMode";
|
||||
|
||||
export enum TABLE_SIZES {
|
||||
COLUMN_HEADER_HEIGHT = 52,
|
||||
TABLE_HEADER_HEIGHT = 61,
|
||||
ROW_HEIGHT = 52,
|
||||
}
|
||||
export type TableSizes = {
|
||||
COLUMN_HEADER_HEIGHT: number;
|
||||
TABLE_HEADER_HEIGHT: number;
|
||||
ROW_HEIGHT: number;
|
||||
};
|
||||
|
||||
export const TABLE_SIZES: { [key: string]: TableSizes } = {
|
||||
[CompactModeTypes.DEFAULT]: {
|
||||
COLUMN_HEADER_HEIGHT: 52,
|
||||
TABLE_HEADER_HEIGHT: 61,
|
||||
ROW_HEIGHT: 52,
|
||||
},
|
||||
[CompactModeTypes.SHORT]: {
|
||||
COLUMN_HEADER_HEIGHT: 52,
|
||||
TABLE_HEADER_HEIGHT: 61,
|
||||
ROW_HEIGHT: 40,
|
||||
},
|
||||
};
|
||||
|
||||
interface TableProps {
|
||||
width: number;
|
||||
|
|
@ -54,24 +71,26 @@ interface TableProps {
|
|||
enableDrag: () => void;
|
||||
searchTableData: (searchKey: any) => void;
|
||||
columnActions?: ColumnAction[];
|
||||
compactMode?: CompactMode;
|
||||
updateCompactMode: (compactMode: CompactMode) => void;
|
||||
}
|
||||
|
||||
const defaultColumn = {
|
||||
minWidth: 30,
|
||||
width: 150,
|
||||
maxWidth: 400,
|
||||
};
|
||||
|
||||
export const Table = (props: TableProps) => {
|
||||
const defaultColumn = React.useMemo(
|
||||
() => ({
|
||||
minWidth: 30,
|
||||
width: 150,
|
||||
maxWidth: 400,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const pageCount = Math.ceil(props.data.length / props.pageSize);
|
||||
const currentPageIndex = props.pageNo < pageCount ? props.pageNo : 0;
|
||||
const data = React.useMemo(() => props.data, [JSON.stringify(props.data)]);
|
||||
const columns = React.useMemo(() => props.columns, [
|
||||
JSON.stringify(props.columns),
|
||||
JSON.stringify(props.columnActions),
|
||||
]);
|
||||
const columnMemoKey = JSON.stringify({
|
||||
columns: props.columns,
|
||||
columnActions: props.columnActions,
|
||||
compactMode: props.compactMode,
|
||||
});
|
||||
const columns = React.useMemo(() => props.columns, [columnMemoKey]);
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
|
|
@ -104,11 +123,19 @@ export const Table = (props: TableProps) => {
|
|||
}
|
||||
const subPage = page.slice(startIndex, endIndex);
|
||||
const selectedRowIndex = props.selectedRowIndex;
|
||||
const tableSizes = TABLE_SIZES[props.compactMode || CompactModeTypes.DEFAULT];
|
||||
/* Subtracting 9px to handling widget padding */
|
||||
const tableRowHeight =
|
||||
(props.height -
|
||||
(tableSizes.COLUMN_HEADER_HEIGHT + tableSizes.TABLE_HEADER_HEIGHT + 9)) /
|
||||
props.pageSize;
|
||||
return (
|
||||
<TableWrapper
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
tableSizes={tableSizes}
|
||||
id={`table${props.widgetId}`}
|
||||
tableRowHeight={tableRowHeight}
|
||||
>
|
||||
<TableHeader
|
||||
width={props.width}
|
||||
|
|
@ -131,6 +158,8 @@ export const Table = (props: TableProps) => {
|
|||
hiddenColumns={props.hiddenColumns}
|
||||
updateHiddenColumns={props.updateHiddenColumns}
|
||||
displayColumnActions={props.displayColumnActions}
|
||||
compactMode={props.compactMode}
|
||||
updateCompactMode={props.updateCompactMode}
|
||||
/>
|
||||
<div className={props.isLoading ? Classes.SKELETON : "tableWrap"}>
|
||||
<div {...getTableProps()} className="table">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Popover,
|
||||
Classes,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Icon,
|
||||
Tooltip,
|
||||
} from "@blueprintjs/core";
|
||||
import { IconWrapper } from "constants/IconConstants";
|
||||
import styled from "styled-components";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { ReactComponent as CompactIcon } from "assets/icons/control/compact.svg";
|
||||
import { TableIconWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
|
||||
|
||||
const DropDownWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: white;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
border: 1px solid ${Colors.ATHENS_GRAY};
|
||||
padding: 8px;
|
||||
`;
|
||||
|
||||
const OptionWrapper = styled.div<{ selected?: boolean }>`
|
||||
display: flex;
|
||||
width: calc(100% - 20px);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
opacity: ${props => (props.selected ? 1 : 0.7)};
|
||||
min-width: 200px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 4px;
|
||||
background: ${props => (props.selected ? Colors.POLAR : Colors.WHITE)};
|
||||
border-left: ${props => (props.selected ? "4px solid #29CCA3" : "none")};
|
||||
border-radius: 4px;
|
||||
.option-title {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
&:hover {
|
||||
background: ${Colors.POLAR};
|
||||
}
|
||||
`;
|
||||
|
||||
export enum CompactModeTypes {
|
||||
SHORT = "SHORT",
|
||||
DEFAULT = "DEFAULT",
|
||||
}
|
||||
|
||||
export type CompactMode = keyof typeof CompactModeTypes;
|
||||
|
||||
type CompactModeItem = {
|
||||
title: string;
|
||||
value: CompactMode;
|
||||
};
|
||||
|
||||
const CompactModes: CompactModeItem[] = [
|
||||
{
|
||||
title: "Short",
|
||||
value: CompactModeTypes.SHORT,
|
||||
},
|
||||
{
|
||||
title: "Default",
|
||||
value: CompactModeTypes.DEFAULT,
|
||||
},
|
||||
];
|
||||
|
||||
interface TableCompactModeProps {
|
||||
compactMode?: CompactMode;
|
||||
updateCompactMode: (mode: CompactMode) => void;
|
||||
}
|
||||
|
||||
const TableCompactMode = (props: TableCompactModeProps) => {
|
||||
const [selected, selectMenu] = React.useState(false);
|
||||
return (
|
||||
<Popover
|
||||
minimal
|
||||
enforceFocus={false}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM}
|
||||
onClose={() => {
|
||||
selectMenu(false);
|
||||
}}
|
||||
>
|
||||
<TableIconWrapper
|
||||
selected={selected}
|
||||
onClick={e => {
|
||||
selectMenu(!selected);
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
autoFocus={false}
|
||||
hoverOpenDelay={1000}
|
||||
content="Row Height"
|
||||
position="top"
|
||||
>
|
||||
<IconWrapper
|
||||
width={20}
|
||||
height={20}
|
||||
color={selected ? Colors.OXFORD_BLUE : Colors.CADET_BLUE}
|
||||
>
|
||||
<CompactIcon />
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
</TableIconWrapper>
|
||||
<DropDownWrapper>
|
||||
{CompactModes.map((item: CompactModeItem, index: number) => {
|
||||
return (
|
||||
<OptionWrapper
|
||||
selected={
|
||||
props.compactMode ? props.compactMode === item.value : false
|
||||
}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
props.updateCompactMode(item.value);
|
||||
}}
|
||||
className={Classes.POPOVER_DISMISS}
|
||||
>
|
||||
{item.title}
|
||||
</OptionWrapper>
|
||||
);
|
||||
})}
|
||||
</DropDownWrapper>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableCompactMode;
|
||||
|
|
@ -12,6 +12,9 @@ import SearchComponent from "components/designSystems/appsmith/SearchComponent";
|
|||
import TableColumnsVisibility from "components/designSystems/appsmith/TableColumnsVisibility";
|
||||
import { ReactTableColumnProps } from "components/designSystems/appsmith/ReactTableComponent";
|
||||
import TableDataDownload from "components/designSystems/appsmith/TableDataDownload";
|
||||
import TableCompactMode, {
|
||||
CompactMode,
|
||||
} from "components/designSystems/appsmith/TableCompactMode";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
const PageNumberInputWrapper = styled(NumericInput)`
|
||||
|
|
@ -72,6 +75,8 @@ interface TableHeaderProps {
|
|||
searchTableData: (searchKey: any) => void;
|
||||
serverSidePaginationEnabled: boolean;
|
||||
displayColumnActions: boolean;
|
||||
compactMode?: CompactMode;
|
||||
updateCompactMode: (compactMode: CompactMode) => void;
|
||||
width: number;
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +104,10 @@ const TableHeader = (props: TableHeaderProps) => {
|
|||
updateHiddenColumns={props.updateHiddenColumns}
|
||||
/>
|
||||
)}
|
||||
<TableCompactMode
|
||||
compactMode={props.compactMode}
|
||||
updateCompactMode={props.updateCompactMode}
|
||||
/>
|
||||
</CommonFunctionsMenuWrapper>
|
||||
{props.serverSidePaginationEnabled && (
|
||||
<PaginationWrapper>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import styled from "styled-components";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { TABLE_SIZES } from "components/designSystems/appsmith/Table";
|
||||
import { TableSizes } from "components/designSystems/appsmith/Table";
|
||||
|
||||
export const TableWrapper = styled.div<{ width: number; height: number }>`
|
||||
export const TableWrapper = styled.div<{
|
||||
width: number;
|
||||
height: number;
|
||||
tableSizes: TableSizes;
|
||||
tableRowHeight?: number;
|
||||
}>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: white;
|
||||
|
|
@ -24,7 +29,8 @@ export const TableWrapper = styled.div<{ width: number; height: number }>`
|
|||
position: relative;
|
||||
overflow-y: auto;
|
||||
/* Subtracting 9px to handling widget padding */
|
||||
height: ${props => props.height - TABLE_SIZES.TABLE_HEADER_HEIGHT - 9}px;
|
||||
height: ${props =>
|
||||
props.height - props.tableSizes.TABLE_HEADER_HEIGHT - 9}px;
|
||||
.thead,
|
||||
.tbody {
|
||||
overflow: hidden;
|
||||
|
|
@ -77,8 +83,14 @@ export const TableWrapper = styled.div<{ width: number; height: number }>`
|
|||
background: ${Colors.ATHENS_GRAY_DARKER};
|
||||
}
|
||||
.td {
|
||||
height: 52px;
|
||||
line-height: 52px;
|
||||
height: ${props =>
|
||||
props.tableRowHeight
|
||||
? props.tableRowHeight
|
||||
: props.tableSizes.ROW_HEIGHT}px;
|
||||
line-height: ${props =>
|
||||
props.tableRowHeight
|
||||
? props.tableRowHeight
|
||||
: props.tableSizes.ROW_HEIGHT}px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
|
@ -263,7 +275,7 @@ export const TableHeaderWrapper = styled.div<{
|
|||
width: number;
|
||||
}>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid ${Colors.GEYSER_LIGHT};
|
||||
min-width: ${props =>
|
||||
|
|
@ -276,7 +288,7 @@ export const TableHeaderWrapper = styled.div<{
|
|||
export const CommonFunctionsMenuWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
height: 60px;
|
||||
`;
|
||||
|
||||
export const RowWrapper = styled.div`
|
||||
|
|
|
|||
|
|
@ -347,7 +347,9 @@ export const renderCell = (
|
|||
) => {
|
||||
switch (columnType) {
|
||||
case ColumnTypes.IMAGE:
|
||||
if (!isString(value)) {
|
||||
if (!value) {
|
||||
return <CellWrapper isHidden={isHidden}></CellWrapper>;
|
||||
} else if (!isString(value)) {
|
||||
return (
|
||||
<CellWrapper isHidden={isHidden}>
|
||||
<div>Invalid Image </div>
|
||||
|
|
@ -384,7 +386,9 @@ export const renderCell = (
|
|||
);
|
||||
case ColumnTypes.VIDEO:
|
||||
const youtubeRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|\?v=)([^#\&\?]*).*/;
|
||||
if (isString(value) && youtubeRegex.test(value)) {
|
||||
if (!value) {
|
||||
return <CellWrapper isHidden={isHidden}></CellWrapper>;
|
||||
} else if (isString(value) && youtubeRegex.test(value)) {
|
||||
return (
|
||||
<CellWrapper isHidden={isHidden} className="video-cell">
|
||||
<VideoComponent url={value} />
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import {
|
|||
} from "components/designSystems/appsmith/TableStyledWrappers";
|
||||
import { useTable, useFlexLayout } from "react-table";
|
||||
import styled from "styled-components";
|
||||
import { CompactModeTypes } from "components/designSystems/appsmith/TableCompactMode";
|
||||
import { TABLE_SIZES } from "components/designSystems/appsmith/Table";
|
||||
|
||||
interface TableProps {
|
||||
data: Record<string, any>[];
|
||||
|
|
@ -59,7 +61,11 @@ const Table = (props: TableProps) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<StyledTableWrapped width={200} height={200}>
|
||||
<StyledTableWrapped
|
||||
width={200}
|
||||
height={200}
|
||||
tableSizes={TABLE_SIZES[CompactModeTypes.DEFAULT]}
|
||||
>
|
||||
<div className="tableWrap">
|
||||
<div {...getTableProps()} className="table">
|
||||
{headerGroups.map((headerGroup: any, index: number) => (
|
||||
|
|
|
|||
|
|
@ -78,7 +78,8 @@ const WidgetsEditor = () => {
|
|||
if (!isFetchingPage && window.location.hash.length > 0) {
|
||||
const widgetIdFromURLHash = window.location.hash.substr(1);
|
||||
flashElementById(widgetIdFromURLHash);
|
||||
selectWidget(widgetIdFromURLHash);
|
||||
if (document.getElementById(widgetIdFromURLHash))
|
||||
selectWidget(widgetIdFromURLHash);
|
||||
}
|
||||
}, [isFetchingPage, selectWidget]);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import React, { useEffect } from "react";
|
|||
import { connect } from "react-redux";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import { TableWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
|
||||
import { CompactModeTypes } from "components/designSystems/appsmith/TableCompactMode";
|
||||
import { TABLE_SIZES } from "components/designSystems/appsmith/Table";
|
||||
import { AppState } from "reducers";
|
||||
import {
|
||||
getAllUsers,
|
||||
|
|
@ -265,7 +267,11 @@ export const OrgSettings = (props: PageProps) => {
|
|||
{props.isFetchAllUsers && props.isFetchAllRoles ? (
|
||||
<Spinner size={30} />
|
||||
) : (
|
||||
<StyledTableWrapped width={200} height={200}>
|
||||
<StyledTableWrapped
|
||||
width={200}
|
||||
height={200}
|
||||
tableSizes={TABLE_SIZES[CompactModeTypes.DEFAULT]}
|
||||
>
|
||||
<div className="tableWrap">
|
||||
<div {...getTableProps()} className="table">
|
||||
{headerGroups.map((headerGroup: any, index: number) => (
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ import {
|
|||
BASE_WIDGET_VALIDATION,
|
||||
} from "utils/ValidationFactory";
|
||||
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
|
||||
import {
|
||||
CompactMode,
|
||||
CompactModeTypes,
|
||||
} from "components/designSystems/appsmith/TableCompactMode";
|
||||
import { TriggerPropertiesMap } from "utils/WidgetFactory";
|
||||
import Skeleton from "components/utils/Skeleton";
|
||||
import moment from "moment";
|
||||
|
|
@ -245,8 +249,10 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
}
|
||||
if (isValidDate) {
|
||||
tableRow[accessor] = moment(value).format(format);
|
||||
} else {
|
||||
} else if (value) {
|
||||
tableRow[accessor] = "Invalid Value";
|
||||
} else {
|
||||
tableRow[accessor] = "";
|
||||
}
|
||||
break;
|
||||
case ColumnTypes.TIME:
|
||||
|
|
@ -259,8 +265,10 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
}
|
||||
if (isValidTime) {
|
||||
tableRow[accessor] = moment(value).format("HH:mm");
|
||||
} else {
|
||||
} else if (value) {
|
||||
tableRow[accessor] = "Invalid Value";
|
||||
} else {
|
||||
tableRow[accessor] = "";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -289,12 +297,22 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
super.updateWidgetMetaProperty("pageNo", pageNo);
|
||||
}
|
||||
const { componentWidth, componentHeight } = this.getComponentDimensions();
|
||||
const pageSize = Math.floor(
|
||||
const tableSizes =
|
||||
TABLE_SIZES[this.props.compactMode || CompactModeTypes.DEFAULT];
|
||||
let pageSize = Math.floor(
|
||||
(componentHeight -
|
||||
TABLE_SIZES.TABLE_HEADER_HEIGHT -
|
||||
TABLE_SIZES.COLUMN_HEADER_HEIGHT) /
|
||||
TABLE_SIZES.ROW_HEIGHT,
|
||||
tableSizes.TABLE_HEADER_HEIGHT -
|
||||
tableSizes.COLUMN_HEADER_HEIGHT) /
|
||||
tableSizes.ROW_HEIGHT,
|
||||
);
|
||||
if (
|
||||
componentHeight -
|
||||
(tableSizes.TABLE_HEADER_HEIGHT +
|
||||
tableSizes.COLUMN_HEADER_HEIGHT +
|
||||
tableSizes.ROW_HEIGHT * pageSize) >
|
||||
10
|
||||
)
|
||||
pageSize += 1;
|
||||
|
||||
if (pageSize !== this.props.pageSize) {
|
||||
super.updateWidgetMetaProperty("pageSize", pageSize);
|
||||
|
|
@ -354,6 +372,10 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
this.disableDrag(disable);
|
||||
}}
|
||||
searchTableData={this.handleSearchTable}
|
||||
compactMode={this.props.compactMode}
|
||||
updateCompactMode={(compactMode: CompactMode) => {
|
||||
super.updateWidgetMetaProperty("compactMode", compactMode);
|
||||
}}
|
||||
sortTableColumn={(column: string, asc: boolean) => {
|
||||
this.resetSelectedRowIndex();
|
||||
super.updateWidgetMetaProperty("sortedColumn", {
|
||||
|
|
@ -467,6 +489,7 @@ export interface TableWidgetProps extends WidgetProps {
|
|||
columnNameMap?: { [key: string]: string };
|
||||
columnTypeMap?: { [key: string]: { type: string; format: string } };
|
||||
columnSizeMap?: { [key: string]: number };
|
||||
compactMode?: CompactMode;
|
||||
sortedColumn?: {
|
||||
column: string;
|
||||
asc: boolean;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.appsmith.external.plugins;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import org.pf4j.ExtensionPoint;
|
||||
|
|
@ -20,7 +21,7 @@ public interface PluginExecutor extends ExtensionPoint {
|
|||
* @param actionConfiguration : These are the configurations which have been used to create an Action from a Datasource.
|
||||
* @return ActionExecutionResult : This object is returned to the user which contains the result values from the execution.
|
||||
*/
|
||||
Mono<Object> execute(Object connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration);
|
||||
Mono<ActionExecutionResult> execute(Object connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration);
|
||||
|
||||
/**
|
||||
* This function is responsible for creating the connection to the data source and returning the connection variable
|
||||
|
|
|
|||
|
|
@ -78,9 +78,9 @@ public class MongoPlugin extends BasePlugin {
|
|||
* @return Result data from executing the action's query.
|
||||
*/
|
||||
@Override
|
||||
public Mono<Object> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
public Mono<ActionExecutionResult> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
MongoClient mongoClient = (MongoClient) connection;
|
||||
if (mongoClient == null) {
|
||||
|
|
|
|||
|
|
@ -51,14 +51,10 @@ public class MySqlPlugin extends BasePlugin {
|
|||
@Extension
|
||||
public static class MySqlPluginExecutor implements PluginExecutor {
|
||||
|
||||
private Mono<Object> pluginErrorMono(Object... args) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
public Mono<ActionExecutionResult> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
Connection conn = (Connection) connection;
|
||||
|
||||
|
|
@ -76,7 +72,7 @@ public class MySqlPlugin extends BasePlugin {
|
|||
String query = actionConfiguration.getBody();
|
||||
|
||||
if (query == null) {
|
||||
return pluginErrorMono("Missing required parameter: Query.");
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Missing required parameter: Query."));
|
||||
}
|
||||
|
||||
List<Map<String, Object>> rowsList = new ArrayList<>(50);
|
||||
|
|
@ -105,14 +101,14 @@ public class MySqlPlugin extends BasePlugin {
|
|||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
return pluginErrorMono(e.getMessage());
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e.getMessage()));
|
||||
|
||||
} finally {
|
||||
if (resultSet != null) {
|
||||
try {
|
||||
resultSet.close();
|
||||
} catch (SQLException e) {
|
||||
log.warn("Error closing MySql ResultSet", e);
|
||||
log.warn("Error closing MySQL ResultSet", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +116,7 @@ public class MySqlPlugin extends BasePlugin {
|
|||
try {
|
||||
statement.close();
|
||||
} catch (SQLException e) {
|
||||
log.warn("Error closing MySql Statement", e);
|
||||
log.warn("Error closing MySQL Statement", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +134,7 @@ public class MySqlPlugin extends BasePlugin {
|
|||
try {
|
||||
Class.forName(JDBC_DRIVER);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return pluginErrorMono("Error loading MySql JDBC Driver class.");
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Error loading MySQL JDBC Driver class."));
|
||||
}
|
||||
|
||||
String url;
|
||||
|
|
@ -178,7 +174,7 @@ public class MySqlPlugin extends BasePlugin {
|
|||
configurationConnection != null && READ_ONLY.equals(configurationConnection.getMode()));
|
||||
return Mono.just(connection);
|
||||
} catch (SQLException e) {
|
||||
return pluginErrorMono("Error connecting to MySQL: " + e.getMessage(), e);
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Error connecting to MySQL: " + e.getMessage(), e));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,9 +63,9 @@ public class PostgresPlugin extends BasePlugin {
|
|||
public static class PostgresPluginExecutor implements PluginExecutor {
|
||||
|
||||
@Override
|
||||
public Mono<Object> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
public Mono<ActionExecutionResult> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
Connection conn = (Connection) connection;
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ public class PostgresPlugin extends BasePlugin {
|
|||
String query = actionConfiguration.getBody();
|
||||
|
||||
if (query == null) {
|
||||
return pluginErrorMono("Missing required parameter: Query.");
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Missing required parameter: Query."));
|
||||
}
|
||||
|
||||
List<Map<String, Object>> rowsList = new ArrayList<>(50);
|
||||
|
|
@ -149,7 +149,7 @@ public class PostgresPlugin extends BasePlugin {
|
|||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
return pluginErrorMono(e.getMessage());
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e.getMessage()));
|
||||
|
||||
} finally {
|
||||
if (resultSet != null) {
|
||||
|
|
@ -177,16 +177,12 @@ public class PostgresPlugin extends BasePlugin {
|
|||
return Mono.just(result);
|
||||
}
|
||||
|
||||
private Mono<Object> pluginErrorMono(Object... args) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
|
||||
try {
|
||||
Class.forName(JDBC_DRIVER);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return pluginErrorMono("Error loading Postgres JDBC Driver class.");
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Error loading Postgres JDBC Driver class."));
|
||||
}
|
||||
|
||||
String url;
|
||||
|
|
@ -233,7 +229,7 @@ public class PostgresPlugin extends BasePlugin {
|
|||
return Mono.just(connection);
|
||||
|
||||
} catch (SQLException e) {
|
||||
return pluginErrorMono("Error connecting to Postgres.", e);
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Error connecting to Postgres.", e));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import org.springframework.web.reactive.function.BodyInserters;
|
|||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -56,9 +57,9 @@ public class RapidApiPlugin extends BasePlugin {
|
|||
private static final String RAPID_API_KEY_VALUE = System.getenv("APPSMITH_RAPID_API_KEY_VALUE");
|
||||
|
||||
@Override
|
||||
public Mono<Object> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
public Mono<ActionExecutionResult> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
if (StringUtils.isEmpty(RAPID_API_KEY_VALUE)) {
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "RapidAPI Key value not set."));
|
||||
|
|
@ -100,10 +101,10 @@ public class RapidApiPlugin extends BasePlugin {
|
|||
}
|
||||
}
|
||||
|
||||
URI uri = null;
|
||||
URI uri;
|
||||
try {
|
||||
uri = createFinalUriWithQueryParams(url, actionConfiguration.getQueryParameters());
|
||||
System.out.println("Final URL is : " + uri.toString());
|
||||
log.info("Final URL is : {}", uri);
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
|
|
@ -114,7 +115,7 @@ public class RapidApiPlugin extends BasePlugin {
|
|||
// First set the header to specify the content type
|
||||
webClientBuilder.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString());
|
||||
|
||||
Map<String, String> keyValueMap = new HashMap<String, String>();
|
||||
Map<String, String> keyValueMap = new HashMap<>();
|
||||
|
||||
List<Property> bodyFormData = actionConfiguration.getBodyFormData();
|
||||
String jsonString = null;
|
||||
|
|
@ -170,14 +171,14 @@ public class RapidApiPlugin extends BasePlugin {
|
|||
headerInJsonString = objectMapper.writeValueAsString(headers);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
}
|
||||
try {
|
||||
// Set headers in the result now
|
||||
result.setHeaders(objectMapper.readTree(headerInJsonString));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +195,7 @@ public class RapidApiPlugin extends BasePlugin {
|
|||
result.setBody(objectMapper.readTree(jsonBody));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
}
|
||||
} else if (MediaType.IMAGE_GIF.equals(contentType) ||
|
||||
MediaType.IMAGE_JPEG.equals(contentType) ||
|
||||
|
|
@ -207,9 +208,17 @@ public class RapidApiPlugin extends BasePlugin {
|
|||
result.setBody(bodyString.trim());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.doOnError(e -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
.onErrorMap(throwable -> {
|
||||
final Throwable actualException = Exceptions.unwrap(throwable);
|
||||
if (actualException instanceof AppsmithPluginException) {
|
||||
return actualException;
|
||||
} else {
|
||||
return new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, actualException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<ClientResponse> httpCall(WebClient webClient, HttpMethod httpMethod, URI uri, String requestBody, int iteration) {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import org.springframework.web.reactive.function.client.ClientResponse;
|
|||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -65,15 +66,14 @@ public class RestApiPlugin extends BasePlugin {
|
|||
public static class RestApiPluginExecutor implements PluginExecutor {
|
||||
|
||||
@Override
|
||||
public Mono<Object> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
public Mono<ActionExecutionResult> execute(Object connection,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
ActionConfiguration actionConfiguration) {
|
||||
|
||||
ActionExecutionResult errorResult = new ActionExecutionResult();
|
||||
errorResult.setStatusCode(AppsmithPluginError.PLUGIN_ERROR.getAppErrorCode().toString());
|
||||
errorResult.setIsExecutionSuccess(false);
|
||||
|
||||
|
||||
String path = (actionConfiguration.getPath() == null) ? "" : actionConfiguration.getPath();
|
||||
String url = datasourceConfiguration.getUrl() + path;
|
||||
String reqContentType = "";
|
||||
|
|
@ -147,20 +147,19 @@ public class RestApiPlugin extends BasePlugin {
|
|||
result.setStatusCode(statusCode.toString());
|
||||
result.setIsExecutionSuccess(statusCode.is2xxSuccessful());
|
||||
|
||||
|
||||
// Convert the headers into json tree to store in the results
|
||||
String headerInJsonString;
|
||||
try {
|
||||
headerInJsonString = objectMapper.writeValueAsString(headers);
|
||||
} catch (JsonProcessingException e) {
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
}
|
||||
|
||||
// Set headers in the result now
|
||||
try {
|
||||
result.setHeaders(objectMapper.readTree(headerInJsonString));
|
||||
} catch (IOException e) {
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
|
|
@ -174,7 +173,7 @@ public class RestApiPlugin extends BasePlugin {
|
|||
String jsonBody = new String(body);
|
||||
result.setBody(objectMapper.readTree(jsonBody));
|
||||
} catch (IOException e) {
|
||||
return Mono.defer(() -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e)));
|
||||
throw Exceptions.propagate(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||
}
|
||||
} else if (MediaType.IMAGE_GIF.equals(contentType) ||
|
||||
MediaType.IMAGE_JPEG.equals(contentType) ||
|
||||
|
|
@ -191,7 +190,7 @@ public class RestApiPlugin extends BasePlugin {
|
|||
return result;
|
||||
})
|
||||
.onErrorResume(e -> {
|
||||
errorResult.setBody(AppsmithPluginError.PLUGIN_ERROR.getMessage(e));
|
||||
errorResult.setBody(Exceptions.unwrap(e).getMessage());
|
||||
errorResult.setRequest(actionExecutionRequest);
|
||||
return Mono.just(errorResult);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,11 +36,10 @@ public class RestApiPluginTest {
|
|||
String requestBody = "{\"key\":\"value\"}";
|
||||
actionConfig.setBody(requestBody);
|
||||
|
||||
Mono<Object> resultMono = pluginExecutor.execute(null, dsConfig, actionConfig);
|
||||
Mono<ActionExecutionResult> resultMono = pluginExecutor.execute(null, dsConfig, actionConfig);
|
||||
|
||||
StepVerifier.create(resultMono)
|
||||
.assertNext(r -> {
|
||||
ActionExecutionResult result = (ActionExecutionResult) r;
|
||||
.assertNext(result -> {
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(result.getBody());
|
||||
JsonNode data = ((ObjectNode) result.getBody()).get("data");
|
||||
|
|
@ -59,11 +58,10 @@ public class RestApiPluginTest {
|
|||
actionConfig.setHeaders(List.of(new Property("content-type", "application/x-www-form-urlencoded")));
|
||||
actionConfig.setHttpMethod(HttpMethod.POST);
|
||||
actionConfig.setBodyFormData(List.of(new Property("key", "value"), new Property("key1", "value1")));
|
||||
Mono<Object> resultMono = pluginExecutor.execute(null, dsConfig, actionConfig);
|
||||
Mono<ActionExecutionResult> resultMono = pluginExecutor.execute(null, dsConfig, actionConfig);
|
||||
|
||||
StepVerifier.create(resultMono)
|
||||
.assertNext(r -> {
|
||||
ActionExecutionResult result = (ActionExecutionResult) r;
|
||||
.assertNext(result -> {
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(result.getBody());
|
||||
JsonNode data = ((ObjectNode) result.getBody()).get("form");
|
||||
|
|
|
|||
|
|
@ -31,8 +31,11 @@ public class CustomFormLoginServiceImpl implements ReactiveUserDetailsService {
|
|||
public Mono<UserDetails> findByUsername(String username) {
|
||||
return repository.findByEmail(username)
|
||||
.switchIfEmpty(Mono.error(new UsernameNotFoundException("Unable to find username: " + username)))
|
||||
// This object cast is required to ensure that we send the right object type back to Spring framework.
|
||||
// Doesn't work without this.
|
||||
.map(user -> (UserDetails) user);
|
||||
.onErrorMap(error -> {
|
||||
log.error("Can't find user {}", username);
|
||||
return error;
|
||||
})
|
||||
// This seemingly useless call to `.map` is required to Java's type checker to compile.
|
||||
.map(user -> user);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
|
@ -50,7 +51,8 @@ public class ActionController extends BaseController<ActionService, Action, Stri
|
|||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Action>> create(@Valid @RequestBody Action resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader,
|
||||
ServerWebExchange exchange) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return actionCollectionService.createAction(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
|
@ -47,7 +48,8 @@ public class ApplicationController extends BaseController<ApplicationService, Ap
|
|||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Application>> create(@Valid @RequestBody Application resource,
|
||||
@RequestParam String orgId) {
|
||||
@RequestParam String orgId,
|
||||
ServerWebExchange exchange) {
|
||||
if (orgId == null) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "organization id"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
|
@ -30,7 +31,8 @@ public abstract class BaseController<S extends CrudService, T extends BaseDomain
|
|||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<T>> create(@Valid @RequestBody T resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader,
|
||||
ServerWebExchange exchange) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return service.create(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
|
@ -33,7 +34,8 @@ public class CollectionController extends BaseController<CollectionService, Coll
|
|||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Collection>> create(@Valid @RequestBody Collection resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader,
|
||||
ServerWebExchange exchange) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return actionCollectionService.createCollection(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
|
@ -37,7 +38,8 @@ public class PageController extends BaseController<PageService, Page, String> {
|
|||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<Page>> create(@Valid @RequestBody Page resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader,
|
||||
ServerWebExchange exchange) {
|
||||
log.debug("Going to create resource {}", resource.getClass().getName());
|
||||
return applicationPageService.createPage(resource)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import com.appsmith.server.dtos.ResponseDTO;
|
|||
import com.appsmith.server.services.SessionUserService;
|
||||
import com.appsmith.server.services.UserOrganizationService;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import com.appsmith.server.solutions.UserSignup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
|
@ -21,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
|
@ -32,24 +35,34 @@ public class UserController extends BaseController<UserService, User, String> {
|
|||
|
||||
private final SessionUserService sessionUserService;
|
||||
private final UserOrganizationService userOrganizationService;
|
||||
private final UserSignup userSignup;
|
||||
|
||||
@Autowired
|
||||
public UserController(UserService service,
|
||||
SessionUserService sessionUserService,
|
||||
UserOrganizationService userOrganizationService) {
|
||||
UserOrganizationService userOrganizationService,
|
||||
UserSignup userSignup) {
|
||||
super(service);
|
||||
this.sessionUserService = sessionUserService;
|
||||
this.userOrganizationService = userOrganizationService;
|
||||
this.userSignup = userSignup;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE})
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<ResponseDTO<User>> create(@Valid @RequestBody User resource,
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader) {
|
||||
return service.createUserAndSendEmail(resource, originHeader)
|
||||
@RequestHeader(name = "Origin", required = false) String originHeader,
|
||||
ServerWebExchange exchange) {
|
||||
return userSignup.signupAndLogin(resource, exchange)
|
||||
.map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
|
||||
}
|
||||
|
||||
@PostMapping(consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Mono<Void> createFormEncoded(ServerWebExchange exchange) {
|
||||
return userSignup.signupAndLoginFromFormData(exchange);
|
||||
}
|
||||
|
||||
@PutMapping("/switchOrganization/{orgId}")
|
||||
public Mono<ResponseDTO<User>> setCurrentOrganization(@PathVariable String orgId) {
|
||||
return service.switchCurrentOrganization(orgId)
|
||||
|
|
|
|||
|
|
@ -355,8 +355,17 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
Mono<PluginExecutor> pluginExecutorMono = pluginExecutorHelper.getPluginExecutor(pluginMono);
|
||||
|
||||
// 4. Execute the query
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = actionMono
|
||||
.flatMap(action -> datasourceMono.zipWith(pluginExecutorMono, (datasource, pluginExecutor) -> {
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = Mono
|
||||
.zip(
|
||||
actionMono,
|
||||
datasourceMono,
|
||||
pluginExecutorMono
|
||||
)
|
||||
.flatMap(tuple -> {
|
||||
final Action action = tuple.getT1();
|
||||
final Datasource datasource = tuple.getT2();
|
||||
final PluginExecutor pluginExecutor = tuple.getT3();
|
||||
|
||||
DatasourceConfiguration datasourceConfigurationTemp;
|
||||
ActionConfiguration actionConfigurationTemp;
|
||||
//Do variable substitution before invoking the plugin
|
||||
|
|
@ -377,8 +386,8 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
(oldValue, newValue) -> oldValue)
|
||||
);
|
||||
|
||||
datasourceConfigurationTemp = (DatasourceConfiguration) variableSubstitution(datasource.getDatasourceConfiguration(), replaceParamsMap);
|
||||
actionConfigurationTemp = (ActionConfiguration) variableSubstitution(action.getActionConfiguration(), replaceParamsMap);
|
||||
datasourceConfigurationTemp = variableSubstitution(datasource.getDatasourceConfiguration(), replaceParamsMap);
|
||||
actionConfigurationTemp = variableSubstitution(action.getActionConfiguration(), replaceParamsMap);
|
||||
} else {
|
||||
datasourceConfigurationTemp = datasource.getDatasourceConfiguration();
|
||||
actionConfigurationTemp = action.getActionConfiguration();
|
||||
|
|
@ -413,7 +422,7 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
action.getPageId(), action.getId(), action.getName(), datasourceConfiguration,
|
||||
actionConfiguration);
|
||||
|
||||
Mono<Object> executionMono = Mono.just(datasource)
|
||||
Mono<ActionExecutionResult> executionMono = Mono.just(datasource)
|
||||
.flatMap(datasourceContextService::getDatasourceContext)
|
||||
// Now that we have the context (connection details), execute the action.
|
||||
.flatMap(
|
||||
|
|
@ -452,9 +461,7 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
}
|
||||
return Mono.just(result);
|
||||
});
|
||||
}))
|
||||
.flatMap(obj -> obj)
|
||||
.map(obj -> (ActionExecutionResult) obj);
|
||||
});
|
||||
|
||||
// Populate the actionExecution result by setting the cached response and saving it to the DB
|
||||
return actionExecutionResultMono
|
||||
|
|
@ -483,11 +490,8 @@ public class ActionServiceImpl extends BaseService<ActionRepository, Action, Str
|
|||
log.debug("Action execution resulted in failure beyond the proxy with the result of {}", result);
|
||||
return Mono.just(action);
|
||||
});
|
||||
return actionFromDbMono.zipWith(resultMono)
|
||||
.map(tuple -> {
|
||||
ActionExecutionResult executionResult = tuple.getT2();
|
||||
return executionResult;
|
||||
});
|
||||
|
||||
return actionFromDbMono.then(resultMono);
|
||||
})
|
||||
.onErrorResume(AppsmithException.class, error -> {
|
||||
ActionExecutionResult result = new ActionExecutionResult();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.appsmith.server.services;
|
|||
import com.appsmith.server.domains.User;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
|
@ -12,10 +13,9 @@ public class SessionUserServiceImpl implements SessionUserService {
|
|||
|
||||
@Override
|
||||
public Mono<User> getCurrentUser() {
|
||||
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(ctx -> ctx.getAuthentication())
|
||||
.map(auth -> auth.getPrincipal())
|
||||
.map(principal -> (User) principal);
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(auth -> (User) auth.getPrincipal());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package com.appsmith.server.solutions;
|
||||
|
||||
import com.appsmith.server.authentication.handlers.AuthenticationSuccessHandler;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.LoginSource;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.domains.UserState;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.services.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
|
||||
import org.springframework.security.web.server.ServerRedirectStrategy;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import static org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class UserSignup {
|
||||
|
||||
private final UserService userService;
|
||||
private final AuthenticationSuccessHandler authenticationSuccessHandler;
|
||||
|
||||
private static final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
|
||||
|
||||
private static final WebFilterChain EMPTY_WEB_FILTER_CHAIN = serverWebExchange -> Mono.empty();
|
||||
|
||||
/**
|
||||
* This function does the sign-up flow of the given user object as a new user, and then logs that user. After the
|
||||
* login is successful, the authentication success handlers will be called directly.
|
||||
* This needed to be pulled out into a separate solution class since it was creating a circular autowiring error if
|
||||
* placed inside UserService.
|
||||
* @param user User object representing the new user to be signed-up and then logged-in.
|
||||
* @param exchange ServerWebExchange object with details of the current web request.
|
||||
* @return Mono of User, published the saved user object with a non-null value for its `getId()`.
|
||||
*/
|
||||
public Mono<User> signupAndLogin(User user, ServerWebExchange exchange) {
|
||||
return Mono
|
||||
.zip(
|
||||
userService.createUserAndSendEmail(user, exchange.getRequest().getHeaders().getOrigin()),
|
||||
exchange.getSession(),
|
||||
ReactiveSecurityContextHolder.getContext()
|
||||
)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.INTERNAL_SERVER_ERROR)))
|
||||
.flatMap(tuple -> {
|
||||
final User savedUser = tuple.getT1();
|
||||
final WebSession session = tuple.getT2();
|
||||
final SecurityContext securityContext = tuple.getT3();
|
||||
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(savedUser, null, savedUser.getAuthorities());
|
||||
securityContext.setAuthentication(authentication);
|
||||
session.getAttributes().put(DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME, securityContext);
|
||||
|
||||
final WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, EMPTY_WEB_FILTER_CHAIN);
|
||||
return authenticationSuccessHandler
|
||||
.onAuthenticationSuccess(webFilterExchange, authentication)
|
||||
.thenReturn(savedUser);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user and logs them in, with the user details taken from the POST body, read as form-data.
|
||||
* @param exchange The `ServerWebExchange` instance representing the request.
|
||||
* @return Publisher of the created user object, with an `id` value.
|
||||
*/
|
||||
public Mono<Void> signupAndLoginFromFormData(ServerWebExchange exchange) {
|
||||
return exchange.getFormData()
|
||||
.map(formData -> {
|
||||
final User user = new User();
|
||||
user.setEmail(formData.getFirst(FieldName.EMAIL));
|
||||
user.setPassword(formData.getFirst("password"));
|
||||
if (formData.containsKey(FieldName.NAME)) {
|
||||
user.setName(formData.getFirst(FieldName.NAME));
|
||||
}
|
||||
if (formData.containsKey("source")) {
|
||||
user.setSource(LoginSource.valueOf(formData.getFirst("source")));
|
||||
}
|
||||
if (formData.containsKey("state")) {
|
||||
user.setState(UserState.valueOf(formData.getFirst("state")));
|
||||
}
|
||||
if (formData.containsKey("isEnabled")) {
|
||||
user.setIsEnabled(Boolean.valueOf(formData.getFirst("isEnabled")));
|
||||
}
|
||||
return user;
|
||||
})
|
||||
.flatMap(user -> signupAndLogin(user, exchange))
|
||||
.then()
|
||||
.onErrorResume(error -> {
|
||||
final String referer = exchange.getRequest().getHeaders().getFirst("referer");
|
||||
final URIBuilder redirectUriBuilder = new URIBuilder(URI.create(referer)).setParameter("error", error.getMessage());
|
||||
URI redirectUri;
|
||||
try {
|
||||
redirectUri = redirectUriBuilder.build();
|
||||
} catch (URISyntaxException e) {
|
||||
log.error("Error building redirect URI with error for signup, {}.", e.getMessage(), error);
|
||||
redirectUri = URI.create(referer);
|
||||
}
|
||||
return redirectStrategy.sendRedirect(exchange, redirectUri);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.appsmith.server.helpers;
|
||||
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.external.models.ActionExecutionResult;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
|
|
@ -12,7 +13,7 @@ import java.util.Set;
|
|||
public class MockPluginExecutor implements PluginExecutor {
|
||||
|
||||
@Override
|
||||
public Mono<Object> execute(Object connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) {
|
||||
public Mono<ActionExecutionResult> execute(Object connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) {
|
||||
System.out.println("In the execute");
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public class PluginServiceTest {
|
|||
|
||||
@Test
|
||||
public void checkPluginExecutor() {
|
||||
Mono<Object> executeMono = pluginExecutor.execute(new Object(), new DatasourceConfiguration(), new ActionConfiguration());
|
||||
Mono<ActionExecutionResult> executeMono = pluginExecutor.execute(new Object(), new DatasourceConfiguration(), new ActionConfiguration());
|
||||
|
||||
StepVerifier
|
||||
.create(executeMono)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user