Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 103 additions & 31 deletions frontend/src/stores/explore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { DateValue } from "@internationalized/date";
import { now, getLocalTimeZone, CalendarDateTime } from "@internationalized/date";
import { useSourcesStore } from "./sources";
import { useTeamsStore } from "@/stores/teams";
import { useLiveLogStore } from "@/stores/liveLog";
import { useBaseStore } from "./base";
import { QueryService } from '@/services/QueryService'
import { parseRelativeTimeString } from "@/utils/time";
Expand All @@ -28,6 +29,8 @@ import { type TimeRange } from '@/types/query';
import { getErrorMessage } from '@/api/types';
import { HistogramService, type HistogramData } from '@/services/HistogramService';

const liveLogStore = useLiveLogStore();

// Helper function to get formatted table name
export function getFormattedTableName(source: any): string {
if (!source) {
Expand Down Expand Up @@ -811,36 +814,7 @@ export const useExploreStore = defineStore("explore", () => {
const response = await state.callApi({
apiCall: async () => exploreApi.getLogs(state.data.value.sourceId, params, currentTeamId),
// Update results ONLY on successful API call with data
onSuccess: (data: QuerySuccessResponse | null) => {
if (data && data.logs) {
// We have new data, update the store
state.data.value.logs = data.logs;
state.data.value.columns = data.columns || [];
state.data.value.queryStats = data.stats || DEFAULT_QUERY_STATS;
// Check if query_id exists in params before accessing it
if (data.params && typeof data.params === 'object' && "query_id" in data.params) {
state.data.value.queryId = data.params.query_id as string;
} else {
state.data.value.queryId = null; // Reset if not present
}
} else {
// Query was successful but returned no logs or null data
console.warn("Query successful but received no logs or null data.");
// Clear the logs, columns, stats now that the API call is complete
state.data.value.logs = [];
state.data.value.columns = [];
state.data.value.queryStats = DEFAULT_QUERY_STATS;
state.data.value.queryId = null;
}

// Update lastExecutedState after successful execution
_updateLastExecutedState();

// Restore the relative time if it was set before
if (relativeTime) {
state.data.value.selectedRelativeTime = relativeTime;
}
},
onSuccess: (data: QuerySuccessResponse | null) => fetchLogData(data),
operationKey: operationKey,
});

Expand Down Expand Up @@ -1307,6 +1281,102 @@ export const useExploreStore = defineStore("explore", () => {
return "1d"; // Default for very long time ranges
}

function prepareQueryParams(sql: string) {

// Prepare parameters for the API call
const params: QueryParams = {
raw_sql: '', // Will be set below
limit: state.data.value.limit,
query_timeout: state.data.value.queryTimeout
};

// Handle empty SQL for both modes
if (!sql || !sql.trim()) {
// Generate default SQL for both LogchefQL and SQL modes when SQL is empty
console.log(`Explore store: Generating default SQL for empty ${state.data.value.activeMode} query`);

// Get source details to check they're fully loaded
const sourcesStore = useSourcesStore();
const sourceDetails = sourcesStore.currentSourceDetails;
if (!sourceDetails) {
throw new Error("Source details not available");
}

const tsField = sourceDetails._meta_ts_field || 'timestamp';
const tableName = sourcesStore.getCurrentSourceTableName || 'default.logs';

// Generate default SQL using SqlManager
const result = SqlManager.generateDefaultSql({
tableName,
tsField,
timeRange: state.data.value.timeRange as TimeRange,
limit: state.data.value.limit,
timezone: state.data.value.selectedTimezoneIdentifier || undefined
});

if (!result.success) {
const operationKey = 'executeQuery';
return state.handleError({
status: "error",
message: "Failed to generate default SQL",
error_type: "ValidationError"
}, operationKey);
}

// Use the generated SQL
sql = result.sql;

// If in SQL mode, update the UI to show the generated SQL
if (state.data.value.activeMode === 'sql') {
state.data.value.rawSql = sql;
}
}

// Set the SQL in the params
params.raw_sql = sql;

return params;
}

function fetchLogData(data: QuerySuccessResponse | null) {
if (data && data.logs) {
// check is live log or not
if(liveLogStore.isOn && !liveLogStore.isPause) {
// append data when live log
state.data.value.logs = [...state.data.value.logs, ...data.logs];
} else {
// We have new data, update the store
state.data.value.logs = data.logs;
}
state.data.value.columns = data.columns || [];
state.data.value.queryStats = data.stats || DEFAULT_QUERY_STATS;
// Check if query_id exists in params before accessing it
if (data.params && typeof data.params === 'object' && "query_id" in data.params) {
state.data.value.queryId = data.params.query_id as string;
} else {
state.data.value.queryId = null; // Reset if not present
}

} else {
// Query was successful but returned no logs or null data
console.warn("Query successful but received no logs or null data.");
// Clear the logs, columns, stats now that the API call is complete
state.data.value.logs = [];
state.data.value.columns = [];
state.data.value.queryStats = DEFAULT_QUERY_STATS;
state.data.value.queryId = null;
}

// Update lastExecutedState after successful execution
_updateLastExecutedState();

// Restore the relative time if it was set before
const relativeTime = state.data.value.selectedRelativeTime;
if (relativeTime) {
state.data.value.selectedRelativeTime = relativeTime;
}
}

// Return the store
return {
// State - exposed as computed properties
Expand Down Expand Up @@ -1375,7 +1445,9 @@ export const useExploreStore = defineStore("explore", () => {
generateAiSql,
clearAiSqlState,
fetchHistogramData,

prepareQueryParams,
fetchLogData,
getTimezoneIdentifier,
// Loading state helpers
isLoadingOperation: state.isLoadingOperation,
};
Expand Down
64 changes: 64 additions & 0 deletions frontend/src/stores/liveLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

import {defineStore} from "pinia";

import {useBaseStore} from "@/stores/base.ts";
import {computed} from "vue";


interface LiveLogState {
isOn: boolean; // is live log on
start: number; // last request start
end: number; // last request end
sql: string | null; // last request sql
isPause: boolean; // is live log paused or not
}

export const useLiveLogStore = defineStore("liveLog", () => {

const state = useBaseStore<LiveLogState>({
isOn: false,
start: Date.now(),
end: Date.now(),
sql: null,
isPause: false,
});



// Computed properties
const isOn = computed(() => state.data.value.isOn);
const start = computed(() => state.data.value.start);
const end = computed(() => state.data.value.end);
const sql = computed(() => state.data.value.sql);
const isPause = computed(() => state.data.value.isPause);

// Set data
function setIsOn(isOn: boolean) {
state.data.value.isOn = isOn;
}
function setStart(start: number) {
state.data.value.start = start;
}
function setEnd(end: number) {
state.data.value.end = end;
}
function setSql(sql: string | null) {
state.data.value.sql = sql;
}
function setIsPause(isPause: boolean) {
state.data.value.isPause = isPause;
}

return {
isOn,
start,
end,
sql,
isPause,
setIsOn,
setStart,
setEnd,
setSql,
setIsPause
};
});
13 changes: 12 additions & 1 deletion frontend/src/views/explore/LogExplorer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useExploreStore } from "@/stores/explore";
import { useTeamsStore } from "@/stores/teams";
import { useSourcesStore } from "@/stores/sources";
import { useSavedQueriesStore } from "@/stores/savedQueries";
import { useLiveLogStore } from "@/stores/liveLog"
import { FieldSideBar } from "@/components/field-sidebar";
import { getErrorMessage } from "@/api/types";
import DataTable from "./table/data-table.vue";
Expand Down Expand Up @@ -145,6 +146,9 @@ const executingQueryId = ref<string | null>(null);
const lastQueryTime = ref<number>(0);
let lastExecutionKey = "";

// live log paused or not
const isPaused = ref(false);

// Display related refs
const displayTimezone = computed(() =>
localStorage.getItem("logchef_timezone") === "utc" ? "utc" : "local"
Expand Down Expand Up @@ -891,6 +895,13 @@ const onSaveQueryModalSave = (formData: SaveQueryFormData) => {
processSaveQueryFromComposable(formData);
};

// handle live log's pause on / off
const liveLogPauseOnOff = (isPause: boolean) => {
isPaused.value = isPause;
const liveLogStore = useLiveLogStore();
liveLogStore.setIsPause(isPause);
};

// Handle query_id changes from URL, especially when component is kept alive
watch(
() => route.query.query_id,
Expand Down Expand Up @@ -1322,7 +1333,7 @@ onBeforeUnmount(() => {
hasValidSource &&
exploreStore.timeRange
">
<QueryControls @execute="handleQueryExecution" @clear="clearQueryEditor" />
<QueryControls @execute="handleQueryExecution" @clear="clearQueryEditor" @liveLog="liveLogPauseOnOff" />
</div>
</div>

Expand Down
Loading