diff --git a/app/client/packages/design-system/ads/src/Icon/Icon.provider.tsx b/app/client/packages/design-system/ads/src/Icon/Icon.provider.tsx index d12bc1f519..47e819cb8a 100644 --- a/app/client/packages/design-system/ads/src/Icon/Icon.provider.tsx +++ b/app/client/packages/design-system/ads/src/Icon/Icon.provider.tsx @@ -137,8 +137,8 @@ const GitPullRequest = importRemixIcon( const GitRepository = importRemixIcon( async () => import("remixicon-react/GitRepositoryLineIcon"), ); -const GlobalLineIcon = importRemixIcon( - async () => import("remixicon-react/GlobalLineIcon"), +const GlobalLineIcon = importSvg( + async () => import("../__assets__/icons/ads/globe-simple.svg"), ); const GuideIcon = importRemixIcon( async () => import("remixicon-react/GuideFillIcon"), diff --git a/app/client/packages/design-system/ads/src/__assets__/icons/ads/globe-simple.svg b/app/client/packages/design-system/ads/src/__assets__/icons/ads/globe-simple.svg new file mode 100644 index 0000000000..73cc64e058 --- /dev/null +++ b/app/client/packages/design-system/ads/src/__assets__/icons/ads/globe-simple.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/client/src/IDE/Components/BottomView.tsx b/app/client/src/IDE/Components/BottomView.tsx index 0d5b7028c4..29f8e6f845 100644 --- a/app/client/src/IDE/Components/BottomView.tsx +++ b/app/client/src/IDE/Components/BottomView.tsx @@ -28,6 +28,7 @@ const Container = styled.div<{ displayMode: ViewDisplayMode }>` const ViewWrapper = styled.div` height: 100%; + &&& { ul.ads-v2-tabs__list { margin: 0 var(--ads-v2-spaces-8); @@ -39,6 +40,7 @@ const ViewWrapper = styled.div` .ads-v2-tabs__list { padding: var(--ads-v2-spaces-1) var(--ads-v2-spaces-7); padding-left: var(--ads-v2-spaces-3); + user-select: none; } } diff --git a/app/client/src/actions/debuggerActions.ts b/app/client/src/actions/debuggerActions.ts index be2052a4e1..c33075972c 100644 --- a/app/client/src/actions/debuggerActions.ts +++ b/app/client/src/actions/debuggerActions.ts @@ -7,6 +7,7 @@ import type { } from "reducers/uiReducers/debuggerReducer"; import type { EventName } from "ee/utils/analyticsUtilTypes"; import type { APP_MODE } from "entities/App"; +import type { GenericEntityItem } from "ee/entities/IDE/constants"; export interface LogDebuggerErrorAnalyticsPayload { entityName: string; @@ -147,3 +148,12 @@ export const showDebuggerLogs = () => { type: ReduxActionTypes.SHOW_DEBUGGER_LOGS, }; }; + +export const setDebuggerStateInspectorSelectedItem = ( + payload: GenericEntityItem, +) => { + return { + type: ReduxActionTypes.SET_DEBUGGER_STATE_INSPECTOR_SELECTED_ITEM, + payload, + }; +}; diff --git a/app/client/src/actions/jsPaneActions.ts b/app/client/src/actions/jsPaneActions.ts index 030f0e2e96..70a01db6ec 100644 --- a/app/client/src/actions/jsPaneActions.ts +++ b/app/client/src/actions/jsPaneActions.ts @@ -1,6 +1,8 @@ -import type { ReduxAction } from "ee/constants/ReduxActionConstants"; -import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; -import type { JSCollection, JSAction } from "entities/JSCollection"; +import { + type ReduxAction, + ReduxActionTypes, +} from "ee/constants/ReduxActionConstants"; +import type { JSAction, JSCollection } from "entities/JSCollection"; import type { RefactorAction, SetFunctionPropertyPayload, @@ -10,6 +12,7 @@ import type { JSEditorTab, JSPaneDebuggerState, } from "reducers/uiReducers/jsPaneReducer"; +import type { JSUpdate } from "../utils/JSPaneUtils"; export const createNewJSCollection = ( pageId: string, @@ -132,3 +135,10 @@ export const setJsPaneDebuggerState = ( type: ReduxActionTypes.SET_JS_PANE_DEBUGGER_STATE, payload, }); + +export const executeJSUpdates = ( + payload: Record, +): ReduxAction => ({ + type: ReduxActionTypes.EXECUTE_JS_UPDATES, + payload, +}); diff --git a/app/client/src/actions/pluginActionActions.ts b/app/client/src/actions/pluginActionActions.ts index a2b309d507..f953c45c00 100644 --- a/app/client/src/actions/pluginActionActions.ts +++ b/app/client/src/actions/pluginActionActions.ts @@ -6,7 +6,6 @@ import { ReduxActionErrorTypes, ReduxActionTypes, } from "ee/constants/ReduxActionConstants"; -import type { JSUpdate } from "utils/JSPaneUtils"; import type { Action, ActionViewMode, @@ -343,13 +342,6 @@ export const executePageLoadActions = ( }; }; -export const executeJSUpdates = ( - payload: Record, -): ReduxAction => ({ - type: ReduxActionTypes.EXECUTE_JS_UPDATES, - payload, -}); - export const setActionsToExecuteOnPageLoad = ( actions: Array<{ executeOnLoad: boolean; @@ -399,6 +391,7 @@ export interface updateActionDataPayloadType { actionDataPayload: actionDataPayload; parentSpan?: Span; } + export const updateActionData = ( payload: actionDataPayload, parentSpan?: Span, diff --git a/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx b/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx index a6825dabbd..a4d7a0165c 100644 --- a/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx +++ b/app/client/src/ce/PluginActionEditor/components/PluginActionResponse/hooks/usePluginActionResponseTabs.tsx @@ -3,7 +3,7 @@ import { usePluginActionContext } from "PluginActionEditor/PluginActionContext"; import type { BottomTab } from "components/editorComponents/EntityBottomTabs"; import { getIDEViewMode } from "selectors/ideSelectors"; import { useSelector } from "react-redux"; -import { EditorViewMode } from "ee/entities/IDE/constants"; +import { EditorViewMode, IDE_TYPE } from "ee/entities/IDE/constants"; import { DEBUGGER_TAB_KEYS } from "components/editorComponents/Debugger/constants"; import { createMessage, @@ -11,6 +11,7 @@ import { DEBUGGER_HEADERS, DEBUGGER_LOGS, DEBUGGER_RESPONSE, + DEBUGGER_STATE, } from "ee/constants/messages"; import ErrorLogs from "components/editorComponents/Debugger/Errors"; import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs"; @@ -32,6 +33,9 @@ import { } from "PluginActionEditor/hooks"; import useDebuggerTriggerClick from "components/editorComponents/Debugger/hooks/useDebuggerTriggerClick"; import { Response } from "PluginActionEditor/components/PluginActionResponse/components/Response"; +import { StateInspector } from "components/editorComponents/Debugger/StateInspector"; +import { useLocation } from "react-router"; +import { getIDETypeByUrl } from "ee/entities/IDE/utils"; function usePluginActionResponseTabs() { const { action, actionResponse, datasource, plugin } = @@ -108,7 +112,6 @@ function usePluginActionResponseTabs() { if ( [ PluginType.DB, - PluginType.AI, PluginType.REMOTE, PluginType.SAAS, PluginType.INTERNAL, @@ -145,6 +148,10 @@ function usePluginActionResponseTabs() { }); } + const location = useLocation(); + + const ideType = getIDETypeByUrl(location.pathname); + if (IDEViewMode === EditorViewMode.FullScreen) { tabs.push( { @@ -159,6 +166,14 @@ function usePluginActionResponseTabs() { panelComponent: , }, ); + + if (ideType === IDE_TYPE.App) { + tabs.push({ + key: DEBUGGER_TAB_KEYS.STATE_TAB, + title: createMessage(DEBUGGER_STATE), + panelComponent: , + }); + } } return tabs; diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index faf894223a..8c31dda880 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -697,6 +697,8 @@ const IDEDebuggerActionTypes = { SET_JS_PANE_DEBUGGER_STATE: "SET_JS_PANE_DEBUGGER_STATE", SET_CANVAS_DEBUGGER_STATE: "SET_CANVAS_DEBUGGER_STATE", SHOW_DEBUGGER_LOGS: "SHOW_DEBUGGER_LOGS", + SET_DEBUGGER_STATE_INSPECTOR_SELECTED_ITEM: + "SET_DEBUGGER_STATE_INSPECTOR_SELECTED_ITEM", }; const ThemeActionTypes = { diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index a209a97714..28c616798e 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -564,6 +564,7 @@ export const DEBUGGER_ERRORS = () => "Linter"; export const DEBUGGER_RESPONSE = () => "Response"; export const DEBUGGER_HEADERS = () => "Headers"; export const DEBUGGER_LOGS = () => "Logs"; +export const DEBUGGER_STATE = () => "State"; export const INSPECT_ENTITY = () => "Inspect entity"; export const INSPECT_ENTITY_BLANK_STATE = () => "Select an entity to inspect"; diff --git a/app/client/src/ce/entities/IDE/constants.ts b/app/client/src/ce/entities/IDE/constants.ts index c16c9b826f..e7fa7a230b 100644 --- a/app/client/src/ce/entities/IDE/constants.ts +++ b/app/client/src/ce/entities/IDE/constants.ts @@ -129,6 +129,8 @@ export interface EntityItem { userPermissions?: string[]; } +export interface GenericEntityItem extends Omit {} + export type UseRoutes = Array<{ key: string; // TODO: Fix this the next time the file is edited diff --git a/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/ListItem.tsx b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/ListItem.tsx index f99283114d..cf79cc6e97 100644 --- a/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/ListItem.tsx +++ b/app/client/src/ce/pages/Editor/IDE/EditorPane/JS/ListItem.tsx @@ -24,7 +24,6 @@ export const JSListItem = (props: JSListItemProps) => { parentEntityType={parentEntityType} searchKeyword={""} step={1} - type={item.type} /> ); diff --git a/app/client/src/ce/selectors/entitiesSelector.ts b/app/client/src/ce/selectors/entitiesSelector.ts index a020e93ad1..d744e14e77 100644 --- a/app/client/src/ce/selectors/entitiesSelector.ts +++ b/app/client/src/ce/selectors/entitiesSelector.ts @@ -23,7 +23,10 @@ import { 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"; +import { + MAIN_CONTAINER_WIDGET_ID, + MAIN_CONTAINER_WIDGET_NAME, +} from "constants/WidgetConstants"; import type { AppStoreState } from "reducers/entityReducers/appReducer"; import type { JSCollectionData, @@ -59,10 +62,15 @@ import { import { MAX_DATASOURCE_SUGGESTIONS } from "constants/DatasourceEditorConstants"; import type { CreateNewActionKeyInterface } from "ee/entities/Engine/actionHelpers"; import { getNextEntityName } from "utils/AppsmithUtils"; -import { EditorEntityTab, type EntityItem } from "ee/entities/IDE/constants"; +import { + EditorEntityTab, + type EntityItem, + type GenericEntityItem, +} from "ee/entities/IDE/constants"; import { ActionUrlIcon, JsFileIconV2, + WidgetIconByType, } from "pages/Editor/Explorer/ExplorerIcons"; import { getAssetUrl } from "ee/utils/airgapHelpers"; import { @@ -979,6 +987,18 @@ export const getAllPageWidgets = createSelector( }, ); +export const getUISegmentItems = createSelector(getCanvasWidgets, (widgets) => { + const items: GenericEntityItem[] = Object.values(widgets) + .filter((widget) => widget.widgetName !== MAIN_CONTAINER_WIDGET_NAME) + .map((widget) => ({ + icon: WidgetIconByType(widget.type), + title: widget.widgetName, + key: widget.widgetId, + })); + + return items; +}); + export const getPageList = createSelector( (state: AppState) => state.entities.pageList.pages, (pages) => pages, diff --git a/app/client/src/components/editorComponents/Debugger/DebuggerTabs.tsx b/app/client/src/components/editorComponents/Debugger/DebuggerTabs.tsx index e44b7f1005..514423108f 100644 --- a/app/client/src/components/editorComponents/Debugger/DebuggerTabs.tsx +++ b/app/client/src/components/editorComponents/Debugger/DebuggerTabs.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useMemo } from "react"; import DebuggerLogs from "./DebuggerLogs"; import { useDispatch, useSelector } from "react-redux"; import { @@ -17,11 +17,16 @@ import { createMessage, DEBUGGER_ERRORS, DEBUGGER_LOGS, + DEBUGGER_STATE, } from "ee/constants/messages"; import { DEBUGGER_TAB_KEYS } from "./constants"; import EntityBottomTabs from "../EntityBottomTabs"; import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants"; import { IDEBottomView, ViewHideBehaviour, ViewDisplayMode } from "IDE"; +import { StateInspector } from "./StateInspector"; +import { getIDETypeByUrl } from "ee/entities/IDE/utils"; +import { useLocation } from "react-router"; +import { IDE_TYPE } from "ee/entities/IDE/constants"; function DebuggerTabs() { const dispatch = useDispatch(); @@ -31,33 +36,59 @@ function DebuggerTabs() { // get the height of the response pane. const responsePaneHeight = useSelector(getResponsePaneHeight); // set the height of the response pane. - const updateResponsePaneHeight = useCallback((height: number) => { - dispatch(setResponsePaneHeight(height)); - }, []); - const setSelectedTab = (tabKey: string) => { - if (tabKey === DEBUGGER_TAB_KEYS.ERROR_TAB) { - AnalyticsUtil.logEvent("OPEN_DEBUGGER", { - source: "WIDGET_EDITOR", + const updateResponsePaneHeight = useCallback( + (height: number) => { + dispatch(setResponsePaneHeight(height)); + }, + [dispatch], + ); + + const setSelectedTab = useCallback( + (tabKey: string) => { + if (tabKey === DEBUGGER_TAB_KEYS.ERROR_TAB) { + AnalyticsUtil.logEvent("OPEN_DEBUGGER", { + source: "WIDGET_EDITOR", + }); + } + + dispatch(setDebuggerSelectedTab(tabKey)); + }, + [dispatch], + ); + + const onClose = useCallback(() => { + dispatch(showDebugger(false)); + }, [dispatch]); + + const location = useLocation(); + + const ideType = getIDETypeByUrl(location.pathname); + + const DEBUGGER_TABS = useMemo(() => { + const tabs = [ + { + key: DEBUGGER_TAB_KEYS.LOGS_TAB, + title: createMessage(DEBUGGER_LOGS), + panelComponent: , + }, + { + key: DEBUGGER_TAB_KEYS.ERROR_TAB, + title: createMessage(DEBUGGER_ERRORS), + count: errorCount, + panelComponent: , + }, + ]; + + if (ideType === IDE_TYPE.App) { + tabs.push({ + key: DEBUGGER_TAB_KEYS.STATE_TAB, + title: createMessage(DEBUGGER_STATE), + panelComponent: , }); } - dispatch(setDebuggerSelectedTab(tabKey)); - }; - const onClose = () => dispatch(showDebugger(false)); - - const DEBUGGER_TABS = [ - { - key: DEBUGGER_TAB_KEYS.LOGS_TAB, - title: createMessage(DEBUGGER_LOGS), - panelComponent: , - }, - { - key: DEBUGGER_TAB_KEYS.ERROR_TAB, - title: createMessage(DEBUGGER_ERRORS), - count: errorCount, - panelComponent: , - }, - ]; + return tabs; + }, [errorCount, ideType]); // Do not render if response, header or schema tab is selected in the bottom bar. const shouldRender = !( diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/StateInspector.test.tsx b/app/client/src/components/editorComponents/Debugger/StateInspector/StateInspector.test.tsx new file mode 100644 index 0000000000..bdf75dabde --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/StateInspector.test.tsx @@ -0,0 +1,143 @@ +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import { StateInspector } from "./StateInspector"; +import { useStateInspectorItems } from "./hooks"; +import { filterEntityGroupsBySearchTerm } from "IDE/utils"; + +jest.mock("./hooks"); +jest.mock("IDE/utils"); + +const mockedUseStateInspectorItems = useStateInspectorItems as jest.Mock; +const mockedFilterEntityGroupsBySearchTerm = + filterEntityGroupsBySearchTerm as jest.Mock; + +describe("StateInspector", () => { + beforeEach(() => { + mockedFilterEntityGroupsBySearchTerm.mockImplementation( + (searchTerm, items) => + items.filter((item: { group: string }) => + item.group.toLowerCase().includes(searchTerm.toLowerCase()), + ), + ); + }); + + it("renders search input and filters items based on search term", () => { + mockedUseStateInspectorItems.mockReturnValue([ + { title: "Item 1", icon: "icon1", code: { key: "value1" } }, + [ + { group: "Group 1", items: [{ title: "Item 1" }] }, + { group: "Group 2", items: [{ title: "Item 2" }] }, + ], + { key: "value1" }, + ]); + render(); + const searchInput = screen.getByPlaceholderText("Search entities"); + + fireEvent.change(searchInput, { target: { value: "Group 1" } }); + expect(screen.getByText("Group 1")).toBeInTheDocument(); + expect(screen.queryByText("Group 2")).not.toBeInTheDocument(); + }); + + it("Calls the onClick of the item", () => { + const mockOnClick = jest.fn(); + + mockedUseStateInspectorItems.mockReturnValue([ + { title: "Item 1", icon: "icon1", code: { key: "value1" } }, + [ + { group: "Group 1", items: [{ title: "Item 1" }] }, + { + group: "Group 2", + items: [{ title: "Item 2", onClick: mockOnClick }], + }, + ], + { key: "value1" }, + ]); + render(); + fireEvent.click(screen.getByText("Item 2")); + + expect(mockOnClick).toHaveBeenCalled(); + }); + + it("Renders the selected item details", () => { + mockedUseStateInspectorItems.mockReturnValue([ + { title: "Item 1", icon: "icon1", code: { key: "value1" } }, + [ + { group: "Group 1", items: [{ title: "Item 1" }] }, + { + group: "Group 2", + items: [{ title: "Item 2" }], + }, + ], + { key: "Value1" }, + ]); + render(); + + expect( + screen.getByTestId("t--selected-entity-details").textContent, + ).toContain("Item 1"); + + expect( + screen.getByTestId("t--selected-entity-details").textContent, + ).toContain("Value1"); + }); + + it("does not render selected item details when no item is selected", () => { + mockedUseStateInspectorItems.mockReturnValue([null, [], null]); + render(); + expect(screen.queryByText("Item 1")).not.toBeInTheDocument(); + }); + + it("renders all items when search term is empty", () => { + mockedUseStateInspectorItems.mockReturnValue([ + { title: "Item 1", icon: "icon1", code: { key: "value1" } }, + [ + { group: "Group 1", items: [{ title: "Item 1" }] }, + { + group: "Group 2", + items: [{ title: "Item 2" }], + }, + ], + { key: "value1" }, + ]); + + render(); + expect(screen.getByText("Group 1")).toBeInTheDocument(); + expect(screen.getByText("Group 2")).toBeInTheDocument(); + }); + it("renders no items when search term does not match any group", () => { + render(); + const searchInput = screen.getByPlaceholderText("Search entities"); + + fireEvent.change(searchInput, { target: { value: "Nonexistent Group" } }); + expect(screen.queryByText("Group 1")).not.toBeInTheDocument(); + expect(screen.queryByText("Group 2")).not.toBeInTheDocument(); + }); + + it("renders no items when items list is empty", () => { + mockedUseStateInspectorItems.mockReturnValue([null, [], null]); + render(); + expect(screen.queryByText("Group 1")).not.toBeInTheDocument(); + expect(screen.queryByText("Group 2")).not.toBeInTheDocument(); + }); + + it("renders correctly when selected item has no code", () => { + mockedUseStateInspectorItems.mockReturnValue([ + { title: "Item 1", icon: "icon1", code: null }, + [ + { group: "Group 1", items: [{ title: "Item 1" }] }, + { group: "Group 2", items: [{ title: "Item 2" }] }, + ], + {}, + ]); + render(); + + expect( + screen.getByTestId("t--selected-entity-details").textContent, + ).toContain("Item 1"); + + expect( + screen.getByTestId("t--selected-entity-details").textContent, + ).toContain("0 items"); + }); +}); diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/StateInspector.tsx b/app/client/src/components/editorComponents/Debugger/StateInspector/StateInspector.tsx new file mode 100644 index 0000000000..9586104e53 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/StateInspector.tsx @@ -0,0 +1,101 @@ +import React, { useState } from "react"; +import ReactJson from "react-json-view"; +import { + Flex, + List, + type ListItemProps, + SearchInput, + Text, +} from "@appsmith/ads"; +import { filterEntityGroupsBySearchTerm } from "IDE/utils"; +import { useStateInspectorItems } from "./hooks"; +import * as Styled from "./styles"; + +export const reactJsonProps = { + name: null, + enableClipboard: false, + displayDataTypes: false, + displayArrayKey: true, + quotesOnKeys: false, + style: { + fontSize: "12px", + }, + collapsed: 1, + indentWidth: 2, + collapseStringsAfterLength: 30, +}; + +export const StateInspector = () => { + const [selectedItem, items, selectedItemCode] = useStateInspectorItems(); + const [searchTerm, setSearchTerm] = useState(""); + + const filteredItemGroups = filterEntityGroupsBySearchTerm< + { group: string }, + ListItemProps + >(searchTerm, items); + + return ( + + + + + + + {filteredItemGroups.map((item) => ( + + + {item.group} + + + + ))} + + + {selectedItem ? ( + + + {selectedItem.icon} + {selectedItem.title} + + + + + + ) : null} + + ); +}; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/index.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/index.ts new file mode 100644 index 0000000000..b17fab687e --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/index.ts @@ -0,0 +1 @@ +export { useStateInspectorItems } from "./useStateInspectorItems"; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetGlobalItemsForStateInspector.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetGlobalItemsForStateInspector.ts new file mode 100644 index 0000000000..24319407e6 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetGlobalItemsForStateInspector.ts @@ -0,0 +1,20 @@ +import { GlobeIcon } from "pages/Editor/Explorer/ExplorerIcons"; +import type { GetGroupHookType } from "../types"; + +export const useGetGlobalItemsForStateInspector: GetGroupHookType = () => { + const appsmithObj = { + key: "appsmith", + title: "appsmith", + icon: GlobeIcon(), + }; + + const appsmithItems = [ + { + id: appsmithObj.key, + title: appsmithObj.title, + startIcon: appsmithObj.icon, + }, + ]; + + return { group: "Globals", items: appsmithItems }; +}; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetJSItemsForStateInspector.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetJSItemsForStateInspector.ts new file mode 100644 index 0000000000..0d273ee76a --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetJSItemsForStateInspector.ts @@ -0,0 +1,15 @@ +import { useSelector } from "react-redux"; +import { getJSSegmentItems } from "ee/selectors/entitiesSelector"; +import type { GetGroupHookType } from "../types"; + +export const useGetJSItemsForStateInspector: GetGroupHookType = () => { + const jsObjects = useSelector(getJSSegmentItems); + + const jsItems = jsObjects.map((jsObject) => ({ + id: jsObject.key, + title: jsObject.title, + startIcon: jsObject.icon, + })); + + return { group: "JS objects", items: jsItems }; +}; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetQueryItemsForStateInspector.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetQueryItemsForStateInspector.ts new file mode 100644 index 0000000000..c271217609 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetQueryItemsForStateInspector.ts @@ -0,0 +1,16 @@ +import { useSelector } from "react-redux"; +import { getQuerySegmentItems } from "ee/selectors/entitiesSelector"; +import type { GetGroupHookType } from "../types"; + +export const useGetQueryItemsForStateInspector: GetGroupHookType = () => { + const queries = useSelector(getQuerySegmentItems); + + const queryItems = queries.map((query) => ({ + id: query.key, + title: query.title, + startIcon: query.icon, + className: "query-item", + })); + + return { group: "Queries", items: queryItems }; +}; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetUIItemsForStateInspector.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetUIItemsForStateInspector.ts new file mode 100644 index 0000000000..a89c2a111f --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useGetUIItemsForStateInspector.ts @@ -0,0 +1,15 @@ +import { useSelector } from "react-redux"; +import { getUISegmentItems } from "ee/selectors/entitiesSelector"; +import type { GetGroupHookType } from "../types"; + +export const useGetUIItemsForStateInspector: GetGroupHookType = () => { + const widgets = useSelector(getUISegmentItems); + + const widgetItems = widgets.map((widget) => ({ + id: widget.key, + title: widget.title, + startIcon: widget.icon, + })); + + return { group: "UI elements", items: widgetItems }; +}; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useStateInspectorItems.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useStateInspectorItems.ts new file mode 100644 index 0000000000..9890980820 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useStateInspectorItems.ts @@ -0,0 +1,100 @@ +import { useEffect, useMemo } from "react"; +import { useStateInspectorState } from "./useStateInspectorState"; +import { useGetGlobalItemsForStateInspector } from "./useGetGlobalItemsForStateInspector"; +import { useGetQueryItemsForStateInspector } from "./useGetQueryItemsForStateInspector"; +import { useGetJSItemsForStateInspector } from "./useGetJSItemsForStateInspector"; +import { useGetUIItemsForStateInspector } from "./useGetUIItemsForStateInspector"; +import type { GroupedItems } from "../types"; +import { enhanceItemForListItem } from "../utils"; +import type { GenericEntityItem } from "ee/entities/IDE/constants"; +import { filterInternalProperties } from "utils/FilterInternalProperties"; +import { getConfigTree, getDataTree } from "selectors/dataTreeSelectors"; +import { getJSCollections } from "ee/selectors/entitiesSelector"; +import { useSelector } from "react-redux"; + +export const useStateInspectorItems: () => [ + GenericEntityItem | undefined, + GroupedItems[], + unknown, +] = () => { + const [selectedItem, setSelectedItem] = useStateInspectorState(); + + const queries = useGetQueryItemsForStateInspector(); + const jsItems = useGetJSItemsForStateInspector(); + const uiItems = useGetUIItemsForStateInspector(); + const globalItems = useGetGlobalItemsForStateInspector(); + + const groups = useMemo(() => { + const returnValue: GroupedItems[] = []; + + if (queries.items.length) { + returnValue.push({ + ...queries, + items: queries.items.map((query) => + enhanceItemForListItem(query, selectedItem, setSelectedItem), + ), + }); + } + + if (jsItems.items.length) { + returnValue.push({ + ...jsItems, + items: jsItems.items.map((jsItem) => + enhanceItemForListItem(jsItem, selectedItem, setSelectedItem), + ), + }); + } + + if (uiItems.items.length) { + returnValue.push({ + ...uiItems, + items: uiItems.items.map((uiItem) => + enhanceItemForListItem(uiItem, selectedItem, setSelectedItem), + ), + }); + } + + if (globalItems.items.length) { + returnValue.push({ + ...globalItems, + items: globalItems.items.map((globalItem) => + enhanceItemForListItem(globalItem, selectedItem, setSelectedItem), + ), + }); + } + + return returnValue; + }, [globalItems, jsItems, queries, selectedItem, setSelectedItem, uiItems]); + + const dataTree = useSelector(getDataTree); + const configTree = useSelector(getConfigTree); + const jsActions = useSelector(getJSCollections); + let filteredData: unknown = ""; + + if (selectedItem && selectedItem.title in dataTree) { + filteredData = filterInternalProperties( + selectedItem.title, + dataTree[selectedItem.title], + jsActions, + dataTree, + configTree, + ); + } + + useEffect( + function handleNoItemSelected() { + if (!selectedItem || !(selectedItem.title in dataTree)) { + const firstItem = groups[0].items[0]; + + setSelectedItem({ + key: firstItem.id as string, + icon: firstItem.startIcon, + title: firstItem.title, + }); + } + }, + [dataTree, groups, selectedItem, setSelectedItem], + ); + + return [selectedItem, groups, filteredData]; +}; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useStateInspectorState.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useStateInspectorState.ts new file mode 100644 index 0000000000..c39d6dd741 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/hooks/useStateInspectorState.ts @@ -0,0 +1,19 @@ +import { useDispatch, useSelector } from "react-redux"; +import type { GenericEntityItem } from "ee/entities/IDE/constants"; +import { setDebuggerStateInspectorSelectedItem } from "actions/debuggerActions"; +import { getDebuggerStateInspectorSelectedItem } from "selectors/debuggerSelectors"; + +export const useStateInspectorState: () => [ + GenericEntityItem | undefined, + (item: GenericEntityItem) => void, +] = () => { + const dispatch = useDispatch(); + + const setSelectedItem = (item: GenericEntityItem) => { + dispatch(setDebuggerStateInspectorSelectedItem(item)); + }; + + const selectedItem = useSelector(getDebuggerStateInspectorSelectedItem); + + return [selectedItem, setSelectedItem]; +}; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/index.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/index.ts new file mode 100644 index 0000000000..e912e429d8 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/index.ts @@ -0,0 +1 @@ +export { StateInspector } from "./StateInspector"; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/styles.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/styles.ts new file mode 100644 index 0000000000..d7f9c0fdb1 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/styles.ts @@ -0,0 +1,23 @@ +import styled, { css } from "styled-components"; +import { Flex, Text } from "@appsmith/ads"; + +const imgSizer = css` + img { + height: 16px; + width: 16px; + } +`; + +export const Group = styled(Flex)` + .query-item { + ${imgSizer} + } +`; + +export const GroupName = styled(Text)` + padding: var(--ads-v2-spaces-1) var(--ads-v2-spaces-3); +`; + +export const SelectedItem = styled(Flex)` + ${imgSizer} +`; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/types.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/types.ts new file mode 100644 index 0000000000..fe8e908dbb --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/types.ts @@ -0,0 +1,15 @@ +import type { ListItemProps } from "@appsmith/ads"; + +export interface GroupedItems { + group: string; + items: ListItemProps[]; +} + +export interface ListItemWithoutOnClick extends Omit { + id: string; +} + +export type GetGroupHookType = () => { + group: string; + items: ListItemWithoutOnClick[]; +}; diff --git a/app/client/src/components/editorComponents/Debugger/StateInspector/utils.ts b/app/client/src/components/editorComponents/Debugger/StateInspector/utils.ts new file mode 100644 index 0000000000..3812b5d055 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/StateInspector/utils.ts @@ -0,0 +1,21 @@ +import type { ListItemWithoutOnClick } from "./types"; +import type { ListItemProps } from "@appsmith/ads"; +import type { GenericEntityItem } from "ee/entities/IDE/constants"; + +export const enhanceItemForListItem = ( + item: ListItemWithoutOnClick, + selectedItem: GenericEntityItem | undefined, + setSelectedItem: (item: GenericEntityItem) => void, +): ListItemProps => { + return { + ...item, + isSelected: selectedItem ? selectedItem.key === item.id : false, + onClick: () => + setSelectedItem({ + key: item.id, + title: item.title, + icon: item.startIcon, + }), + size: "md", + }; +}; diff --git a/app/client/src/components/editorComponents/Debugger/constants.ts b/app/client/src/components/editorComponents/Debugger/constants.ts index 86dda1d560..44f4cf57fe 100644 --- a/app/client/src/components/editorComponents/Debugger/constants.ts +++ b/app/client/src/components/editorComponents/Debugger/constants.ts @@ -5,4 +5,5 @@ export enum DEBUGGER_TAB_KEYS { HEADER_TAB = "HEADERS_TAB", ERROR_TAB = "ERROR_TAB", LOGS_TAB = "LOGS_TAB", + STATE_TAB = "STATE_TAB", } diff --git a/app/client/src/components/editorComponents/JSResponseView.tsx b/app/client/src/components/editorComponents/JSResponseView.tsx index 8266e70b62..185d45408f 100644 --- a/app/client/src/components/editorComponents/JSResponseView.tsx +++ b/app/client/src/components/editorComponents/JSResponseView.tsx @@ -1,15 +1,12 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { connect, useDispatch, useSelector } from "react-redux"; -import type { RouteComponentProps } from "react-router"; -import { withRouter } from "react-router"; +import { useDispatch, useSelector } from "react-redux"; import styled from "styled-components"; -import type { AppState } from "ee/reducers"; -import type { JSEditorRouteParams } from "constants/routes"; import { createMessage, DEBUGGER_ERRORS, DEBUGGER_LOGS, DEBUGGER_RESPONSE, + DEBUGGER_STATE, EXECUTING_FUNCTION, NO_JS_FUNCTION_RETURN_VALUE, UPDATING_JS_COLLECTION, @@ -35,11 +32,15 @@ import { import { getJsPaneDebuggerState } from "selectors/jsPaneSelectors"; import { setJsPaneDebuggerState } from "actions/jsPaneActions"; import { getIDEViewMode } from "selectors/ideSelectors"; -import { EditorViewMode } from "ee/entities/IDE/constants"; +import { EditorViewMode, IDE_TYPE } from "ee/entities/IDE/constants"; import ErrorLogs from "./Debugger/Errors"; import { isBrowserExecutionAllowed } from "ee/utils/actionExecutionUtils"; import JSRemoteExecutionView from "ee/components/JSRemoteExecutionView"; import { IDEBottomView, ViewHideBehaviour } from "IDE"; +import { StateInspector } from "./Debugger/StateInspector"; +import { getErrorCount } from "selectors/debuggerSelectors"; +import { getIDETypeByUrl } from "ee/entities/IDE/utils"; +import { useLocation } from "react-router"; const ResponseTabWrapper = styled.div` display: flex; @@ -61,43 +62,54 @@ const NoReturnValueWrapper = styled.div` padding-top: ${(props) => props.theme.spaces[6]}px; `; -interface ReduxStateProps { - errorCount: number; +interface Props { + currentFunction: JSAction | null; + theme?: EditorTheme; + errors: Array; + disabled: boolean; + isLoading: boolean; + onButtonClick: (e: React.MouseEvent) => void; + jsCollectionData: JSCollectionData | undefined; + debuggerLogsDefaultName?: string; } -type Props = ReduxStateProps & - RouteComponentProps & { - currentFunction: JSAction | null; - theme?: EditorTheme; - errors: Array; - disabled: boolean; - isLoading: boolean; - onButtonClick: (e: React.MouseEvent) => void; - jsCollectionData: JSCollectionData | undefined; - debuggerLogsDefaultName?: string; - }; - function JSResponseView(props: Props) { const { currentFunction, disabled, - errorCount, errors, isLoading, jsCollectionData, onButtonClick, + theme, } = props; const [responseStatus, setResponseStatus] = useState( JSResponseState.NoResponse, ); - const responses = (jsCollectionData && jsCollectionData.data) || {}; - const isDirty = (jsCollectionData && jsCollectionData.isDirty) || {}; - const isExecuting = (jsCollectionData && jsCollectionData.isExecuting) || {}; + const errorCount = useSelector(getErrorCount); + + const { isDirty, isExecuting, responses } = useMemo(() => { + return { + responses: (jsCollectionData && jsCollectionData.data) || {}, + isDirty: (jsCollectionData && jsCollectionData.isDirty) || {}, + isExecuting: (jsCollectionData && jsCollectionData.isExecuting) || {}, + }; + }, [jsCollectionData]); + const dispatch = useDispatch(); - const response = - currentFunction && currentFunction.id && currentFunction.id in responses - ? responses[currentFunction.id] - : ""; + + const response = useMemo(() => { + if ( + !currentFunction || + !currentFunction.id || + !(currentFunction.id in responses) + ) { + return { value: "" }; + } + + return { value: responses[currentFunction.id] as string }; + }, [currentFunction, responses]); + // parse error found while trying to execute function const hasExecutionParseErrors = responseStatus === JSResponseState.IsDirty; // error found while trying to parse JS Object @@ -122,94 +134,119 @@ function JSResponseView(props: Props) { ); }, [jsCollectionData?.config, currentFunction]); + const JSResponseTab = useMemo(() => { + return ( + <> + {localExecutionAllowed && hasExecutionParseErrors && ( + + +
+ Function failed to execute. Check logs for more information. +
+
+
+ )} + + + <> + {localExecutionAllowed && ( + <> + {responseStatus === JSResponseState.NoResponse && ( + + )} + {responseStatus === JSResponseState.IsExecuting && ( + + {createMessage(EXECUTING_FUNCTION)} + + )} + {responseStatus === JSResponseState.NoReturnValue && ( + + + {createMessage( + NO_JS_FUNCTION_RETURN_VALUE, + currentFunction?.name, + )} + + + )} + {responseStatus === JSResponseState.ShowResponse && ( + + )} + + )} + {!localExecutionAllowed && ( + + )} + {responseStatus === JSResponseState.IsUpdating && ( + + {createMessage(UPDATING_JS_COLLECTION)} + + )} + + + + + ); + }, [ + currentFunction?.name, + disabled, + errors.length, + hasExecutionParseErrors, + isLoading, + jsCollectionData, + localExecutionAllowed, + onButtonClick, + theme, + response, + responseStatus, + ]); + const ideViewMode = useSelector(getIDEViewMode); + const location = useLocation(); - const tabs: BottomTab[] = [ - { - key: DEBUGGER_TAB_KEYS.RESPONSE_TAB, - title: createMessage(DEBUGGER_RESPONSE), - panelComponent: ( - <> - {localExecutionAllowed && hasExecutionParseErrors && ( - - -
- Function failed to execute. Check logs for more information. -
-
-
- )} - - - <> - {localExecutionAllowed && ( - <> - {responseStatus === JSResponseState.NoResponse && ( - - )} - {responseStatus === JSResponseState.IsExecuting && ( - - {createMessage(EXECUTING_FUNCTION)} - - )} - {responseStatus === JSResponseState.NoReturnValue && ( - - - {createMessage( - NO_JS_FUNCTION_RETURN_VALUE, - currentFunction?.name, - )} - - - )} - {responseStatus === JSResponseState.ShowResponse && ( - - )} - - )} - {!localExecutionAllowed && ( - - )} - {responseStatus === JSResponseState.IsUpdating && ( - - {createMessage(UPDATING_JS_COLLECTION)} - - )} - - - - - ), - }, - { - key: DEBUGGER_TAB_KEYS.LOGS_TAB, - title: createMessage(DEBUGGER_LOGS), - panelComponent: , - }, - ]; + const ideType = getIDETypeByUrl(location.pathname); - if (ideViewMode === EditorViewMode.FullScreen) { - tabs.push({ - key: DEBUGGER_TAB_KEYS.ERROR_TAB, - title: createMessage(DEBUGGER_ERRORS), - count: errorCount, - panelComponent: , - }); - } + const tabs = useMemo(() => { + const jsTabs: BottomTab[] = [ + { + key: DEBUGGER_TAB_KEYS.RESPONSE_TAB, + title: createMessage(DEBUGGER_RESPONSE), + panelComponent: JSResponseTab, + }, + { + key: DEBUGGER_TAB_KEYS.LOGS_TAB, + title: createMessage(DEBUGGER_LOGS), + panelComponent: , + }, + ]; + + if (ideViewMode === EditorViewMode.FullScreen) { + jsTabs.push({ + key: DEBUGGER_TAB_KEYS.ERROR_TAB, + title: createMessage(DEBUGGER_ERRORS), + count: errorCount, + panelComponent: , + }); + + if (ideType === IDE_TYPE.App) { + jsTabs.push({ + key: DEBUGGER_TAB_KEYS.STATE_TAB, + title: createMessage(DEBUGGER_STATE), + panelComponent: , + }); + } + } + + return jsTabs; + }, [JSResponseTab, errorCount, ideType, ideViewMode]); // get the selected tab from the store. const { open, responseTabHeight, selectedTab } = useSelector( @@ -217,18 +254,24 @@ function JSResponseView(props: Props) { ); // set the selected tab in the store. - const setSelectedResponseTab = useCallback((selectedTab: string) => { - dispatch(setJsPaneDebuggerState({ open: true, selectedTab })); - }, []); + const setSelectedResponseTab = useCallback( + (selectedTab: string) => { + dispatch(setJsPaneDebuggerState({ open: true, selectedTab })); + }, + [dispatch], + ); // set the height of the response pane on resize. - const setResponseHeight = useCallback((height: number) => { - dispatch(setJsPaneDebuggerState({ responseTabHeight: height })); - }, []); + const setResponseHeight = useCallback( + (height: number) => { + dispatch(setJsPaneDebuggerState({ responseTabHeight: height })); + }, + [dispatch], + ); // close the debugger const onToggle = useCallback( () => dispatch(setJsPaneDebuggerState({ open: !open })), - [open], + [dispatch, open], ); // Do not render if header tab is selected in the bottom bar. @@ -251,12 +294,4 @@ function JSResponseView(props: Props) { ); } -const mapStateToProps = (state: AppState) => { - const errorCount = state.ui.debugger.context.errorCount; - - return { - errorCount, - }; -}; - -export default connect(mapStateToProps)(withRouter(JSResponseView)); +export default JSResponseView; diff --git a/app/client/src/pages/Editor/Explorer/ExplorerIcons.tsx b/app/client/src/pages/Editor/Explorer/ExplorerIcons.tsx index 589a2667fc..d4e02619c8 100644 --- a/app/client/src/pages/Editor/Explorer/ExplorerIcons.tsx +++ b/app/client/src/pages/Editor/Explorer/ExplorerIcons.tsx @@ -12,6 +12,9 @@ import { PRIMARY_KEY, FOREIGN_KEY } from "constants/DatasourceEditorConstants"; import { Icon } from "@appsmith/ads"; import { getAssetUrl } from "ee/utils/airgapHelpers"; import { importSvg } from "@appsmith/ads-old"; +import WidgetFactory from "WidgetProvider/factory"; +import WidgetTypeIcon from "pages/Editor/Explorer/Widgets/WidgetIcon"; +import type { WidgetType } from "constants/WidgetConstants"; const ApiIcon = importSvg( async () => import("assets/icons/menu/api-colored.svg"), @@ -225,6 +228,7 @@ const EntityIconWrapper = styled.div<{ justify-content: center; text-align: center; border-radius: var(--ads-v2-border-radius); + svg, img { height: 100% !important; @@ -357,3 +361,13 @@ export function DefaultModuleIcon() { ); } + +export function WidgetIconByType(widgetType: WidgetType) { + const { IconCmp } = WidgetFactory.getWidgetMethods(widgetType); + + return IconCmp ? : ; +} + +export function GlobeIcon() { + return ; +} diff --git a/app/client/src/pages/Editor/Explorer/Files/index.tsx b/app/client/src/pages/Editor/Explorer/Files/index.tsx index 171dee0330..9f4aa303c0 100644 --- a/app/client/src/pages/Editor/Explorer/Files/index.tsx +++ b/app/client/src/pages/Editor/Explorer/Files/index.tsx @@ -125,7 +125,6 @@ function Files() { parentEntityType={parentEntityType} searchKeyword={""} step={2} - type={type} /> ); } else { diff --git a/app/client/src/pages/Editor/Explorer/JSActions/JSActionEntity.tsx b/app/client/src/pages/Editor/Explorer/JSActions/JSActionEntity.tsx index e1c1c20e2b..117c9b71b2 100644 --- a/app/client/src/pages/Editor/Explorer/JSActions/JSActionEntity.tsx +++ b/app/client/src/pages/Editor/Explorer/JSActions/JSActionEntity.tsx @@ -7,7 +7,6 @@ import { getJsCollectionByBaseId } from "ee/selectors/entitiesSelector"; import type { AppState } from "ee/reducers"; import type { JSCollection } from "entities/JSCollection"; import { JsFileIconV2 } from "../ExplorerIcons"; -import type { PluginType } from "entities/Action"; import { jsCollectionIdURL } from "ee/RouteBuilder"; import AnalyticsUtil from "ee/utils/AnalyticsUtil"; import { useLocation } from "react-router"; @@ -26,7 +25,6 @@ interface ExplorerJSCollectionEntityProps { searchKeyword?: string; baseCollectionId: string; isActive: boolean; - type: PluginType; parentEntityId: string; parentEntityType: ActionParentEntityTypeInterface; } diff --git a/app/client/src/reducers/uiReducers/debuggerReducer.ts b/app/client/src/reducers/uiReducers/debuggerReducer.ts index 8523a5ddb2..e86f1b6d61 100644 --- a/app/client/src/reducers/uiReducers/debuggerReducer.ts +++ b/app/client/src/reducers/uiReducers/debuggerReducer.ts @@ -6,6 +6,7 @@ import { omit, isUndefined, isEmpty } from "lodash"; import equal from "fast-deep-equal"; import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants"; import { klona } from "klona"; +import type { GenericEntityItem } from "ee/entities/IDE/constants"; export const DefaultDebuggerContext = { scrollPosition: 0, @@ -22,6 +23,7 @@ const initialState: DebuggerReduxState = { expandId: "", hideErrors: true, context: DefaultDebuggerContext, + stateInspector: {}, }; // check the last message from the current log and update the occurrence count @@ -185,6 +187,17 @@ const debuggerReducer = createImmerReducer(initialState, { }, }; }, + [ReduxActionTypes.SET_DEBUGGER_STATE_INSPECTOR_SELECTED_ITEM]: ( + state: DebuggerReduxState, + action: ReduxAction, + ): DebuggerReduxState => { + return { + ...state, + stateInspector: { + selectedItem: action.payload, + }, + }; + }, // Resetting debugger state after env switch [ReduxActionTypes.SWITCH_ENVIRONMENT_SUCCESS]: () => { return klona(initialState); @@ -198,6 +211,9 @@ export interface DebuggerReduxState { expandId: string; hideErrors: boolean; context: DebuggerContext; + stateInspector: { + selectedItem?: GenericEntityItem; + }; } export interface DebuggerContext { diff --git a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts index 49affe4244..af55b7ae0f 100644 --- a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts @@ -5,7 +5,6 @@ import { put, select, take, - takeEvery, takeLatest, } from "redux-saga/effects"; import * as Sentry from "@sentry/react"; @@ -19,10 +18,7 @@ import { updateAction, updateActionData, } from "actions/pluginActionActions"; -import { - handleExecuteJSFunctionSaga, - makeUpdateJSCollection, -} from "sagas/JSPaneSagas"; +import { handleExecuteJSFunctionSaga } from "sagas/JSPaneSagas"; import type { ApplicationPayload } from "entities/Application"; import type { ReduxAction } from "ee/constants/ReduxActionConstants"; @@ -1665,6 +1661,5 @@ export function* watchPluginActionExecutionSagas() { executePageLoadActionsSaga, ), takeLatest(ReduxActionTypes.PLUGIN_SOFT_REFRESH, softRefreshActionsSaga), - takeEvery(ReduxActionTypes.EXECUTE_JS_UPDATES, makeUpdateJSCollection), ]); } diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 17f8b40246..8c3c497ed5 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -95,10 +95,10 @@ import type { ActionDescription } from "ee/workers/Evaluation/fns"; import { handleEvalWorkerRequestSaga } from "./EvalWorkerActionSagas"; import { getAppsmithConfigs } from "ee/configs"; import { - executeJSUpdates, type actionDataPayload, type updateActionDataPayloadType, } from "actions/pluginActionActions"; +import { executeJSUpdates } from "actions/jsPaneActions"; import { setEvaluatedActionSelectorField } from "actions/actionSelectorActions"; import { waitForWidgetConfigBuild } from "./InitSagas"; import { logDynamicTriggerExecution } from "ee/sagas/analyticsSaga"; @@ -545,6 +545,7 @@ interface BUFFERED_ACTION { hasBufferedAction: boolean; actionDataPayloadConsolidated: actionDataPayload[]; } + export function evalQueueBuffer() { let canTake = false; let hasDebouncedHandleUpdate = false; diff --git a/app/client/src/sagas/JSPaneSagas.ts b/app/client/src/sagas/JSPaneSagas.ts index 6f4f1f7a22..f9f96f59c0 100644 --- a/app/client/src/sagas/JSPaneSagas.ts +++ b/app/client/src/sagas/JSPaneSagas.ts @@ -928,5 +928,6 @@ export default function* root() { ReduxActionTypes.CREATE_NEW_JS_FROM_ACTION_CREATOR, handleCreateNewJSFromActionCreator, ), + takeEvery(ReduxActionTypes.EXECUTE_JS_UPDATES, makeUpdateJSCollection), ]); } diff --git a/app/client/src/selectors/debuggerSelectors.tsx b/app/client/src/selectors/debuggerSelectors.tsx index 89c2c46df4..a827b65b3a 100644 --- a/app/client/src/selectors/debuggerSelectors.tsx +++ b/app/client/src/selectors/debuggerSelectors.tsx @@ -184,3 +184,6 @@ export const getCanvasDebuggerState = createSelector( }; }, ); + +export const getDebuggerStateInspectorSelectedItem = (state: AppState) => + state.ui.debugger.stateInspector.selectedItem;