Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8e24f09
update: remove dependence on pageId from copy action saga
ayushpahwa Oct 16, 2024
2558f69
update: signature for copy action request
ayushpahwa Oct 16, 2024
7a25592
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 16, 2024
0aa8f37
update: create ee export file for duplication hook
ayushpahwa Oct 16, 2024
1278d2b
update: typo fix
ayushpahwa Oct 16, 2024
8a4eef5
update: fix func call
ayushpahwa Oct 16, 2024
65c94e3
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 16, 2024
0e6a414
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 16, 2024
5d94282
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 16, 2024
991b26a
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 16, 2024
9662d00
Merge branch 'release' into feat/query-duplication-wf
ayushpahwa Oct 18, 2024
56ad42b
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 18, 2024
84b5cda
Merge branch 'release' into feat/query-duplication-wf
ayushpahwa Oct 18, 2024
098b318
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 18, 2024
67eb34d
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 18, 2024
fe4c2ee
update: remove unused file
ayushpahwa Oct 18, 2024
5fe13ea
Merge branch 'release' into feat/query-duplication-wf
ayushpahwa Oct 18, 2024
6a18c58
Merge branch 'release' into feat/query-duplication-wf
ayushpahwa Oct 21, 2024
7f4a43e
Merge branch 'release' into feat/query-duplication-wf
ayushpahwa Oct 22, 2024
fe98984
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 22, 2024
04dfbfb
Sync changes from EE excluding enterprise directory
ayushpahwa Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/client/src/PluginActionEditor/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { useActionSettingsConfig } from "ee/PluginActionEditor/hooks/useActionSettingsConfig";
export { useHandleDeleteClick } from "ee/PluginActionEditor/hooks/useHandleDeleteClick";
export { useHandleDuplicateClick } from "ee/PluginActionEditor/hooks/useHandleDuplicateClick";
export { useHandleRunClick } from "ee/PluginActionEditor/hooks/useHandleRunClick";
export { useBlockExecution } from "ee/PluginActionEditor/hooks/useBlockExecution";
export { useAnalyticsOnRunClick } from "ee/PluginActionEditor/hooks/useAnalyticsOnRunClick";
5 changes: 3 additions & 2 deletions app/client/src/actions/pluginActionActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { ApiResponse } from "api/ApiResponses";
import type { JSCollection } from "entities/JSCollection";
import type { ErrorActionPayload } from "sagas/ErrorSagas";
import type { EventLocation } from "ee/utils/analyticsUtilTypes";
import type { GenerateDestinationIdInfoReturnType } from "ee/sagas/helpers";

export const createActionRequest = (payload: Partial<Action>) => {
return {
Expand Down Expand Up @@ -225,7 +226,7 @@ export const moveActionError = (

export const copyActionRequest = (payload: {
id: string;
destinationPageId: string;
destinationEditorId: string;
name: string;
}) => {
return {
Expand All @@ -244,7 +245,7 @@ export const copyActionSuccess = (payload: Action) => {
export const copyActionError = (
payload: {
id: string;
destinationPageId: string;
destinationEditorIdInfo: GenerateDestinationIdInfoReturnType;
} & ErrorActionPayload,
) => {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { copyActionRequest } from "actions/pluginActionActions";
import { usePluginActionContext } from "PluginActionEditor/PluginActionContext";
import { useCallback } from "react";
import { useDispatch } from "react-redux";

function useHandleDuplicateClick() {
const { action } = usePluginActionContext();
const dispatch = useDispatch();

const handleDuplicateClick = useCallback(
(destinationEditorId: string) => {
dispatch(
copyActionRequest({
id: action.id,
destinationEditorId,
name: action.name,
}),
);
},
[action.id, action.name, dispatch],
);

return { handleDuplicateClick };
}

export { useHandleDuplicateClick };
3 changes: 2 additions & 1 deletion app/client/src/ce/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ export const ACTION_MOVE_SUCCESS = (actionName: string, pageName: string) =>
export const ERROR_ACTION_MOVE_FAIL = (actionName: string) =>
`Error while moving action ${actionName}`;
export const ACTION_COPY_SUCCESS = (actionName: string, pageName: string) =>
`${actionName} action copied to page ${pageName} successfully`;
`${actionName} action copied ${pageName.length > 0 ? "to page " + pageName : ""} successfully`;
export const ERROR_ACTION_COPY_FAIL = (actionName: string) =>
`Error while copying action ${actionName}`;
export const ERROR_ACTION_RENAME_FAIL = (actionName: string) =>
Expand Down Expand Up @@ -1731,6 +1731,7 @@ export const CONTEXT_RENAME = () => "Rename";
export const CONTEXT_SHOW_BINDING = () => "Show bindings";
export const CONTEXT_MOVE = () => "Move to page";
export const CONTEXT_COPY = () => "Copy to page";
export const CONTEXT_DUPLICATE = () => "Duplicate";
export const CONTEXT_DELETE = () => "Delete";
export const CONFIRM_CONTEXT_DELETE = () => "Are you sure?";
export const CONFIRM_CONTEXT_DELETING = () => "Deleting";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const Copy = () => {
dispatch(
copyActionRequest({
id: action.id,
destinationPageId: pageId,
destinationEditorId: pageId,
name: action.name,
}),
),
Expand Down
17 changes: 17 additions & 0 deletions app/client/src/ce/sagas/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ export interface ResolveParentEntityMetadataReturnType {
parentEntityKey?: CreateNewActionKeyInterface;
}

// This function is extended in EE. Please check the EE implementation before any modification.
export interface GenerateDestinationIdInfoReturnType {
pageId?: string;
}
Comment on lines +13 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Interface implementation needs to be moved to EE directory

The interface is currently defined in CE but imported from EE (import type { GenerateDestinationIdInfoReturnType } from "ee/sagas/helpers"). This mismatch needs to be addressed:

  • Move the interface definition from app/client/src/ce/sagas/helpers.ts to the corresponding EE file
  • Update imports to reference the correct EE location

The current setup could lead to import resolution issues and doesn't align with the codebase's CE/EE separation pattern.

🔗 Analysis chain

LGTM! Verify EE implementation compatibility.

The interface is well-defined and follows TypeScript best practices.

Let's verify the EE implementation:


The previous search in the ee/ directory failed because the directory might be named differently or located elsewhere. Let's search the entire codebase for any references or extensions of this interface.


Let's check for EE implementations since the comment indicates this function is extended in EE. The previous search only found the CE implementation, so we need to look for actual extensions or usages.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for any EE-specific extensions of this interface
rg -A 5 "interface GenerateDestinationIdInfoReturnType" "ee/"

Length of output: 105


Script:

#!/bin/bash
# Let's try searching in the entire codebase for EE-specific extensions
rg -g '!node_modules' -g '!dist' -A 5 "interface GenerateDestinationIdInfoReturnType"

# Also search for any files that might contain EE implementations
fd -g '*.ts' -g '*.js' -E 'node_modules' -E 'dist' -x rg -l "GenerateDestinationIdInfoReturnType"

Length of output: 718


Script:

#!/bin/bash
# Let's search for any EE-specific implementations or extensions
rg -g '!node_modules' -g '!dist' -A 10 "generateDestinationIdInfoForQueryDuplication" --type ts

# Also check for any imports of this interface
rg -g '!node_modules' -g '!dist' "import.*GenerateDestinationIdInfoReturnType" --type ts

Length of output: 2599


// This function is extended in EE. Please check the EE implementation before any modification.
export function generateDestinationIdInfoForQueryDuplication(
destinationEditorId: string,
parentEntityKey: CreateNewActionKeyInterface,
): GenerateDestinationIdInfoReturnType {
if (parentEntityKey === CreateNewActionKey.PAGE) {
return { pageId: destinationEditorId };
}

return {};
}

// This function is extended in EE. Please check the EE implementation before any modification.
export const resolveParentEntityMetadata = (
action: Partial<Action>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "ce/PluginActionEditor/hooks/useHandleDuplicateClick";
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CONTEXT_NO_PAGE,
CONTEXT_SHOW_BINDING,
createMessage,
CONTEXT_DUPLICATE,
} from "ee/constants/messages";
import { builderURL } from "ee/RouteBuilder";

Expand All @@ -33,6 +34,7 @@ import { useConvertToModuleOptions } from "ee/pages/Editor/Explorer/hooks";
import { MODULE_TYPE } from "ee/constants/ModuleConstants";
import { PluginType } from "entities/Action";
import { convertToBaseParentEntityIdSelector } from "selectors/pageListSelectors";
import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers";

interface EntityContextMenuProps {
id: string;
Expand All @@ -45,20 +47,20 @@ interface EntityContextMenuProps {
export function ActionEntityContextMenu(props: EntityContextMenuProps) {
// Import the context
const context = useContext(FilesContext);
const { menuItems, parentEntityId } = context;
const { menuItems, parentEntityId, parentEntityType } = context;
const baseParentEntityId = useSelector((state) =>
convertToBaseParentEntityIdSelector(state, parentEntityId),
);

const { canDeleteAction, canManageAction } = props;
const dispatch = useDispatch();
const [confirmDelete, setConfirmDelete] = useState(false);
const copyActionToPage = useCallback(
(actionId: string, actionName: string, pageId: string) =>
const copyAction = useCallback(
(actionId: string, actionName: string, destinationEditorId: string) =>
dispatch(
copyActionRequest({
id: actionId,
destinationPageId: pageId,
destinationEditorId,
name: actionName,
}),
),
Expand Down Expand Up @@ -129,14 +131,24 @@ export function ActionEntityContextMenu(props: EntityContextMenuProps) {
menuItems.includes(ActionEntityContextMenuItemsEnum.COPY) &&
canManageAction && {
value: "copy",
onSelect: noop,
label: createMessage(CONTEXT_COPY),
children: menuPages.map((page) => {
return {
...page,
onSelect: () => copyActionToPage(props.id, props.name, page.id),
};
}),
onSelect:
parentEntityType === ActionParentEntityType.PAGE
? noop
: () => {
copyAction(props.id, props.name, parentEntityId);
},
label: createMessage(
menuPages.length > 0 ? CONTEXT_COPY : CONTEXT_DUPLICATE,
),
children:
parentEntityType === ActionParentEntityType.PAGE &&
menuPages.length > 0 &&
menuPages.map((page) => {
return {
...page,
onSelect: () => copyAction(props.id, props.name, page.id),
};
}),
Comment on lines +134 to +153

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting copy action logic into separate functions.

The copy action configuration is complex and would benefit from being broken down into smaller, more testable functions.

Consider this refactoring:

const getCopyActionLabel = (parentEntityType: ActionParentEntityType) => 
  parentEntityType === ActionParentEntityType.PAGE
    ? createMessage(CONTEXT_COPY)
    : createMessage(CONTEXT_DUPLICATE);

const getCopyActionHandler = (
  parentEntityType: ActionParentEntityType,
  id: string,
  name: string,
  parentEntityId: string,
) =>
  parentEntityType === ActionParentEntityType.PAGE
    ? noop
    : () => copyAction(id, name, parentEntityId);

const getCopyActionChildren = (
  parentEntityType: ActionParentEntityType,
  menuPages: Array<any>,
  id: string,
  name: string,
) =>
  parentEntityType === ActionParentEntityType.PAGE && menuPages.length > 0
    ? menuPages.map((page) => ({
        ...page,
        onSelect: () => copyAction(id, name, page.id),
      }))
    : undefined;

// In optionsTree:
{
  value: "copy",
  onSelect: getCopyActionHandler(parentEntityType, props.id, props.name, parentEntityId),
  label: getCopyActionLabel(parentEntityType),
  children: getCopyActionChildren(parentEntityType, menuPages, props.id, props.name),
}

},
menuItems.includes(ActionEntityContextMenuItemsEnum.MOVE) &&
canManageAction && {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function MoreActionsMenu(props: EntityContextMenuProps) {
dispatch(
copyActionRequest({
id: actionId,
destinationPageId: pageId,
destinationEditorId: pageId,
name: actionName,
}),
Comment on lines +66 to 68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider unifying the parameter naming between copy and move operations.

The copy operation uses destinationEntityId while the move operation still uses destinationPageId. This inconsistency might cause confusion.

Consider updating the move operation to match:

  moveActionRequest({
    id: actionId,
-   destinationPageId,
+   destinationEntityId: destinationPageId,
    originalPageId: propPageId ?? "",
    name: actionName,
  }),

Also applies to: 73-80

),
Expand Down
74 changes: 52 additions & 22 deletions app/client/src/sagas/ActionSagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ import { sendAnalyticsEventSaga } from "./AnalyticsSaga";
import { EditorModes } from "components/editorComponents/CodeEditor/EditorConfig";
import { updateActionAPICall } from "ee/sagas/ApiCallerSagas";
import FocusRetention from "./FocusRetentionSaga";
import { resolveParentEntityMetadata } from "ee/sagas/helpers";
import {
generateDestinationIdInfoForQueryDuplication,
resolveParentEntityMetadata,
} from "ee/sagas/helpers";
import { handleQueryEntityRedirect } from "./IDESaga";
import { EditorViewMode, IDE_TYPE } from "ee/entities/IDE/constants";
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
Expand All @@ -144,7 +147,8 @@ import {
} from "actions/ideActions";
import { getIsSideBySideEnabled } from "selectors/ideSelectors";
import { CreateNewActionKey } from "ee/entities/Engine/actionHelpers";
import { convertToBasePageIdSelector } from "selectors/pageListSelectors";
import { objectKeys } from "@appsmith/utils";
import { convertToBaseParentEntityIdSelector } from "selectors/pageListSelectors";

export const DEFAULT_PREFIX = {
QUERY: "Query",
Expand Down Expand Up @@ -745,17 +749,34 @@ function* moveActionSaga(
}

function* copyActionSaga(
action: ReduxAction<{ id: string; destinationPageId: string; name: string }>,
action: ReduxAction<{
id: string;
destinationEditorId: string;
name: string;
}>,
) {
let actionObject: Action = yield select(getAction, action.payload.id);

const { parentEntityId, parentEntityKey } =
resolveParentEntityMetadata(actionObject);

if (!parentEntityId || !parentEntityKey) return;

Comment on lines +758 to +765

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential undefined parentEntityId or parentEntityKey

In copyActionSaga, if parentEntityId or parentEntityKey are undefined, the function returns early. Ensure that this behavior is expected and that any necessary error handling or user feedback is in place.

const newName: string = yield select(getNewEntityName, {
prefix: action.payload.name,
parentEntityId: action.payload.destinationPageId,
parentEntityKey: CreateNewActionKey.PAGE,
parentEntityId,
parentEntityKey,
suffix: "Copy",
startWithoutIndex: true,
});

const destinationEditorIdInfo = generateDestinationIdInfoForQueryDuplication(
action.payload.destinationEditorId,
parentEntityKey,
);

if (objectKeys(destinationEditorIdInfo).length === 0) return;

try {
if (!actionObject) throw new Error("Could not find action to copy");

Expand All @@ -768,7 +789,7 @@ function* copyActionSaga(

const copyAction = Object.assign({}, actionObject, {
name: newName,
pageId: action.payload.destinationPageId,
...destinationEditorIdInfo,
}) as Partial<Action>;

// Indicates that source of action creation is copy action
Expand All @@ -781,11 +802,15 @@ function* copyActionSaga(
const datasources: Datasource[] = yield select(getDatasources);

const isValidResponse: boolean = yield validateResponse(response);
const pageName: string = yield select(
getPageNameByPageId,
// @ts-expect-error: pageId not present on ActionCreateUpdateResponse
response.data.pageId,
);
let pageName: string = "";

if (parentEntityKey === CreateNewActionKey.PAGE) {
pageName = yield select(
getPageNameByPageId,
// @ts-expect-error: pageId not present on ActionCreateUpdateResponse
response.data.pageId,
);
}

if (isValidResponse) {
toast.show(
Expand All @@ -807,6 +832,8 @@ function* copyActionSaga(
AnalyticsUtil.logEvent("DUPLICATE_ACTION", {
// @ts-expect-error: name not present on ActionCreateUpdateResponse
actionName: response.data.name,
parentEntityId,
parentEntityKey,
pageName: pageName,
actionId: response.data.id,
originalActionId,
Expand Down Expand Up @@ -836,7 +863,8 @@ function* copyActionSaga(

yield put(
copyActionError({
...action.payload,
id: action.payload.id,
destinationEditorIdInfo,
show: true,
error: {
message: errorMessage,
Expand Down Expand Up @@ -1039,21 +1067,23 @@ function* toggleActionExecuteOnLoadSaga(
}

function* handleMoveOrCopySaga(actionPayload: ReduxAction<Action>) {
const {
baseId: baseActionId,
pageId,
pluginId,
pluginType,
} = actionPayload.payload;
const { baseId: baseActionId, pluginId, pluginType } = actionPayload.payload;
const isApi = pluginType === PluginType.API;
const isQuery = pluginType === PluginType.DB;
const isSaas = pluginType === PluginType.SAAS;
const basePageId: string = yield select(convertToBasePageIdSelector, pageId);
const { parentEntityId } = resolveParentEntityMetadata(actionPayload.payload);

if (!parentEntityId) return;

Comment on lines +1075 to +1078

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure parentEntityId is defined before proceeding

In handleMoveOrCopySaga, if parentEntityId is undefined, the function returns early. Verify that this is the intended behavior and handle any necessary error messaging or fallback logic.

const baseParentEntityId: string = yield select(
convertToBaseParentEntityIdSelector,
parentEntityId,
);
Comment on lines +1079 to +1082

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use baseParentEntityId safely

You're selecting baseParentEntityId using parentEntityId. Since parentEntityId might be undefined (as checked earlier), ensure that baseParentEntityId is valid before using it to avoid potential errors.


if (isApi) {
history.push(
apiEditorIdURL({
basePageId,
baseParentEntityId,
baseApiId: baseActionId,
}),
);
Expand All @@ -1062,7 +1092,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<Action>) {
if (isQuery) {
history.push(
queryEditorIdURL({
basePageId,
baseParentEntityId,
baseQueryId: baseActionId,
}),
);
Expand All @@ -1076,7 +1106,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<Action>) {

history.push(
saasEditorApiIdURL({
basePageId,
baseParentEntityId,
pluginPackageName: plugin.packageName,
baseApiId: baseActionId,
}),
Expand Down