mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
refactor(frontend): migration of metrics-slice.ts to zustand (#11018)
This commit is contained in:
parent
e09f93aa75
commit
b89f2e51e4
@ -1,10 +1,9 @@
|
||||
import { screen, waitFor, within } from "@testing-library/react";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { QueryClientConfig } from "@tanstack/react-query";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { createRoutesStub } from "react-router";
|
||||
import React from "react";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { renderWithQueryAndI18n } from "test-utils";
|
||||
import { ConversationPanel } from "#/components/features/conversation-panel/conversation-panel";
|
||||
import ConversationService from "#/api/conversation-service/conversation-service.api";
|
||||
import { Conversation } from "#/api/open-hands.types";
|
||||
@ -18,16 +17,7 @@ describe("ConversationPanel", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const renderConversationPanel = (config?: QueryClientConfig) =>
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
const renderConversationPanel = () => renderWithQueryAndI18n(<RouterStub />);
|
||||
|
||||
beforeAll(() => {
|
||||
vi.mock("react-router", async (importOriginal) => ({
|
||||
@ -297,15 +287,7 @@ describe("ConversationPanel", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
renderWithProviders(<MyRouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
renderWithQueryAndI18n(<MyRouterStub />);
|
||||
|
||||
const toggleButton = screen.getByText("Toggle");
|
||||
|
||||
|
||||
@ -57,11 +57,6 @@ describe("MicroagentManagement", () => {
|
||||
const renderMicroagentManagement = (config?: QueryClientConfig) =>
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
addMicroagentModalVisible: false,
|
||||
updateMicroagentModalVisible: false,
|
||||
@ -1351,11 +1346,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with modal already visible in Redux state
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: null,
|
||||
addMicroagentModalVisible: true, // Start with modal visible
|
||||
@ -1646,11 +1636,6 @@ describe("MicroagentManagement", () => {
|
||||
const renderMicroagentManagementMain = (selectedMicroagentItem: any) =>
|
||||
renderWithProviders(<MicroagentManagementMain />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
addMicroagentModalVisible: false,
|
||||
selectedRepository: {
|
||||
@ -1998,11 +1983,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible in Redux state
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForUpdate,
|
||||
@ -2037,11 +2017,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible and selected microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForUpdate,
|
||||
@ -2075,11 +2050,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible and selected microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForUpdate,
|
||||
@ -2118,11 +2088,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible and selected microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForUpdate,
|
||||
@ -2174,11 +2139,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForUpdate,
|
||||
@ -2225,11 +2185,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForUpdate,
|
||||
@ -2279,11 +2234,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible but no microagent data
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: null,
|
||||
addMicroagentModalVisible: false,
|
||||
@ -2325,11 +2275,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible and microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForUpdate,
|
||||
@ -2374,11 +2319,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with update modal visible and microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForUpdate,
|
||||
@ -2561,11 +2501,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with selected microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForLearn,
|
||||
@ -2601,11 +2536,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with selected microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForLearn,
|
||||
@ -2658,11 +2588,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with selected microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForLearn,
|
||||
@ -2718,11 +2643,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with selected microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForLearn,
|
||||
@ -2776,11 +2696,6 @@ describe("MicroagentManagement", () => {
|
||||
// Render with selected microagent
|
||||
renderWithProviders(<RouterStub />, {
|
||||
preloadedState: {
|
||||
metrics: {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
},
|
||||
microagentManagement: {
|
||||
selectedMicroagentItem: {
|
||||
microagent: mockMicroagentForLearn,
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { BaseModal } from "../../../shared/modals/base-modal/base-modal";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { CostSection } from "./cost-section";
|
||||
import { UsageSection } from "./usage-section";
|
||||
import { ContextWindowSection } from "./context-window-section";
|
||||
import { EmptyState } from "./empty-state";
|
||||
import useMetricsStore from "#/stores/metrics-store";
|
||||
|
||||
interface MetricsModalProps {
|
||||
isOpen: boolean;
|
||||
@ -15,7 +14,7 @@ interface MetricsModalProps {
|
||||
|
||||
export function MetricsModal({ isOpen, onOpenChange }: MetricsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const metrics = useSelector((state: RootState) => state.metrics);
|
||||
const metrics = useMetricsStore();
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
|
||||
@ -2,10 +2,9 @@ import { useTranslation } from "react-i18next";
|
||||
import React from "react";
|
||||
import posthog from "posthog-js";
|
||||
import { useParams, useNavigate } from "react-router";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useWsClient } from "#/context/ws-client-provider";
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
import { RootState } from "#/store";
|
||||
import useMetricsStore from "#/stores/metrics-store";
|
||||
import { isSystemMessage } from "#/types/core/guards";
|
||||
import { ConversationStatus } from "#/types/conversation-status";
|
||||
import ConversationService from "#/api/conversation-service/conversation-service.api";
|
||||
@ -36,7 +35,7 @@ export function useConversationNameContextMenu({
|
||||
const { mutate: deleteConversation } = useDeleteConversation();
|
||||
const { mutate: stopConversation } = useStopConversation();
|
||||
const { mutate: getTrajectory } = useGetTrajectory();
|
||||
const metrics = useSelector((state: RootState) => state.metrics);
|
||||
const metrics = useMetricsStore();
|
||||
|
||||
const [metricsModalVisible, setMetricsModalVisible] = React.useState(false);
|
||||
const [systemModalVisible, setSystemModalVisible] = React.useState(false);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { trackError } from "#/utils/error-handler";
|
||||
import { appendSecurityAnalyzerInput } from "#/state/security-analyzer-slice";
|
||||
import useMetricsStore from "#/stores/metrics-store";
|
||||
import { useStatusStore } from "#/state/status-store";
|
||||
import { setMetrics } from "#/state/metrics-slice";
|
||||
import store from "#/store";
|
||||
import ActionType from "#/types/action-type";
|
||||
import {
|
||||
@ -26,7 +26,7 @@ export function handleActionMessage(message: ActionMessage) {
|
||||
max_budget_per_task: message.llm_metrics?.max_budget_per_task ?? null,
|
||||
usage: message.llm_metrics?.accumulated_token_usage ?? null,
|
||||
};
|
||||
store.dispatch(setMetrics(metrics));
|
||||
useMetricsStore.getState().setMetrics(metrics);
|
||||
}
|
||||
|
||||
if (message.action === ActionType.RUN) {
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface MetricsState {
|
||||
cost: number | null;
|
||||
max_budget_per_task: number | null;
|
||||
usage: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
cache_read_tokens: number;
|
||||
cache_write_tokens: number;
|
||||
context_window: number;
|
||||
per_turn_token: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
const initialState: MetricsState = {
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
};
|
||||
|
||||
const metricsSlice = createSlice({
|
||||
name: "metrics",
|
||||
initialState,
|
||||
reducers: {
|
||||
setMetrics: (state, action: PayloadAction<MetricsState>) => {
|
||||
state.cost = action.payload.cost;
|
||||
state.max_budget_per_task = action.payload.max_budget_per_task;
|
||||
state.usage = action.payload.usage;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setMetrics } = metricsSlice.actions;
|
||||
export default metricsSlice.reducer;
|
||||
@ -4,7 +4,6 @@ import browserReducer from "./state/browser-slice";
|
||||
import fileStateReducer from "./state/file-state-slice";
|
||||
import { jupyterReducer } from "./state/jupyter-slice";
|
||||
import securityAnalyzerReducer from "./state/security-analyzer-slice";
|
||||
import metricsReducer from "./state/metrics-slice";
|
||||
import microagentManagementReducer from "./state/microagent-management-slice";
|
||||
import conversationReducer from "./state/conversation-slice";
|
||||
import eventMessageReducer from "./state/event-message-slice";
|
||||
@ -15,7 +14,6 @@ export const rootReducer = combineReducers({
|
||||
agent: agentReducer,
|
||||
jupyter: jupyterReducer,
|
||||
securityAnalyzer: securityAnalyzerReducer,
|
||||
metrics: metricsReducer,
|
||||
microagentManagement: microagentManagementReducer,
|
||||
conversation: conversationReducer,
|
||||
eventMessage: eventMessageReducer,
|
||||
|
||||
27
frontend/src/stores/metrics-store.ts
Normal file
27
frontend/src/stores/metrics-store.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
interface MetricsState {
|
||||
cost: number | null;
|
||||
max_budget_per_task: number | null;
|
||||
usage: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
cache_read_tokens: number;
|
||||
cache_write_tokens: number;
|
||||
context_window: number;
|
||||
per_turn_token: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface MetricsStore extends MetricsState {
|
||||
setMetrics: (metrics: MetricsState) => void;
|
||||
}
|
||||
|
||||
const useMetricsStore = create<MetricsStore>((set) => ({
|
||||
cost: null,
|
||||
max_budget_per_task: null,
|
||||
usage: null,
|
||||
setMetrics: (metrics) => set(metrics),
|
||||
}));
|
||||
|
||||
export default useMetricsStore;
|
||||
@ -79,6 +79,28 @@ export function renderWithProviders(
|
||||
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
|
||||
}
|
||||
|
||||
// Export a render function for components that only need QueryClient and i18next providers
|
||||
// (without Redux store)
|
||||
export function renderWithQueryAndI18n(
|
||||
ui: React.ReactElement,
|
||||
renderOptions: Omit<RenderOptions, "wrapper"> = {},
|
||||
) {
|
||||
function Wrapper({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<QueryClientProvider
|
||||
client={
|
||||
new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
})
|
||||
}
|
||||
>
|
||||
<I18nextProvider i18n={i18n}>{children}</I18nextProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
return render(ui, { wrapper: Wrapper, ...renderOptions });
|
||||
}
|
||||
|
||||
export const createAxiosNotFoundErrorObject = () =>
|
||||
new AxiosError(
|
||||
"Request failed with status code 404",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user