diff --git a/app/client/src/ce/hooks/datasourceEditorHooks.tsx b/app/client/src/ce/hooks/datasourceEditorHooks.tsx index b2795eb61493..385a328f0c24 100644 --- a/app/client/src/ce/hooks/datasourceEditorHooks.tsx +++ b/app/client/src/ce/hooks/datasourceEditorHooks.tsx @@ -1,34 +1,34 @@ -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 { generateTemplateFormURL } from "@appsmith/RouteBuilder"; import { GENERATE_NEW_PAGE_BUTTON_TEXT, createMessage, } from "@appsmith/constants/messages"; +import { ActionParentEntityType } from "@appsmith/entities/Engine/actionHelpers"; +import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag"; +import type { AppState } from "@appsmith/reducers"; +import { getPlugin } from "@appsmith/selectors/entitiesSelector"; import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; -import history from "utils/history"; -import { generateTemplateFormURL } from "@appsmith/RouteBuilder"; +import { + getHasCreatePagePermission, + hasCreateDSActionPermissionInApp, +} from "@appsmith/utils/BusinessFeatures/permissionPageHelpers"; +import { Button } from "design-system"; +import type { Datasource } from "entities/Datasource"; +import type { ApiDatasourceForm } from "entities/Datasource/RestAPIForm"; +import NewActionButton from "pages/Editor/DataSourceEditor/NewActionButton"; +import { useShowPageGenerationOnHeader } from "pages/Editor/DataSourceEditor/hooks"; +import React from "react"; +import { useSelector } from "react-redux"; import { getCurrentApplication, getCurrentApplicationId, 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"; -import { ActionParentEntityType } from "@appsmith/entities/Engine/actionHelpers"; import { isEnabledForPreviewData } from "utils/editorContextUtils"; -import { getPlugin } from "@appsmith/selectors/entitiesSelector"; +import history from "utils/history"; +import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; +import { EditorNames } from "./"; export interface HeaderActionProps { datasource: Datasource | ApiDatasourceForm | undefined; @@ -48,6 +48,9 @@ export const useHeaderActions = ( ) => { const pageId = useSelector(getCurrentPageId); const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + const releaseDragDropBuildingBlocks = useFeatureFlag( + FEATURE_FLAG.release_drag_drop_building_blocks_enabled, + ); const userAppPermissions = useSelector( (state: AppState) => getCurrentApplication(state)?.userPermissions ?? [], ); @@ -65,6 +68,10 @@ export const useHeaderActions = ( const isPluginAllowedToPreviewData = !!plugin && isEnabledForPreviewData(datasource as Datasource, plugin); + const shouldShowSecondaryGenerateButton = releaseDragDropBuildingBlocks + ? false + : !!isPluginAllowedToPreviewData; + if (editorType === EditorNames.APPLICATION) { const canCreateDatasourceActions = hasCreateDSActionPermissionInApp({ isEnabled: isFeatureEnabled, @@ -99,7 +106,7 @@ export const useHeaderActions = ( datasource={datasource as Datasource} disabled={!canCreateDatasourceActions || !isPluginAuthorized} eventFrom="datasource-pane" - isNewQuerySecondaryButton={!!isPluginAllowedToPreviewData} + isNewQuerySecondaryButton={shouldShowSecondaryGenerateButton} pluginType={pluginType} /> ); diff --git a/app/client/src/pages/Editor/DatasourceInfo/DatasourceViewModeSchema.tsx b/app/client/src/pages/Editor/DatasourceInfo/DatasourceViewModeSchema.tsx index 3f2e78e3a189..b0d668b26e0a 100644 --- a/app/client/src/pages/Editor/DatasourceInfo/DatasourceViewModeSchema.tsx +++ b/app/client/src/pages/Editor/DatasourceInfo/DatasourceViewModeSchema.tsx @@ -77,6 +77,9 @@ const DatasourceViewModeSchema = (props: Props) => { ); const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + const releaseDragDropBuildingBlocks = useFeatureFlag( + FEATURE_FLAG.release_drag_drop_building_blocks_enabled, + ); const editorType = useEditorType(history.location.pathname); @@ -230,7 +233,9 @@ const DatasourceViewModeSchema = (props: Props) => { // if there was a failure in the fetching of the data // if tableName from schema is availble // if the user has permissions + // if drag and drop building blocks are not enabled const showGeneratePageBtn = + !releaseDragDropBuildingBlocks && !isDatasourceStructureLoading && !isLoading && !failedFetchingPreviewData && diff --git a/app/client/src/pages/Editor/DatasourceInfo/GoogleSheetSchema.tsx b/app/client/src/pages/Editor/DatasourceInfo/GoogleSheetSchema.tsx index 4d126e81505f..5d8f8fc8d83c 100644 --- a/app/client/src/pages/Editor/DatasourceInfo/GoogleSheetSchema.tsx +++ b/app/client/src/pages/Editor/DatasourceInfo/GoogleSheetSchema.tsx @@ -347,6 +347,9 @@ function GoogleSheetSchema(props: Props) { ); const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + const releaseDragDropBuildingBlocks = useFeatureFlag( + FEATURE_FLAG.release_drag_drop_building_blocks_enabled, + ); const editorType = useEditorType(history.location.pathname); @@ -372,6 +375,7 @@ function GoogleSheetSchema(props: Props) { ); const showGeneratePageBtn = + !releaseDragDropBuildingBlocks && !isLoading && !isError && sheetData?.length && diff --git a/app/client/src/pages/Editor/DatasourceInfo/HideGeneratePageButton.test.tsx b/app/client/src/pages/Editor/DatasourceInfo/HideGeneratePageButton.test.tsx new file mode 100644 index 000000000000..001589da2361 --- /dev/null +++ b/app/client/src/pages/Editor/DatasourceInfo/HideGeneratePageButton.test.tsx @@ -0,0 +1,556 @@ +import { + DATASOURCE_GENERATE_PAGE_BUTTON, + NEW_AI_BUTTON_TEXT, + NEW_API_BUTTON_TEXT, + NEW_QUERY_BUTTON_TEXT, + createMessage, +} from "@appsmith/constants/messages"; +import { getNumberOfEntitiesInCurrentPage } from "@appsmith/selectors/entitiesSelector"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import { PluginType } from "entities/Action"; +import { DatasourceConnectionMode, type Datasource } from "entities/Datasource"; +import { SSLType } from "entities/Datasource/RestAPIForm"; +import { unitTestBaseMockStore } from "layoutSystems/common/dropTarget/unitTestUtils"; +import React from "react"; +import { Provider, useSelector } from "react-redux"; +import { useParams } from "react-router"; +import configureStore from "redux-mock-store"; +import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; +import { DSFormHeader } from "../DataSourceEditor/DSFormHeader"; +import DatasourceViewModeSchema from "./DatasourceViewModeSchema"; +import GoogleSheetSchema from "./GoogleSheetSchema"; +/* eslint-disable @typescript-eslint/no-var-requires */ +const reactRouter = require("react-router"); + +jest.mock("utils/hooks/useFeatureFlag"); +jest.mock("react-router", () => ({ + ...jest.requireActual("react-router"), + useParams: jest.fn(), +})); +jest.mock("react-redux", () => ({ + ...jest.requireActual("react-redux"), + useSelector: jest.fn(), +})); + +const mockStore = configureStore([]); + +const mockSetDatasourceViewModeFlag = jest.fn(); + +const renderBaseDatasourceComponent = () => { + render( + + + , + ); +}; + +const renderGoogleSheetDSComponent = () => { + render( + + + , + ); +}; + +const renderDSFormHeader = () => { + render( + + true} + viewMode + /> + , + ); +}; + +const getCreateButtonText = (pluginType: PluginType) => { + switch (pluginType) { + case PluginType.DB: + case PluginType.SAAS: + return createMessage(NEW_QUERY_BUTTON_TEXT); + case PluginType.AI: + return createMessage(NEW_AI_BUTTON_TEXT); + default: + return createMessage(NEW_API_BUTTON_TEXT); + } +}; + +describe("DatasourceViewModeSchema Component", () => { + it("1. should not render the 'generate page' button when release_drag_drop_building_blocks_enabled is enabled", () => { + (useFeatureFlag as jest.Mock).mockReturnValue(true); + (useParams as jest.Mock).mockReturnValue({ + pageId: unitTestBaseMockStore.entities.pageList.currentPageId, + }); + (useSelector as jest.Mock).mockImplementation((selector) => { + if (selector === getNumberOfEntitiesInCurrentPage) { + return 0; + } + return selector(baseStoreForSpec); // Default case for other selectors + }); + renderBaseDatasourceComponent(); + + // Check that the "generate page" button is not rendered + const generatePageButton = screen.queryByText( + createMessage(DATASOURCE_GENERATE_PAGE_BUTTON), + ); + expect(generatePageButton).not.toBeInTheDocument(); + }); + + it("2. should render new query button as primary when release_drag_drop_building_blocks_enabled is enabled", () => { + (useFeatureFlag as jest.Mock).mockReturnValue(true); + const mockHistoryPush = jest.fn(); + const mockHistoryReplace = jest.fn(); + const mockHistoryLocation = { + pathname: "/", + search: "", + hash: "", + state: {}, + }; + + jest.spyOn(reactRouter, "useHistory").mockReturnValue({ + push: mockHistoryPush, + replace: mockHistoryReplace, + location: mockHistoryLocation, + }); + + jest.spyOn(reactRouter, "useLocation").mockReturnValue(mockHistoryLocation); + + renderDSFormHeader(); + + // Check that the "New Query" button is rendered as primary + const newQuerySpan = screen.getByText(getCreateButtonText(PluginType.DB)); + const newQueryButton = newQuerySpan.closest("button"); + expect(newQueryButton).toHaveAttribute("kind", "primary"); + }); +}); + +describe("GoogleSheetSchema Component", () => { + it("should not render the 'generate page' button when release_drag_drop_building_blocks_enabled is enabled", () => { + (useFeatureFlag as jest.Mock).mockReturnValue(true); + (useParams as jest.Mock).mockReturnValue({ + pageId: unitTestBaseMockStore.entities.pageList.currentPageId, + }); + (useSelector as jest.Mock).mockImplementation((selector) => { + if (selector === getNumberOfEntitiesInCurrentPage) { + return 0; + } + return selector(baseStoreForSpec); // Default case for other selectors + }); + renderGoogleSheetDSComponent(); + + // Check that the "generate page" button is not rendered + const generatePageButton = screen.queryByText( + createMessage(DATASOURCE_GENERATE_PAGE_BUTTON), + ); + expect(generatePageButton).not.toBeInTheDocument(); + }); +}); + +const mockDatasource: Datasource = { + id: "667941878b418b52eb273895", + userPermissions: [ + "execute:datasources", + "delete:datasources", + "manage:datasources", + "read:datasources", + ], + name: "Users", + pluginId: "656eeb1024ec7f5154c9ba00", + workspaceId: "6679402f8b418b52eb27388d", + datasourceStorages: { + unused_env: { + datasourceId: "667941878b418b52eb273895", + environmentId: "unused_env", + datasourceConfiguration: { + url: "", + connection: { + mode: DatasourceConnectionMode.READ_WRITE, + ssl: { + authType: SSLType.DEFAULT, + authTypeControl: false, + certificateFile: {} as any, + }, + }, + authentication: { + authenticationType: "dbAuth", + username: "users", + }, + }, + isConfigured: true, + isValid: true, + }, + }, + invalids: [], + messages: [], + isMock: true, +}; + +const baseStoreForSpec = { + entities: { + ...unitTestBaseMockStore.entities, + plugins: { + list: [ + { + id: "656eeb1024ec7f5154c9ba00", + userPermissions: [], + name: "PostgreSQL", + type: "DB", + packageName: "postgres-plugin", + iconLocation: "https://assets.appsmith.com/logo/postgresql.svg", + documentationLink: + "https://docs.appsmith.com/reference/datasources/querying-postgres#create-crud-queries", + responseType: "TABLE", + uiComponent: "DbEditorForm", + datasourceComponent: "AutoForm", + generateCRUDPageComponent: "PostgreSQL", + allowUserDatasources: true, + isRemotePlugin: false, + templates: { + CREATE: + "INSERT INTO users\n (name, gender, email)\nVALUES\n (\n {{ nameInput.text }},\n {{ genderDropdown.selectedOptionValue }},\n {{ emailInput.text }}\n );", + SELECT: + "SELECT * FROM <> LIMIT 10;\n\n-- Please enter a valid table name and hit RUN", + UPDATE: + "UPDATE users\n SET status = 'APPROVED'\n WHERE id = {{ usersTable.selectedRow.id }};\n", + DELETE: "DELETE FROM users WHERE id = -1;", + }, + remotePlugin: false, + new: false, + }, + { + id: "656eeb1024ec7f5154c9ba01", + userPermissions: [], + name: "REST API", + type: "API", + packageName: "restapi-plugin", + iconLocation: "https://assets.appsmith.com/RestAPI.png", + uiComponent: "ApiEditorForm", + datasourceComponent: "RestAPIDatasourceForm", + allowUserDatasources: true, + isRemotePlugin: false, + templates: {}, + remotePlugin: false, + new: false, + }, + ], + }, + datasources: { + list: [ + { + id: "667941878b418b52eb273895", + userPermissions: [ + "execute:datasources", + "delete:datasources", + "manage:datasources", + "read:datasources", + ], + name: "Users", + pluginId: "656eeb1024ec7f5154c9ba00", + workspaceId: "6679402f8b418b52eb27388d", + datasourceStorages: { + unused_env: { + id: "667941878b418b52eb273896", + datasourceId: "667941878b418b52eb273895", + environmentId: "unused_env", + datasourceConfiguration: { + connection: { + mode: "READ_WRITE", + ssl: { + authType: "DEFAULT", + }, + }, + endpoints: [ + { + host: "mockdb.internal.appsmith.com", + }, + ], + authentication: { + authenticationType: "dbAuth", + username: "users", + databaseName: "users", + }, + }, + isConfigured: true, + invalids: [], + messages: [], + isValid: true, + }, + }, + invalids: [], + messages: [], + isRecentlyCreated: true, + isMock: true, + isValid: true, + new: false, + }, + ], + loading: false, + isTesting: false, + isListing: false, + fetchingDatasourceStructure: { + "66793e2a8b418b52eb27388a": false, + "667941878b418b52eb273895": false, + }, + structure: { + "66793e2a8b418b52eb27388a": { + tables: [ + { + type: "TABLE", + schema: "public", + name: "public.users", + columns: [ + { + name: "id", + type: "int4", + defaultValue: "nextval('users_id_seq'::regclass)", + isAutogenerated: true, + }, + { + name: "gender", + type: "text", + isAutogenerated: false, + }, + { + name: "latitude", + type: "text", + isAutogenerated: false, + }, + { + name: "longitude", + type: "text", + isAutogenerated: false, + }, + { + name: "dob", + type: "timestamptz", + isAutogenerated: false, + }, + { + name: "phone", + type: "text", + isAutogenerated: false, + }, + { + name: "email", + type: "text", + isAutogenerated: false, + }, + { + name: "image", + type: "text", + isAutogenerated: false, + }, + { + name: "country", + type: "text", + isAutogenerated: false, + }, + { + name: "name", + type: "text", + isAutogenerated: false, + }, + { + name: "created_at", + type: "timestamp", + isAutogenerated: false, + }, + { + name: "updated_at", + type: "timestamp", + isAutogenerated: false, + }, + ], + keys: [ + { + name: "users_pkey", + columnNames: ["id"], + type: "primary key", + }, + ], + templates: [ + { + title: "SELECT", + body: 'SELECT * FROM public."users" LIMIT 10;', + suggested: true, + }, + { + title: "INSERT", + body: 'INSERT INTO public."users" ("gender", "latitude", "longitude", "dob", "phone", "email", "image", "country", "name", "created_at", "updated_at")\n VALUES (\'\', \'\', \'\', TIMESTAMP WITH TIME ZONE \'2019-07-01 06:30:00 CET\', \'\', \'\', \'\', \'\', \'\', TIMESTAMP \'2019-07-01 10:00:00\', TIMESTAMP \'2019-07-01 10:00:00\');', + suggested: false, + }, + { + title: "UPDATE", + body: 'UPDATE public."users" SET\n "gender" = \'\',\n "latitude" = \'\',\n "longitude" = \'\',\n "dob" = TIMESTAMP WITH TIME ZONE \'2019-07-01 06:30:00 CET\',\n "phone" = \'\',\n "email" = \'\',\n "image" = \'\',\n "country" = \'\',\n "name" = \'\',\n "created_at" = TIMESTAMP \'2019-07-01 10:00:00\',\n "updated_at" = TIMESTAMP \'2019-07-01 10:00:00\'\n WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may update every row in the table!', + suggested: false, + }, + { + title: "DELETE", + body: 'DELETE FROM public."users"\n WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may delete everything in the table!', + suggested: false, + }, + ], + }, + ], + }, + "667941878b418b52eb273895": { + tables: [ + { + type: "TABLE", + schema: "public", + name: "public.users", + columns: [ + { + name: "id", + type: "int4", + defaultValue: "nextval('users_id_seq'::regclass)", + isAutogenerated: true, + }, + { + name: "gender", + type: "text", + isAutogenerated: false, + }, + { + name: "latitude", + type: "text", + isAutogenerated: false, + }, + { + name: "longitude", + type: "text", + isAutogenerated: false, + }, + { + name: "dob", + type: "timestamptz", + isAutogenerated: false, + }, + { + name: "phone", + type: "text", + isAutogenerated: false, + }, + { + name: "email", + type: "text", + isAutogenerated: false, + }, + { + name: "image", + type: "text", + isAutogenerated: false, + }, + { + name: "country", + type: "text", + isAutogenerated: false, + }, + { + name: "name", + type: "text", + isAutogenerated: false, + }, + { + name: "created_at", + type: "timestamp", + isAutogenerated: false, + }, + { + name: "updated_at", + type: "timestamp", + isAutogenerated: false, + }, + ], + keys: [ + { + name: "users_pkey", + columnNames: ["id"], + type: "primary key", + }, + ], + templates: [ + { + title: "SELECT", + body: 'SELECT * FROM public."users" LIMIT 10;', + suggested: true, + }, + { + title: "INSERT", + body: 'INSERT INTO public."users" ("gender", "latitude", "longitude", "dob", "phone", "email", "image", "country", "name", "created_at", "updated_at")\n VALUES (\'\', \'\', \'\', TIMESTAMP WITH TIME ZONE \'2019-07-01 06:30:00 CET\', \'\', \'\', \'\', \'\', \'\', TIMESTAMP \'2019-07-01 10:00:00\', TIMESTAMP \'2019-07-01 10:00:00\');', + suggested: false, + }, + { + title: "UPDATE", + body: 'UPDATE public."users" SET\n "gender" = \'\',\n "latitude" = \'\',\n "longitude" = \'\',\n "dob" = TIMESTAMP WITH TIME ZONE \'2019-07-01 06:30:00 CET\',\n "phone" = \'\',\n "email" = \'\',\n "image" = \'\',\n "country" = \'\',\n "name" = \'\',\n "created_at" = TIMESTAMP \'2019-07-01 10:00:00\',\n "updated_at" = TIMESTAMP \'2019-07-01 10:00:00\'\n WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may update every row in the table!', + suggested: false, + }, + { + title: "DELETE", + body: 'DELETE FROM public."users"\n WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may delete everything in the table!', + suggested: false, + }, + ], + }, + ], + }, + }, + isFetchingMockDataSource: false, + mockDatasourceList: [ + { + pluginType: "db", + packageName: "mongo-plugin", + description: "This contains a standard movies collection", + name: "Movies", + }, + { + pluginType: "db", + packageName: "postgres-plugin", + description: "This contains a standard users information", + name: "Users", + }, + ], + executingDatasourceQuery: false, + isReconnectingModalOpen: false, + unconfiguredList: [], + isDatasourceBeingSaved: false, + isDatasourceBeingSavedFromPopup: false, + gsheetToken: "", + gsheetProjectID: "", + gsheetStructure: { + spreadsheets: {}, + sheets: {}, + columns: {}, + isFetchingSpreadsheets: false, + isFetchingSheets: false, + isFetchingColumns: false, + }, + recentDatasources: [], + isDeleting: false, + }, + }, + ui: { + ...unitTestBaseMockStore.ui, + datasourcePane: { + selectedTableName: "users", + }, + datasourceName: { + isSaving: [mockDatasource.id], + errors: [mockDatasource.id], + }, + }, +};