Merge branch 'release'
This commit is contained in:
commit
66aed61d16
|
|
@ -1,11 +1,6 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const dsl = require("../../../fixtures/commondsl.json");
|
||||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const testdata = require("../../../fixtures/testdata.json");
|
||||
const pages = require("../../../locators/Pages.json");
|
||||
const dsl = require("../../../fixtures/displayWidgetDsl.json");
|
||||
const apiwidget = require("../../../locators/apiWidgetslocator.json");
|
||||
const explorer = require("../../../locators/explorerlocators.json");
|
||||
const pageid = "MyPage";
|
||||
|
||||
describe("Entity explorer tests related to widgets and validation", function() {
|
||||
beforeEach(() => {
|
||||
|
|
@ -13,31 +8,26 @@ describe("Entity explorer tests related to widgets and validation", function() {
|
|||
});
|
||||
|
||||
it("Widget edit/delete/copy to clipboard validation", function() {
|
||||
cy.openPropertyPane("textwidget");
|
||||
cy.widgetText("Api", widgetsPage.textWidget, widgetsPage.textInputval);
|
||||
cy.testCodeMirror("/api/users/2");
|
||||
cy.NavigateToEntityExplorer();
|
||||
cy.wait(5000);
|
||||
cy.SearchEntityandOpen("Api");
|
||||
cy.SearchEntityandOpen("Text1");
|
||||
cy.get(explorer.collapse)
|
||||
.last()
|
||||
.click({ force: true });
|
||||
cy.get(explorer.property)
|
||||
.last()
|
||||
.click({ force: true });
|
||||
cy.wait(2000);
|
||||
cy.get(apiwidget.propertyList).then(function($lis) {
|
||||
expect($lis).to.have.length(2);
|
||||
expect($lis.eq(0)).to.contain("{{Api.isVisible}}");
|
||||
expect($lis.eq(1)).to.contain("{{Api.text}}");
|
||||
expect($lis.eq(0)).to.contain("{{Text1.isVisible}}");
|
||||
expect($lis.eq(1)).to.contain("{{Text1.text}}");
|
||||
});
|
||||
cy.GlobalSearchEntity("Api");
|
||||
cy.EditApiNameFromExplorer("ApiUpdated");
|
||||
cy.GlobalSearchEntity("ApiUpdated");
|
||||
cy.GlobalSearchEntity("Text1");
|
||||
cy.EditApiNameFromExplorer("TextUpdated");
|
||||
cy.GlobalSearchEntity("TextUpdated");
|
||||
cy.get(apiwidget.propertyList).then(function($lis) {
|
||||
expect($lis).to.have.length(2);
|
||||
expect($lis.eq(0)).to.contain("{{ApiUpdated.isVisible}}");
|
||||
expect($lis.eq(1)).to.contain("{{ApiUpdated.text}}");
|
||||
expect($lis.eq(0)).to.contain("{{TextUpdated.isVisible}}");
|
||||
expect($lis.eq(1)).to.contain("{{TextUpdated.text}}");
|
||||
});
|
||||
cy.DeleteWidgetFromSideBar();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
const pages = require("../../../locators/Pages.json");
|
||||
|
||||
describe("Pages", function() {
|
||||
it("Clone page", function() {
|
||||
cy.xpath(pages.popover)
|
||||
.last()
|
||||
.click({ force: true });
|
||||
cy.get(pages.clonePage).click({ force: true });
|
||||
|
||||
cy.wait("@clonePage").should(
|
||||
"have.nested.property",
|
||||
"response.body.responseMeta.status",
|
||||
201,
|
||||
);
|
||||
|
||||
cy.get(".t--entity-name:contains(Page1 Copy)");
|
||||
});
|
||||
});
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
"entityExplorer": ".t--nav-link-entity-explorer",
|
||||
"popover": "//div[contains(@class,'t--entity page')]//*[local-name()='g' and @id='Icon/Outline/more-vertical']",
|
||||
"editName": ".single-select >div:contains('Edit Name')",
|
||||
"clonePage": ".single-select >div:contains('Clone')",
|
||||
"deletePage": ".single-select >div:contains('Delete')",
|
||||
"entityQuery": ".t--entity-name:contains('Queries')"
|
||||
}
|
||||
|
|
@ -1478,6 +1478,7 @@ Cypress.Commands.add("startServerAndRoutes", () => {
|
|||
);
|
||||
cy.route("GET", "/api/v1/users/me").as("getUser");
|
||||
cy.route("POST", "/api/v1/pages").as("createPage");
|
||||
cy.route("POST", "/api/v1/pages/clone/*").as("clonePage");
|
||||
});
|
||||
|
||||
Cypress.Commands.add("alertValidate", text => {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,30 @@ export const createPage = (applicationId: string, pageName: string) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const clonePageInit = (pageId: string) => {
|
||||
return {
|
||||
type: ReduxActionTypes.CLONE_PAGE_INIT,
|
||||
payload: {
|
||||
id: pageId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const clonePageSuccess = (
|
||||
pageId: string,
|
||||
pageName: string,
|
||||
layoutId: string,
|
||||
) => {
|
||||
return {
|
||||
type: ReduxActionTypes.CLONE_PAGE_SUCCESS,
|
||||
payload: {
|
||||
pageId,
|
||||
pageName,
|
||||
layoutId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const updatePage = (id: string, name: string) => {
|
||||
return {
|
||||
type: ReduxActionTypes.UPDATE_PAGE_INIT,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ export interface DeletePageRequest {
|
|||
id: string;
|
||||
}
|
||||
|
||||
export interface ClonePageRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface UpdateWidgetNameRequest {
|
||||
pageId: string;
|
||||
layoutId: string;
|
||||
|
|
@ -150,6 +154,10 @@ class PageApi extends Api {
|
|||
return Api.delete(PageApi.url + "/" + request.id);
|
||||
}
|
||||
|
||||
static clonePage(request: ClonePageRequest): AxiosPromise<ApiResponse> {
|
||||
return Api.post(PageApi.url + "/clone/" + request.id);
|
||||
}
|
||||
|
||||
static updateWidgetName(
|
||||
request: UpdateWidgetNameRequest,
|
||||
): AxiosPromise<UpdateWidgetNameResponse> {
|
||||
|
|
|
|||
109
app/client/src/components/ads/TableDropdown.tsx
Normal file
109
app/client/src/components/ads/TableDropdown.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import React, { useCallback, useState } from "react";
|
||||
import { CommonComponentProps, hexToRgba } from "./common";
|
||||
import { ReactComponent as DownArrow } from "../../assets/icons/ads/down_arrow.svg";
|
||||
import Text, { TextType } from "./Text";
|
||||
import styled from "styled-components";
|
||||
|
||||
type DropdownOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type DropdownProps = CommonComponentProps & {
|
||||
options: DropdownOption[];
|
||||
onSelect: (selectedValue: string) => void;
|
||||
selectedOption: DropdownOption;
|
||||
};
|
||||
|
||||
const DropdownWrapper = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const SelectedItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
span {
|
||||
margin-right: ${props => props.theme.spaces[1] + 1}px;
|
||||
}
|
||||
`;
|
||||
|
||||
const OptionsWrapper = styled.div`
|
||||
position: absolute;
|
||||
margin-top: ${props => props.theme.spaces[8]}px;
|
||||
left: -60px;
|
||||
width: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: ${props => props.theme.colors.blackShades[3]};
|
||||
box-shadow: ${props => props.theme.spaces[0]}px
|
||||
${props => props.theme.spaces[5]}px ${props => props.theme.spaces[13] - 2}px
|
||||
${props => hexToRgba(props.theme.colors.blackShades[0], 0.75)};
|
||||
`;
|
||||
|
||||
const DropdownOption = styled.div<{
|
||||
selected: DropdownOption;
|
||||
option: DropdownOption;
|
||||
}>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
background-color: ${props =>
|
||||
props.option.label === props.selected.label
|
||||
? props.theme.colors.blackShades[4]
|
||||
: "transparent"};
|
||||
|
||||
span:last-child {
|
||||
margin-top: ${props => props.theme.spaces[1] + 1}px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
span {
|
||||
color: ${props => props.theme.colors.blackShades[9]};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TableDropdown = (props: DropdownProps) => {
|
||||
const [selected, setSelected] = useState(props.selectedOption);
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
|
||||
const dropdownHandler = () => {
|
||||
setIsDropdownOpen(!isDropdownOpen);
|
||||
};
|
||||
|
||||
const optionSelector = (option: DropdownOption) => {
|
||||
setSelected(option);
|
||||
setIsDropdownOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownWrapper>
|
||||
<SelectedItem onClick={() => dropdownHandler()}>
|
||||
<Text type={TextType.P1}>{selected.label}</Text>
|
||||
<DownArrow />
|
||||
</SelectedItem>
|
||||
{isDropdownOpen ? (
|
||||
<OptionsWrapper>
|
||||
{props.options.map((el: DropdownOption, index: number) => (
|
||||
<DropdownOption
|
||||
key={index}
|
||||
selected={selected}
|
||||
option={el}
|
||||
onClick={() => optionSelector(el)}
|
||||
>
|
||||
<Text type={TextType.H5}>{el.label}</Text>
|
||||
<Text type={TextType.P3}>{el.value}</Text>
|
||||
</DropdownOption>
|
||||
))}
|
||||
</OptionsWrapper>
|
||||
) : null}
|
||||
</DropdownWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableDropdown;
|
||||
37
app/client/src/components/stories/TableDropdown.stories.tsx
Normal file
37
app/client/src/components/stories/TableDropdown.stories.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import { withKnobs, select, boolean, text } from "@storybook/addon-knobs";
|
||||
import { withDesign } from "storybook-addon-designs";
|
||||
import TableDropdown from "../ads/TableDropdown";
|
||||
|
||||
export default {
|
||||
title: "Dropdown",
|
||||
component: TableDropdown,
|
||||
decorators: [withKnobs, withDesign],
|
||||
};
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: "Admin",
|
||||
value: "Can edit, view and invite other user to an app",
|
||||
},
|
||||
{
|
||||
label: "Developer",
|
||||
value: "Can view and invite other user to an app",
|
||||
},
|
||||
{
|
||||
label: "User",
|
||||
value: "Can view and invite other user to an app and...",
|
||||
},
|
||||
];
|
||||
|
||||
export const TableDropdownStory = () => (
|
||||
<div
|
||||
style={{ padding: "50px 200px", height: "500px", background: "#1A191C" }}
|
||||
>
|
||||
<TableDropdown
|
||||
options={options}
|
||||
onSelect={(selectedValue: string) => console.log(selectedValue)}
|
||||
selectedOption={options[0]}
|
||||
></TableDropdown>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -161,6 +161,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
DELETE_APPLICATION_SUCCESS: "DELETE_APPLICATION_SUCCESS",
|
||||
DELETE_PAGE_INIT: "DELETE_PAGE_INIT",
|
||||
DELETE_PAGE_SUCCESS: "DELETE_PAGE_SUCCESS",
|
||||
CLONE_PAGE_INIT: "CLONE_PAGE_INIT",
|
||||
CLONE_PAGE_SUCCESS: "CLONE_PAGE_SUCCESS",
|
||||
SET_DEFAULT_APPLICATION_PAGE_INIT: "SET_DEFAULT_APPLICATION_PAGE_INIT",
|
||||
SET_DEFAULT_APPLICATION_PAGE_SUCCESS: "SET_DEFAULT_APPLICATION_PAGE_SUCCESS",
|
||||
CREATE_ORGANIZATION_INIT: "CREATE_ORGANIZATION_INIT",
|
||||
|
|
@ -312,6 +314,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
MOVE_ACTION_ERROR: "MOVE_ACTION_ERROR",
|
||||
COPY_ACTION_ERROR: "COPY_ACTION_ERROR",
|
||||
DELETE_PAGE_ERROR: "DELETE_PAGE_ERROR",
|
||||
CLONE_PAGE_ERROR: "CLONE_PAGE_ERROR",
|
||||
DELETE_APPLICATION_ERROR: "DELETE_APPLICATION_ERROR",
|
||||
SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR",
|
||||
CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR",
|
||||
|
|
@ -394,6 +397,13 @@ export interface Page {
|
|||
latest?: boolean;
|
||||
}
|
||||
|
||||
export interface ClonePageSuccessPayload {
|
||||
pageName: string;
|
||||
pageId: string;
|
||||
layoutId: string;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
export type PageListPayload = Array<Page>;
|
||||
|
||||
export type ApplicationPayload = {
|
||||
|
|
|
|||
|
|
@ -125,6 +125,11 @@ export const WidgetIcons: {
|
|||
<ModalIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
FORM_BUTTON_WIDGET: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<ButtonIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
};
|
||||
|
||||
export type WidgetIcon = typeof WidgetIcons[keyof typeof WidgetIcons];
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
JSExecutorResult,
|
||||
} from "./JSExecutionManagerSingleton";
|
||||
import JSONFn from "json-fn";
|
||||
import log from "loglevel";
|
||||
declare let Realm: any;
|
||||
|
||||
export default class RealmExecutor implements JSExecutor {
|
||||
|
|
@ -104,7 +105,8 @@ export default class RealmExecutor implements JSExecutor {
|
|||
triggers,
|
||||
};
|
||||
} catch (e) {
|
||||
// console.error(`Error: "${e.message}" when evaluating {{${sourceText}}}`);
|
||||
log.debug(`Error: "${e.message}" when evaluating {{${sourceText}}}`);
|
||||
log.debug(e);
|
||||
return { result: undefined, triggers: [] };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ const Control = styled.div<{ fixed?: boolean }>`
|
|||
|
||||
.${Classes.BUTTON_TEXT} {
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.more {
|
||||
|
|
@ -269,7 +270,7 @@ export const ApplicationCard = (props: ApplicationCardProps) => {
|
|||
|
||||
{hasEditPermission && (
|
||||
<Button
|
||||
onClick={() => history.push(editApplicationURL)}
|
||||
href={editApplicationURL}
|
||||
filled
|
||||
text="EDIT"
|
||||
intent="primary"
|
||||
|
|
@ -285,8 +286,8 @@ export const ApplicationCard = (props: ApplicationCardProps) => {
|
|||
/>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => history.push(viewApplicationURL)}
|
||||
intent="none"
|
||||
href={viewApplicationURL}
|
||||
outline
|
||||
fluid
|
||||
text="LAUNCH"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
|||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { ContextMenuPopoverModifiers } from "../helpers";
|
||||
import { initExplorerEntityNameEdit } from "actions/explorerActions";
|
||||
import { clonePageInit } from "actions/pageActions";
|
||||
|
||||
export const PageContextMenu = (props: {
|
||||
pageId: string;
|
||||
|
|
@ -52,12 +53,22 @@ export const PageContextMenu = (props: {
|
|||
[dispatch, props.pageId],
|
||||
);
|
||||
|
||||
const clonePage = useCallback(() => dispatch(clonePageInit(props.pageId)), [
|
||||
dispatch,
|
||||
props.pageId,
|
||||
]);
|
||||
|
||||
const optionTree: TreeDropdownOption[] = [
|
||||
{
|
||||
value: "rename",
|
||||
onSelect: editPageName,
|
||||
label: "Edit Name",
|
||||
},
|
||||
{
|
||||
value: "clone",
|
||||
onSelect: clonePage,
|
||||
label: "Clone",
|
||||
},
|
||||
];
|
||||
if (!props.isDefaultPage) {
|
||||
optionTree.push({
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ export default (props: PopperProps) => {
|
|||
},
|
||||
},
|
||||
);
|
||||
_popper.disableEventListeners();
|
||||
return () => {
|
||||
_popper.destroy();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
PageListPayload,
|
||||
ClonePageSuccessPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
|
||||
const initialState: PageListReduxState = {
|
||||
|
|
@ -51,6 +52,17 @@ const pageListReducer = createReducer(initialState, {
|
|||
_state.pages.push({ ...action.payload, latest: true });
|
||||
return { ..._state };
|
||||
},
|
||||
[ReduxActionTypes.CLONE_PAGE_SUCCESS]: (
|
||||
state: PageListReduxState,
|
||||
action: ReduxAction<ClonePageSuccessPayload>,
|
||||
): PageListReduxState => {
|
||||
return {
|
||||
...state,
|
||||
pages: state.pages
|
||||
.map(page => ({ ...page, latest: false }))
|
||||
.concat([{ ...action.payload, latest: true }]),
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.SET_DEFAULT_APPLICATION_PAGE_SUCCESS]: (
|
||||
state: PageListReduxState,
|
||||
action: ReduxAction<{ pageId: string; applicationId: string }>,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ const initialState: EditorReduxState = {
|
|||
isPageSwitching: false,
|
||||
creatingPage: false,
|
||||
creatingPageError: false,
|
||||
cloningPage: false,
|
||||
cloningPageError: false,
|
||||
updatingWidgetName: false,
|
||||
updateWidgetNameError: false,
|
||||
},
|
||||
|
|
@ -111,6 +113,20 @@ const editorReducer = createReducer(initialState, {
|
|||
currentApplicationId,
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.CLONE_PAGE_INIT]: (state: EditorReduxState) => {
|
||||
state.loadingStates.cloningPage = true;
|
||||
state.loadingStates.cloningPageError = false;
|
||||
return { ...state };
|
||||
},
|
||||
[ReduxActionTypes.CLONE_PAGE_ERROR]: (state: EditorReduxState) => {
|
||||
state.loadingStates.cloningPageError = true;
|
||||
state.loadingStates.cloningPage = false;
|
||||
return { ...state };
|
||||
},
|
||||
[ReduxActionTypes.CLONE_PAGE_SUCCESS]: (state: EditorReduxState) => {
|
||||
state.loadingStates.cloningPage = false;
|
||||
return { ...state };
|
||||
},
|
||||
[ReduxActionTypes.CREATE_PAGE_INIT]: (state: EditorReduxState) => {
|
||||
state.loadingStates.creatingPage = true;
|
||||
state.loadingStates.creatingPageError = false;
|
||||
|
|
@ -162,6 +178,8 @@ export interface EditorReduxState {
|
|||
pageSwitchingError: boolean;
|
||||
creatingPage: boolean;
|
||||
creatingPageError: boolean;
|
||||
cloningPage: boolean;
|
||||
cloningPageError: boolean;
|
||||
updatingWidgetName: boolean;
|
||||
updateWidgetNameError: boolean;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ const explorerReducer = createReducer(initialState, {
|
|||
[ReduxActionTypes.FETCH_PAGE_ERROR]: setEntityUpdateError,
|
||||
[ReduxActionTypes.FETCH_PAGE_SUCCESS]: setEntityUpdateSuccess,
|
||||
|
||||
[ReduxActionTypes.CLONE_PAGE_INIT]: setUpdatingEntity,
|
||||
[ReduxActionTypes.CLONE_PAGE_ERROR]: setEntityUpdateError,
|
||||
[ReduxActionTypes.CLONE_PAGE_SUCCESS]: setEntityUpdateSuccess,
|
||||
|
||||
[ReduxActionTypes.MOVE_ACTION_INIT]: setUpdatingEntity,
|
||||
[ReduxActionErrorTypes.MOVE_ACTION_ERROR]: setEntityUpdateError,
|
||||
[ReduxActionTypes.MOVE_ACTION_SUCCESS]: setEntityUpdateSuccess,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "constants/ReduxActionConstants";
|
||||
import {
|
||||
deletePageSuccess,
|
||||
clonePageSuccess,
|
||||
fetchPageSuccess,
|
||||
fetchPublishedPageSuccess,
|
||||
savePageSuccess,
|
||||
|
|
@ -31,6 +32,7 @@ import PageApi, {
|
|||
UpdatePageRequest,
|
||||
UpdateWidgetNameRequest,
|
||||
UpdateWidgetNameResponse,
|
||||
ClonePageRequest,
|
||||
} from "api/PageApi";
|
||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import {
|
||||
|
|
@ -382,6 +384,40 @@ export function* deletePageSaga(action: ReduxAction<DeletePageRequest>) {
|
|||
}
|
||||
}
|
||||
|
||||
export function* clonePageSaga(clonePageAction: ReduxAction<ClonePageRequest>) {
|
||||
try {
|
||||
const request: ClonePageRequest = clonePageAction.payload;
|
||||
const response: FetchPageResponse = yield call(PageApi.clonePage, request);
|
||||
const applicationId = yield select(
|
||||
(state: AppState) => state.entities.pageList.applicationId,
|
||||
);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (isValidResponse) {
|
||||
yield put(
|
||||
clonePageSuccess(
|
||||
response.data.id,
|
||||
response.data.name,
|
||||
response.data.layouts[0].id,
|
||||
),
|
||||
);
|
||||
yield put({
|
||||
type: ReduxActionTypes.FETCH_PAGE_DSL_INIT,
|
||||
payload: {
|
||||
pageId: response.data.id,
|
||||
},
|
||||
});
|
||||
history.push(BUILDER_PAGE_URL(applicationId, response.data.id));
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.CLONE_PAGE_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* updateWidgetNameSaga(
|
||||
action: ReduxAction<{ id: string; newName: string }>,
|
||||
) {
|
||||
|
|
@ -477,6 +513,7 @@ export default function* pageSagas() {
|
|||
),
|
||||
takeLatest(ReduxActionTypes.UPDATE_LAYOUT, saveLayoutSaga),
|
||||
takeLeading(ReduxActionTypes.CREATE_PAGE_INIT, createPageSaga),
|
||||
takeLeading(ReduxActionTypes.CLONE_PAGE_INIT, clonePageSaga),
|
||||
takeLatest(ReduxActionTypes.FETCH_PAGE_LIST_INIT, fetchPageListSaga),
|
||||
takeLatest(ReduxActionTypes.UPDATE_PAGE_INIT, updatePageSaga),
|
||||
takeLatest(ReduxActionTypes.DELETE_PAGE_INIT, deletePageSaga),
|
||||
|
|
|
|||
|
|
@ -2,12 +2,16 @@ import React from "react";
|
|||
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
|
||||
import { TriggerPropertiesMap } from "utils/WidgetFactory";
|
||||
import { WidgetType, WidgetTypes } from "constants/WidgetConstants";
|
||||
|
||||
import styled from "styled-components";
|
||||
import IconComponent, {
|
||||
IconType,
|
||||
} from "components/designSystems/appsmith/IconComponent";
|
||||
import { EventType, ExecutionResult } from "constants/ActionConstants";
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
class IconWidget extends BaseWidget<IconWidgetProps, WidgetState> {
|
||||
static getTriggerPropertyMap(): TriggerPropertiesMap {
|
||||
return {
|
||||
|
|
@ -32,13 +36,15 @@ class IconWidget extends BaseWidget<IconWidgetProps, WidgetState> {
|
|||
|
||||
getPageView() {
|
||||
return (
|
||||
<IconComponent
|
||||
iconName={this.props.iconName}
|
||||
disabled={this.props.disabled}
|
||||
iconSize={this.props.iconSize}
|
||||
color={this.props.color}
|
||||
onClick={this.onClick}
|
||||
/>
|
||||
<IconWrapper>
|
||||
<IconComponent
|
||||
iconName={this.props.iconName}
|
||||
disabled={this.props.disabled}
|
||||
iconSize={this.props.iconSize}
|
||||
color={this.props.color}
|
||||
onClick={this.onClick}
|
||||
/>
|
||||
</IconWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
pageSize: undefined,
|
||||
selectedRowIndex: -1,
|
||||
searchText: undefined,
|
||||
selectedRow: undefined,
|
||||
selectedRow: {},
|
||||
// The following meta property is used for rendering the table.
|
||||
filteredTableData: undefined,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.appsmith.external.plugins.BasePlugin;
|
|||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.MongoCommandException;
|
||||
import com.mongodb.MongoTimeoutException;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
|
|
@ -299,19 +300,41 @@ public class MongoPlugin extends BasePlugin {
|
|||
|
||||
@Override
|
||||
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||
final Connection.Type connectionType = datasourceConfiguration.getConnection().getType();
|
||||
return datasourceCreate(datasourceConfiguration)
|
||||
.map(mongoClient -> {
|
||||
.map(mongoClientObj -> {
|
||||
final MongoClient mongoClient = (MongoClient) mongoClientObj;
|
||||
ClientSession clientSession = null;
|
||||
|
||||
try {
|
||||
// Not using try-with-resources here since we want to close the *session* before closing the
|
||||
// MongoClient instance.
|
||||
clientSession = ((MongoClient) mongoClient).startSession();
|
||||
if (Connection.Type.REPLICA_SET.equals(connectionType)) {
|
||||
// For REPLICA_SET connections, we check by creating a session, as this is faster.
|
||||
clientSession = mongoClient.startSession();
|
||||
|
||||
} else {
|
||||
// For DIRECT connections, we check by running a DB command, as it's the only reliable
|
||||
// method of checking if the connection is usable.
|
||||
mongoClient
|
||||
.getDatabase("admin")
|
||||
.runCommand(new Document("listDatabases", 1));
|
||||
return new DatasourceTestResult();
|
||||
|
||||
}
|
||||
|
||||
} catch (MongoTimeoutException e) {
|
||||
log.warn("Timeout connecting to MongoDB from MongoPlugin.", e);
|
||||
return new DatasourceTestResult("Timed out trying to connect to MongoDB host.");
|
||||
|
||||
} catch(MongoCommandException e) {
|
||||
// The fact that we got a response saying "Unauthorized" means that the connection to the
|
||||
// MongoDB instance is valid. It also means we don't have access to the admin database, but
|
||||
// that's okay for our purposes here.
|
||||
return "Unauthorized".equals(e.getErrorCodeName())
|
||||
? new DatasourceTestResult()
|
||||
: new DatasourceTestResult(e.getMessage());
|
||||
|
||||
} catch (Exception e) {
|
||||
return new DatasourceTestResult(e.getMessage());
|
||||
|
||||
|
|
@ -319,8 +342,8 @@ public class MongoPlugin extends BasePlugin {
|
|||
if (clientSession != null) {
|
||||
clientSession.close();
|
||||
}
|
||||
if (mongoClient instanceof MongoClient) {
|
||||
((MongoClient) mongoClient).close();
|
||||
if (mongoClient != null) {
|
||||
mongoClient.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user