diff --git a/web/src/composables/useLogs/useStreamFields.ts b/web/src/composables/useLogs/useStreamFields.ts index e74d4a0558..52439ef8fe 100644 --- a/web/src/composables/useLogs/useStreamFields.ts +++ b/web/src/composables/useLogs/useStreamFields.ts @@ -1033,6 +1033,8 @@ export const useStreamFields = () => { ); return; } + // A page that stays mounted keeps an earlier link's not-found message until a readable URL stream replaces it. + if (pick.source === "url") searchObj.data.filterErrMsg = ""; const current = searchObj.data.stream.selectedStream; if ( !Array.isArray(current) || diff --git a/web/src/plugins/logs/Index.autoRun.spec.ts b/web/src/plugins/logs/Index.autoRun.spec.ts index 4492438126..6487329046 100644 --- a/web/src/plugins/logs/Index.autoRun.spec.ts +++ b/web/src/plugins/logs/Index.autoRun.spec.ts @@ -191,6 +191,91 @@ describe("Logs Index — first landing and URL loads (item 2)", { timeout: 30000 expect(logs.mock.calls[0][0]).toMatchObject({ reason: "url", kind: "entry" }); }); + describe("re-applied query links (AI chat, search history)", () => { + const reApply = async (query: Record) => { + store.state.zoConfig = { ...originalZoConfig, auto_query_enabled: true }; + (store.state as any).logs = { isInitialized: false, logs: {} }; + vi.spyOn(store, "dispatch").mockResolvedValue(undefined as any); + (getFieldsFromQuery as Mock).mockResolvedValue({ fields: [], filters: [], streamName: "" }); + await router.replace({ name: "logs", query: { org_identifier: org, ...query } }); + wrapper = mount(Index, { + attachTo: "#app", + global: { provide: { store }, plugins: [i18n, router] }, + }); + await vi.waitFor( + () => expect(wrapper.vm.searchObj.data.stream.streamLists.length).toBeGreaterThan(0), + { timeout: 10000 }, + ); + await flushPromises(); + }; + + it.each(["ai_chat_query", "search_history_re_apply"])( + "%s naming a missing stream shows stream-not-found", + async (type) => { + await reApply({ type, stream: "app_query_metrics", stream_type: "logs", period: "15m" }); + expect(wrapper.vm.searchObj.data.stream.selectedStream).toEqual([]); + expect(wrapper.vm.searchObj.data.filterErrMsg).toContain("app_query_metrics"); + await vi.waitFor(() => + expect(wrapper.find('[data-test="logs-search-filter-error-message"]').exists()).toBe( + true, + ), + ); + }, + ); + + it.each(["ai_chat_query", "search_history_re_apply"])( + "%s naming an existing stream selects it with no error", + async (type) => { + await reApply({ type, stream: "k8s_json", stream_type: "logs", period: "15m" }); + expect(wrapper.vm.searchObj.data.stream.selectedStream).toEqual(["k8s_json"]); + expect(wrapper.vm.searchObj.data.filterErrMsg).toBe(""); + }, + ); + + it("an ai_chat_query link opened on a loaded page shows stream-not-found", async () => { + await reApply({ stream: "k8s_json", stream_type: "logs", period: "15m" }); + await router.replace({ + name: "logs", + query: { + org_identifier: org, + type: "ai_chat_query", + stream: "app_query_metrics", + stream_type: "logs", + period: "15m", + }, + }); + await vi.waitFor(() => + expect(wrapper.vm.searchObj.data.filterErrMsg).toContain("app_query_metrics"), + ); + expect(wrapper.vm.searchObj.data.stream.selectedStream).toEqual([]); + }); + + it("re-applying an existing stream after a missing one clears the not-found error", async () => { + await reApply({ + type: "ai_chat_query", + stream: "app_query_metrics", + stream_type: "logs", + period: "15m", + }); + expect(wrapper.vm.searchObj.data.filterErrMsg).toContain("app_query_metrics"); + await router.replace({ + name: "logs", + query: { + org_identifier: org, + type: "search_history_re_apply", + stream: "k8s_json", + stream_type: "logs", + period: "15m", + }, + }); + await vi.waitFor(() => + expect(wrapper.vm.searchObj.data.stream.selectedStream).toEqual(["k8s_json"]), + ); + await flushPromises(); + expect(wrapper.vm.searchObj.data.filterErrMsg).toBe(""); + }); + }); + describe("entry points and explicit runs after landing", () => { const ready = async (zoConfig: Record = {}) => { const logs = await land( diff --git a/web/src/plugins/logs/Index.vue b/web/src/plugins/logs/Index.vue index 70d08c4577..226be2d499 100644 --- a/web/src/plugins/logs/Index.vue +++ b/web/src/plugins/logs/Index.vue @@ -1219,7 +1219,7 @@ export default defineComponent({ // loadLogsData(); //instead of loadLogsData so I have used all the functions that are used in that and removed getQuerydata from the list //of functions of loadLogsData to stop run query whenever this gets redirecited - await getStreamList(); + await getStreamList(true, { fromUrl: true }); await getFunctions(); await extractFields(); refreshData(); @@ -1709,7 +1709,7 @@ export default defineComponent({ async function applyReAppliedQuery() { resetRunStateForReapply(); searchObj.meta.searchApplied = false; - await getStreamList(); + await getStreamList(true, { fromUrl: true }); await getFunctions(); await extractFields(); refreshData();