chore: Refactor DataSidePane to accept selector as prop to show usage messages in EE (#32870)

## Description
Adds a couple of props to alter the data displayed in the datasources
left pane to show the right count of entities using datasource. This
props are going to be used in Packages and Workflow editors datasources
page.

PR for https://github.com/appsmithorg/appsmith-ee/pull/4026

## Automation

/ok-to-test tags="@tag.IDE, @tag.Datasource, @tag.Sanity"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/8843287060>
> Commit: 66a5282be88c27fab11316bd136d88bc74de5d5b
> Cypress dashboard url: <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=8843287060&attempt=1"
target="_blank">Click here!</a>

<!-- end of auto-generated comment: Cypress test results  -->










## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **New Features**
- Enhanced the `DataSidePane` to display the name and count of actions,
improving the informational context for users.
- Added a new selector function to calculate and display the count of
queries for each datasource in the app.
- Introduced a `DatasourceFactory` for generating mock datasource
objects for testing purposes.
- Expanded the list of plugin package names in the `MockPluginsState` to
include additional plugins like `MY_SQL`, `S3`, `SNOWFLAKE`,
`FIRESTORE`, `GRAPHQL`, `APPSMITH_AI`, `MS_SQL`, `ORACLE`, and
`WORKFLOW`.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
ashit-rath 2024-04-26 15:38:25 +05:30 committed by GitHub
parent 85fa8cb452
commit a72ae578bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 201 additions and 10 deletions

View File

@ -20,7 +20,7 @@ import {
PluginPackageName,
PluginType,
} from "entities/Action";
import { find, get, groupBy, keyBy, sortBy } from "lodash";
import { countBy, find, get, groupBy, keyBy, sortBy } from "lodash";
import ImageAlt from "assets/images/placeholder-image.svg";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
@ -1540,3 +1540,22 @@ export const getJSSegmentItems = createSelector(
export const getSelectedTableName = (state: AppState) =>
state.ui.datasourcePane.selectedTableName;
export const getDatasourceUsageCountForApp = createSelector(
getActions,
getDatasources,
(state: AppState, editorType: string) => editorType,
(actions, datasources, editorType) => {
const actionCount = countBy(actions, "config.datasource.id");
const actionDsMap: Record<string, string> = {};
datasources.forEach((ds) => {
actionDsMap[ds.id] = `No queries in this ${editorType}`;
});
Object.keys(actionCount).forEach((dsId) => {
actionDsMap[dsId] = `${actionCount[dsId]} queries in this ${editorType}`;
});
return actionDsMap;
},
);

View File

@ -0,0 +1,102 @@
import React from "react";
import "@testing-library/jest-dom";
import { screen } from "@testing-library/react";
import DataSidePane from "./DataSidePane";
import { datasourceFactory } from "test/factories/DatasourceFactory";
import { getIDETestState } from "test/factories/AppIDEFactoryUtils";
import { PostgresFactory } from "test/factories/Actions/Postgres";
import type { AppState } from "@appsmith/reducers";
import { render } from "test/testUtils";
const productsDS = datasourceFactory().build({
name: "Products",
id: "products-ds-id",
});
const usersDS = datasourceFactory().build({ name: "Users", id: "users-ds-id" });
const ordersDS = datasourceFactory().build({
name: "Orders",
id: "orders-ds-id",
});
const usersAction1 = PostgresFactory.build({
datasource: {
id: usersDS.id,
},
});
const usersAction2 = PostgresFactory.build({
datasource: {
id: usersDS.id,
},
});
const ordersAction1 = PostgresFactory.build({
datasource: {
id: ordersDS.id,
},
});
describe("DataSidePane", () => {
it("renders the ds and count by using the default selector if dsUsageSelector not passed as a props", () => {
const state = getIDETestState({
actions: [usersAction1, usersAction2, ordersAction1],
datasources: [productsDS, usersDS, ordersDS],
}) as AppState;
render(<DataSidePane />, {
url: "app/untitled-application-1/page1/edit/datasource/users-ds-id",
initialState: state,
});
expect(screen.getByText("Databases")).toBeInTheDocument();
expect(screen.getByText("Products")).toBeInTheDocument();
expect(screen.getByText("Users")).toBeInTheDocument();
const usersDSParentElement =
screen.getByText("Users").parentElement?.parentElement;
expect(usersDSParentElement).toHaveTextContent("2 queries in this app");
const productsDSParentElement =
screen.getByText("Products").parentElement?.parentElement;
expect(productsDSParentElement).toHaveTextContent("No queries in this app");
const ortdersDSParentElement =
screen.getByText("Orders").parentElement?.parentElement;
expect(ortdersDSParentElement).toHaveTextContent("1 queries in this app");
});
it("it uses the selector dsUsageSelector passed as prop", () => {
const state = getIDETestState({
datasources: [productsDS, usersDS, ordersDS],
}) as AppState;
const usageSelector = () => {
return {
[usersDS.id]: "Rendering description for users",
[productsDS.id]: "Rendering description for products",
};
};
render(<DataSidePane dsUsageSelector={usageSelector} />, {
url: "app/untitled-application-1/page1/edit/datasource/users-ds-id",
initialState: state,
});
expect(screen.getByText("Databases")).toBeInTheDocument();
expect(screen.getByText("Products")).toBeInTheDocument();
expect(screen.getByText("Users")).toBeInTheDocument();
const usersDSParentElement =
screen.getByText("Users").parentElement?.parentElement;
expect(usersDSParentElement).toHaveTextContent(
"Rendering description for users",
);
const productsDSParentElement =
screen.getByText("Products").parentElement?.parentElement;
expect(productsDSParentElement).toHaveTextContent(
"Rendering description for products",
);
});
});

View File

@ -3,8 +3,8 @@ import styled from "styled-components";
import { Flex, List, Text } from "design-system";
import { useSelector } from "react-redux";
import {
getActions,
getCurrentPageId,
getDatasourceUsageCountForApp,
getDatasources,
getDatasourcesGroupedByPluginCategory,
getPlugins,
@ -15,7 +15,7 @@ import {
integrationEditorURL,
} from "@appsmith/RouteBuilder";
import { getSelectedDatasourceId } from "@appsmith/navigation/FocusSelectors";
import { countBy, keyBy } from "lodash";
import { get, keyBy } from "lodash";
import CreateDatasourcePopover from "./CreateDatasourcePopover";
import { useLocation } from "react-router";
import {
@ -54,7 +54,12 @@ const StyledList = styled(List)`
gap: 0;
`;
const DataSidePane = () => {
interface DataSidePaneProps {
dsUsageSelector?: (...args: any[]) => Record<string, string>;
}
const DataSidePane = (props: DataSidePaneProps) => {
const { dsUsageSelector = getDatasourceUsageCountForApp } = props;
const editorType = useEditorType(history.location.pathname);
const pageId = useSelector(getCurrentPageId) as string;
const [currentSelectedDatasource, setCurrentSelectedDatasource] = useState<
@ -64,8 +69,7 @@ const DataSidePane = () => {
const groupedDatasources = useSelector(getDatasourcesGroupedByPluginCategory);
const plugins = useSelector(getPlugins);
const groupedPlugins = keyBy(plugins, "id");
const actions = useSelector(getActions);
const actionCount = countBy(actions, "config.datasource.id");
const dsUsageMap = useSelector((state) => dsUsageSelector(state, editorType));
const goToDatasource = useCallback((id: string) => {
history.push(datasourcesEditorIdURL({ datasourceId: id }));
}, []);
@ -135,9 +139,7 @@ const DataSidePane = () => {
className: "t--datasource",
title: data.name,
onClick: () => goToDatasource(data.id),
description: `${
actionCount[data.id] || "No"
} queries in this ${editorType}`,
description: get(dsUsageMap, data.id, ""),
descriptionType: "block",
isSelected: currentSelectedDatasource === data.id,
startIcon: (

View File

@ -8,6 +8,7 @@ import type { IDETabs } from "reducers/uiReducers/ideReducer";
import { IDETabsDefaultValue } from "reducers/uiReducers/ideReducer";
import type { JSCollection } from "entities/JSCollection";
import type { FocusHistory } from "reducers/uiReducers/focusHistoryReducer";
import type { Datasource } from "entities/Datasource";
interface IDEStateArgs {
ideView?: EditorViewMode;
@ -17,11 +18,13 @@ interface IDEStateArgs {
tabs?: IDETabs;
branch?: string;
focusHistory?: FocusHistory;
datasources?: Datasource[];
}
export const getIDETestState = ({
actions = [],
branch,
datasources = [],
focusHistory = {},
ideView = EditorViewMode.FullScreen,
js = [],
@ -47,6 +50,10 @@ export const getIDETestState = ({
...initialState,
entities: {
...initialState.entities,
datasources: {
...initialState.entities.datasources,
list: datasources,
},
plugins: MockPluginsState,
pageList: pageList,
actions: actionData,

View File

@ -0,0 +1,52 @@
import * as Factory from "factory.ts";
import { PluginPackageName } from "entities/Action";
import { PluginIDs } from "test/factories/MockPluginsState";
import { DatasourceConnectionMode, type Datasource } from "entities/Datasource";
import { SSLType } from "entities/Datasource/RestAPIForm";
interface DatasourceFactory extends Datasource {
pluginPackageName?: PluginPackageName;
}
export const datasourceFactory = (pluginPackageName?: PluginPackageName) =>
Factory.Sync.makeFactory<DatasourceFactory>({
id: "ds-id",
userPermissions: [
"create:datasourceActions",
"execute:datasources",
"delete:datasources",
"manage:datasources",
"read:datasources",
],
name: "Mock_DB",
pluginId: PluginIDs[pluginPackageName || PluginPackageName.POSTGRES],
workspaceId: "workspace-id",
datasourceStorages: {
"65fc11feb48e3e52a6d91d34": {
datasourceId: "65fc124fb48e3e52a6d91d44",
environmentId: "65fc11feb48e3e52a6d91d34",
datasourceConfiguration: {
url: "mockdb.internal.appsmith.com",
connection: {
mode: DatasourceConnectionMode.READ_ONLY,
ssl: {
authType: SSLType.DEFAULT,
authTypeControl: false,
certificateFile: {
name: "",
base64Content: null,
},
},
},
authentication: {
authenticationType: "dbAuth",
username: "mockdb",
},
},
isConfigured: true,
isValid: true,
},
},
invalids: [],
messages: [],
});

View File

@ -1,12 +1,21 @@
import type { PluginDataState } from "reducers/entityReducers/pluginsReducer";
import { PluginPackageName } from "entities/Action";
export const PluginIDs = {
export const PluginIDs: Record<PluginPackageName, string> = {
[PluginPackageName.POSTGRES]: "65e58df196506a506bd7069c",
[PluginPackageName.REST_API]: "65e58df196506a506bd7069d",
[PluginPackageName.MONGO]: "65e58df196506a506bd7069e",
[PluginPackageName.GOOGLE_SHEETS]: "65e58df296506a506bd706a9",
[PluginPackageName.JS]: "65e58df296506a506bd706ad",
[PluginPackageName.MY_SQL]: "65e58df296506a506bd7069f",
[PluginPackageName.S3]: "65e58df296506a506bd706a8",
[PluginPackageName.SNOWFLAKE]: "65e58df296506a506bd706ab",
[PluginPackageName.FIRESTORE]: "65e58df296506a506bd706a6",
[PluginPackageName.GRAPHQL]: "65e58df396506a506bd706be",
[PluginPackageName.APPSMITH_AI]: "65e58fe2225bee69e71c536a",
[PluginPackageName.MS_SQL]: "65e58df296506a506bd706a5",
[PluginPackageName.ORACLE]: "65e58df396506a506bd706bf",
[PluginPackageName.WORKFLOW]: "<replace-me-with-default-plugin-id>", // this is added for the typing of PluginIDs to pass
};
export default {