From 35b9e0b54411b6eef65c94a4ac4f1679a1f39b58 Mon Sep 17 00:00:00 2001 From: Ankita Kinger Date: Fri, 8 Dec 2023 17:12:14 +0530 Subject: [PATCH] chore: Refactoring header actions on datasource page to support reusable queries on EE (#29442) ## Description Refactoring header actions on datasource page to support reusable queries on EE #### PR fixes following issue(s) Fixes [#28568](https://github.com/appsmithorg/appsmith/issues/28568) #### Type of change - Chore (housekeeping or task changes that don't impact user perception) ## Testing #### How Has This Been Tested? - [x] Manual - [ ] JUnit - [ ] Jest - [x] Cypress ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed ## Summary by CodeRabbit - **New Features** - Introduced a new header action management system in the data source editor, allowing for dynamic action button generation based on user permissions and editor type. - Implemented a new editor type detection hook to improve editor experience. - **Improvements** - Streamlined the data source editor interface by removing unused permissions checks and simplifying action button logic. - Enhanced the editor's routing system with additional constants for deprecated paths. - **Refactor** - Refactored the `DSFormHeader` component to utilize the new `useHeaderActions` hook for a cleaner and more maintainable codebase. - Updated Redux state management to include a new action handler for resetting editor state. - **Style** - Adjusted the layout of the `LeftPaneContainer` by removing the `min-width` property for a more responsive design. - **Chores** - Cleaned up imports and unused variables across multiple files to improve code clarity and maintainability. --- .../src/ce/hooks/datasourceEditorHooks.tsx | 119 ++++++++++++++++++ app/client/src/ce/hooks/index.ts | 27 ++++ .../ce/reducers/uiReducers/editorReducer.tsx | 12 ++ app/client/src/constants/routes/appRoutes.ts | 1 + .../src/ee/hooks/datasourceEditorHooks.tsx | 1 + app/client/src/ee/hooks/index.ts | 1 + .../Editor/DataSourceEditor/DSFormHeader.tsx | 83 +++--------- .../pages/Editor/DataSourceEditor/index.tsx | 13 -- .../src/pages/Editor/IDE/LeftPane/index.tsx | 1 - .../Editor/SaaSEditor/DatasourceForm.tsx | 13 -- 10 files changed, 178 insertions(+), 93 deletions(-) create mode 100644 app/client/src/ce/hooks/datasourceEditorHooks.tsx create mode 100644 app/client/src/ce/hooks/index.ts create mode 100644 app/client/src/ee/hooks/datasourceEditorHooks.tsx create mode 100644 app/client/src/ee/hooks/index.ts diff --git a/app/client/src/ce/hooks/datasourceEditorHooks.tsx b/app/client/src/ce/hooks/datasourceEditorHooks.tsx new file mode 100644 index 0000000000..631b7e7707 --- /dev/null +++ b/app/client/src/ce/hooks/datasourceEditorHooks.tsx @@ -0,0 +1,119 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import NewActionButton from "pages/Editor/DataSourceEditor/NewActionButton"; +import { EditorNames } from "./"; +import type { Datasource } from "entities/Datasource"; +import type { ApiDatasourceForm } from "entities/Datasource/RestAPIForm"; +import { Button } from "design-system"; +import { + GENERATE_NEW_PAGE_BUTTON_TEXT, + createMessage, +} from "@appsmith/constants/messages"; +import AnalyticsUtil from "utils/AnalyticsUtil"; +import history from "utils/history"; +import { generateTemplateFormURL } from "@appsmith/RouteBuilder"; +import { + getCurrentApplication, + getCurrentPageId, + getPagePermissions, +} from "selectors/editorSelectors"; +import { useShowPageGenerationOnHeader } from "pages/Editor/DataSourceEditor/hooks"; +import type { AppState } from "@appsmith/reducers"; +import { + getHasCreatePagePermission, + hasCreateDSActionPermissionInApp, +} from "@appsmith/utils/BusinessFeatures/permissionPageHelpers"; +import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag"; +import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; + +export interface HeaderActionProps { + datasource: Datasource | ApiDatasourceForm | undefined; + isPluginAuthorized: boolean; + pluginType: string; + showReconnectButton?: boolean; +} + +export const useHeaderActions = ( + editorType: string, + { + datasource, + isPluginAuthorized, + pluginType, + showReconnectButton = false, + }: HeaderActionProps, +) => { + const pageId = useSelector(getCurrentPageId); + const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + const userAppPermissions = useSelector( + (state: AppState) => getCurrentApplication(state)?.userPermissions ?? [], + ); + const pagePermissions = useSelector((state: AppState) => + getPagePermissions(state), + ); + const showGenerateButton = useShowPageGenerationOnHeader( + datasource as Datasource, + ); + + if (editorType === EditorNames.APPLICATION) { + const canCreateDatasourceActions = hasCreateDSActionPermissionInApp( + isFeatureEnabled, + datasource?.userPermissions ?? [], + pagePermissions, + ); + const canCreatePages = getHasCreatePagePermission( + isFeatureEnabled, + userAppPermissions, + ); + const canGeneratePage = canCreateDatasourceActions && canCreatePages; + + const routeToGeneratePage = () => { + if (!showGenerateButton) { + // disable button when it doesn't support page generation + return; + } + AnalyticsUtil.logEvent("DATASOURCE_CARD_GEN_CRUD_PAGE_ACTION"); + history.push( + generateTemplateFormURL({ + pageId, + params: { + datasourceId: (datasource as Datasource).id, + new_page: true, + }, + }), + ); + }; + + const newActionButton = ( + + ); + + const generatePageButton = + showGenerateButton && !showReconnectButton ? ( + + ) : null; + + return { + newActionButton, + generatePageButton, + }; + } + + return {}; +}; diff --git a/app/client/src/ce/hooks/index.ts b/app/client/src/ce/hooks/index.ts new file mode 100644 index 0000000000..03e0e0e80e --- /dev/null +++ b/app/client/src/ce/hooks/index.ts @@ -0,0 +1,27 @@ +import { + BUILDER_BASE_PATH_DEPRECATED, + BUILDER_VIEWER_PATH_PREFIX, +} from "constants/routes"; +import { matchPath } from "react-router"; + +export const EditorNames = { + APPLICATION: "appEditor", +}; + +export interface EditorType { + [key: string]: string; +} + +export const editorType: EditorType = { + [BUILDER_VIEWER_PATH_PREFIX]: EditorNames.APPLICATION, +}; + +export const useEditorType = (path: string) => { + const basePath = matchPath(path, { + path: [BUILDER_VIEWER_PATH_PREFIX, BUILDER_BASE_PATH_DEPRECATED], + }); + + return basePath + ? editorType[basePath.path] + : editorType[BUILDER_VIEWER_PATH_PREFIX]; +}; diff --git a/app/client/src/ce/reducers/uiReducers/editorReducer.tsx b/app/client/src/ce/reducers/uiReducers/editorReducer.tsx index d82bf44b9e..5520a13dd5 100644 --- a/app/client/src/ce/reducers/uiReducers/editorReducer.tsx +++ b/app/client/src/ce/reducers/uiReducers/editorReducer.tsx @@ -42,6 +42,18 @@ export const initialState: EditorReduxState = { }; export const handlers = { + [ReduxActionTypes.RESET_EDITOR_REQUEST]: (state: EditorReduxState) => { + return { + ...state, + currentPageId: undefined, + currentPageName: undefined, + currentLayoutId: undefined, + currentApplicationId: undefined, + pageWidgetId: undefined, + pageActions: undefined, + layoutOnLoadActionErrors: undefined, + }; + }, [ReduxActionTypes.RESET_EDITOR_SUCCESS]: (state: EditorReduxState) => { return { ...state, initialized: false }; }, diff --git a/app/client/src/constants/routes/appRoutes.ts b/app/client/src/constants/routes/appRoutes.ts index b29a299326..00a5f403ae 100644 --- a/app/client/src/constants/routes/appRoutes.ts +++ b/app/client/src/constants/routes/appRoutes.ts @@ -4,6 +4,7 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const { match } = require("path-to-regexp"); +export const BUILDER_BASE_PATH_DEPRECATED = "/applications"; export const BUILDER_VIEWER_PATH_PREFIX = "/app/"; export const BUILDER_PATH = `${BUILDER_VIEWER_PATH_PREFIX}:applicationSlug/:pageSlug(.*\-):pageId/edit`; export const BUILDER_CUSTOM_PATH = `${BUILDER_VIEWER_PATH_PREFIX}:customSlug(.*\-):pageId/edit`; diff --git a/app/client/src/ee/hooks/datasourceEditorHooks.tsx b/app/client/src/ee/hooks/datasourceEditorHooks.tsx new file mode 100644 index 0000000000..1778e62819 --- /dev/null +++ b/app/client/src/ee/hooks/datasourceEditorHooks.tsx @@ -0,0 +1 @@ +export * from "ce/hooks/datasourceEditorHooks"; diff --git a/app/client/src/ee/hooks/index.ts b/app/client/src/ee/hooks/index.ts new file mode 100644 index 0000000000..de9b260629 --- /dev/null +++ b/app/client/src/ee/hooks/index.ts @@ -0,0 +1 @@ +export * from "ce/hooks"; diff --git a/app/client/src/pages/Editor/DataSourceEditor/DSFormHeader.tsx b/app/client/src/pages/Editor/DataSourceEditor/DSFormHeader.tsx index 3b47238010..59074acf28 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/DSFormHeader.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/DSFormHeader.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; import FormTitle from "./FormTitle"; -import NewActionButton from "./NewActionButton"; import { getAssetUrl } from "@appsmith/utils/airgapHelpers"; import type { Datasource } from "entities/Datasource"; import { @@ -9,7 +8,6 @@ import { CONTEXT_DELETE, EDIT, createMessage, - GENERATE_NEW_PAGE_BUTTON_TEXT, } from "@appsmith/constants/messages"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { useDispatch, useSelector } from "react-redux"; @@ -20,21 +18,15 @@ import { MenuWrapper, StyledMenu } from "components/utils/formComponents"; import styled from "styled-components"; import { Button, MenuContent, MenuItem, MenuTrigger } from "design-system"; import { DatasourceEditEntryPoints } from "constants/Datasource"; -import { useShowPageGenerationOnHeader } from "./hooks"; -import { generateTemplateFormURL } from "@appsmith/RouteBuilder"; -import { getCurrentPageId } from "selectors/editorSelectors"; -import history from "utils/history"; import { DB_NOT_SUPPORTED, isEnvironmentConfigured, } from "@appsmith/utils/Environments"; -import { getHasCreatePagePermission } from "@appsmith/utils/BusinessFeatures/permissionPageHelpers"; -import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; -import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag"; -import type { AppState } from "@appsmith/reducers"; -import { getCurrentApplication } from "@appsmith/selectors/applicationSelectors"; import { getCurrentEnvironmentId } from "@appsmith/selectors/environmentSelectors"; import type { PluginType } from "entities/Action"; +import { useEditorType } from "@appsmith/hooks"; +import { useHistory } from "react-router"; +import { useHeaderActions } from "@appsmith/hooks/datasourceEditorHooks"; export const ActionWrapper = styled.div` display: flex; @@ -84,7 +76,6 @@ export const PluginImage = (props: any) => { }; interface DSFormHeaderProps { - canCreateDatasourceActions: boolean; canDeleteDatasource: boolean; canManageDatasource: boolean; datasource: Datasource | ApiDatasourceForm | undefined; @@ -105,7 +96,6 @@ interface DSFormHeaderProps { export const DSFormHeader = (props: DSFormHeaderProps) => { const { - canCreateDatasourceActions, canDeleteDatasource, canManageDatasource, datasource, @@ -123,16 +113,8 @@ export const DSFormHeader = (props: DSFormHeaderProps) => { const [confirmDelete, setConfirmDelete] = useState(false); const dispatch = useDispatch(); - - const pageId = useSelector(getCurrentPageId); - const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); - const userAppPermissions = useSelector( - (state: AppState) => getCurrentApplication(state)?.userPermissions ?? [], - ); - const canCreatePages = getHasCreatePagePermission( - isFeatureEnabled, - userAppPermissions, - ); + const history = useHistory(); + const editorType = useEditorType(history.location.pathname); const deleteAction = () => { if (isDeleting) return; @@ -142,10 +124,6 @@ export const DSFormHeader = (props: DSFormHeaderProps) => { const onCloseMenu = debounce(() => setConfirmDelete(false), 20); - const showGenerateButton = useShowPageGenerationOnHeader( - datasource as Datasource, - ); - const renderMenuOptions = () => { return [ { ]; }; - const canGeneratePage = canCreateDatasourceActions && canCreatePages; - const currentEnv = useSelector(getCurrentEnvironmentId); const envSupportedDs = !DB_NOT_SUPPORTED.includes(pluginType as PluginType); @@ -184,22 +160,12 @@ export const DSFormHeader = (props: DSFormHeaderProps) => { : true) ); - const routeToGeneratePage = () => { - if (!showGenerateButton) { - // disable button when it doesn't support page generation - return; - } - AnalyticsUtil.logEvent("DATASOURCE_CARD_GEN_CRUD_PAGE_ACTION"); - history.push( - generateTemplateFormURL({ - pageId, - params: { - datasourceId: (datasource as Datasource).id, - new_page: true, - }, - }), - ); - }; + const headerActions = useHeaderActions(editorType, { + datasource, + isPluginAuthorized, + pluginType, + showReconnectButton, + }); return (
@@ -254,27 +220,12 @@ export const DSFormHeader = (props: DSFormHeaderProps) => { > {createMessage(EDIT)} - - {showGenerateButton && !showReconnectButton && ( - - )} + {headerActions && headerActions.newActionButton + ? headerActions.newActionButton + : null} + {headerActions && headerActions.generatePageButton + ? headerActions.generatePageButton + : null} )}
diff --git a/app/client/src/pages/Editor/DataSourceEditor/index.tsx b/app/client/src/pages/Editor/DataSourceEditor/index.tsx index a7185a5fc2..cd92830eba 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/index.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/index.tsx @@ -42,7 +42,6 @@ import { DatasourceComponentTypes } from "api/PluginApi"; import DatasourceSaasForm from "../SaaSEditor/DatasourceForm"; import { getCurrentApplicationId, - getPagePermissions, selectURLSlugs, } from "selectors/editorSelectors"; import { saasEditorDatasourceIdURL } from "@appsmith/RouteBuilder"; @@ -103,14 +102,12 @@ import { isGACEnabled } from "@appsmith/utils/planHelpers"; import { getHasDeleteDatasourcePermission, getHasManageDatasourcePermission, - hasCreateDSActionPermissionInApp, } from "@appsmith/utils/BusinessFeatures/permissionPageHelpers"; import DatasourceTabs from "../DatasourceInfo/DatasorceTabs"; import DatasourceInformation, { ViewModeWrapper } from "./DatasourceSection"; import { getIsAppSidebarEnabled } from "../../../selectors/ideSelectors"; interface ReduxStateProps { - canCreateDatasourceActions: boolean; canDeleteDatasource: boolean; canManageDatasource: boolean; datasourceButtonConfiguration: string[] | undefined; @@ -899,7 +896,6 @@ class DatasourceEditorRouter extends React.Component { render() { const { - canCreateDatasourceActions, canDeleteDatasource, canManageDatasource, datasource, @@ -968,7 +964,6 @@ class DatasourceEditorRouter extends React.Component { {isAppSidebarEnabled || !!isOnboardingFlow ? null : } {!isInsideReconnectModal && ( { isFeatureEnabled, datasourcePermissions, ); - - const pagePermissions = getPagePermissions(state); - const canCreateDatasourceActions = hasCreateDSActionPermissionInApp( - isFeatureEnabled, - datasourcePermissions, - pagePermissions, - ); // Debugger render flag const showDebugger = showDebuggerFlag(state); const pluginPackageName = plugin?.packageName ?? ""; @@ -1166,7 +1154,6 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => { const isAppSidebarEnabled = getIsAppSidebarEnabled(state); return { - canCreateDatasourceActions, canDeleteDatasource, canManageDatasource, datasourceButtonConfiguration, diff --git a/app/client/src/pages/Editor/IDE/LeftPane/index.tsx b/app/client/src/pages/Editor/IDE/LeftPane/index.tsx index ba1e814cd9..0ff950ab66 100644 --- a/app/client/src/pages/Editor/IDE/LeftPane/index.tsx +++ b/app/client/src/pages/Editor/IDE/LeftPane/index.tsx @@ -23,7 +23,6 @@ import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag"; export const LeftPaneContainer = styled.div` height: 100%; - min-width: 150px; border-right: 1px solid var(--ads-v2-color-border); background: var(--ads-v2-color-bg); `; diff --git a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx index 35ee300b3a..d00ca8288c 100644 --- a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx +++ b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx @@ -33,7 +33,6 @@ import { getCurrentApplicationId, getGsheetProjectID, getGsheetToken, - getPagePermissions, } from "selectors/editorSelectors"; import DatasourceAuth from "pages/common/datasourceAuth"; import EntityNotFoundPane from "../EntityNotFoundPane"; @@ -86,7 +85,6 @@ import { DEFAULT_ENV_ID } from "@appsmith/api/ApiUtils"; import { getHasDeleteDatasourcePermission, getHasManageDatasourcePermission, - hasCreateDSActionPermissionInApp, } from "@appsmith/utils/BusinessFeatures/permissionPageHelpers"; import { selectFeatureFlagCheck } from "@appsmith/selectors/featureFlagsSelectors"; import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag"; @@ -104,7 +102,6 @@ interface StateProps extends JSONtoFormProps { applicationId: string; canManageDatasource?: boolean; canDeleteDatasource?: boolean; - canCreateDatasourceActions?: boolean; isSaving: boolean; isTesting: boolean; isDeleting: boolean; @@ -548,7 +545,6 @@ class DatasourceSaaSEditor extends JSONtoForm { renderDataSourceConfigForm = (sections: any) => { const { - canCreateDatasourceActions, canDeleteDatasource, canManageDatasource, datasource, @@ -601,7 +597,6 @@ class DatasourceSaaSEditor extends JSONtoForm { <> {!hiddenHeader && ( { datasourcePermissions, ); - const pagePermissions = getPagePermissions(state); - const canCreateDatasourceActions = hasCreateDSActionPermissionInApp( - isFeatureEnabled, - datasourcePermissions, - pagePermissions, - ); - const gsheetToken = getGsheetToken(state); const gsheetProjectID = getGsheetProjectID(state); const documentationLinks = getPluginDocumentationLinks(state); @@ -858,7 +846,6 @@ const mapStateToProps = (state: AppState, props: any) => { isDatasourceBeingSavedFromPopup: state.entities.datasources.isDatasourceBeingSavedFromPopup, isFormDirty, - canCreateDatasourceActions, gsheetToken, gsheetProjectID, showDebugger,