chore: entity name generation refactor to include other entity types (#30316)

## Description
When we create an entity (JS or Query or API), the name is always
generated based the lookup of only that particular type. This leads to
name clash if a different entity has the same name format.
Example:
If a js object has the name "Query1" then if a query is being created;
the generated name would be "Query1" and it would fail to create as
there is a name clash.
To avoid this; all entities are considered to generate name.
This PR also makes sure that the name generation logic is done at a
central place (saga) rather than the creator of the entity. This avoid
code duplication.

#### PR fixes following issue(s)
PR for https://github.com/appsmithorg/appsmith-ee/pull/3306
> if no issue exists, please create an issue and ask the maintainers
about this first
>
>
#### Media
> A video or a GIF is preferred. when using Loom, don’t embed because it
looks like it’s a GIF. instead, just link to the video
>
>
#### Type of change
> Please delete options that are not relevant.
- Chore (housekeeping or task changes that don't impact user perception)

## Testing
>
#### How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Also
list any relevant details for your test configuration.
> Delete anything that is not relevant
- [ ] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress
>
>
#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
>
>
## Checklist:
#### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] 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


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

- **New Features**
- Introduced a new action creation process with enhanced naming
conventions.
- Implemented a new selector to generate entity names based on existing
entities.

- **Improvements**
- Streamlined action creation flow by integrating new helper functions.
  
- **Refactor**
- Updated various components to utilize the new entity naming selector.
- Consolidated action creation logic across different parts of the
application.

- **Bug Fixes**
- Fixed inconsistencies in action naming during the creation of APIs and
Queries.

- **Code Cleanup**
- Removed unused code and imports related to deprecated naming
utilities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
ashit-rath 2024-01-17 15:34:50 +05:30 committed by GitHub
parent 54926b5b5b
commit 03fc50cd79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 125 additions and 44 deletions

View File

@ -17,6 +17,12 @@ import type { ModalInfo } from "reducers/uiReducers/modalActionReducer";
import type { OtlpSpan } from "UITelemetry/generateTraces";
export const createActionRequest = (payload: Partial<Action>) => {
return {
type: ReduxActionTypes.CREATE_ACTION_REQUEST,
payload,
};
};
export const createActionInit = (payload: Partial<Action>) => {
return {
type: ReduxActionTypes.CREATE_ACTION_INIT,
payload,

View File

@ -256,6 +256,7 @@ const ActionTypes = {
FETCH_PROPERTY_PANE_CONFIGS_SUCCESS: "FETCH_PROPERTY_PANE_CONFIGS_SUCCESS",
FETCH_CONFIGS_INIT: "FETCH_CONFIGS_INIT",
ADD_WIDGET_REF: "ADD_WIDGET_REF",
CREATE_ACTION_REQUEST: "CREATE_ACTION_REQUEST",
CREATE_ACTION_INIT: "CREATE_ACTION_INIT",
CREATE_ACTION_SUCCESS: "CREATE_ACTION_SUCCESS",
FETCH_ACTIONS_INIT: "FETCH_ACTIONS_INIT",

View File

@ -0,0 +1,22 @@
import type { CreateNewActionKeyInterface } from "@appsmith/entities/Engine/actionHelpers";
import { CreateNewActionKey } from "@appsmith/entities/Engine/actionHelpers";
import type { Action } from "entities/Action";
export interface ResolveParentEntityMetadataReturnType {
parentEntityId?: string;
parentEntityKey?: CreateNewActionKeyInterface;
}
// This function is extended in EE. Please check the EE implementation before any modification.
export const resolveParentEntityMetadata = (
action: Partial<Action>,
): ResolveParentEntityMetadataReturnType => {
if (action.pageId) {
return {
parentEntityId: action.pageId,
parentEntityKey: CreateNewActionKey.PAGE,
};
}
return { parentEntityId: undefined, parentEntityKey: undefined };
};

View File

@ -57,6 +57,8 @@ import {
getCurrentWorkflowJSActions,
} from "@appsmith/selectors/workflowSelectors";
import { MAX_DATASOURCE_SUGGESTIONS } from "constants/DatasourceEditorConstants";
import type { CreateNewActionKeyInterface } from "@appsmith/entities/Engine/actionHelpers";
import { getNextEntityName } from "utils/AppsmithUtils";
export const getEntities = (state: AppState): AppState["entities"] =>
state.entities;
@ -74,6 +76,12 @@ export enum PluginCategory {
Others = "Others",
}
export interface NewEntityNameOptions {
prefix: string;
parentEntityId: string;
parentEntityKey: CreateNewActionKeyInterface;
}
export type DatasourceGroupByPluginCategory = Record<
PluginCategory,
Datasource[]
@ -1432,3 +1440,21 @@ export const getAllJSCollections = createSelector(
export const getIsActionConverting = (state: AppState, actionId: string) => {
return false;
};
export const getNewEntityName = createSelector(
getActions,
getJSCollections,
(_state: AppState, options: NewEntityNameOptions) => options,
(actions, jsCollections, options) => {
const { parentEntityId, parentEntityKey, prefix } = options;
const actionNames = actions
.filter((a) => a.config[parentEntityKey] === parentEntityId)
.map((a) => a.config.name);
const jsActionNames = jsCollections
.filter((a) => a.config[parentEntityKey] === parentEntityId)
.map((a) => a.config.name);
return getNextEntityName(prefix, actionNames.concat(jsActionNames));
},
);

View File

@ -0,0 +1 @@
export * from "ce/sagas/helpers";

View File

@ -283,11 +283,11 @@ export const SCHEMA_SECTION_ID = "t--api-right-pane-schema";
export interface CreateApiActionDefaultsParams {
apiType: string;
from?: EventLocation;
newActionName: string;
newActionName?: string;
}
export interface CreateActionDefaultsParams {
datasourceId: string;
from?: EventLocation;
newActionName: string;
newActionName?: string;
}

View File

@ -1,9 +1,8 @@
import React, { useMemo } from "react";
import CurlImportForm from "./CurlImportForm";
import { createNewApiName } from "utils/AppsmithUtils";
import { curlImportSubmitHandler } from "./helpers";
import { getActions } from "@appsmith/selectors/entitiesSelector";
import { getNewEntityName } from "@appsmith/selectors/entitiesSelector";
import { getIsImportingCurl } from "selectors/ui";
import { showDebuggerFlag } from "selectors/debuggerSelectors";
import { useSelector } from "react-redux";
@ -12,19 +11,27 @@ import type { BuilderRouteParams } from "constants/routes";
import CloseEditor from "components/editorComponents/CloseEditor";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { CreateNewActionKey } from "@appsmith/entities/Engine/actionHelpers";
import { DEFAULT_PREFIX } from "sagas/ActionSagas";
type CurlImportEditorProps = RouteComponentProps<BuilderRouteParams>;
function CurlImportEditor(props: CurlImportEditorProps) {
const actions = useSelector(getActions);
const { pageId } = props.match.params;
const actionName = useSelector((state) =>
getNewEntityName(state, {
prefix: DEFAULT_PREFIX.API,
parentEntityId: pageId,
parentEntityKey: CreateNewActionKey.PAGE,
}),
);
const showDebugger = useSelector(showDebuggerFlag);
const isImportingCurl = useSelector(getIsImportingCurl);
const initialFormValues = {
pageId,
name: createNewApiName(actions, pageId),
name: actionName,
};
const isPagesPaneEnabled = useFeatureFlag(
FEATURE_FLAG.release_show_new_sidebar_pages_pane_enabled,

View File

@ -2,7 +2,6 @@ import React, { useCallback, useContext } from "react";
import { useDispatch, useSelector } from "react-redux";
import { createActionRequest } from "actions/pluginActionActions";
import type { AppState } from "@appsmith/reducers";
import { createNewQueryName } from "utils/AppsmithUtils";
import {
getCurrentApplicationId,
getCurrentPageId,
@ -81,7 +80,6 @@ export function QueryTemplates(props: QueryTemplatesProps) {
);
const createQueryAction = useCallback(
(template: QueryTemplate) => {
const newQueryName = createNewQueryName(actions, currentPageId || "");
const queryactionConfiguration: Partial<QueryAction> = {
actionConfiguration: {
body: template.body,
@ -93,7 +91,6 @@ export function QueryTemplates(props: QueryTemplatesProps) {
dispatch(
createActionRequest({
name: newQueryName,
pageId: currentPageId,
pluginId: dataSource?.pluginId,
datasource: {

View File

@ -12,7 +12,6 @@ import { createDatasourceFromForm } from "actions/datasourceActions";
import type { SaaSAction } from "entities/Action";
import { createActionRequest } from "actions/pluginActionActions";
import type { Datasource } from "entities/Datasource";
import { createNewApiName } from "utils/AppsmithUtils";
import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer";
// Design
@ -88,7 +87,6 @@ class ListView extends React.Component<Props> {
handleCreateNewAPI = (datasource: Datasource) => {
const {
actions,
location,
match: {
params: { pageId },
@ -100,10 +98,7 @@ class ListView extends React.Component<Props> {
pgId = pageId;
}
if (pgId) {
const newApiName = createNewApiName(actions, pgId);
this.props.createAction({
name: newApiName,
pageId: pgId,
pluginId: datasource.pluginId,
datasource: {

View File

@ -31,6 +31,7 @@ import type {
import {
copyActionError,
copyActionSuccess,
createActionInit,
createActionSuccess,
deleteActionSuccess,
fetchActionsForPage,
@ -79,6 +80,7 @@ import {
getPlugin,
getSettingConfig,
getPageActions,
getNewEntityName,
} from "@appsmith/selectors/entitiesSelector";
import history from "utils/history";
import { INTEGRATION_TABS } from "constants/routes";
@ -142,6 +144,12 @@ import { selectFeatureFlagCheck } from "@appsmith/selectors/featureFlagsSelector
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
import { identifyEntityFromPath } from "../navigation/FocusEntity";
import { getActionConfig } from "../pages/Editor/Explorer/Actions/helpers";
import { resolveParentEntityMetadata } from "@appsmith/sagas/helpers";
export const DEFAULT_PREFIX = {
QUERY: "Query",
API: "Api",
} as const;
export function* createDefaultActionPayloadWithPluginDefaults(
props: CreateActionDefaultsParams,
@ -245,6 +253,52 @@ export function* getPluginActionDefaultValues(pluginId: string) {
return initialValues;
}
/**
* This saga prepares the action request i.e it helps generating a
* new name of an action. This is to reduce any dependency on name generation
* on the caller of this saga.
*/
export function* createActionRequestSaga(
actionPayload: ReduxAction<
Partial<Action> & { eventData: any; pluginId: string }
>,
) {
const payload = { ...actionPayload.payload };
const pluginId =
actionPayload.payload.pluginId ||
actionPayload.payload.datasource?.pluginId;
if (!actionPayload.payload.name) {
const { parentEntityId, parentEntityKey } = resolveParentEntityMetadata(
actionPayload.payload,
);
if (!parentEntityId || !parentEntityKey) return;
const plugin: Plugin | undefined = yield select(getPlugin, pluginId || "");
const isQueryType =
plugin?.type === PluginType.DB ||
plugin?.packageName === PluginPackageName.APPSMITH_AI;
const prefix = isQueryType ? DEFAULT_PREFIX.QUERY : DEFAULT_PREFIX.API;
if (
plugin?.type === PluginType.DB ||
plugin?.packageName === PluginPackageName.APPSMITH_AI
) {
DEFAULT_PREFIX.QUERY;
}
const name: string = yield select(getNewEntityName, {
prefix,
parentEntityId,
parentEntityKey,
});
payload.name = name;
}
yield put(createActionInit(payload));
}
export function* createActionSaga(
actionPayload: ReduxAction<
Partial<Action> & { eventData: any; pluginId: string }
@ -1128,6 +1182,7 @@ export function* watchActionSagas() {
ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_INIT,
fetchActionsForViewModeSaga,
),
takeEvery(ReduxActionTypes.CREATE_ACTION_REQUEST, createActionRequestSaga),
takeEvery(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),

View File

@ -34,19 +34,13 @@ import history from "utils/history";
import { INTEGRATION_EDITOR_MODES, INTEGRATION_TABS } from "constants/routes";
import { initialize, autofill, change, reset } from "redux-form";
import type { Property } from "api/ActionAPI";
import { createNewApiName, createNewQueryName } from "utils/AppsmithUtils";
import { getQueryParams } from "utils/URLUtils";
import { getPluginIdOfPackageName } from "sagas/selectors";
import {
getAction,
getActions,
getDatasourceActionRouteInfo,
getPlugin,
} from "@appsmith/selectors/entitiesSelector";
import type {
ActionData,
ActionDataState,
} from "@appsmith/reducers/entityReducers/actionsReducer";
import {
createActionRequest,
setActionProperty,
@ -724,16 +718,6 @@ function* handleCreateNewApiActionSaga(
const { apiType = PluginPackageName.REST_API, from, pageId } = action.payload;
if (pageId) {
const actions: ActionDataState = yield select(getActions);
const pageActions = actions.filter(
(a: ActionData) => a.config.pageId === pageId,
);
let newActionName = createNewApiName(pageActions, pageId);
// Change the action name to Query if the plugin is Appsmith AI
if (apiType === PluginPackageName.APPSMITH_AI) {
newActionName = createNewQueryName(pageActions, pageId);
}
// Note: Do NOT send pluginId on top level here.
// It breaks embedded rest datasource flow.
@ -742,7 +726,6 @@ function* handleCreateNewApiActionSaga(
{
apiType,
from,
newActionName,
},
);

View File

@ -36,7 +36,6 @@ import {
getSettingConfig,
getPlugins,
getGenerateCRUDEnabledPluginMap,
getActions,
} from "@appsmith/selectors/entitiesSelector";
import type { Action, QueryAction } from "entities/Action";
import { PluginType } from "entities/Action";
@ -86,8 +85,6 @@ import { selectFeatureFlags } from "@appsmith/selectors/featureFlagsSelectors";
import { isGACEnabled } from "@appsmith/utils/planHelpers";
import { getHasManageActionPermission } from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
import type { ChangeQueryPayload } from "actions/queryPaneActions";
import { createNewApiName, createNewQueryName } from "utils/AppsmithUtils";
import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer";
import {
getApplicationByIdFromWorkspaces,
getCurrentApplicationIdForCreateNewApp,
@ -499,23 +496,14 @@ function* createNewQueryForDatasourceSaga(
from: EventLocation;
}>,
) {
const { datasourceId, from, pageId } = action.payload;
const { datasourceId, from } = action.payload;
if (!datasourceId) return;
const actions: ActionDataState = yield select(getActions);
const datasource: Datasource = yield select(getDatasource, datasourceId);
const plugin: Plugin = yield select(getPlugin, datasource?.pluginId);
const newActionName =
plugin?.type === PluginType.DB
? createNewQueryName(actions, pageId || "")
: createNewApiName(actions, pageId || "");
const createActionPayload: Partial<Action> = yield call(
createDefaultActionPayloadWithPluginDefaults,
{
datasourceId,
from,
newActionName,
},
);