fix: Issue 1538 autorun loading (#11428)

This commit is contained in:
Bhargav
2026-04-23 17:23:28 +00:00
committed by GitHub
parent bf3f1fbcd8
commit 74fea9cd8f
7 changed files with 67 additions and 36 deletions
@@ -311,34 +311,47 @@ describe("FieldValuesPanel.vue", () => {
expect((wrapper.vm as any).selectedValues).toEqual([]);
});
it("emits 'add-multiple-search-terms' with include when Include mode button clicked", async () => {
wrapper = createWrapper({ showMultiSelect: true, fieldValues: buildFieldValues(3) });
// Set filterMode to 'exclude' while selectedValues is empty so no premature emit
(wrapper.vm as any).filterMode = "exclude";
await nextTick();
(wrapper.vm as any).selectedValues = ["value-1", "value-2"];
await nextTick();
const includeBtn = wrapper.find(
'[data-test="field-values-panel-include-mode-btn"]'
);
await includeBtn.trigger("click");
const emitted = wrapper.emitted("add-multiple-search-terms");
expect(emitted).toBeTruthy();
expect(emitted![0]).toEqual(["status", ["value-1", "value-2"], "include"]);
});
// Mode-switch emissions are debounced (300 ms) so fake timers are required
// to flush the debounce before asserting the emitted event.
describe("mode switch emits (debounced)", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("emits 'add-multiple-search-terms' with exclude when Exclude mode button clicked", async () => {
wrapper = createWrapper({ showMultiSelect: true, fieldValues: buildFieldValues(3) });
// filterMode defaults to 'include'; set selectedValues then click exclude
(wrapper.vm as any).selectedValues = ["value-1"];
await nextTick();
const excludeBtn = wrapper.find(
'[data-test="field-values-panel-exclude-mode-btn"]'
);
await excludeBtn.trigger("click");
const emitted = wrapper.emitted("add-multiple-search-terms");
expect(emitted).toBeTruthy();
expect(emitted![0]).toEqual(["status", ["value-1"], "exclude"]);
it("emits 'add-multiple-search-terms' with include when Include mode button clicked", async () => {
wrapper = createWrapper({ showMultiSelect: true, fieldValues: buildFieldValues(3) });
// Set filterMode to 'exclude' while selectedValues is empty so no premature emit
(wrapper.vm as any).filterMode = "exclude";
await nextTick();
(wrapper.vm as any).selectedValues = ["value-1", "value-2"];
await nextTick();
const includeBtn = wrapper.find(
'[data-test="field-values-panel-include-mode-btn"]'
);
await includeBtn.trigger("click");
vi.runAllTimers();
const emitted = wrapper.emitted("add-multiple-search-terms");
expect(emitted).toBeTruthy();
expect(emitted![0]).toEqual(["status", ["value-1", "value-2"], "include"]);
});
it("emits 'add-multiple-search-terms' with exclude when Exclude mode button clicked", async () => {
wrapper = createWrapper({ showMultiSelect: true, fieldValues: buildFieldValues(3) });
// filterMode defaults to 'include'; set selectedValues then click exclude
(wrapper.vm as any).selectedValues = ["value-1"];
await nextTick();
const excludeBtn = wrapper.find(
'[data-test="field-values-panel-exclude-mode-btn"]'
);
await excludeBtn.trigger("click");
vi.runAllTimers();
const emitted = wrapper.emitted("add-multiple-search-terms");
expect(emitted).toBeTruthy();
expect(emitted![0]).toEqual(["status", ["value-1"], "exclude"]);
});
});
it("does NOT clear selectedValues after applying multi-select include", async () => {
+17 -6
View File
@@ -207,7 +207,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<script setup lang="ts">
import { computed, nextTick, ref, watch } from "vue";
import { watchDebounced } from "@vueuse/core";
import { useDebounceFn, watchDebounced } from "@vueuse/core";
import EqualIcon from "@/components/icons/EqualIcon.vue";
import NotEqualIcon from "@/components/icons/NotEqualIcon.vue";
import { formatLargeNumber } from "@/utils/zincutils";
@@ -323,18 +323,29 @@ watchDebounced(
{ debounce: 300 },
);
/**
* Debounced emission for mode changes. The UI toggle updates immediately
* (instant feedback) but the API call is consolidated so that rapid
* include→exclude→include clicks only fire one search at the end, preventing
* a brief "no events found" flash (from an intermediate result set) that
* would reset the field-list scroll position.
*/
const debouncedEmitModeChange = useDebounceFn((mode: "include" | "exclude") => {
if (selectedValues.value.length > 0) {
emit("add-multiple-search-terms", props.fieldName, [...selectedValues.value], mode);
}
}, 300);
/**
* Called when the user explicitly clicks the include/exclude toggle.
* Sets filterMode and immediately re-applies the current selection under
* the new mode so the query updates in real time.
* Updates filterMode immediately for instant visual feedback, then
* debounces the actual query emission to avoid rapid successive API calls.
* This is the ONLY path that should emit add-multiple-search-terms for
* a mode change — the prop-sync watcher below only updates the ref silently.
*/
const setFilterMode = (mode: "include" | "exclude") => {
filterMode.value = mode;
if (selectedValues.value.length > 0) {
emit("add-multiple-search-terms", props.fieldName, [...selectedValues.value], mode);
}
debouncedEmitModeChange(mode);
};
// Keep filterMode in sync with the external filter state.
+2 -1
View File
@@ -220,7 +220,8 @@ const useLogs = () => {
if (
searchObj.loading == false &&
searchObj.loadingHistogram == false &&
searchObj.meta.logsVisualizeToggle == "logs"
searchObj.meta.logsVisualizeToggle == "logs" &&
searchObj.data.stream.selectedStream.length > 0
) {
searchObj.loading = true;
await getQueryData(false);
@@ -326,6 +326,7 @@ export const useSearchBar = () => {
//if empty then we are displaying no events found... message on the UI instead of throwing in an error format
if (!allStreamFields.length) {
// searchObj.data.errorMsg = t("search.noFieldFound");
searchObj.loading = false;
return;
}
+2
View File
@@ -639,6 +639,8 @@ describe("Logs Index", async () => {
});
it("Should execute runQueryFn and show job scheduler", async () => {
// runQueryFn guards against empty selectedStream, so a stream must be present.
wrapper.vm.searchObj.data.stream.selectedStream = ["stream1"];
// do not rely on spying internal closures; assert state change
await wrapper.vm.runQueryFn();
expect(wrapper.vm.showJobScheduler).toBe(true);
+2
View File
@@ -980,6 +980,7 @@ export default defineComponent({
const runQueryFn = async () => {
// searchObj.data.resultGrid.currentPage = 0;
// searchObj.runQuery = false;
if (!searchObj.data.stream.selectedStream.length) return;
try {
searchObj.loading = true;
searchObj.meta.refreshHistogram = true;
@@ -2252,6 +2253,7 @@ export default defineComponent({
store.state.zoConfig?.auto_query_enabled &&
store.state.zoConfig?.query_on_stream_selection !== false &&
searchObj.meta.logsVisualizeToggle === "logs" &&
searchObj.data.stream.selectedStream.length > 0 &&
!searchObj.loading &&
!searchObj.loadingHistogram
) {
+3 -2
View File
@@ -63,8 +63,9 @@ export const removeFieldCondition = (
fieldName: string,
): string => {
const esc = fieldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const fieldPattern = new RegExp(`^"?${esc}"?\\s*[=!<>]`, "i");
const multiPattern = new RegExp(`^\\(\\s*"?${esc}"?\\s*[=!<>]`, "i");
// Match both comparison operators (=, !=, <, >) and IS / IS NOT (for null checks)
const fieldPattern = new RegExp(`^"?${esc}"?\\s*(?:[=!<>]|is\\s)`, "i");
const multiPattern = new RegExp(`^\\(\\s*"?${esc}"?\\s*(?:[=!<>]|is\\s)`, "i");
const remaining = queryStr
.trim()