Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import React, { useState } from "react";
import styled from "styled-components";

import { Text } from "@appsmith/ads";

import type { ActionExecutionHistoryEntry } from "PluginActionEditor/store";

const Container = styled.div`
display: grid;
grid-template-columns: minmax(240px, 320px) 1fr;
height: 100%;
background: var(--ads-v2-color-bg);
`;

const HistoryList = styled.div`
border-right: 1px solid var(--ads-v2-color-border);
overflow: auto;
`;

const HistoryItem = styled.button<{ $isSelected: boolean }>`
width: 100%;
display: grid;
grid-template-columns: 1fr auto;
gap: var(--ads-v2-spaces-2);
padding: var(--ads-v2-spaces-3);
border: 0;
border-bottom: 1px solid var(--ads-v2-color-border);
background: ${({ $isSelected }) =>
$isSelected
? "var(--ads-v2-color-bg-muted)"
: "var(--ads-v2-color-bg)"};
color: var(--ads-v2-color-fg);
cursor: pointer;
text-align: left;

&:hover {
background: var(--ads-v2-color-bg-muted);
}
`;

const Status = styled.span<{ $status: ActionExecutionHistoryEntry["status"] }>`
color: ${({ $status }) =>
$status === "SUCCESS"
? "var(--ads-v2-color-fg-success)"
: $status === "FAILURE"
? "var(--ads-v2-color-fg-error)"
: "var(--ads-v2-color-fg-warning)"};
font-size: 12px;
font-weight: 600;
`;

const DetailPane = styled.div`
overflow: auto;
padding: var(--ads-v2-spaces-4);
`;

const Metadata = styled.div`
display: flex;
flex-wrap: wrap;
gap: var(--ads-v2-spaces-3);
padding-bottom: var(--ads-v2-spaces-4);
border-bottom: 1px solid var(--ads-v2-color-border);
`;

const PreviewSection = styled.div`
margin-top: var(--ads-v2-spaces-4);
`;

const Preview = styled.pre`
margin: var(--ads-v2-spaces-2) 0 0;
padding: var(--ads-v2-spaces-3);
border: 1px solid var(--ads-v2-color-border);
border-radius: var(--ads-v2-border-radius);
background: var(--ads-v2-color-bg-muted);
color: var(--ads-v2-color-fg);
font-size: 12px;
line-height: 18px;
overflow: auto;
white-space: pre-wrap;
`;

const EmptyState = styled.div`
display: flex;
height: 100%;
align-items: center;
justify-content: center;
color: var(--ads-v2-color-fg-muted);
`;

function formatTime(timestamp: number) {
return new Intl.DateTimeFormat(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}).format(new Date(timestamp));
}

interface ActionExecutionHistoryTabProps {
history: ActionExecutionHistoryEntry[];
}

export function ActionExecutionHistoryTab({
history,
}: ActionExecutionHistoryTabProps) {
const [selectedId, setSelectedId] = useState<string>();
const selectedRun =
history.find((entry) => entry.id === selectedId) || history[0];

if (!selectedRun) {
return (
<EmptyState>
<Text kind="body-m">Run this query to see execution history.</Text>

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 | 🟡 Minor | ⚡ Quick win

Use context-neutral empty-state copy.

Line [112] says “Run this query…”, but this component is also used in action debugger contexts. Please switch to neutral wording (for example, “Run this action to see execution history.”).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/client/src/PluginActionEditor/components/PluginActionResponse/components/ActionExecutionHistoryTab.tsx`
at line 112, The current empty-state copy in ActionExecutionHistoryTab.tsx is
context-specific ("Run this query…"); update the Text element (the node
rendering Text with kind="body-m" inside the ActionExecutionHistoryTab
component) to use neutral wording such as "Run this action to see execution
history." so it applies both in query and action debugger contexts.

</EmptyState>
);
}

return (
<Container>
<HistoryList>
{history.map((entry) => (
<HistoryItem
$isSelected={entry.id === selectedRun.id}
data-testid="t--action-execution-history-item"
key={entry.id}
onClick={() => setSelectedId(entry.id)}
type="button"
>
<div>
<Text kind="body-m">{formatTime(entry.createdAt)}</Text>
<Text kind="body-s">{entry.environmentName || "Environment"}</Text>
</div>
<div>
<Status $status={entry.status}>{entry.status}</Status>
<Text kind="body-s">{entry.duration || "0"}ms</Text>
</div>
</HistoryItem>
))}
</HistoryList>
<DetailPane>
<Metadata>
<Text kind="body-m">Status: {selectedRun.status}</Text>
<Text kind="body-m">Duration: {selectedRun.duration || "0"}ms</Text>
<Text kind="body-m">
Environment: {selectedRun.environmentName || "Environment"}
</Text>
</Metadata>
<PreviewSection>
<Text kind="heading-s">Request preview</Text>
<Preview>{selectedRun.requestPreview || "No request preview"}</Preview>
</PreviewSection>
<PreviewSection>
<Text kind="heading-s">Response preview</Text>
<Preview>
{selectedRun.responsePreview || "No response preview"}
</Preview>
</PreviewSection>
</DetailPane>
</Container>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { PluginEditorDebuggerState } from "./pluginEditorReducer";
import type {
ActionExecutionHistoryEntry,
PluginEditorDebuggerState,
} from "./pluginEditorReducer";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { type ReduxAction } from "actions/ReduxActionTypes";
import type { Action } from "entities/Action";
Expand All @@ -24,6 +27,13 @@ export const openPluginActionSettings = (payload: boolean) => ({
},
});

export const addActionExecutionHistoryEntry = (
payload: ActionExecutionHistoryEntry,
) => ({
type: ReduxActionTypes.ADD_ACTION_EXECUTION_HISTORY_ENTRY,
payload,
});

export const changeApi = (
id: string,
isSaas: boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ export const getPluginActionConfigSelectedTab = (state: DefaultRootState) =>
export const getPluginActionDebuggerState = (state: DefaultRootState) =>
state.ui.pluginActionEditor.debugger;

const getActionExecutionHistoryState = (state: DefaultRootState) =>
state.ui.pluginActionEditor.actionExecutionHistory;

export const getActionExecutionHistory = (id: string) =>
createSelector([getActionExecutionHistoryState], (historyMap) => {
return historyMap[id] || [];
});

export const isPluginActionCreating = (state: DefaultRootState) =>
state.ui.pluginActionEditor.isCreating;

Expand Down
31 changes: 31 additions & 0 deletions app/client/src/PluginActionEditor/store/pluginEditorReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,25 @@ import { omit, set } from "lodash";
import { objectKeys } from "@appsmith/utils";
import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";

const ACTION_EXECUTION_HISTORY_LIMIT = 20;

export interface PluginEditorDebuggerState {
open: boolean;
responseTabHeight: number;
selectedTab?: string;
}

export interface ActionExecutionHistoryEntry {
id: string;
actionId: string;
status: "SUCCESS" | "FAILURE" | "CANCELLED";
duration: string;
environmentName?: string;
requestPreview?: string;
responsePreview?: string;
createdAt: number;
}

export interface PluginActionEditorState {
isCreating: boolean;
isRunning: Record<string, boolean>;
Expand All @@ -26,6 +39,7 @@ export interface PluginActionEditorState {
isDeleting: Record<string, boolean>;
isDirty: Record<string, boolean>;
runErrorMessage: Record<string, string>;
actionExecutionHistory: Record<string, ActionExecutionHistoryEntry[]>;
selectedConfigTab?: string;
debugger: PluginEditorDebuggerState;
settingsOpen?: boolean;
Expand All @@ -39,6 +53,7 @@ const initialState: PluginActionEditorState = {
isDeleting: {},
isDirty: {},
runErrorMessage: {},
actionExecutionHistory: {},
debugger: {
open: false,
responseTabHeight: ActionExecutionResizerHeight,
Expand Down Expand Up @@ -124,6 +139,22 @@ export const handlers = {
) => {
set(state, ["isRunning", action.payload.id], false);
},
[ReduxActionTypes.ADD_ACTION_EXECUTION_HISTORY_ENTRY]: (
state: PluginActionEditorState,
action: ReduxAction<ActionExecutionHistoryEntry>,
) => {
const { actionId } = action.payload;
const existingHistory = state.actionExecutionHistory[actionId] || [];

set(
state,
["actionExecutionHistory", actionId],
[action.payload, ...existingHistory].slice(
0,
ACTION_EXECUTION_HISTORY_LIMIT,
),
);
},

[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
state: PluginActionEditorState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ApiResponseHeaders } from "PluginActionEditor/components/PluginActionRe
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
import { getErrorCount } from "selectors/debuggerSelectors";
import {
getActionExecutionHistory,
getPluginActionDebuggerState,
isActionRunning,
} from "PluginActionEditor/store";
Expand All @@ -37,6 +38,9 @@ import { Response } from "PluginActionEditor/components/PluginActionResponse/com
import { StateInspector } from "components/editorComponents/Debugger/StateInspector";
import { useLocation } from "react-router";
import { getIDETypeByUrl } from "ee/entities/IDE/utils";
import { ActionExecutionHistoryTab } from "PluginActionEditor/components/PluginActionResponse/components/ActionExecutionHistoryTab";

const ACTION_EXECUTION_HISTORY_TAB = "ACTION_EXECUTION_HISTORY_TAB";

function usePluginActionResponseTabs() {
const { action, actionResponse, datasource, plugin } =
Expand All @@ -47,6 +51,9 @@ function usePluginActionResponseTabs() {
const IDEViewMode = useSelector(getIDEViewMode);
const errorCount = useSelector(getErrorCount);
const pluginRequireDatasource = doesPluginRequireDatasource(plugin);
const actionExecutionHistory = useSelector(
getActionExecutionHistory(action.id),
);

const showSchema = useShowSchema(plugin.id) && pluginRequireDatasource;

Expand Down Expand Up @@ -94,6 +101,14 @@ function usePluginActionResponseTabs() {
/>
),
},
{
key: ACTION_EXECUTION_HISTORY_TAB,
title: "History",
count: actionExecutionHistory.length,
panelComponent: (
<ActionExecutionHistoryTab history={actionExecutionHistory} />
),
},
{
key: DEBUGGER_TAB_KEYS.HEADER_TAB,
title: createMessage(DEBUGGER_HEADERS),
Expand Down Expand Up @@ -149,6 +164,15 @@ function usePluginActionResponseTabs() {
/>
),
});

tabs.push({
key: ACTION_EXECUTION_HISTORY_TAB,
title: "History",
count: actionExecutionHistory.length,
panelComponent: (
<ActionExecutionHistoryTab history={actionExecutionHistory} />
),
});
}

const location = useLocation();
Expand Down
1 change: 1 addition & 0 deletions app/client/src/ce/constants/ReduxActionConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ const ActionExecutionTypes = {
RUN_ACTION_REQUEST: "RUN_ACTION_REQUEST",
RUN_ACTION_CANCELLED: "RUN_ACTION_CANCELLED",
RUN_ACTION_SUCCESS: "RUN_ACTION_SUCCESS",
ADD_ACTION_EXECUTION_HISTORY_ENTRY: "ADD_ACTION_EXECUTION_HISTORY_ENTRY",
GENERATE_JS_FUNCTION_SCHEMA_REQUEST: "GENERATE_JS_FUNCTION_SCHEMA_REQUEST",
GENERATE_JS_FUNCTION_SCHEMA_CANCELLED:
"GENERATE_JS_FUNCTION_SCHEMA_CANCELLED",
Expand Down
16 changes: 16 additions & 0 deletions app/client/src/pages/Editor/QueryEditor/QueryDebuggerTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { DatasourceComponentTypes } from "entities/Plugin";
import { fetchDatasourceStructure } from "actions/datasourceActions";
import { DatasourceStructureContext } from "entities/Datasource";
import {
getActionExecutionHistory,
getPluginActionDebuggerState,
setPluginActionEditorDebuggerState,
} from "PluginActionEditor/store";
Expand All @@ -34,6 +35,9 @@ import { getIDEViewMode } from "selectors/ideSelectors";
import { EditorViewMode } from "IDE/Interfaces/EditorTypes";
import { IDEBottomView, ViewHideBehaviour } from "IDE";
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
import { ActionExecutionHistoryTab } from "PluginActionEditor/components/PluginActionResponse/components/ActionExecutionHistoryTab";

const ACTION_EXECUTION_HISTORY_TAB = "ACTION_EXECUTION_HISTORY_TAB";

interface QueryDebuggerTabsProps {
actionSource: SourceEntity;
Expand Down Expand Up @@ -61,6 +65,9 @@ function QueryDebuggerTabs({
const { open, responseTabHeight, selectedTab } = useSelector(
getPluginActionDebuggerState,
);
const actionExecutionHistory = useSelector(
getActionExecutionHistory(currentActionConfig?.id || ""),
);

const { responseDisplayFormat } =
actionResponseDisplayDataFormats(actionResponse);
Expand Down Expand Up @@ -212,6 +219,15 @@ function QueryDebuggerTabs({
/>
),
});

responseTabs.push({
key: ACTION_EXECUTION_HISTORY_TAB,
title: "History",
count: actionExecutionHistory.length,
panelComponent: (
<ActionExecutionHistoryTab history={actionExecutionHistory} />
),
});
}

if (showSchema && currentActionConfig && currentActionConfig.datasource) {
Expand Down
Loading