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:
parent
54926b5b5b
commit
03fc50cd79
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
22
app/client/src/ce/sagas/helpers.ts
Normal file
22
app/client/src/ce/sagas/helpers.ts
Normal 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 };
|
||||
};
|
||||
|
|
@ -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));
|
||||
},
|
||||
);
|
||||
|
|
|
|||
1
app/client/src/ee/sagas/helpers.ts
Normal file
1
app/client/src/ee/sagas/helpers.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/sagas/helpers";
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user