Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Handling the updation of action name in the plugin action toolbar #36560

Merged
merged 9 commits into from
Sep 27, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from "react";
import { useSelector } from "react-redux";
import ActionNameEditor from "components/editorComponents/ActionNameEditor";
import { usePluginActionContext } from "PluginActionEditor/PluginActionContext";
import { getActionByBaseId, getPlugin } from "ee/selectors/entitiesSelector";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { getHasManageActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import { PluginType } from "entities/Action";
import type { ReduxAction } from "ee/constants/ReduxActionConstants";
import styled from "styled-components";
import type { AppState } from "ee/reducers";
import { getSavingStatusForActionName } from "selectors/actionSelectors";
import { getAssetUrl } from "ee/utils/airgapHelpers";
import { ActionUrlIcon } from "pages/Editor/Explorer/ExplorerIcons";

export interface SaveActionNameParams {
id: string;
name: string;
}

export interface PluginActionNameEditorProps {
saveActionName: (
params: SaveActionNameParams,
) => ReduxAction<SaveActionNameParams>;
}

const ActionNameEditorWrapper = styled.div`
& .ads-v2-box {
gap: var(--ads-v2-spaces-2);
}

&& .t--action-name-edit-field {
font-size: 12px;

.bp3-editable-text-content {
height: unset !important;
line-height: unset !important;
}
}

& .t--plugin-icon-box {
height: 12px;
width: 12px;

img {
width: 12px;
height: auto;
}
}
`;

const PluginActionNameEditor = (props: PluginActionNameEditorProps) => {
const { action, plugin } = usePluginActionContext();
const currentActionConfig = useSelector((state) =>
action.baseId ? getActionByBaseId(state, action.baseId) : undefined,
);
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const isChangePermitted = getHasManageActionPermission(
isFeatureEnabled,
currentActionConfig?.userPermissions,
);

const currentPlugin = useSelector((state: AppState) =>
getPlugin(state, currentActionConfig?.pluginId || ""),
);

const saveStatus = useSelector((state) =>
getSavingStatusForActionName(state, currentActionConfig?.id || ""),
);

const iconUrl = getAssetUrl(currentPlugin?.iconLocation) || "";

const icon = ActionUrlIcon(iconUrl);

return (
<ActionNameEditorWrapper>
<ActionNameEditor
actionConfig={currentActionConfig}
disabled={!isChangePermitted}
enableFontStyling={plugin?.type === PluginType.API}
icon={icon}
saveActionName={props.saveActionName}
saveStatus={saveStatus}
/>
</ActionNameEditorWrapper>
);
};

export default PluginActionNameEditor;
5 changes: 5 additions & 0 deletions app/client/src/PluginActionEditor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ export {
export { default as PluginActionToolbar } from "./components/PluginActionToolbar";
export { default as PluginActionForm } from "./components/PluginActionForm";
export { default as PluginActionResponse } from "./components/PluginActionResponse";
export type {
SaveActionNameParams,
PluginActionNameEditorProps,
} from "./components/PluginActionNameEditor";
export { default as PluginActionNameEditor } from "./components/PluginActionNameEditor";
71 changes: 27 additions & 44 deletions app/client/src/components/editorComponents/ActionNameEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
import React, { memo } from "react";
import { useSelector } from "react-redux";

import { useParams } from "react-router-dom";
import EditableText, {
EditInteractionKind,
} from "components/editorComponents/EditableText";
import { removeSpecialChars } from "utils/helpers";
import type { AppState } from "ee/reducers";

import { saveActionName } from "actions/pluginActionActions";
import { Flex } from "@appsmith/ads";
import { getActionByBaseId, getPlugin } from "ee/selectors/entitiesSelector";
import NameEditorComponent, {
IconBox,
IconWrapper,
NameWrapper,
} from "components/utils/NameEditorComponent";
import {
ACTION_ID_NOT_FOUND_IN_URL,
ACTION_NAME_PLACEHOLDER,
createMessage,
} from "ee/constants/messages";
import { getAssetUrl } from "ee/utils/airgapHelpers";
import { getSavingStatusForActionName } from "selectors/actionSelectors";
import type { ReduxAction } from "ee/constants/ReduxActionConstants";
import type { SaveActionNameParams } from "PluginActionEditor";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import type { Action } from "entities/Action";
import type { ModuleInstance } from "ee/constants/ModuleInstanceConstants";

interface SaveActionNameParams {
id: string;
name: string;
}
interface ActionNameEditorProps {
/*
This prop checks if page is API Pane or Query Pane or Curl Pane
Expand All @@ -38,38 +31,34 @@ interface ActionNameEditorProps {
*/
enableFontStyling?: boolean;
disabled?: boolean;
saveActionName?: (
saveActionName: (
params: SaveActionNameParams,
) => ReduxAction<SaveActionNameParams>;
actionConfig?: Action | ModuleInstance;
icon?: JSX.Element;
saveStatus: { isSaving: boolean; error: boolean };
}

function ActionNameEditor(props: ActionNameEditorProps) {
const params = useParams<{ baseApiId?: string; baseQueryId?: string }>();

const currentActionConfig = useSelector((state: AppState) =>
getActionByBaseId(state, params.baseApiId || params.baseQueryId || ""),
);

const currentPlugin = useSelector((state: AppState) =>
getPlugin(state, currentActionConfig?.pluginId || ""),
);
const {
actionConfig,
disabled = false,
enableFontStyling = false,
icon = "",
saveActionName,
saveStatus,
} = props;

const saveStatus = useSelector((state) =>
getSavingStatusForActionName(state, currentActionConfig?.id || ""),
const isActionRedesignEnabled = useFeatureFlag(
FEATURE_FLAG.release_actions_redesign_enabled,
);

return (
<NameEditorComponent
/**
* This component is used by module editor in EE which uses a different
* action to save the name of an action. The current callers of this component
* pass the existing saveAction action but as fallback the saveActionName is used here
* as a guard.
*/
dispatchAction={props.saveActionName || saveActionName}
id={currentActionConfig?.id}
id={actionConfig?.id}
idUndefinedErrorMessage={ACTION_ID_NOT_FOUND_IN_URL}
name={currentActionConfig?.name}
name={actionConfig?.name}
onSaveName={saveActionName}
saveStatus={saveStatus}
>
{({
Expand All @@ -85,28 +74,22 @@ function ActionNameEditor(props: ActionNameEditorProps) {
isNew: boolean;
saveStatus: { isSaving: boolean; error: boolean };
}) => (
<NameWrapper enableFontStyling={props.enableFontStyling}>
<NameWrapper enableFontStyling={enableFontStyling}>
<Flex
alignItems="center"
gap="spaces-3"
overflow="hidden"
width="100%"
>
{currentPlugin && (
<IconBox>
<IconWrapper
alt={currentPlugin.name}
src={getAssetUrl(currentPlugin?.iconLocation)}
/>
</IconBox>
)}
{icon && <IconBox className="t--plugin-icon-box">{icon}</IconBox>}
<EditableText
className="t--action-name-edit-field"
defaultValue={currentActionConfig ? currentActionConfig.name : ""}
disabled={props.disabled}
defaultValue={actionConfig ? actionConfig.name : ""}
disabled={disabled}
editInteractionKind={EditInteractionKind.SINGLE}
errorTooltipClass="t--action-name-edit-error"
forceDefault={forceUpdate}
iconSize={isActionRedesignEnabled ? "sm" : "md"}
isEditingDefault={isNew}
isInvalid={isInvalidNameForEntity}
onTextChanged={handleNameChange}
Expand Down
12 changes: 10 additions & 2 deletions app/client/src/components/editorComponents/EditableText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import {
} from "@blueprintjs/core";
import styled from "styled-components";
import _ from "lodash";
import { Button, Spinner, toast, Tooltip } from "@appsmith/ads";
import {
Button,
Spinner,
toast,
Tooltip,
type ButtonSizes,
} from "@appsmith/ads";
import { INVALID_NAME_ERROR, createMessage } from "ee/constants/messages";

export enum EditInteractionKind {
Expand Down Expand Up @@ -39,6 +45,7 @@ interface EditableTextProps {
minLines?: number;
customErrorTooltip?: string;
useFullWidth?: boolean;
iconSize?: ButtonSizes;
}

// using the !important keyword here is mandatory because a style is being applied to that element using the style attribute
Expand Down Expand Up @@ -129,6 +136,7 @@ export function EditableText(props: EditableTextProps) {
errorTooltipClass,
forceDefault,
hideEditIcon,
iconSize = "md",
isEditingDefault,
isInvalid,
maxLength,
Expand Down Expand Up @@ -275,7 +283,7 @@ export function EditableText(props: EditableTextProps) {
className="t--action-name-edit-icon"
isIconButton
kind="tertiary"
size="md"
size={iconSize}
startIcon="pencil-line"
/>
))}
Expand Down
13 changes: 8 additions & 5 deletions app/client/src/components/utils/NameEditorComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from "ee/constants/messages";
import styled from "styled-components";
import { Classes } from "@blueprintjs/core";
import type { SaveActionNameParams } from "PluginActionEditor";
import type { ReduxAction } from "ee/constants/ReduxActionConstants";

export const NameWrapper = styled.div<{ enableFontStyling?: boolean }>`
min-width: 50%;
Expand Down Expand Up @@ -71,9 +73,10 @@ interface NameEditorProps {
children: (params: any) => JSX.Element;
id?: string;
name?: string;
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dispatchAction: (a: any) => any;
onSaveName: (
params: SaveActionNameParams,
) => ReduxAction<SaveActionNameParams>;
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
suffixErrorMessage?: (params?: any) => string;
Expand All @@ -90,10 +93,10 @@ interface NameEditorProps {

function NameEditor(props: NameEditorProps) {
const {
dispatchAction,
id: entityId,
idUndefinedErrorMessage,
name: entityName,
onSaveName,
saveStatus,
suffixErrorMessage = ACTION_NAME_CONFLICT_ERROR,
} = props;
Expand Down Expand Up @@ -131,8 +134,8 @@ function NameEditor(props: NameEditorProps) {

const handleNameChange = useCallback(
(name: string) => {
if (name !== entityName && !isInvalidNameForEntity(name)) {
dispatch(dispatchAction({ id: entityId, name }));
if (name !== entityName && !isInvalidNameForEntity(name) && entityId) {
dispatch(onSaveName({ id: entityId, name }));
}
},
[dispatch, isInvalidNameForEntity, entityId, entityName],
Expand Down
8 changes: 2 additions & 6 deletions app/client/src/pages/Editor/APIEditor/ApiEditorContext.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import type { ReduxAction } from "ee/constants/ReduxActionConstants";
import type { PaginationField } from "api/ActionAPI";
import React, { createContext, useMemo } from "react";

interface SaveActionNameParams {
id: string;
name: string;
}
import type { SaveActionNameParams } from "PluginActionEditor";

interface ApiEditorContextContextProps {
moreActionsMenu?: React.ReactNode;
Expand All @@ -15,7 +11,7 @@ interface ApiEditorContextContextProps {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
settingsConfig: any;
saveActionName?: (
saveActionName: (
params: SaveActionNameParams,
) => ReduxAction<SaveActionNameParams>;
closeEditorLink?: React.ReactNode;
Expand Down
18 changes: 18 additions & 0 deletions app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import {
InfoFields,
RequestTabs,
} from "PluginActionEditor/components/PluginActionForm/components/CommonEditorForm";
import { getSavingStatusForActionName } from "selectors/actionSelectors";
import { getAssetUrl } from "ee/utils/airgapHelpers";
import { ActionUrlIcon } from "../Explorer/ExplorerIcons";

const Form = styled.form`
position: relative;
Expand Down Expand Up @@ -245,6 +248,18 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
currentActionConfig?.userPermissions,
);

const currentPlugin = useSelector((state: AppState) =>
getPlugin(state, currentActionConfig?.pluginId || ""),
);

const saveStatus = useSelector((state) =>
getSavingStatusForActionName(state, currentActionConfig?.id || ""),
);

const iconUrl = getAssetUrl(currentPlugin?.iconLocation) || "";

const icon = ActionUrlIcon(iconUrl);

const plugin = useSelector((state: AppState) =>
getPlugin(state, pluginId ?? ""),
);
Expand Down Expand Up @@ -281,9 +296,12 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
<FormRow className="form-row-header">
<NameWrapper className="t--nameOfApi">
<ActionNameEditor
actionConfig={currentActionConfig}
disabled={!isChangePermitted}
enableFontStyling
icon={icon}
saveActionName={saveActionName}
saveStatus={saveStatus}
/>
</NameWrapper>
<ActionButtons className="t--formActionButtons">
Expand Down
Loading
Loading