diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/Widget_loading_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/Widget_loading_spec.js index bdf1f5b459..d9936a0cf5 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/Widget_loading_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/Widget_loading_spec.js @@ -58,7 +58,6 @@ describe("Binding the multiple widgets and validating default data", function() ); }); - it("Input widget test with default value update with query data", function() { cy.SearchEntityandOpen("Input1"); cy.get(widgetsPage.defaultInput).type(testdata.defaultInputQuery); diff --git a/app/client/cypress/integration/Smoke_TestSuite/Datasources/MongoDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Datasources/MongoDatasource_spec.js index 15d78e012b..0a7473938d 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Datasources/MongoDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Datasources/MongoDatasource_spec.js @@ -1,5 +1,4 @@ const datasource = require("../../../locators/DatasourcesEditor.json"); -let pageid; describe("Create, test, save then delete a mongo datasource", function() { it("Create, test, save then delete a mongo datasource", function() { diff --git a/app/client/cypress/integration/Smoke_TestSuite/Datasources/PostgresDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Datasources/PostgresDatasource_spec.js index 3d18bb62ce..c70aaf4367 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Datasources/PostgresDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Datasources/PostgresDatasource_spec.js @@ -1,11 +1,38 @@ const datasource = require("../../../locators/DatasourcesEditor.json"); +const queryEditor = require("../../../locators/QueryEditor.json"); -describe("Create, test, save then delete a postgres datasource", function() { +let datasourceName; + +describe("Postgres datasource test cases", function() { it("Create, test, save then delete a postgres datasource", function() { cy.NavigateToDatasourceEditor(); cy.get(datasource.PostgreSQL).click(); cy.getPluginFormsAndCreateDatasource(); cy.fillPostgresDatasourceForm(); - cy.testSaveDeleteDatasource(); + cy.get("@createDatasource").then(httpResponse => { + datasourceName = httpResponse.response.body.data.name; + }); + cy.testSaveDatasource(); + }); + + it("Create a new query from the datasource editor", function() { + cy.saveDatasource(); + cy.get(datasource.createQuerty).click(); + cy.wait("@createNewApi").should( + "have.nested.property", + "response.body.responseMeta.status", + 201, + ); + + cy.get(queryEditor.deleteQuery).click(); + cy.wait("@deleteAction").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + + cy.GlobalSearchEntity(`${datasourceName}`); + cy.get(`.t--entity-name:contains(${datasourceName})`).click(); + cy.deleteDataSource(); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Table_spec.js b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Table_spec.js index c90c7359a2..bc8f415335 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Table_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/DisplayWidgets/Table_spec.js @@ -224,7 +224,6 @@ describe("Table Widget Functionality", function() { cy.get(publish.canvas) .first() .click(); - }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_CopyQuery_RenameDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_CopyQuery_RenameDatasource_spec.js index 39a8fc5798..069b2e124b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_CopyQuery_RenameDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_CopyQuery_RenameDatasource_spec.js @@ -86,6 +86,7 @@ describe("Entity explorer tests related to copy query", function() { cy.log("sliced id :" + updatedName); cy.EditEntityNameByDoubleClick(datasourceName, updatedName); cy.SearchEntityandOpen(updatedName); + cy.get(datasource.editDatasource).click(); cy.testSaveDatasource(); cy.hoverAndClick(); cy.get(apiwidget.delete).click({ force: true }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js index 587af0cba4..f0de36ae15 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js @@ -50,6 +50,7 @@ describe("Entity explorer datasource structure", function() { "response.body.responseMeta.status", 200, ); + cy.deletePostgresDatasource(datasourceName); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/QueryPane/MongoDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/QueryPane/MongoDatasource_spec.js index dee40069e2..a3835102b2 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/QueryPane/MongoDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/QueryPane/MongoDatasource_spec.js @@ -44,6 +44,8 @@ describe("Create a query with a mongo datasource, run, save and then delete the cy.get(`.t--entity-name:contains(${datasourceName})`).click(); }); + + cy.get(datasource.editDatasource).click(); cy.get(".t--delete-datasource").click(); cy.wait("@deleteDatasource").should( "have.nested.property", diff --git a/app/client/cypress/integration/Smoke_TestSuite/QueryPane/PostgreDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/QueryPane/PostgreDatasource_spec.js index 15f80ed9aa..e14eb56d25 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/QueryPane/PostgreDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/QueryPane/PostgreDatasource_spec.js @@ -50,6 +50,7 @@ describe("Create a query with a postgres datasource, run, save and then delete t it("Deletes a datasource", () => { cy.NavigateToDatasourceEditor(); cy.get(`.t--entity-name:contains(${datasourceName})`).click(); + cy.get(datasource.editDatasource).click(); cy.get(".t--delete-datasource").click(); cy.wait("@deleteDatasource").should( diff --git a/app/client/cypress/locators/DatasourcesEditor.json b/app/client/cypress/locators/DatasourcesEditor.json index 95b812fa9d..9fd3710f5c 100644 --- a/app/client/cypress/locators/DatasourcesEditor.json +++ b/app/client/cypress/locators/DatasourcesEditor.json @@ -14,5 +14,7 @@ "sectionAuthentication": "[data-cy=section-Authentication]", "sectionSSL": "[data-cy=section-SSL\\ \\(optional\\)]", "addDatasourceEntity": ".plugins .t--entity-add-btn", - "PostgresEntity": ".t--entity-name:contains(PostgreSQL)" + "PostgresEntity": ".t--entity-name:contains(PostgreSQL)", + "createQuerty": ".t--create-query", + "editDatasource": ".t--edit-datasource" } diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index e30d003dfc..6704710e71 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1315,6 +1315,8 @@ Cypress.Commands.add("testSaveDeleteDatasource", () => { 200, ); + cy.get(datasourceEditor.editDatasource).click(); + cy.get(".t--delete-datasource").click(); cy.wait("@deleteDatasource").should( "have.nested.property", @@ -1360,6 +1362,7 @@ Cypress.Commands.add("saveDatasource", () => { Cypress.Commands.add("testSaveDatasource", () => { cy.saveDatasource(); + cy.get(datasourceEditor.editDatasource).click(); cy.testDatasource(); }); @@ -1420,7 +1423,7 @@ Cypress.Commands.add("createPostgresDatasource", () => { Cypress.Commands.add("deletePostgresDatasource", datasourceName => { cy.NavigateToDatasourceEditor(); cy.get(`.t--entity-name:contains(${datasourceName})`).click(); - + cy.get(datasourceEditor.editDatasource).click(); cy.get(".t--delete-datasource").click(); cy.wait("@deleteDatasource").should( "have.nested.property", diff --git a/app/client/src/AppRouter.tsx b/app/client/src/AppRouter.tsx index d539b60e35..6a71c3413c 100644 --- a/app/client/src/AppRouter.tsx +++ b/app/client/src/AppRouter.tsx @@ -34,6 +34,7 @@ import { setThemeMode } from "actions/themeActions"; import { connect } from "react-redux"; import * as Sentry from "@sentry/react"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const SentryRoute = Sentry.withSentryRouting(Route); const loadingIndicator = ; @@ -51,15 +52,25 @@ function changeAppBackground(currentTheme: any) { } class AppRouter extends React.Component { + unlisten: any; + + componentDidMount() { + // This is needed for the route switch. + AnalyticsUtil.logEvent("ROUTE_CHANGE", { path: window.location.pathname }); + this.unlisten = history.listen((location: any) => { + AnalyticsUtil.logEvent("ROUTE_CHANGE", { path: location.pathname }); + changeAppBackground(this.props.currentTheme); + }); + } + + componentWillUnmount() { + this.unlisten(); + } + render() { const { currentTheme } = this.props; // This is needed for the theme switch. changeAppBackground(currentTheme); - // This is needed for the route switch. - history.listen(() => { - changeAppBackground(currentTheme); - }); - return ( diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts index 83bea3120c..f10c641f9e 100644 --- a/app/client/src/actions/datasourceActions.ts +++ b/app/client/src/actions/datasourceActions.ts @@ -80,6 +80,16 @@ export const deleteDatasource = (payload: Partial) => { }; }; +export const setDatsourceEditorMode = (payload: { + id: string; + viewMode: boolean; +}) => { + return { + type: ReduxActionTypes.SET_DATASOURCE_EDITOR_MODE, + payload, + }; +}; + export const fetchDatasources = () => { return { type: ReduxActionTypes.FETCH_DATASOURCES_INIT, diff --git a/app/client/src/api/DatasourcesApi.ts b/app/client/src/api/DatasourcesApi.ts index d6db6fc9a6..fa690d1b88 100644 --- a/app/client/src/api/DatasourcesApi.ts +++ b/app/client/src/api/DatasourcesApi.ts @@ -2,6 +2,7 @@ import API from "./Api"; import { GenericApiResponse } from "./ApiResponses"; import { AxiosPromise } from "axios"; import { DEFAULT_TEST_DATA_SOURCE_TIMEOUT_MS } from "constants/ApiConstants"; +import { Property } from "entities/Action"; interface DatasourceAuthentication { authType?: string; @@ -45,7 +46,7 @@ export interface Datasource { url: string; authentication?: DatasourceAuthentication; properties?: Record; - headers?: Record; + headers?: Property[]; databaseName?: string; }; invalids?: string[]; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index beacd4a55a..fdef7b795d 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -63,6 +63,7 @@ export const ReduxActionTypes: { [key: string]: string } = { UPDATE_ACTION_INIT: "UPDATE_ACTION_INIT", UPDATE_ACTION_SUCCESS: "UPDATE_ACTION_SUCCESS", DELETE_ACTION_INIT: "DELETE_ACTION_INIT", + SET_DATASOURCE_EDITOR_MODE: "SET_DATASOURCE_EDITOR_MODE", DELETE_ACTION_SUCCESS: "DELETE_ACTION_SUCCESS", SHOW_RUN_ACTION_CONFIRM_MODAL: "SHOW_RUN_ACTION_CONFIRM_MODAL", CANCEL_RUN_ACTION_CONFIRM_MODAL: "CANCEL_RUN_ACTION_CONFIRM_MODAL", diff --git a/app/client/src/pages/Editor/DataSourceEditor/Connected.tsx b/app/client/src/pages/Editor/DataSourceEditor/Connected.tsx new file mode 100644 index 0000000000..7e5dd9be7a --- /dev/null +++ b/app/client/src/pages/Editor/DataSourceEditor/Connected.tsx @@ -0,0 +1,254 @@ +import React, { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useParams } from "react-router"; +import { AppState } from "reducers"; +import { isNil, map, get } from "lodash"; +import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; +import { getDatasource, getPlugin } from "selectors/entitiesSelector"; +import { Colors } from "constants/Colors"; +import { HeaderIcons } from "icons/HeaderIcons"; +import history from "utils/history"; +import styled from "styled-components"; +import { createActionRequest } from "actions/actionActions"; +import { + API_EDITOR_URL_WITH_SELECTED_PAGE_ID, + QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID, +} from "constants/routes"; +import { createNewApiName, createNewQueryName } from "utils/AppsmithUtils"; +import { getCurrentPageId } from "selectors/editorSelectors"; +import { Datasource } from "api/DatasourcesApi"; +import { DEFAULT_API_ACTION } from "constants/ApiEditorConstants"; +import { ApiActionConfig, PluginType } from "entities/Action"; +import { AppToaster } from "components/editorComponents/ToastComponent"; + +const ConnectedText = styled.div` + color: ${Colors.GREEN}; + font-size: 17px; + font-weight: bold; + display: flex; + align-items: center; +`; + +const Header = styled.div` + flex-direction: row; + display: flex; + align-items: center; + justify-content: space-between; +`; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + border-top: 1px solid #d0d7dd; + border-bottom: 1px solid #d0d7dd; + padding-top: 24px; + padding-bottom: 24px; + margin-top: 18px; +`; + +const ActionButton = styled(BaseButton)` + &&& { + max-width: 140px; + align-self: center; + } +`; + +const Key = styled.div` + color: #6d6d6d; + font-size: 14px; + font-weight: 500; + display: inline-block; +`; + +const Value = styled.div` + font-size: 14px; + font-weight: 400; + display: inline-block; + margin-left: 5px; +`; + +const ValueWrapper = styled.div` + display: inline-block; + margin-left: 10px; +`; + +const Connected = () => { + const params = useParams<{ datasourceId: string; applicationId: string }>(); + const datasource = useSelector((state: AppState) => + getDatasource(state, params.datasourceId), + ); + const dispatch = useDispatch(); + const actions = useSelector((state: AppState) => state.entities.actions); + const currentPageId = useSelector(getCurrentPageId); + const datasourceFormConfigs = useSelector( + (state: AppState) => state.entities.plugins.formConfigs, + ); + const plugin = useSelector((state: AppState) => + getPlugin(state, datasource?.pluginId ?? ""), + ); + const isDBDatasource = plugin?.type === PluginType.DB; + + const createQueryAction = useCallback(() => { + const newQueryName = createNewQueryName(actions, currentPageId || ""); + + dispatch( + createActionRequest({ + name: newQueryName, + pageId: currentPageId, + pluginId: datasource?.pluginId, + datasource: { + id: datasource?.id, + }, + actionConfiguration: {}, + eventData: { + actionType: "Query", + from: "datasource-pane", + }, + }), + ); + history.push( + QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID( + params.applicationId, + currentPageId, + currentPageId, + ), + ); + }, [dispatch, actions, currentPageId, params.applicationId, datasource]); + + const createApiAction = useCallback(() => { + const newApiName = createNewApiName(actions, currentPageId || ""); + const headers = datasource?.datasourceConfiguration?.headers ?? []; + const defaultAction: Partial | undefined = { + ...DEFAULT_API_ACTION.actionConfiguration, + headers: headers.length + ? headers + : DEFAULT_API_ACTION.actionConfiguration?.headers, + }; + + if (!datasource?.datasourceConfiguration?.url) { + AppToaster.show({ + message: "Unable to create API. Try adding a url to the datasource", + type: "error", + }); + + return; + } + + dispatch( + createActionRequest({ + name: newApiName, + pageId: currentPageId, + pluginId: datasource?.pluginId, + datasource: { + id: datasource?.id, + }, + eventData: { + actionType: "API", + from: "datasource-pane", + }, + actionConfiguration: { + ...defaultAction, + }, + }), + ); + history.push( + API_EDITOR_URL_WITH_SELECTED_PAGE_ID( + params.applicationId, + currentPageId, + currentPageId, + ), + ); + }, [dispatch, actions, currentPageId, params.applicationId, datasource]); + const currentFormConfig: Array = + datasourceFormConfigs[datasource?.pluginId ?? ""]; + + return ( + +
+ + +
Datasource Saved
+
+ +
+
+ {!isNil(currentFormConfig) + ? renderSection(currentFormConfig[0], datasource) + : undefined} +
+
+ ); +}; + +const renderSection = ( + section: any, + datasource: Datasource | undefined, +): any => { + return ( + <> + {map(section.children, subSection => { + if ("children" in subSection) { + return renderSection(subSection, datasource); + } else { + try { + const { label, configProperty, controlType } = subSection; + let value = get(datasource, configProperty); + + if (controlType === "KEYVALUE_ARRAY") { + const configPropertyInfo = configProperty.split("[*]."); + const values = get(datasource, configPropertyInfo[0], null); + + if (values) { + const keyValuePair = values[0]; + value = keyValuePair[configPropertyInfo[1]]; + } else { + value = ""; + } + } + + if (controlType === "KEY_VAL_INPUT") { + return ( +
+ {label} + {value.map((val: { key: string; value: string }) => { + return ( +
+
+ Key: + {val.key} +
+ + Value: + {val.value} + +
+ ); + })} +
+ ); + } + + return ( +
+ {label}: {value} +
+ ); + } catch (e) {} + } + })} + + ); +}; + +export default Connected; diff --git a/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx index 25d3906633..4cc8618828 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx @@ -11,6 +11,7 @@ import FormTitle from "./FormTitle"; import { ControlProps } from "components/formControls/BaseControl"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleHelp"; +import Connected from "./Connected"; import FormControlFactory from "utils/FormControlFactory"; import { HelpBaseURL, HelpMap } from "constants/HelpConstants"; @@ -27,6 +28,7 @@ interface DatasourceDBEditorProps { onSave: (formValues: Datasource) => void; onTest: (formValus: Datasource) => void; handleDelete: (id: string) => void; + setDatasourceEditorMode: (id: string, viewMode: boolean) => void; selectedPluginPackage: string; isSaving: boolean; isDeleting: boolean; @@ -39,10 +41,11 @@ interface DatasourceDBEditorProps { formConfig: any[]; isNewDatasource: boolean; pluginImage: string; + viewMode: boolean; } interface DatasourceDBEditorState { - isNameEditable: boolean; + viewMode: boolean; } type Props = DatasourceDBEditorProps & @@ -74,6 +77,13 @@ export const FormTitleContainer = styled.div` flex-direction: row; display: flex; align-items: center; +`; + +export const Header = styled.div` + flex-direction: row; + display: flex; + align-items: center; + justify-content: space-between; margin-top: 16px; `; @@ -123,16 +133,32 @@ class DatasourceDBEditor extends React.Component< super(props); this.state = { - isNameEditable: true, + viewMode: true, }; this.requiredFields = {}; this.configDetails = {}; } + componentDidMount() { + if (this.props.isNewDatasource) { + this.setState({ + viewMode: false, + }); + } + } + componentDidUpdate(prevProps: Props) { if (prevProps.datasourceId !== this.props.datasourceId) { this.requiredFields = {}; this.configDetails = {}; + this.setState({ + viewMode: true, + }); + } + if (!this.state.viewMode && this.props.viewMode) { + this.setState({ + viewMode: true, + }); } } @@ -238,7 +264,7 @@ class DatasourceDBEditor extends React.Component< if (newValues.length) { formData = _.set(formData, properties[0], newValues); } else { - _.unset(formData, properties[0]); + formData = _.set(formData, properties[0], []); } } else if (controlType === "KEY_VAL_INPUT") { if (checked[configProperty]) continue; @@ -259,7 +285,7 @@ class DatasourceDBEditor extends React.Component< if (newValues.length) { formData = _.set(formData, configProperty, newValues); } else { - _.unset(formData, configProperty); + formData = _.set(formData, configProperty, []); } } } @@ -295,6 +321,7 @@ class DatasourceDBEditor extends React.Component< datasourceId, handleDelete, } = this.props; + const { viewMode } = this.state; return (

- - - - +
+ + + + + {viewMode && ( + { + this.setState({ + viewMode: false, + }); + this.props.setDatasourceEditorMode( + this.props.datasourceId, + false, + ); + }} + /> + )} +
{cloudHosting && ( @@ -338,36 +383,42 @@ class DatasourceDBEditor extends React.Component< )} - {!_.isNil(sections) - ? _.map(sections, this.renderMainSection) - : undefined} - - handleDelete(datasourceId)} - /> + {!viewMode ? ( + <> + {!_.isNil(sections) + ? _.map(sections, this.renderMainSection) + : undefined} + + handleDelete(datasourceId)} + /> - - - + + + + + ) : ( + + )} ); }; diff --git a/app/client/src/pages/Editor/DataSourceEditor/index.tsx b/app/client/src/pages/Editor/DataSourceEditor/index.tsx index c8bfa17516..c49a2dbb9a 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/index.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/index.tsx @@ -13,6 +13,7 @@ import { testDatasource, deleteDatasource, switchDatasource, + setDatsourceEditorMode, } from "actions/datasourceActions"; import { DATASOURCE_DB_FORM } from "constants/forms"; import DatasourceHome from "./DatasourceHome"; @@ -31,6 +32,7 @@ interface ReduxStateProps { newDatasource: string; pluginImages: Record; pluginId: string; + viewMode: boolean; } type Props = ReduxStateProps & @@ -80,6 +82,8 @@ class DataSourceEditor extends React.Component { newDatasource, pluginImages, pluginId, + viewMode, + setDatasourceEditorMode, } = this.props; return ( @@ -102,6 +106,8 @@ class DataSourceEditor extends React.Component { loadingFormConfigs={loadingFormConfigs} formConfig={formConfig} handleDelete={deleteDatasource} + viewMode={viewMode} + setDatasourceEditorMode={setDatasourceEditorMode} /> ) : ( { formConfig: formConfigs[datasourcePane.selectedPlugin] || [], loadingFormConfigs, newDatasource: datasourcePane.newDatasource, + viewMode: datasourcePane.viewMode[datasource?.id ?? ""] ?? true, }; }; @@ -149,6 +156,8 @@ const mapDispatchToProps = (dispatch: any): DatasourcePaneFunctions => ({ testDatasource: (data: Datasource) => dispatch(testDatasource(data)), deleteDatasource: (id: string) => dispatch(deleteDatasource({ id })), switchDatasource: (id: string) => dispatch(switchDatasource(id)), + setDatasourceEditorMode: (id: string, viewMode: boolean) => + dispatch(setDatsourceEditorMode({ id, viewMode })), }); export interface DatasourcePaneFunctions { @@ -157,6 +166,7 @@ export interface DatasourcePaneFunctions { testDatasource: (data: Datasource) => void; deleteDatasource: (id: string) => void; switchDatasource: (id: string) => void; + setDatasourceEditorMode: (id: string, viewMode: boolean) => void; } export default connect(mapStateToProps, mapDispatchToProps)(DataSourceEditor); diff --git a/app/client/src/pages/Editor/QueryEditor/QueryHomeScreen.tsx b/app/client/src/pages/Editor/QueryEditor/QueryHomeScreen.tsx index 2bff86768b..39f31236a1 100644 --- a/app/client/src/pages/Editor/QueryEditor/QueryHomeScreen.tsx +++ b/app/client/src/pages/Editor/QueryEditor/QueryHomeScreen.tsx @@ -13,7 +13,6 @@ import { QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID, DATA_SOURCES_EDITOR_URL, } from "constants/routes"; -import AnalyticsUtil from "utils/AnalyticsUtil"; import { QueryAction } from "entities/Action"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; diff --git a/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts b/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts index 52609dae11..2db8c41d0c 100644 --- a/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts +++ b/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts @@ -10,6 +10,7 @@ const initialState: DatasourcePaneReduxState = { actionRouteInfo: {}, expandDatasourceId: "", newDatasource: "", + viewMode: {}, }; export interface DatasourcePaneReduxState { @@ -23,6 +24,7 @@ export interface DatasourcePaneReduxState { applicationId: string; }>; newDatasource: string; + viewMode: Record; } const datasourcePaneReducer = createReducer(initialState, { @@ -92,6 +94,18 @@ const datasourcePaneReducer = createReducer(initialState, { expandDatasourceId: action.payload.id, }; }, + [ReduxActionTypes.SET_DATASOURCE_EDITOR_MODE]: ( + state: DatasourcePaneReduxState, + action: ReduxAction<{ id: string; viewMode: boolean }>, + ) => { + return { + ...state, + viewMode: { + ...state.viewMode, + [action.payload.id]: action.payload.viewMode, + }, + }; + }, [ReduxActionTypes.EXPAND_DATASOURCE_ENTITY]: ( state: DatasourcePaneReduxState, action: ReduxAction, diff --git a/app/client/src/sagas/ActionExecutionSagas.ts b/app/client/src/sagas/ActionExecutionSagas.ts index 089f9557c9..86cac74ffd 100644 --- a/app/client/src/sagas/ActionExecutionSagas.ts +++ b/app/client/src/sagas/ActionExecutionSagas.ts @@ -298,11 +298,12 @@ export function* executeActionSaga( ); try { const api: RestAction = yield select(getAction, actionId); - + const currentAppId = yield select(getCurrentApplicationId); AnalyticsUtil.logEvent("EXECUTE_ACTION", { type: api.pluginType, name: api.name, pageId: api.pageId, + appId: currentAppId, }); if (api.confirmBeforeExecute) { const confirmed = yield call(confirmRunActionSaga); @@ -616,6 +617,7 @@ function* executePageLoadAction(pageAction: PageAction) { PerformanceTransactionName.EXECUTE_PAGE_LOAD_ACTIONS, ); const pageId = yield select(getCurrentPageId); + const appId = yield select(getCurrentApplicationId); yield put(executeApiActionRequest({ id: pageAction.id })); const params: Property[] = yield call( getActionParams, @@ -629,6 +631,7 @@ function* executePageLoadAction(pageAction: PageAction) { type: pageAction.pluginType, name: pageAction.name, pageId: pageId, + appId: appId, onPageLoad: true, }); const response: ActionApiResponse = yield ActionAPI.executeAction( diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index 8efe7ba926..92a6b7ab52 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -29,6 +29,8 @@ import { selectPlugin, createDatasource, changeDatasource, + testDatasource, + setDatsourceEditorMode, expandDatasourceEntity, fetchDatasourceStructure, } from "actions/datasourceActions"; @@ -149,13 +151,9 @@ export function* deleteDatasourceSaga( }); } } catch (error) { - AppToaster.show({ - message: error.message, - type: ToastType.ERROR, - }); yield put({ type: ReduxActionErrorTypes.DELETE_DATASOURCE_ERROR, - payload: { error, id: actionPayload.payload.id }, + payload: { error, id: actionPayload.payload.id, show: false }, }); } } @@ -193,6 +191,9 @@ function* updateDatasourceSaga(actionPayload: ReduxAction) { id: response.data.id, }, }); + yield put( + setDatsourceEditorMode({ id: datasourcePayload.id, viewMode: true }), + ); if (expandDatasourceId === response.data.id && !datasourceStruture) { yield put(fetchDatasourceStructure(response.data.id)); @@ -270,6 +271,10 @@ function* testDatasourceSaga(actionPayload: ReduxAction) { message: responseData.invalids[0], type: ToastType.ERROR, }); + yield put({ + type: ReduxActionErrorTypes.TEST_DATASOURCE_ERROR, + payload: { show: false }, + }); } else { AnalyticsUtil.logEvent("TEST_DATA_SOURCE_SUCCESS", { datasource: payload.name, @@ -278,11 +283,11 @@ function* testDatasourceSaga(actionPayload: ReduxAction) { message: `${payload.name} is valid`, type: ToastType.SUCCESS, }); + yield put({ + type: ReduxActionTypes.TEST_DATASOURCE_SUCCESS, + payload: datasource, + }); } - yield put({ - type: ReduxActionTypes.TEST_DATASOURCE_SUCCESS, - payload: datasource, - }); } } catch (error) { yield put({ @@ -370,6 +375,9 @@ function* createDatasourceFromFormSaga( type: ReduxActionTypes.CREATE_DATASOURCE_SUCCESS, payload: response.data, }); + yield put( + setDatsourceEditorMode({ id: response.data.id, viewMode: false }), + ); const applicationId = yield select(getCurrentApplicationId); const pageId = yield select(getCurrentPageId); diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index 1326ad71a6..c309180bc2 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -203,6 +203,10 @@ export const getActionsForCurrentPage = createSelector( }, ); +export const getPlugin = (state: AppState, pluginId: string) => { + return state.entities.plugins.list.find(plugin => plugin.id === pluginId); +}; + export const getActionResponses = createSelector(getActions, actions => { const responses: Record = {}; diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx index 3055933ce1..cd1086abe0 100644 --- a/app/client/src/utils/AnalyticsUtil.tsx +++ b/app/client/src/utils/AnalyticsUtil.tsx @@ -82,6 +82,7 @@ export type EventName = | "WIDGET_DELETE_VIA_SHORTCUT" | "OPEN_HELP" | "INVITE_USER" + | "ROUTE_CHANGE" | "PROPERTY_PANE_CLOSE_CLICK" | "APPLICATIONS_PAGE_LOAD" | "EXECUTE_ACTION";