diff --git a/web/src/composables/useLogs.ts b/web/src/composables/useLogs.ts
index b3f5886d0f..373213dc74 100644
--- a/web/src/composables/useLogs.ts
+++ b/web/src/composables/useLogs.ts
@@ -15,7 +15,7 @@
import { useQuasar } from "quasar";
import { useI18n } from "vue-i18n";
-import { reactive, onBeforeMount } from "vue";
+import { reactive, onBeforeMount, nextTick } from "vue";
import { useStore } from "vuex";
import { useRouter } from "vue-router";
import { cloneDeep } from "lodash-es";
@@ -285,7 +285,8 @@ const useLogs = () => {
// in-progress and user select stream from dropdown in that case it loads data but it should wait for
// additional details from the user like filter conditions and time range selection before load data
// it should work in case of page refresh, navigate user from streams page or short url
- let initialStreamSelected: boolean = searchObj.data.stream.selectedStream.length > 0;
+ let initialStreamSelected: boolean =
+ searchObj.data.stream.selectedStream.length > 0;
await getStreamList();
await getFunctions();
@@ -355,7 +356,7 @@ const useLogs = () => {
if (
Object.hasOwn(router.currentRoute.value.query, "type") &&
(router.currentRoute.value.query.type == "search_history_re_apply" ||
- router.currentRoute.value.query.type == "ai_chat_query")
+ router.currentRoute.value.query.type == "ai_chat_query")
) {
delete router.currentRoute.value.query.type;
}
@@ -394,6 +395,10 @@ const useLogs = () => {
if (queryParams.query) {
searchObj.meta.sqlMode = queryParams.sql_mode == "true" ? true : false;
+ // Added nextTick() for a purpose, don't remove.
+ // If value of sqlMode is changed, watcher gets called which resets the editor and query value
+ // The editor value and query value assigned below, is overrided by the watcher
+ await nextTick();
searchObj.data.editorValue = b64DecodeUnicode(queryParams.query);
searchObj.data.query = b64DecodeUnicode(queryParams.query);
}
@@ -513,7 +518,10 @@ const useLogs = () => {
}
};
- const reorderArrayByReference = (arr1: string[], arr2: string[]): string[] => {
+ const reorderArrayByReference = (
+ arr1: string[],
+ arr2: string[],
+ ): string[] => {
return [...arr1].sort((a, b) => {
const indexA = arr2.indexOf(a);
const indexB = arr2.indexOf(b);
diff --git a/web/src/composables/useStreams.spec.ts b/web/src/composables/useStreams.spec.ts
index 3d2a0e2ad7..48eb202c9e 100644
--- a/web/src/composables/useStreams.spec.ts
+++ b/web/src/composables/useStreams.spec.ts
@@ -636,14 +636,16 @@ describe("useStreams Composable", () => {
expect(setStreamsCalls[0][1].streams.list).toEqual(existingStreams);
});
- it("should handle addNewStreams with empty cache", () => {
+ it("should not seed cache when addNewStreams called with empty cache", () => {
const newStreams = [{ name: "new-stream", stream_type: "logs" }];
-
+
mockStore.state.streams.logs = null;
-
+
streamsInstance.addNewStreams("logs", newStreams);
-
- expect(mockStore.dispatch).toHaveBeenCalled();
+
+ // Should NOT dispatch — partial results must not seed the cache,
+ // otherwise a full fetch on the logs page would be skipped.
+ expect(mockStore.dispatch).not.toHaveBeenCalled();
});
});
diff --git a/web/src/composables/useStreams.ts b/web/src/composables/useStreams.ts
index 6630081b70..d43a38cb75 100644
--- a/web/src/composables/useStreams.ts
+++ b/web/src/composables/useStreams.ts
@@ -784,6 +784,11 @@ const useStreams = () => {
};
const addNewStreams = (streamType: string, streamList: any[]) => {
+ // Don't seed the cache with partial results — only merge into an
+ // already-populated cache. Otherwise a paginated fetch (e.g. from the
+ // streams management page) would prevent a full fetch on the logs page.
+ if (!streamsCache[streamType]?.value?.list) return;
+
// Check if stream exist in store, if not then add it
const streamsToAdd = [...(streamsCache[streamType].value?.list || [])];
diff --git a/web/src/composables/useTraces.spec.ts b/web/src/composables/useTraces.spec.ts
index b8bccf6b84..822b3fb4aa 100644
--- a/web/src/composables/useTraces.spec.ts
+++ b/web/src/composables/useTraces.spec.ts
@@ -46,6 +46,7 @@ vi.mock("vuex", () => ({
},
},
},
+ dispatch: vi.fn(),
})),
}));
@@ -486,9 +487,9 @@ describe("useTraces", () => {
// navigateToLogs
// -------------------------------------------------------------------------
describe("navigateToLogs", () => {
- it("calls router.push with /logs path", () => {
+ it("calls router.push with /logs path", async () => {
const { navigateToLogs } = useTraces();
- navigateToLogs({
+ await navigateToLogs({
stream: "default",
from: 1000,
to: 2000,
@@ -502,9 +503,9 @@ describe("useTraces", () => {
);
});
- it("passes all query parameters to router.push", () => {
+ it("passes all query parameters to router.push", async () => {
const { navigateToLogs } = useTraces();
- navigateToLogs({
+ await navigateToLogs({
stream: "my-stream",
from: 500,
to: 1000,
diff --git a/web/src/composables/useTraces.ts b/web/src/composables/useTraces.ts
index 1f0791386d..6a574faef4 100644
--- a/web/src/composables/useTraces.ts
+++ b/web/src/composables/useTraces.ts
@@ -13,7 +13,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import { reactive, computed } from "vue";
+import { reactive, computed, nextTick } from "vue";
import {
b64EncodeStandard,
b64EncodeUnicode,
@@ -343,8 +343,8 @@ const useTraces = () => {
return {
stream: searchObj.data.traceDetails.selectedLogStreams.join(","),
- from: span.startTimeMs * 1000 - 60000000,
- to: span.endTimeMs * 1000 + 60000000,
+ from: Math.floor(span.start_time / 1000) - 60000000,
+ to: Math.ceil(span.end_time / 1000) + 60000000,
refresh: 0,
query,
orgIdentifier: store.state.selectedOrganization.identifier,
@@ -352,7 +352,9 @@ const useTraces = () => {
};
// Function to navigate to logs with the provided query details
- const navigateToLogs = (queryDetails: any) => {
+ const navigateToLogs = async (queryDetails: any) => {
+ store.dispatch("logs/setIsInitialized", false);
+ await nextTick();
router.push({
path: "/logs",
query: {
diff --git a/web/src/plugins/logs/Index.vue b/web/src/plugins/logs/Index.vue
index 516617868d..bb652ab36a 100644
--- a/web/src/plugins/logs/Index.vue
+++ b/web/src/plugins/logs/Index.vue
@@ -1148,7 +1148,7 @@ export default defineComponent({
searchObj.meta.showHistogram = isHistogramEnabled();
- restoreUrlQueryParams(dashboardPanelData);
+ await restoreUrlQueryParams(dashboardPanelData);
if (
store.state.zoConfig?.auto_query_enabled &&
@@ -1302,21 +1302,21 @@ export default defineComponent({
};
// Helper function for handling the trace explorer
- function handleTraceExplorer(queryParams) {
+ async function handleTraceExplorer(queryParams) {
searchObj.organizationIdentifier = queryParams.org_identifier;
searchObj.data.stream.selectedStream.value = queryParams.stream;
searchObj.data.stream.streamType = queryParams.stream_type;
resetSearchObj();
resetStreamData();
- restoreUrlQueryParams(dashboardPanelData);
+ await restoreUrlQueryParams(dashboardPanelData);
loadLogsData();
}
// Helper function for handling the stream explorer
- function handleStreamExplorer() {
+ async function handleStreamExplorer() {
resetSearchObj();
resetStreamData();
- restoreUrlQueryParams(dashboardPanelData);
+ await restoreUrlQueryParams(dashboardPanelData);
loadLogsData();
}
diff --git a/web/src/plugins/logs/IndexList.vue b/web/src/plugins/logs/IndexList.vue
index be04529041..695b20e945 100644
--- a/web/src/plugins/logs/IndexList.vue
+++ b/web/src/plugins/logs/IndexList.vue
@@ -24,7 +24,10 @@ along with this program. If not, see .
style="max-width: 100%; overflow: hidden"
>
.
data-cy="index-dropdown"
:placeholder="placeHolderText"
input-debounce="0"
- popup-content-style="height: 400px"
behavior="menu"
borderless
dense
@@ -73,7 +75,7 @@ along with this program. If not, see .
-
+
{{ opt.label }}
@@ -102,7 +104,8 @@ along with this program. If not, see .
data-test="logs-search-no-field-found-text"
class="text-center col-10 q-mx-none q-pt-md"
>
-
+
{{ t("search.noFieldFoundInStream") }}
@@ -135,7 +138,9 @@ along with this program. If not, see .
:active-exclude-field-values="activeExcludeFilterValues"
:expanded-fields="expandedFields"
:selected-streams-count="searchObj.data.stream.selectedStream.length"
- :default-values-count="store.state.zoConfig?.query_values_default_num || 10"
+ :default-values-count="
+ store.state.zoConfig?.query_values_default_num || 10
+ "
:show-user-defined-schema-toggle="showUserDefinedSchemaToggle"
:use-user-defined-schemas="searchObj.meta.useUserDefinedSchemas"
:user-defined-schema-btn-group-option="userDefinedSchemaBtnGroupOption"
@@ -220,10 +225,7 @@ import { useSearchStream } from "@/composables/useLogs/useSearchStream";
import { searchState } from "@/composables/useLogs/searchState";
import { useStreamFields } from "@/composables/useLogs/useStreamFields";
import { captureFromValuesApi } from "@/composables/useFieldValueStore";
-import {
- saveLogsStreamType,
- saveLogsStream,
-} from "@/utils/streamPersist";
+import { saveLogsStreamType, saveLogsStream } from "@/utils/streamPersist";
import { quoteSqlIdentifierIfNeeded } from "@/utils/query/sqlIdentifiers";
interface Filter {
@@ -297,7 +299,8 @@ export default defineComponent({
extractValueQuery,
} = useLogs();
- const { filterHitsColumns, extractFields, getStreamList } = useStreamFields();
+ const { filterHitsColumns, extractFields, getStreamList } =
+ useStreamFields();
const { searchObj, streamSchemaFieldsIndexMapping } = searchState();
@@ -366,16 +369,27 @@ export default defineComponent({
const lastFieldFetchPayloads = ref>({});
// Caches the original (no-keyword) values so clearing the search box
// restores them instantly without a new API call.
- const cachedFieldValues = ref>({});
+ const cachedFieldValues = ref<
+ Record
+ >({});
// Caches the per-stream values alongside cachedFieldValues so "load more"
// appends correctly after a search is cleared.
- const cachedStreamFieldValues = ref>>({});
+ const cachedStreamFieldValues = ref<
+ Record<
+ string,
+ Record
+ >
+ >({});
// Tracks the cumulative size requested per field (grows on "load more").
const fieldValuesCurrentSize = ref>({});
// Stores finalized values from previous pages (immutable during streaming).
- const fieldValuesFinalizedValues = ref>({});
+ const fieldValuesFinalizedValues = ref<
+ Record
+ >({});
// Stores the pinned time range from the first request per field.
- const fieldValuesTimeRange = ref>({});
+ const fieldValuesTimeRange = ref<
+ Record
+ >({});
// Tracks the active keyword search term per field so "load more" re-applies it.
const fieldSearchKeywords = ref>({});
@@ -397,12 +411,18 @@ export default defineComponent({
{ label: t("search.logs"), value: "logs", icon: outlinedSearch },
{ label: t("search.traces"), value: "traces", icon: outlinedAccountTree },
{ label: t("search.metrics"), value: "metrics", icon: outlinedBarChart },
- { label: t("search.enrichmentTables"), value: "enrichment_tables", icon: outlinedTableView },
+ {
+ label: t("search.enrichmentTables"),
+ value: "enrichment_tables",
+ icon: outlinedTableView,
+ },
];
const streamTypeIcon = computed(() => {
const current = searchObj.data.stream.streamType;
- return streamTypes.find((t) => t.value === current)?.icon ?? outlinedSearch;
+ return (
+ streamTypes.find((t) => t.value === current)?.icon ?? outlinedSearch
+ );
});
const streamTypeLabel = computed(() => {
@@ -555,7 +575,7 @@ export default defineComponent({
() => streamList.value,
() => {
if (streamOptions.value.length === 0) {
- streamOptions.value = streamList.value;
+ streamOptions.value = [...streamList.value];
}
},
{
@@ -677,11 +697,15 @@ export default defineComponent({
const filterStreamFn = (val: string, update: any) => {
update(() => {
- streamOptions.value = streamList.value;
const needle = val.toLowerCase();
- streamOptions.value = streamOptions.value.filter(
- (v: any) => v.label.toLowerCase().indexOf(needle) > -1,
- );
+ const source = streamList.value || [];
+ if (!needle) {
+ streamOptions.value = [...source];
+ } else {
+ streamOptions.value = source.filter(
+ (v: any) => v.label.toLowerCase().indexOf(needle) > -1,
+ );
+ }
});
};
@@ -733,8 +757,7 @@ export default defineComponent({
/^([^=!<>\s()"]+)(\s*(?:!=|=)\s*.*)$/,
);
if (fieldAndOperator) {
- searchObj.data.stream.addToFilter =
- `${quoteSqlIdentifierIfNeeded(fieldAndOperator[1])}${fieldAndOperator[2]}`;
+ searchObj.data.stream.addToFilter = `${quoteSqlIdentifierIfNeeded(fieldAndOperator[1])}${fieldAndOperator[2]}`;
return;
}
}
@@ -862,9 +885,13 @@ export default defineComponent({
lastFieldFetchPayloads.value[name] = [];
delete cachedFieldValues.value[name];
delete cachedStreamFieldValues.value[name];
- fieldValuesCurrentSize.value[name] = store.state.zoConfig?.query_values_default_num || 10;
+ fieldValuesCurrentSize.value[name] =
+ store.state.zoConfig?.query_values_default_num || 10;
fieldValuesFinalizedValues.value[name] = [];
- fieldValuesTimeRange.value[name] = { start_time: startISOTimestamp, end_time: endISOTimestamp };
+ fieldValuesTimeRange.value[name] = {
+ start_time: startISOTimestamp,
+ end_time: endISOTimestamp,
+ };
delete fieldSearchKeywords.value[name];
let query_context = "";
let query = searchObj.data.query;
@@ -989,7 +1016,10 @@ export default defineComponent({
// Build SQL with the expanded field's own filter condition removed so
// field value counts are not constrained by that filter.
- const rawSQL = query_context.replace("[INDEX_NAME]", selectedStream);
+ const rawSQL = query_context.replace(
+ "[INDEX_NAME]",
+ selectedStream,
+ );
let sqlForValues = rawSQL;
try {
const parsedForValues = fnParsedSQL(rawSQL);
@@ -1133,7 +1163,12 @@ export default defineComponent({
fetchValuesWithWebsocket({
...payload,
size: newSize,
- ...(pinnedTime ? { start_time: pinnedTime.start_time, end_time: pinnedTime.end_time } : {}),
+ ...(pinnedTime
+ ? {
+ start_time: pinnedTime.start_time,
+ end_time: pinnedTime.end_time,
+ }
+ : {}),
...(keyword ? { keyword } : {}),
});
}
@@ -1141,7 +1176,8 @@ export default defineComponent({
const searchFieldValues = (fieldName: string, searchTerm: string) => {
// Reset pagination whenever the search term changes.
- fieldValuesCurrentSize.value[fieldName] = store.state.zoConfig?.query_values_default_num || 10;
+ fieldValuesCurrentSize.value[fieldName] =
+ store.state.zoConfig?.query_values_default_num || 10;
delete fieldValuesFinalizedValues.value[fieldName];
// Track the active keyword so "load more" can re-apply it.
fieldSearchKeywords.value[fieldName] = searchTerm;
@@ -1484,8 +1520,14 @@ export default defineComponent({
// Pre-allocate the stream slot on fresh loads (skip on "load more"
// so existing per-stream values survive until replaced by new data).
- const isLoadMore = (fieldValuesFinalizedValues.value[fieldName]?.length || 0) > 0;
- if (!isLoadMore && fieldName && streamName && streamFieldValues.value[fieldName])
+ const isLoadMore =
+ (fieldValuesFinalizedValues.value[fieldName]?.length || 0) > 0;
+ if (
+ !isLoadMore &&
+ fieldName &&
+ streamName &&
+ streamFieldValues.value[fieldName]
+ )
streamFieldValues.value[fieldName][streamName] = { values: [] };
const wsPayload = {
@@ -1665,7 +1707,8 @@ export default defineComponent({
// Merge with finalized values from previous pages.
const finalized = fieldValuesFinalizedValues.value[fieldName] || [];
- const currentSize = fieldValuesCurrentSize.value[fieldName] || pageSize;
+ const currentSize =
+ fieldValuesCurrentSize.value[fieldName] || pageSize;
if (finalized.length > 0) {
const finalizedKeys = new Set(finalized.map((v) => v.key));