diff --git a/package.json b/package.json
index 8bcec67cd..a1fbfc0ca 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
"fuse.js": "7.0.0",
"lodash.isequal": "4.5.0",
"lodash.mergewith": "4.6.2",
- "loglevel": "^1.9.2",
+ "loglevel": "1.9.2",
"prism-react-renderer": "2.4.1",
"prismjs": "1.29.0",
"react": "18.3.1",
diff --git a/src/components/App/App.test.tsx b/src/components/App/App.test.tsx
index 8794a1706..9c546435c 100644
--- a/src/components/App/App.test.tsx
+++ b/src/components/App/App.test.tsx
@@ -1,5 +1,4 @@
import { render, screen } from "@testing-library/react";
-import log from "loglevel";
import * as reactGA from "react-ga";
import { Provider } from "react-redux";
import configureStore from "redux-mock-store";
@@ -27,21 +26,9 @@ vi.mock("react-router", async () => {
};
});
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
const mockStore = configureStore([]);
describe("App", () => {
- beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
- });
-
afterEach(() => {
vi.resetAllMocks();
vi.unstubAllEnvs();
diff --git a/src/components/LogIn/UserPassForm/UserPassForm.test.tsx b/src/components/LogIn/UserPassForm/UserPassForm.test.tsx
index d816db4dd..0cfae1653 100644
--- a/src/components/LogIn/UserPassForm/UserPassForm.test.tsx
+++ b/src/components/LogIn/UserPassForm/UserPassForm.test.tsx
@@ -1,6 +1,5 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import log from "loglevel";
import { vi } from "vitest";
import { thunks as appThunks } from "store/app";
@@ -10,27 +9,9 @@ import { generalStateFactory } from "testing/factories/general";
import { rootStateFactory } from "testing/factories/root";
import { renderComponent } from "testing/utils";
-import { Label } from "../types";
-
import UserPassForm from "./UserPassForm";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("UserPassForm", () => {
- beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
- });
-
- afterEach(() => {
- vi.restoreAllMocks();
- });
-
it("should log in", async () => {
// Mock the result of the thunk to be a normal action so that it can be tested
// for. This is necessary because we don't have a full store set up which
@@ -70,31 +51,4 @@ describe("UserPassForm", () => {
type: "connectAndStartPolling",
});
});
-
- it("should display console error when trying to log in", async () => {
- vi.spyOn(appThunks, "connectAndStartPolling").mockImplementation(
- vi.fn().mockReturnValue({ type: "connectAndStartPolling" }),
- );
- vi.spyOn(dashboardStore, "useAppDispatch").mockImplementation(
- vi
- .fn()
- .mockReturnValue((action: unknown) =>
- action instanceof Object &&
- "type" in action &&
- action.type === "connectAndStartPolling"
- ? Promise.reject(
- new Error("Error while dispatching connectAndStartPolling!"),
- )
- : null,
- ),
- );
-
- renderComponent();
- await userEvent.click(screen.getByRole("button"));
- expect(appThunks.connectAndStartPolling).toHaveBeenCalledTimes(1);
- expect(log.error).toHaveBeenCalledWith(
- Label.POLLING_ERROR,
- new Error("Error while dispatching connectAndStartPolling!"),
- );
- });
});
diff --git a/src/components/LogIn/UserPassForm/UserPassForm.tsx b/src/components/LogIn/UserPassForm/UserPassForm.tsx
index 04cbf36d5..2e7fbccf4 100644
--- a/src/components/LogIn/UserPassForm/UserPassForm.tsx
+++ b/src/components/LogIn/UserPassForm/UserPassForm.tsx
@@ -1,5 +1,4 @@
import { unwrapResult } from "@reduxjs/toolkit";
-import log from "loglevel";
import type { FormEvent } from "react";
import bakery from "juju/bakery";
@@ -7,6 +6,7 @@ import { thunks as appThunks } from "store/app";
import { actions as generalActions } from "store/general";
import { getWSControllerURL } from "store/general/selectors";
import { useAppDispatch, useAppSelector } from "store/store";
+import { logger } from "utils/logger";
import { Label } from "../types";
@@ -36,7 +36,7 @@ const UserPassForm = () => {
if (bakery) {
dispatch(appThunks.connectAndStartPolling())
.then(unwrapResult)
- .catch((error) => log.error(Label.POLLING_ERROR, error));
+ .catch((error) => logger.error(Label.POLLING_ERROR, error));
}
}
diff --git a/src/components/ShareCard/ShareCard.test.tsx b/src/components/ShareCard/ShareCard.test.tsx
index a6271dd89..7a189b08e 100644
--- a/src/components/ShareCard/ShareCard.test.tsx
+++ b/src/components/ShareCard/ShareCard.test.tsx
@@ -1,24 +1,11 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import log from "loglevel";
import { vi } from "vitest";
import ShareCard from "./ShareCard";
import { Label } from "./types";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("Share Card", () => {
- beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
- });
-
it("should display appropriate text", () => {
render(
{
await userEvent.selectOptions(screen.getByRole("combobox"), "write");
expect(accessSelectChangeFn).toHaveBeenCalled();
});
-
- it("should log error when trying to change access", async () => {
- const removeUserFn = vi.fn();
- const accessSelectChangeFn = vi.fn(() => Promise.reject(new Error()));
- render(
- ,
- );
- await userEvent.selectOptions(screen.getByRole("combobox"), "write");
- expect(log.error).toHaveBeenCalledWith(
- Label.ACCESS_CHANGE_ERROR,
- new Error(),
- );
- });
});
diff --git a/src/components/ShareCard/ShareCard.tsx b/src/components/ShareCard/ShareCard.tsx
index c18de08a7..422f6852e 100644
--- a/src/components/ShareCard/ShareCard.tsx
+++ b/src/components/ShareCard/ShareCard.tsx
@@ -1,11 +1,11 @@
import type { ErrorResults } from "@canonical/jujulib/dist/api/facades/model-manager/ModelManagerV9";
import { Button, Select } from "@canonical/react-components";
-import log from "loglevel";
import { useEffect, useState } from "react";
import SlideDownFadeOut from "animations/SlideDownFadeOut";
import TruncatedTooltip from "components/TruncatedTooltip";
import { formatFriendlyDateToNow } from "components/utils";
+import { logger } from "utils/logger";
import "./_share-card.scss";
import { Label } from "./types";
@@ -118,7 +118,7 @@ export default function ShareCard({
return;
})
.catch((error) =>
- log.error(Label.ACCESS_CHANGE_ERROR, error),
+ logger.error(Label.ACCESS_CHANGE_ERROR, error),
);
}}
value={access}
diff --git a/src/hooks/useLocalStorage.test.ts b/src/hooks/useLocalStorage.test.ts
index def992eec..9604fe7b2 100644
--- a/src/hooks/useLocalStorage.test.ts
+++ b/src/hooks/useLocalStorage.test.ts
@@ -1,22 +1,8 @@
import { act, renderHook } from "@testing-library/react";
-import log from "loglevel";
-import { vi } from "vitest";
import useLocalStorage from "./useLocalStorage";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("useLocalStorage", () => {
- beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
- });
-
afterEach(() => {
localStorage.clear();
});
@@ -43,10 +29,6 @@ describe("useLocalStorage", () => {
const { result } = renderHook(() =>
useLocalStorage("test-key", "init-val"),
);
- expect(log.error).toHaveBeenCalledWith(
- "Unable to parse local storage:",
- expect.any(Error),
- );
const [value] = result.current;
expect(value).toBe("init-val");
});
@@ -76,7 +58,6 @@ describe("useLocalStorage", () => {
act(() => {
setValue(circular as unknown as string);
});
- expect(log.error).toHaveBeenCalledWith(expect.any(Error));
const [value] = result.current;
expect(value).toBe("init-val");
expect(JSON.parse(localStorage.getItem("test-key") ?? "")).toBe("init-val");
diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts
index d75e78579..d5cf66082 100644
--- a/src/hooks/useLocalStorage.ts
+++ b/src/hooks/useLocalStorage.ts
@@ -1,6 +1,7 @@
-import log from "loglevel";
import { useState } from "react";
+import { logger } from "utils/logger";
+
function useLocalStorage(
key: string,
initialValue: V,
@@ -13,7 +14,7 @@ function useLocalStorage(
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// Not shown in UI. Logged for debugging purposes.
- log.error("Unable to parse local storage:", error);
+ logger.error("Unable to parse local storage:", error);
return initialValue;
}
});
@@ -27,7 +28,7 @@ function useLocalStorage(
window.localStorage.setItem(key, stringified);
} catch (error) {
// Not shown in UI. Logged for debugging purposes.
- log.error(error);
+ logger.error(error);
}
};
diff --git a/src/hooks/useLogout.test.tsx b/src/hooks/useLogout.test.tsx
index 9e607d11a..b4a5c3cb8 100644
--- a/src/hooks/useLogout.test.tsx
+++ b/src/hooks/useLogout.test.tsx
@@ -1,6 +1,5 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import log from "loglevel";
import { vi } from "vitest";
import { Label } from "hooks/useLogout";
@@ -10,19 +9,7 @@ import { renderComponent, renderWrappedHook } from "testing/utils";
import useLogout from "./useLogout";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("useLogout", () => {
- beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
- });
-
it("should logout", async () => {
vi.spyOn(appThunks, "logOut").mockImplementation(
vi.fn().mockReturnValue({ type: "logOut", catch: vi.fn() }),
diff --git a/src/hooks/useLogout.tsx b/src/hooks/useLogout.tsx
index ffa5effcc..ed849e943 100644
--- a/src/hooks/useLogout.tsx
+++ b/src/hooks/useLogout.tsx
@@ -1,11 +1,11 @@
import { Button } from "@canonical/react-components";
import { unwrapResult } from "@reduxjs/toolkit";
-import log from "loglevel";
import reactHotToast from "react-hot-toast";
import ToastCard from "components/ToastCard";
import { thunks as appThunks } from "store/app";
import { useAppDispatch } from "store/store";
+import { logger } from "utils/logger";
export enum Label {
LOGOUT_ERROR = "Error when trying to logout.",
@@ -31,7 +31,7 @@ const useLogout = () => {
>
));
- log.error(Label.LOGOUT_ERROR, error);
+ logger.error(Label.LOGOUT_ERROR, error);
});
};
};
diff --git a/src/index.test.tsx b/src/index.test.tsx
index a86b263d8..9b8b540aa 100644
--- a/src/index.test.tsx
+++ b/src/index.test.tsx
@@ -1,6 +1,4 @@
import { screen, waitFor } from "@testing-library/dom";
-import log from "loglevel";
-import type { UnknownAction } from "redux";
import { vi } from "vitest";
import * as storeModule from "store";
@@ -34,14 +32,6 @@ vi.mock("store", () => {
};
});
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
const appVersion = packageJSON.version;
describe("renderApp", () => {
@@ -54,8 +44,6 @@ describe("renderApp", () => {
enumerable: true,
value: new URL(window.location.href),
});
- // Hide the config errors from the test output.
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
rootNode = document.createElement("div");
rootNode.setAttribute("id", ROOT_ID);
document.body.appendChild(rootNode);
@@ -257,10 +245,6 @@ describe("renderApp", () => {
});
describe("getControllerAPIEndpointErrors", () => {
- beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
- });
-
it("should handle secure protocol", () => {
expect(
getControllerAPIEndpointErrors("wss://example.com:80/api"),
@@ -306,34 +290,4 @@ describe("getControllerAPIEndpointErrors", () => {
"controllerAPIEndpoint (example.com:80/api) must be an absolute path or begin with ws:// or wss://.",
);
});
-
- it("should show console error when dispatching connectAndStartPolling", async () => {
- vi.spyOn(appThunks, "connectAndStartPolling").mockImplementation(
- vi.fn().mockReturnValue({ type: "connectAndStartPolling" }),
- );
- vi.spyOn(storeModule.default, "dispatch").mockImplementation(
- (action) =>
- (action instanceof Object &&
- "type" in action &&
- action.type === "connectAndStartPolling"
- ? Promise.reject(
- new Error("Error while dispatching connectAndStartPolling!"),
- )
- : null) as unknown as UnknownAction,
- );
- const config = configFactory.build({
- baseControllerURL: null,
- identityProviderURL: "/candid",
- isJuju: true,
- });
- window.jujuDashboardConfig = config;
- renderApp();
- expect(appThunks.connectAndStartPolling).toHaveBeenCalledTimes(1);
- await waitFor(() =>
- expect(log.error).toHaveBeenCalledWith(
- Label.POLLING_ERROR,
- new Error("Error while dispatching connectAndStartPolling!"),
- ),
- );
- });
});
diff --git a/src/index.tsx b/src/index.tsx
index c29c5f275..179304647 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,7 +1,6 @@
import { Notification, Strip } from "@canonical/react-components";
import { unwrapResult } from "@reduxjs/toolkit";
import * as Sentry from "@sentry/browser";
-import log from "loglevel";
import { StrictMode } from "react";
import type { Root } from "react-dom/client";
import { createRoot } from "react-dom/client";
@@ -14,6 +13,7 @@ import { thunks as appThunks } from "store/app";
import { actions as generalActions } from "store/general";
import { AuthMethod } from "store/general/types";
import type { WindowConfig } from "types";
+import { logger } from "utils/logger";
import packageJSON from "../package.json";
@@ -125,7 +125,7 @@ function bootstrap() {
,
);
- log.error(error);
+ logger.error(error);
return;
}
// It's possible that the charm is generating a relative path for the
@@ -160,7 +160,7 @@ function bootstrap() {
reduxStore
.dispatch(appThunks.connectAndStartPolling())
.then(unwrapResult)
- .catch((error) => log.error(Label.POLLING_ERROR, error));
+ .catch((error) => logger.error(Label.POLLING_ERROR, error));
}
getRoot()?.render(
diff --git a/src/juju/api.test.ts b/src/juju/api.test.ts
index 5ce448447..be3452409 100644
--- a/src/juju/api.test.ts
+++ b/src/juju/api.test.ts
@@ -2,7 +2,6 @@ import type { Client, Connection } from "@canonical/jujulib";
import * as jujuLib from "@canonical/jujulib";
import * as jujuLibVersions from "@canonical/jujulib/dist/api/versions";
import { waitFor } from "@testing-library/react";
-import log from "loglevel";
import { vi } from "vitest";
import { actions as generalActions } from "store/general";
@@ -68,17 +67,8 @@ vi.mock("@canonical/jujulib/dist/api/versions", () => ({
jujuUpdateAvailable: vi.fn(),
}));
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("Juju API", () => {
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
vi.useFakeTimers();
});
@@ -379,11 +369,6 @@ describe("Juju API", () => {
await expect(response).rejects.toStrictEqual(
new Error(Label.LOGIN_TIMEOUT_ERROR),
);
- expect(log.error).toHaveBeenCalledWith(
- "Error connecting to model:",
- "abc123",
- new Error(Label.LOGIN_TIMEOUT_ERROR),
- );
});
it("can fetch the status", async () => {
@@ -554,11 +539,6 @@ describe("Juju API", () => {
await expect(response).rejects.toStrictEqual(
new Error("Unable to fetch the status. Uh oh!"),
);
- expect(log.error).toHaveBeenCalledWith(
- "Error connecting to model:",
- "abc123",
- new Error("Unable to fetch the status. Uh oh!"),
- );
});
});
@@ -691,11 +671,6 @@ describe("Juju API", () => {
await expect(response).rejects.toStrictEqual(
new Error("Unable to fetch the status. Status not returned."),
);
- expect(log.error).toHaveBeenCalledWith(
- "Error connecting to model:",
- "abc123",
- new Error("Unable to fetch the status. Status not returned."),
- );
expect(dispatch).not.toHaveBeenCalled();
});
});
@@ -879,12 +854,6 @@ describe("Juju API", () => {
() => state,
);
expect(dispatch).toHaveBeenCalledTimes(2);
- await waitFor(() =>
- expect(log.error).toHaveBeenCalledWith(
- "Error when trying to add controller cloud and region data.",
- new Error("Error while trying to dispatch!"),
- ),
- );
});
it("should return a rejected promise when retrieving data for some models fails", async () => {
diff --git a/src/juju/api.ts b/src/juju/api.ts
index 5ce66fe9d..48c4f5636 100644
--- a/src/juju/api.ts
+++ b/src/juju/api.ts
@@ -17,7 +17,6 @@ import { jujuUpdateAvailable } from "@canonical/jujulib/dist/api/versions";
import type { ValueOf } from "@canonical/react-components";
import { unwrapResult } from "@reduxjs/toolkit";
import Limiter from "async-limiter";
-import log from "loglevel";
import type { Dispatch } from "redux";
import bakery from "juju/bakery";
@@ -41,6 +40,7 @@ import type {
import { ModelsError } from "store/middleware/model-poller";
import type { RootState, Store } from "store/store";
import { toErrorString } from "utils";
+import { logger } from "utils/logger";
import { getModelByUUID } from "../store/juju/selectors";
@@ -124,7 +124,7 @@ function startPingerLoop(conn: ConnectionWithFacades) {
conn.facades.pinger?.ping(null).catch((e) => {
// If the pinger fails for whatever reason then cancel the ping.
// Not shown in UI. Logged for debugging purposes.
- log.error("pinger stopped,", e);
+ logger.error("pinger stopped,", e);
stopPingerLoop(intervalId);
});
}, PING_TIME);
@@ -271,7 +271,7 @@ export async function fetchModelStatus(
}
logout();
} catch (error) {
- log.error("Error connecting to model:", modelUUID, error);
+ logger.error("Error connecting to model:", modelUUID, error);
throw error;
}
}
@@ -390,7 +390,7 @@ export async function fetchAllModelStatuses(
.then(unwrapResult)
.catch((error) =>
// Not shown in UI. Logged for debugging purposes.
- log.error(
+ logger.error(
"Error when trying to add controller cloud and region data.",
error,
),
diff --git a/src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.test.tsx b/src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.test.tsx
index b3cd96fa0..70c5e827e 100644
--- a/src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.test.tsx
+++ b/src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.test.tsx
@@ -1,7 +1,6 @@
import { screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { add } from "date-fns";
-import log from "loglevel";
import { vi } from "vitest";
import * as actionsHooks from "juju/api-hooks/actions";
@@ -23,6 +22,7 @@ import {
modelDataInfoFactory,
} from "testing/factories/juju/juju";
import { renderComponent } from "testing/utils";
+import { logger } from "utils/logger";
import ActionLogs from "./ActionLogs";
import { Label, Output } from "./types";
@@ -103,21 +103,13 @@ vi.mock("juju/api-hooks/actions", () => {
};
});
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("Action Logs", () => {
let state: RootState;
const path = "/models/:userName/:modelName";
const url = "/models/eggman@external/group-test?activeView=action-logs";
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
+ vi.spyOn(logger, "error").mockImplementation(() => vi.fn());
vi.spyOn(actionsHooks, "useQueryOperationsList").mockImplementation(() =>
vi.fn().mockImplementation(() => Promise.resolve(mockOperationResults)),
);
@@ -377,7 +369,7 @@ describe("Action Logs", () => {
renderComponent(, { path, url, state });
expect(queryOperationsListSpy).toHaveBeenCalledTimes(1);
await waitFor(() => {
- expect(log.error).toHaveBeenCalledWith(
+ expect(logger.error).toHaveBeenCalledWith(
Label.FETCH_ERROR,
new Error("Error while querying operations list."),
);
diff --git a/src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.tsx b/src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.tsx
index c4cddd1a6..6b6bed198 100644
--- a/src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.tsx
+++ b/src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.tsx
@@ -8,7 +8,6 @@ import {
Icon,
ModularTable,
} from "@canonical/react-components";
-import log from "loglevel";
import type { ReactNode } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useSelector } from "react-redux";
@@ -28,6 +27,7 @@ import { getModelStatus, getModelUUID } from "store/juju/selectors";
import type { ModelData } from "store/juju/types";
import type { RootState } from "store/store";
import urls from "urls";
+import { logger } from "utils/logger";
import "./_action-logs.scss";
import ActionPayloadModal from "./ActionPayloadModal";
@@ -114,7 +114,10 @@ const generateApplicationRow = (
const appName = parts && parts[1];
if (!appName) {
// Not shown in UI. Logged for debugging purposes.
- log.error("Unable to parse action receiver", actionData.action?.receiver);
+ logger.error(
+ "Unable to parse action receiver",
+ actionData.action?.receiver,
+ );
return null;
}
return {
@@ -193,7 +196,7 @@ export default function ActionLogs() {
setInlineErrors(InlineErrors.FETCH, null);
} catch (error) {
setInlineErrors(InlineErrors.FETCH, Label.FETCH_ERROR);
- log.error(Label.FETCH_ERROR, error);
+ logger.error(Label.FETCH_ERROR, error);
} finally {
setFetchedOperations(true);
}
diff --git a/src/pages/ModelDetails/ModelDetails.test.tsx b/src/pages/ModelDetails/ModelDetails.test.tsx
index f748bb0c8..ac19e68f4 100644
--- a/src/pages/ModelDetails/ModelDetails.test.tsx
+++ b/src/pages/ModelDetails/ModelDetails.test.tsx
@@ -1,7 +1,6 @@
import type { Connection } from "@canonical/jujulib";
import * as jujuLib from "@canonical/jujulib";
import { screen, waitFor } from "@testing-library/react";
-import log from "loglevel";
import { vi } from "vitest";
import * as juju from "juju/api";
@@ -16,7 +15,6 @@ import { renderComponent } from "testing/utils";
import urls from "urls";
import ModelDetails from "./ModelDetails";
-import { Label as ModelDetailsLabel } from "./types";
vi.mock("pages/EntityDetails/App", () => {
return { default: () => };
@@ -38,14 +36,6 @@ vi.mock("@canonical/jujulib", () => ({
connectAndLogin: vi.fn(),
}));
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("ModelDetails", () => {
let state: RootState;
let client: {
@@ -59,7 +49,6 @@ describe("ModelDetails", () => {
});
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
state = rootStateFactory.withGeneralConfig().build({
juju: jujuStateFactory.build({
models: {
@@ -242,9 +231,5 @@ describe("ModelDetails", () => {
);
unmount();
await waitFor(() => expect(juju.stopModelWatcher).toHaveBeenCalledTimes(1));
- expect(log.error).toHaveBeenCalledWith(
- ModelDetailsLabel.MODEL_WATCHER_ERROR,
- new Error("Failed to stop model watcher!"),
- );
});
});
diff --git a/src/pages/ModelDetails/ModelDetails.tsx b/src/pages/ModelDetails/ModelDetails.tsx
index f971eb06e..6886c5b95 100644
--- a/src/pages/ModelDetails/ModelDetails.tsx
+++ b/src/pages/ModelDetails/ModelDetails.tsx
@@ -1,5 +1,4 @@
import type { AllWatcherId } from "@canonical/jujulib/dist/api/facades/client/ClientV6";
-import log from "loglevel";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Route, Routes, useParams } from "react-router";
@@ -17,6 +16,7 @@ import { getModelUUIDFromList } from "store/juju/selectors";
import { useAppStore } from "store/store";
import urls from "urls";
import { getMajorMinorVersion, toErrorString } from "utils";
+import { logger } from "utils/logger";
import { Label } from "./types";
@@ -71,7 +71,7 @@ export default function ModelDetails() {
).catch((error) =>
// Error doesn’t interfere with the user’s interaction with the
// dashboard. Not shown in UI. Logged for debugging purposes.
- log.error(Label.MODEL_WATCHER_ERROR, error),
+ logger.error(Label.MODEL_WATCHER_ERROR, error),
);
}
};
diff --git a/src/panels/ActionsPanel/ActionsPanel.test.tsx b/src/panels/ActionsPanel/ActionsPanel.test.tsx
index f71c0a0d8..dc6718b7e 100644
--- a/src/panels/ActionsPanel/ActionsPanel.test.tsx
+++ b/src/panels/ActionsPanel/ActionsPanel.test.tsx
@@ -1,6 +1,5 @@
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import log from "loglevel";
import { vi } from "vitest";
import * as actionsHooks from "juju/api-hooks/actions";
@@ -61,14 +60,6 @@ vi.mock("juju/api-hooks/actions", () => {
};
});
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("ActionsPanel", () => {
let state: RootState;
const path = "/models/:userName/:modelName/app/:appName";
@@ -76,7 +67,6 @@ describe("ActionsPanel", () => {
"/models/user-eggman@external/group-test/app/kubernetes-master?panel=execute-action&units=ceph%2F0,ceph%2F1";
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
const getActionsForApplicationSpy = vi
.fn()
.mockImplementation(() => Promise.resolve(mockResponse));
@@ -335,10 +325,6 @@ describe("ActionsPanel", () => {
await waitFor(() =>
expect(getActionsForApplicationSpy).toHaveBeenCalledTimes(1),
);
- expect(log.error).toHaveBeenCalledWith(
- ActionsPanelLabel.GET_ACTIONS_ERROR,
- new Error("Error while trying to get actions for application!"),
- );
await waitFor(() =>
expect(
screen.getByText(ActionsPanelLabel.GET_ACTIONS_ERROR, { exact: false }),
diff --git a/src/panels/ActionsPanel/ActionsPanel.tsx b/src/panels/ActionsPanel/ActionsPanel.tsx
index 537996682..3176b86c7 100644
--- a/src/panels/ActionsPanel/ActionsPanel.tsx
+++ b/src/panels/ActionsPanel/ActionsPanel.tsx
@@ -1,5 +1,4 @@
import { ActionButton, Button } from "@canonical/react-components";
-import log from "loglevel";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useSelector } from "react-redux";
import { useParams } from "react-router";
@@ -18,6 +17,7 @@ import { getModelUUID } from "store/juju/selectors";
import { pluralize } from "store/juju/utils/models";
import type { RootState } from "store/store";
import { useAppStore } from "store/store";
+import { logger } from "utils/logger";
import ActionOptions from "./ActionOptions";
import ConfirmationDialog from "./ConfirmationDialog";
@@ -101,7 +101,7 @@ export default function ActionsPanel(): JSX.Element {
})
.catch((error) => {
setInlineErrors(InlineErrors.GET_ACTION, Label.GET_ACTIONS_ERROR);
- log.error(Label.GET_ACTIONS_ERROR, error);
+ logger.error(Label.GET_ACTIONS_ERROR, error);
})
.finally(() => {
setFetchingActionData(false);
diff --git a/src/panels/ActionsPanel/ConfirmationDialog/ConfirmationDialog.test.tsx b/src/panels/ActionsPanel/ConfirmationDialog/ConfirmationDialog.test.tsx
index 4a3858de0..d356dae9c 100644
--- a/src/panels/ActionsPanel/ConfirmationDialog/ConfirmationDialog.test.tsx
+++ b/src/panels/ActionsPanel/ConfirmationDialog/ConfirmationDialog.test.tsx
@@ -1,6 +1,5 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import log from "loglevel";
import { vi } from "vitest";
import * as actionsHooks from "juju/api-hooks/actions";
@@ -12,23 +11,11 @@ import { InlineErrors } from "../types";
import ConfirmationDialog from "./ConfirmationDialog";
import { Label } from "./types";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("ConfirmationDialog", () => {
const path = "/models/:userName/:modelName/app/:appName";
const url =
"/models/user-eggman@external/group-test/app/kubernetes-master?panel=execute-action&units=ceph%2F0,ceph%2F1";
- beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
- });
-
afterEach(() => {
vi.resetModules();
vi.restoreAllMocks();
@@ -146,9 +133,5 @@ describe("ConfirmationDialog", () => {
InlineErrors.EXECUTE_ACTION,
Label.EXECUTE_ACTION_ERROR,
);
- expect(log.error).toHaveBeenCalledWith(
- Label.EXECUTE_ACTION_ERROR,
- new Error("mock error"),
- );
});
});
diff --git a/src/panels/ActionsPanel/ConfirmationDialog/ConfirmationDialog.tsx b/src/panels/ActionsPanel/ConfirmationDialog/ConfirmationDialog.tsx
index d43632525..7f427dfba 100644
--- a/src/panels/ActionsPanel/ConfirmationDialog/ConfirmationDialog.tsx
+++ b/src/panels/ActionsPanel/ConfirmationDialog/ConfirmationDialog.tsx
@@ -1,5 +1,4 @@
import { ConfirmationModal } from "@canonical/react-components";
-import log from "loglevel";
import { useParams } from "react-router";
import usePortal from "react-useportal";
@@ -8,6 +7,7 @@ import { type SetError } from "hooks/useInlineErrors";
import { useExecuteActionOnUnits } from "juju/api-hooks";
import type { ConfirmTypes } from "panels/types";
import { ConfirmType } from "panels/types";
+import { logger } from "utils/logger";
import { InlineErrors, type ActionOptionValue } from "../types";
@@ -68,7 +68,7 @@ const ConfirmationDialog = ({
Label.EXECUTE_ACTION_ERROR,
);
setIsExecutingAction(false);
- log.error(Label.EXECUTE_ACTION_ERROR, error);
+ logger.error(Label.EXECUTE_ACTION_ERROR, error);
});
}}
close={() => setConfirmType(null)}
diff --git a/src/panels/CharmsAndActionsPanel/CharmsAndActionsPanel.test.tsx b/src/panels/CharmsAndActionsPanel/CharmsAndActionsPanel.test.tsx
index 90bab568c..26072e488 100644
--- a/src/panels/CharmsAndActionsPanel/CharmsAndActionsPanel.test.tsx
+++ b/src/panels/CharmsAndActionsPanel/CharmsAndActionsPanel.test.tsx
@@ -1,6 +1,5 @@
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import log from "loglevel";
import { vi } from "vitest";
import * as juju from "juju/api";
@@ -26,14 +25,6 @@ import { CharmsPanelLabel } from "../CharmsPanel";
import CharmsAndActionsPanel from "./CharmsAndActionsPanel";
import { Label as CharmsAndActionsPanelLabel } from "./types";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("CharmsAndActionsPanel", () => {
let state: RootState;
const path = urls.model.index(null);
@@ -43,7 +34,6 @@ describe("CharmsAndActionsPanel", () => {
});
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
vi.resetAllMocks();
state = rootStateFactory.build({
@@ -167,14 +157,10 @@ describe("CharmsAndActionsPanel", () => {
} = renderComponent(, { path, url, state });
expect(juju.getCharmsURLFromApplications).toHaveBeenCalledTimes(1);
await waitFor(() =>
- expect(log.error).toHaveBeenCalledWith(
- CharmsAndActionsPanelLabel.GET_URL_ERROR,
- new Error("Error while calling getCharmsURLFromApplications"),
+ expect(container.querySelector(".p-panel__title")).toContainHTML(
+ CharmsPanelLabel.PANEL_TITLE,
),
);
- expect(container.querySelector(".p-panel__title")).toContainHTML(
- CharmsPanelLabel.PANEL_TITLE,
- );
const getCharmsURLErrorNotification = screen.getByText(
CharmsAndActionsPanelLabel.GET_URL_ERROR,
{ exact: false },
diff --git a/src/panels/CharmsAndActionsPanel/CharmsAndActionsPanel.tsx b/src/panels/CharmsAndActionsPanel/CharmsAndActionsPanel.tsx
index 747015c92..26681aa83 100644
--- a/src/panels/CharmsAndActionsPanel/CharmsAndActionsPanel.tsx
+++ b/src/panels/CharmsAndActionsPanel/CharmsAndActionsPanel.tsx
@@ -1,5 +1,4 @@
import { Button } from "@canonical/react-components";
-import log from "loglevel";
import { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useParams } from "react-router";
@@ -16,6 +15,7 @@ import {
getSelectedApplications,
} from "store/juju/selectors";
import { useAppStore } from "store/store";
+import { logger } from "utils/logger";
import { Label } from "./types";
@@ -77,7 +77,7 @@ const CharmsAndActionsPanel = () => {
})
.catch((error) => {
setInlineErrors(InlineErrors.GET_URL, Label.GET_URL_ERROR);
- log.error(Label.GET_URL_ERROR, error);
+ logger.error(Label.GET_URL_ERROR, error);
});
}, [dispatch, getState, modelUUID, selectedApplications, setInlineErrors]);
diff --git a/src/panels/ConfigPanel/ConfigPanel.test.tsx b/src/panels/ConfigPanel/ConfigPanel.test.tsx
index a143ab627..7794842db 100644
--- a/src/panels/ConfigPanel/ConfigPanel.test.tsx
+++ b/src/panels/ConfigPanel/ConfigPanel.test.tsx
@@ -1,6 +1,5 @@
import { screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import log from "loglevel";
import type { Mock } from "vitest";
import { vi } from "vitest";
@@ -51,14 +50,6 @@ vi.mock("juju/api-hooks/secrets", () => {
};
});
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("ConfigPanel", () => {
let state: RootState;
const params = new URLSearchParams({
@@ -72,7 +63,6 @@ describe("ConfigPanel", () => {
let getApplicationConfig: Mock;
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
vi.resetModules();
vi.spyOn(secretHooks, "useListSecrets").mockImplementation(() => vi.fn());
state = rootStateFactory.build({
@@ -401,13 +391,7 @@ describe("ConfigPanel", () => {
);
renderComponent(, { state, path, url });
expect(getApplicationConfig).toHaveBeenCalledTimes(1);
- await waitFor(() => {
- expect(log.error).toHaveBeenCalledWith(
- ConfigPanelLabel.GET_CONFIG_ERROR,
- new Error("Error while calling getApplicationConfig"),
- );
- });
- const configErrorNotification = screen.getByText(
+ const configErrorNotification = await screen.findByText(
ConfigPanelLabel.GET_CONFIG_ERROR,
{
exact: false,
@@ -457,12 +441,6 @@ describe("ConfigPanel", () => {
name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON,
}),
);
- await waitFor(() =>
- expect(log.error).toHaveBeenCalledWith(
- ConfirmationDialogLabel.SUBMIT_TO_JUJU_ERROR,
- new Error("Error while trying to save"),
- ),
- );
expect(
screen.getByText(ConfirmationDialogLabel.SUBMIT_TO_JUJU_ERROR, {
exact: false,
diff --git a/src/panels/ConfigPanel/ConfigPanel.tsx b/src/panels/ConfigPanel/ConfigPanel.tsx
index 4641c947b..8f6abe93b 100644
--- a/src/panels/ConfigPanel/ConfigPanel.tsx
+++ b/src/panels/ConfigPanel/ConfigPanel.tsx
@@ -2,7 +2,6 @@ import type { ListSecretResult } from "@canonical/jujulib/dist/api/facades/secre
import { ActionButton, Button } from "@canonical/react-components";
import classnames from "classnames";
import cloneDeep from "clone-deep";
-import log from "loglevel";
import type { MouseEvent } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useParams } from "react-router";
@@ -24,6 +23,7 @@ import { actions as jujuActions } from "store/juju";
import { getModelSecrets, getModelByUUID } from "store/juju/selectors";
import { useAppSelector, useAppDispatch } from "store/store";
import { secretIsAppOwned } from "utils";
+import { logger } from "utils/logger";
import BooleanConfig from "./BooleanConfig";
import type { SetNewValue, SetSelectedConfig } from "./ConfigField";
@@ -362,7 +362,7 @@ function getConfig(
})
.catch((error) => {
setInlineError(InlineErrors.GET_CONFIG, Label.GET_CONFIG_ERROR);
- log.error(Label.GET_CONFIG_ERROR, error);
+ logger.error(Label.GET_CONFIG_ERROR, error);
})
.finally(() => setIsLoading(false));
}
diff --git a/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.test.tsx b/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.test.tsx
index 81c3292a9..79e3e09d2 100644
--- a/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.test.tsx
+++ b/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.test.tsx
@@ -1,6 +1,5 @@
-import { screen, waitFor } from "@testing-library/react";
+import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import log from "loglevel";
import type { Mock } from "vitest";
import { vi } from "vitest";
@@ -24,14 +23,6 @@ import { ConfigConfirmType } from "../types";
import ConfirmationDialog from "./ConfirmationDialog";
import { InlineErrors, Label, Label as ConfirmationDialogLabel } from "./types";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("ConfirmationDialog", () => {
let state: RootState;
const params = new URLSearchParams({
@@ -79,7 +70,6 @@ describe("ConfirmationDialog", () => {
};
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
mockSetConfirmType = vi.fn();
mockSetInlineError = vi.fn();
mockHandleRemovePanelQueryParams = vi.fn();
@@ -177,7 +167,6 @@ describe("ConfirmationDialog", () => {
default: "eggman",
}),
});
- expect(log.error).not.toHaveBeenCalled();
expect(mockHandleRemovePanelQueryParams).toHaveBeenCalledOnce();
});
@@ -229,7 +218,6 @@ describe("ConfirmationDialog", () => {
default: "eggman",
}),
});
- expect(log.error).not.toHaveBeenCalled();
expect(mockSetConfirmType).toHaveBeenCalledWith(ConfigConfirmType.GRANT);
});
@@ -275,12 +263,6 @@ describe("ConfirmationDialog", () => {
default: "eggman",
}),
});
- await waitFor(() =>
- expect(log.error).toHaveBeenCalledWith(
- Label.SUBMIT_TO_JUJU_ERROR,
- new Error("Error while trying to save"),
- ),
- );
expect(mockSetInlineError).toHaveBeenCalledWith(
InlineErrors.SUBMIT_TO_JUJU,
Label.SUBMIT_TO_JUJU_ERROR,
@@ -375,7 +357,6 @@ describe("ConfirmationDialog", () => {
expect(mockSetConfirmType).toHaveBeenCalledWith(null);
expect(mockSetInlineError).toHaveBeenCalledWith(InlineErrors.FORM, null);
expect(grantSecret).toHaveBeenCalledWith("secret:aabbccdd", ["easyrsa"]);
- expect(log.error).not.toHaveBeenCalled();
expect(mockHandleRemovePanelQueryParams).toHaveBeenCalledOnce();
});
@@ -418,12 +399,6 @@ describe("ConfirmationDialog", () => {
expect(mockSetConfirmType).toHaveBeenCalledWith(null);
expect(mockSetInlineError).toHaveBeenCalledWith(InlineErrors.FORM, null);
expect(grantSecret).toHaveBeenCalledWith("secret:aabbccdd", ["easyrsa"]);
- await waitFor(() =>
- expect(log.error).toHaveBeenCalledWith(
- Label.GRANT_ERROR,
- new Error("Caught error"),
- ),
- );
expect(mockSetInlineError).toHaveBeenCalledWith(
InlineErrors.SUBMIT_TO_JUJU,
Label.GRANT_ERROR,
diff --git a/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.tsx b/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.tsx
index 069504e8e..574ba3863 100644
--- a/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.tsx
+++ b/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.tsx
@@ -1,5 +1,4 @@
import { ConfirmationModal } from "@canonical/react-components";
-import log from "loglevel";
import { useParams } from "react-router";
import usePortal from "react-useportal";
@@ -13,6 +12,7 @@ import type { usePanelQueryParams } from "panels/hooks";
import { ConfirmType as DefaultConfirmType } from "panels/types";
import { getModelSecrets } from "store/juju/selectors";
import { useAppSelector } from "store/store";
+import { logger } from "utils/logger";
import ChangedKeyValues from "../ChangedKeyValues";
import type { Config, ConfigQueryParams, ConfirmTypes } from "../types";
@@ -110,7 +110,7 @@ const ConfirmationDialog = ({
InlineErrors.SUBMIT_TO_JUJU,
Label.SUBMIT_TO_JUJU_ERROR,
);
- log.error(Label.SUBMIT_TO_JUJU_ERROR, error);
+ logger.error(Label.SUBMIT_TO_JUJU_ERROR, error);
});
}}
close={() => setConfirmType(null)}
@@ -150,7 +150,7 @@ const ConfirmationDialog = ({
handleRemovePanelQueryParams();
} catch (error) {
setInlineError(InlineErrors.SUBMIT_TO_JUJU, Label.GRANT_ERROR);
- log.error(Label.GRANT_ERROR, error);
+ logger.error(Label.GRANT_ERROR, error);
}
})();
}}
diff --git a/src/store/app/thunks.test.ts b/src/store/app/thunks.test.ts
index 876feeb8a..5c24fb3fd 100644
--- a/src/store/app/thunks.test.ts
+++ b/src/store/app/thunks.test.ts
@@ -1,4 +1,3 @@
-import log from "loglevel";
import { vi } from "vitest";
import { pollWhoamiStop } from "juju/jimm/listeners";
@@ -21,19 +20,10 @@ import type { WindowConfig } from "types";
import { logOut, connectAndStartPolling } from "./thunks";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("thunks", () => {
let state: RootState;
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
window.jujuDashboardConfig = {
controllerAPIEndpoint: "wss://example.com/api",
} as WindowConfig;
@@ -161,10 +151,6 @@ describe("thunks", () => {
isJuju: true,
}),
);
- expect(log.error).toHaveBeenCalledWith(
- "Error while triggering the connection and polling of models.",
- "Error while dispatching connectAndPollControllers!",
- );
expect(dispatch).toHaveBeenCalledWith(
generalActions.storeConnectionError(
"Unable to connect: Error while dispatching connectAndPollControllers!",
@@ -192,10 +178,6 @@ describe("thunks", () => {
isJuju: true,
}),
);
- expect(log.error).toHaveBeenCalledWith(
- "Error while triggering the connection and polling of models.",
- new Error("Error while dispatching connectAndPollControllers!"),
- );
expect(dispatch).toHaveBeenCalledWith(
generalActions.storeConnectionError(
"Unable to connect: Error while dispatching connectAndPollControllers!",
@@ -224,10 +206,6 @@ describe("thunks", () => {
isJuju: true,
}),
);
- expect(log.error).toHaveBeenCalledWith(
- "Error while triggering the connection and polling of models.",
- ["Unknown error."],
- );
expect(dispatch).toHaveBeenCalledWith(
generalActions.storeConnectionError(
"Unable to connect: Something went wrong. View the console log for more details.",
diff --git a/src/store/app/thunks.ts b/src/store/app/thunks.ts
index cae2977f6..ae724b7af 100644
--- a/src/store/app/thunks.ts
+++ b/src/store/app/thunks.ts
@@ -1,5 +1,4 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
-import log from "loglevel";
import bakery from "juju/bakery";
import { pollWhoamiStop } from "juju/jimm/listeners";
@@ -16,6 +15,7 @@ import {
import { AuthMethod } from "store/general/types";
import { actions as jujuActions } from "store/juju";
import type { RootState } from "store/store";
+import { logger } from "utils/logger";
import type { ControllerArgs } from "./actions";
@@ -85,7 +85,7 @@ export const connectAndStartPolling = createAsyncThunk<
} catch (error) {
// a common error logged to the console by this is:
// Error while triggering the connection and polling of models. cannot send request {"type":"ModelManager","request":"ListModels","version":5,"params":...}: connection state 3 is not open
- log.error(
+ logger.error(
"Error while triggering the connection and polling of models.",
error,
);
diff --git a/src/store/middleware/check-auth.test.ts b/src/store/middleware/check-auth.test.ts
index 398e1a265..30c6cf2bc 100644
--- a/src/store/middleware/check-auth.test.ts
+++ b/src/store/middleware/check-auth.test.ts
@@ -1,4 +1,3 @@
-import log from "loglevel";
import type { UnknownAction, MiddlewareAPI } from "redux";
import type { Mock } from "vitest";
import { vi } from "vitest";
@@ -15,14 +14,6 @@ import {
import { checkAuthMiddleware } from "./check-auth";
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("model poller", () => {
let fakeStore: MiddlewareAPI;
let next: Mock;
@@ -31,7 +22,6 @@ describe("model poller", () => {
let state: RootState;
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
console.log = vi.fn();
vi.useFakeTimers();
next = vi.fn();
diff --git a/src/store/middleware/check-auth.ts b/src/store/middleware/check-auth.ts
index ae9bb9a38..df0dcd104 100644
--- a/src/store/middleware/check-auth.ts
+++ b/src/store/middleware/check-auth.ts
@@ -3,7 +3,6 @@
has been allowed.
*/
-import log from "loglevel";
import { isAction, type Middleware } from "redux";
import * as jimmListeners from "juju/jimm/listeners";
@@ -15,10 +14,11 @@ import { actions as jujuActions } from "store/juju";
import { addControllerCloudRegion } from "store/juju/thunks";
import type { RootState, Store } from "store/store";
import { isPayloadAction } from "types";
+import { logger } from "utils/logger";
function error(name: string, wsControllerURL?: string | null) {
// Not shown in UI. Logged for debugging purposes.
- log.error(
+ logger.error(
"Unable to perform action: ",
name,
wsControllerURL
@@ -33,7 +33,7 @@ function error(name: string, wsControllerURL?: string | null) {
export const checkLoggedIn = (state: RootState, wsControllerURL: string) => {
if (!wsControllerURL) {
// Not shown in UI. Logged for debugging purposes.
- log.error(
+ logger.error(
"Unable to determine logged in status. " +
"'wsControllerURL' was not provided in the action that was dispatched.",
);
diff --git a/src/store/middleware/model-poller.test.ts b/src/store/middleware/model-poller.test.ts
index 3bb3d25fb..c5e94e574 100644
--- a/src/store/middleware/model-poller.test.ts
+++ b/src/store/middleware/model-poller.test.ts
@@ -1,5 +1,4 @@
import type { Client, Connection, Transport } from "@canonical/jujulib";
-import log from "loglevel";
import type { UnknownAction, MiddlewareAPI } from "redux";
import type { Mock } from "vitest";
import { vi } from "vitest";
@@ -41,14 +40,6 @@ vi.mock("juju/jimm/api", () => ({
findAuditEvents: vi.fn(),
}));
-vi.mock("loglevel", async () => {
- const actual = await vi.importActual("loglevel");
- return {
- ...actual,
- error: vi.fn(),
- };
-});
-
describe("model poller", () => {
let fakeStore: MiddlewareAPI;
let next: Mock;
@@ -93,7 +84,6 @@ describe("model poller", () => {
});
beforeEach(() => {
- vi.spyOn(log, "error").mockImplementation(() => vi.fn());
vi.useFakeTimers();
next = vi.fn();
fakeStore = {
@@ -495,10 +485,6 @@ describe("model poller", () => {
new Error(ModelsError.LOAD_ALL_MODELS),
);
await runMiddleware();
- expect(log.error).toHaveBeenCalledWith(
- ModelsError.LOAD_ALL_MODELS,
- new Error(ModelsError.LOAD_ALL_MODELS),
- );
expect(fakeStore.dispatch).toHaveBeenCalledWith(
jujuActions.updateModelsError({
modelsError: ModelsError.LOAD_ALL_MODELS,
@@ -518,10 +504,6 @@ describe("model poller", () => {
new Error(ModelsError.LOAD_SOME_MODELS),
);
await runMiddleware();
- expect(log.error).toHaveBeenCalledWith(
- ModelsError.LOAD_SOME_MODELS,
- new Error(ModelsError.LOAD_SOME_MODELS),
- );
expect(fakeStore.dispatch).toHaveBeenCalledWith(
jujuActions.updateModelsError({
modelsError: ModelsError.LOAD_SOME_MODELS,
@@ -549,14 +531,10 @@ describe("model poller", () => {
);
runMiddleware({
payload: { controllers, isJuju: true, poll: 1 },
- }).catch(log.error);
+ }).catch(() => vi.fn());
vi.advanceTimersByTime(30000);
// Resolve the async calls again.
await vi.runAllTimersAsync();
- expect(log.error).toHaveBeenCalledWith(
- ModelsError.LOAD_LATEST_MODELS,
- new Error(ModelsError.LOAD_SOME_MODELS),
- );
expect(fakeStore.dispatch).toHaveBeenCalledWith(
jujuActions.updateModelsError({
modelsError: ModelsError.LOAD_LATEST_MODELS,
@@ -578,10 +556,6 @@ describe("model poller", () => {
}),
);
await runMiddleware();
- expect(log.error).toHaveBeenCalledWith(
- ModelsError.LIST_OR_UPDATE_MODELS,
- new Error(ModelsError.LIST_OR_UPDATE_MODELS),
- );
expect(fakeStore.dispatch).toHaveBeenCalledWith(
jujuActions.updateModelsError({
modelsError: ModelsError.LIST_OR_UPDATE_MODELS,
@@ -599,7 +573,7 @@ describe("model poller", () => {
juju,
}));
runMiddleware({ payload: { controllers, isJuju: true, poll: 1 } }).catch(
- log.error,
+ () => vi.fn(),
);
vi.advanceTimersByTime(30000);
// Resolve the async calls again.
@@ -736,10 +710,6 @@ describe("model poller", () => {
expect(fakeStore.dispatch).toHaveBeenCalledWith(
jujuActions.updateAuditEventsErrors("Uh oh!"),
);
- expect(log.error).toHaveBeenCalledWith(
- "Could not fetch audit events.",
- new Error("Uh oh!"),
- );
});
it("handles fetching cross model query results", async () => {
@@ -826,10 +796,6 @@ describe("model poller", () => {
query: ".",
});
await middleware(next)(action);
- expect(log.error).toHaveBeenCalledWith(
- "Could not perform cross model query.",
- new Error("Uh oh!"),
- );
expect(fakeStore.dispatch).toHaveBeenCalledWith(
jujuActions.updateCrossModelQueryErrors("Uh oh!"),
);
@@ -850,10 +816,6 @@ describe("model poller", () => {
query: ".",
});
await middleware(next)(action);
- expect(log.error).toHaveBeenCalledWith(
- "Could not perform cross model query.",
- "Uh oh!",
- );
expect(fakeStore.dispatch).toHaveBeenCalledWith(
jujuActions.updateCrossModelQueryErrors(
"Unable to perform search. Please try again later.",
diff --git a/src/store/middleware/model-poller.ts b/src/store/middleware/model-poller.ts
index d3c755a95..e505ec385 100644
--- a/src/store/middleware/model-poller.ts
+++ b/src/store/middleware/model-poller.ts
@@ -1,7 +1,6 @@
import type { Client } from "@canonical/jujulib";
import { unwrapResult } from "@reduxjs/toolkit";
import * as Sentry from "@sentry/browser";
-import log from "loglevel";
import { isAction, type Middleware } from "redux";
import {
@@ -23,6 +22,7 @@ import { actions as jujuActions } from "store/juju";
import type { RootState, Store } from "store/store";
import { isSpecificAction } from "types";
import { toErrorString } from "utils";
+import { logger } from "utils/logger";
export enum LoginError {
LOG = "Unable to log into controller.",
@@ -236,7 +236,7 @@ export const modelPollerMiddleware: Middleware<
} else {
errorMessage = ModelsError.LIST_OR_UPDATE_MODELS;
}
- log.error(errorMessage, error);
+ logger.error(errorMessage, error);
reduxStore.dispatch(
jujuActions.updateModelsError({
modelsError: errorMessage,
@@ -306,7 +306,7 @@ export const modelPollerMiddleware: Middleware<
const auditEvents = await findAuditEvents(conn, params);
reduxStore.dispatch(jujuActions.updateAuditEvents(auditEvents.events));
} catch (error) {
- log.error("Could not fetch audit events.", error);
+ logger.error("Could not fetch audit events.", error);
reduxStore.dispatch(
jujuActions.updateAuditEventsErrors(toErrorString(error)),
);
@@ -342,7 +342,7 @@ export const modelPollerMiddleware: Middleware<
),
);
} catch (error) {
- log.error("Could not perform cross model query.", error);
+ logger.error("Could not perform cross model query.", error);
const errorMessage =
error instanceof Error
? error.message
diff --git a/src/store/store.ts b/src/store/store.ts
index b51377709..2016e2ebc 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -1,6 +1,5 @@
import type { UnknownAction } from "@reduxjs/toolkit";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
-import log from "loglevel";
import { useCallback } from "react";
import type { TypedUseSelectorHook } from "react-redux";
import { useDispatch, useSelector, useStore } from "react-redux";
@@ -9,6 +8,7 @@ import generalReducer from "store/general";
import jujuReducer from "store/juju";
import checkAuth from "store/middleware/check-auth";
import { modelPollerMiddleware } from "store/middleware/model-poller";
+import { logger } from "utils/logger";
import { listenerMiddleware } from "./listenerMiddleware";
@@ -17,7 +17,7 @@ if (!import.meta.env.PROD && process.env.VITE_APP_MOCK_STORE) {
try {
preloadedState = JSON.parse(process.env.VITE_APP_MOCK_STORE);
} catch (error) {
- log.error("VITE_APP_MOCK_STORE could not be parsed");
+ logger.error("VITE_APP_MOCK_STORE could not be parsed");
}
}
diff --git a/src/testing/setup.ts b/src/testing/setup.ts
index c43293aa3..4485e8993 100644
--- a/src/testing/setup.ts
+++ b/src/testing/setup.ts
@@ -3,6 +3,8 @@ import type { DetachedWindowAPI } from "happy-dom";
import { vi } from "vitest";
import createFetchMock from "vitest-fetch-mock";
+import { logger } from "utils/logger";
+
vi.mock("react-ga");
const fetchMocker = createFetchMock(vi);
@@ -17,6 +19,8 @@ declare global {
var jest: object;
}
+logger.setDefaultLevel(logger.levels.SILENT);
+
// Fix for RTL using fake timers:
// https://github.com/testing-library/user-event/issues/1115#issuecomment-1565730917
globalThis.jest = {
diff --git a/src/utils/logger.ts b/src/utils/logger.ts
new file mode 100644
index 000000000..b2c8232b7
--- /dev/null
+++ b/src/utils/logger.ts
@@ -0,0 +1,3 @@
+import log from "loglevel";
+
+export const logger = log.getLogger("JujuDashboard");
diff --git a/yarn.lock b/yarn.lock
index 7d799b909..4d9427077 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8357,7 +8357,7 @@ __metadata:
jest-websocket-mock: "npm:2.5.0"
lodash.isequal: "npm:4.5.0"
lodash.mergewith: "npm:4.6.2"
- loglevel: "npm:^1.9.2"
+ loglevel: "npm:1.9.2"
npm-package-json-lint: "npm:8.0.0"
postcss: "npm:8.4.49"
prettier: "npm:3.4.2"
@@ -8535,7 +8535,7 @@ __metadata:
languageName: node
linkType: hard
-"loglevel@npm:1.9.2, loglevel@npm:^1.9.2":
+"loglevel@npm:1.9.2":
version: 1.9.2
resolution: "loglevel@npm:1.9.2"
checksum: 10c0/1e317fa4648fe0b4a4cffef6de037340592cee8547b07d4ce97a487abe9153e704b98451100c799b032c72bb89c9366d71c9fb8192ada8703269263ae77acdc7