mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
fix(web): stop O2 AI chat opening via the ungated Ctrl+B shortcut on OSS (#14165)
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech> Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
This commit is contained in:
co-authored by
ktx-vaidehi
ktx-vaidehi
parent
0e557a11f9
commit
630ecf41f3
@@ -113,6 +113,7 @@ vi.mock("@/composables/contextProviders", () => ({
|
||||
|
||||
// Component import must come after all vi.mock() declarations.
|
||||
import O2AIChat from "./O2AIChat.vue";
|
||||
import { getManager, resetManager } from "@/lib/vue-shortcut-manager";
|
||||
|
||||
// ── Stub definitions ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -241,6 +242,23 @@ describe("O2AIChat", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ctrl+B ownership", () => {
|
||||
beforeEach(() => resetManager());
|
||||
afterEach(() => resetManager());
|
||||
|
||||
// A second ctrl+b here overwrites MainLayout's gated aiChatToggle (same key + scope).
|
||||
it("should not register ctrl+b, leaving the gated global binding in place", () => {
|
||||
wrapper = mountO2AIChat({ isOpen: false });
|
||||
|
||||
const keys = getManager()!
|
||||
.getAll()
|
||||
.map((s) => s.key);
|
||||
expect(keys).not.toContain("ctrl+b");
|
||||
expect(keys).not.toContain("meta+b");
|
||||
expect(keys).toContain("escape");
|
||||
});
|
||||
});
|
||||
|
||||
describe("addNewChat", () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mountO2AIChat({ isOpen: true });
|
||||
|
||||
@@ -3536,13 +3536,6 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "aiChatExpand",
|
||||
key: "ctrl+b",
|
||||
keyForMac: "meta+b",
|
||||
description: t("shortcuts.actions.aiChatExpand"),
|
||||
handler: toggleExpand,
|
||||
},
|
||||
]);
|
||||
|
||||
const addNewChat = () => {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2026 OpenObserve Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { mount, VueWrapper, flushPromises } from "@vue/test-utils";
|
||||
import { defineComponent, h } from "vue";
|
||||
import store from "@/test/unit/helpers/store";
|
||||
import i18n from "@/locales";
|
||||
import router from "@/test/unit/helpers/router";
|
||||
import { getManager, resetManager } from "@/lib/vue-shortcut-manager";
|
||||
import config from "@/aws-exports";
|
||||
|
||||
vi.mock("@/services/config", () => ({
|
||||
default: { get_config_full: vi.fn(() => new Promise(() => {})) },
|
||||
}));
|
||||
|
||||
vi.mock("@openobserve/browser-rum", () => ({
|
||||
openobserveRum: { setUser: vi.fn(), startView: vi.fn() },
|
||||
}));
|
||||
|
||||
import MainLayout from "./MainLayout.vue";
|
||||
|
||||
const StubDiv = defineComponent({ setup: () => () => h("div") });
|
||||
|
||||
function mountMainLayout() {
|
||||
return mount(MainLayout, {
|
||||
global: {
|
||||
plugins: [store, i18n, router],
|
||||
stubs: {
|
||||
AppHeader: StubDiv,
|
||||
ONavbar: StubDiv,
|
||||
O2AIChat: StubDiv,
|
||||
WebinarBanner: StubDiv,
|
||||
AnnouncementBanner: StubDiv,
|
||||
ShortcutCheatsheet: StubDiv,
|
||||
GetStarted: StubDiv,
|
||||
CommunitySlackInvite: StubDiv,
|
||||
ThemeSwitcher: StubDiv,
|
||||
PredefinedThemes: StubDiv,
|
||||
SlackIcon: StubDiv,
|
||||
ManagementIcon: StubDiv,
|
||||
ODialog: StubDiv,
|
||||
"router-view": StubDiv,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("MainLayout — AI chat shortcut gate", () => {
|
||||
let wrapper: VueWrapper | undefined;
|
||||
const originalIsEnterprise = config.isEnterprise;
|
||||
|
||||
beforeEach(() => {
|
||||
resetManager();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper?.unmount();
|
||||
wrapper = undefined;
|
||||
config.isEnterprise = originalIsEnterprise;
|
||||
store.state.zoConfig.ai_enabled = false;
|
||||
store.state.isAiChatEnabled = false;
|
||||
resetManager();
|
||||
});
|
||||
|
||||
const ctrlB = () => getManager()?.getById("aiChatToggle");
|
||||
|
||||
it("should not register ctrl+b on OSS builds", async () => {
|
||||
config.isEnterprise = "false";
|
||||
store.state.zoConfig.ai_enabled = true;
|
||||
|
||||
wrapper = mountMainLayout();
|
||||
await flushPromises();
|
||||
|
||||
expect(ctrlB()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should register ctrl+b on enterprise builds", async () => {
|
||||
config.isEnterprise = "true";
|
||||
|
||||
wrapper = mountMainLayout();
|
||||
await flushPromises();
|
||||
|
||||
expect(ctrlB()?.key).toBe("ctrl+b");
|
||||
});
|
||||
|
||||
it("should not open the chat while ai_enabled is off", async () => {
|
||||
config.isEnterprise = "true";
|
||||
store.state.zoConfig.ai_enabled = false;
|
||||
|
||||
wrapper = mountMainLayout();
|
||||
await router.push({ name: "logs" });
|
||||
await flushPromises();
|
||||
|
||||
ctrlB()!.handler();
|
||||
expect(store.state.isAiChatEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it("should open the chat when ai_enabled is on", async () => {
|
||||
config.isEnterprise = "true";
|
||||
store.state.zoConfig.ai_enabled = true;
|
||||
|
||||
wrapper = mountMainLayout();
|
||||
await router.push({ name: "logs" });
|
||||
await flushPromises();
|
||||
|
||||
ctrlB()!.handler();
|
||||
expect(store.state.isAiChatEnabled).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1248,6 +1248,8 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const toggleAIChat = () => {
|
||||
// ai_enabled arrives with the async /config response, so it can't gate registration.
|
||||
if (!store.state.zoConfig.ai_enabled) return;
|
||||
// On the home page, switch to the AI tab instead of opening the side panel
|
||||
if (router.currentRoute.value.name === "home") {
|
||||
window.dispatchEvent(new CustomEvent("o2:home-switch-tab", { detail: "ai" }));
|
||||
@@ -1360,7 +1362,10 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
// ── Global shortcuts: AI Chat ─────────────────────────────────────────
|
||||
useShortcuts([{ id: "aiChatToggle", handler: () => toggleAIChat() }]);
|
||||
// O2 AI is enterprise-only: on OSS the Ctrl+B binding must not exist at all.
|
||||
if (config.isEnterprise == "true") {
|
||||
useShortcuts([{ id: "aiChatToggle", handler: () => toggleAIChat() }]);
|
||||
}
|
||||
|
||||
return {
|
||||
isDark,
|
||||
|
||||
Reference in New Issue
Block a user