diff --git a/web/src/composables/dashboard/usePanelDataLoader.ts b/web/src/composables/dashboard/usePanelDataLoader.ts
index 92fcee28df..2ca97afaff 100644
--- a/web/src/composables/dashboard/usePanelDataLoader.ts
+++ b/web/src/composables/dashboard/usePanelDataLoader.ts
@@ -45,6 +45,7 @@ import { convertOffsetToSeconds } from "@/utils/dashboard/convertDataIntoUnitVal
import useSearchWebSocket from "@/composables/useSearchWebSocket";
import { useAnnotations } from "./useAnnotations";
import useHttpStreamingSearch from "../useStreamingSearch";
+import { checkIfConfigChangeRequiredApiCallOrNot } from "@/utils/dashboard/checkConfigChangeApiCall";
/**
* debounce time in milliseconds for panel data loader
@@ -1644,13 +1645,33 @@ export const usePanelDataLoader = (
watch(
// Watching for changes in panelSchema, selectedTimeObj and forceLoad
- () => [panelSchema?.value, selectedTimeObj?.value, forceLoad?.value],
+ () => [selectedTimeObj?.value, forceLoad?.value],
async () => {
log("PanelSchema/Time Wather: called");
loadData(); // Loading the data
},
);
+ watch(
+ () => [panelSchema?.value],
+ async (newVal, oldVal) => {
+ const [newSchema] = newVal;
+ const [oldSchema] = oldVal;
+
+ const configNeedsApiCall = checkIfConfigChangeRequiredApiCallOrNot(
+ oldSchema,
+ newSchema,
+ );
+
+ if (!configNeedsApiCall) {
+ return;
+ }
+
+ loadData();
+ },
+ { deep: true },
+ );
+
/**
* Replaces the query with the corresponding variable values.
*
diff --git a/web/src/composables/useDashboardPanel.ts b/web/src/composables/useDashboardPanel.ts
index 689793a864..15f00d7556 100644
--- a/web/src/composables/useDashboardPanel.ts
+++ b/web/src/composables/useDashboardPanel.ts
@@ -1061,6 +1061,49 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => {
dashboardPanelData.data.customChartContent =
getDefaultCustomChartText();
+ dashboardPanelData.data.queries[
+ dashboardPanelData.layout.currentQueryIndex
+ ].config.time_shift = [];
+ break;
+ case "metric":
+ dashboardPanelData.data.queries[
+ dashboardPanelData.layout.currentQueryIndex
+ ].fields.x = [];
+ dashboardPanelData.data.queries[
+ dashboardPanelData.layout.currentQueryIndex
+ ].fields.y.forEach((itemY: any) => {
+ if (itemY.aggregationFunction === null && !itemY.isDerived) {
+ itemY.aggregationFunction = "count";
+ }
+ });
+ dashboardPanelData.data.queries[
+ dashboardPanelData.layout.currentQueryIndex
+ ].fields.z = [];
+ dashboardPanelData.data.queries[
+ dashboardPanelData.layout.currentQueryIndex
+ ].fields.breakdown = [];
+ // we have multiple queries for geomap, so if we are moving away, we need to reset
+ // the values of lat, lng and weight in all the queries
+ dashboardPanelData.data.queries?.forEach((query: any) => {
+ query.fields.latitude = null;
+ query.fields.longitude = null;
+ query.fields.weight = null;
+ query.fields.name = null;
+ query.fields.value_for_maps = null;
+ query.fields.source = null;
+ query.fields.target = null;
+ query.fields.value = null;
+ });
+ if (dashboardPanelData.data.queryType === "sql") {
+ dashboardPanelData.layout.currentQueryIndex = 0;
+ dashboardPanelData.data.queries =
+ dashboardPanelData.data.queries.slice(0, 1);
+ }
+ dashboardPanelData.data.htmlContent = "";
+ dashboardPanelData.data.markdownContent = "";
+ dashboardPanelData.data.customChartContent =
+ getDefaultCustomChartText();
+
dashboardPanelData.data.queries[
dashboardPanelData.layout.currentQueryIndex
].config.time_shift = [];
@@ -2035,7 +2078,8 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => {
const { sqlParser }: any = useSqlParser.default();
parser = await sqlParser();
- updateQueryValue();
+ // do not allow to modify custom query fields for logs page
+ updateQueryValue(pageKey == "logs" ? true : false);
};
/**
diff --git a/web/src/plugins/logs/Index.vue b/web/src/plugins/logs/Index.vue
index 45ec40c28e..4bf78e1e0b 100644
--- a/web/src/plugins/logs/Index.vue
+++ b/web/src/plugins/logs/Index.vue
@@ -389,6 +389,7 @@ import {
getFieldsFromQuery,
} from "@/utils/query/sqlUtils";
import useNotifications from "@/composables/useNotifications";
+import { checkIfConfigChangeRequiredApiCallOrNot } from "@/utils/dashboard/checkConfigChangeApiCall";
import SearchBar from "@/plugins/logs/SearchBar.vue";
import SearchHistory from "@/plugins/logs/SearchHistory.vue";
import SearchSchedulersList from "@/plugins/logs/SearchSchedulersList.vue";
@@ -1666,6 +1667,26 @@ export default defineComponent({
},
);
+ // Auto-apply config changes that don't require API calls (similar to dashboard)
+ const debouncedUpdateChartConfig = debounce((newVal) => {
+ if (searchObj.meta.logsVisualizeToggle === "visualize") {
+
+ let configNeedsApiCall = checkIfConfigChangeRequiredApiCallOrNot(
+ visualizeChartData.value,
+ newVal,
+ );
+
+ if (!configNeedsApiCall) {
+ visualizeChartData.value = JSON.parse(JSON.stringify(newVal));
+ window.dispatchEvent(new Event("resize"));
+ }
+ }
+ }, 1000);
+
+ watch(() => dashboardPanelData.data, debouncedUpdateChartConfig, {
+ deep: true,
+ });
+
watch(
() => [
searchObj.data.datetime.type,
diff --git a/web/src/plugins/logs/VisualizeLogsQuery.vue b/web/src/plugins/logs/VisualizeLogsQuery.vue
index 6ff9d52f83..a6d60de866 100644
--- a/web/src/plugins/logs/VisualizeLogsQuery.vue
+++ b/web/src/plugins/logs/VisualizeLogsQuery.vue
@@ -404,6 +404,7 @@ import { isEqual } from "lodash-es";
import { onActivated } from "vue";
import useNotifications from "@/composables/useNotifications";
import CustomChartEditor from "@/components/dashboards/addPanel/CustomChartEditor.vue";
+import { checkIfConfigChangeRequiredApiCallOrNot } from "@/utils/dashboard/checkConfigChangeApiCall";
const ConfigPanel = defineAsyncComponent(() => {
return import("@/components/dashboards/addPanel/ConfigPanel.vue");
@@ -466,6 +467,7 @@ export default defineComponent({
const { dashboardPanelData, resetAggregationFunction, validatePanel } =
useDashboardPanelData(dashboardPanelDataPageKey);
const metaData = ref(null);
+ const resultMetaData = ref(null);
const seriesData = ref([] as any[]);
const seriesDataUpdate = (data: any) => {
seriesData.value = data;
@@ -492,12 +494,17 @@ export default defineComponent({
);
const isOutDated = computed(() => {
- //compare chartdata and dashboardpaneldata
- // ignore histogram query comparison
- return (
- !is_ui_histogram.value &&
- !isEqual(chartData.value, dashboardPanelData.data)
- );
+ const configChanged = !isEqual(chartData.value, dashboardPanelData.data);
+
+ // skip outdate check if doesnt require to call api
+ let configNeedsApiCall = false;
+ if (configChanged) {
+ configNeedsApiCall = checkIfConfigChangeRequiredApiCallOrNot(
+ chartData.value,
+ dashboardPanelData.data,
+ );
+ }
+ return configNeedsApiCall;
});
watch(isOutDated, () => {
@@ -569,6 +576,13 @@ export default defineComponent({
provide("hoveredSeriesState", hoveredSeriesState);
const addToDashboard = () => {
+
+ // only copy if is_ui_histogram is true
+ if (resultMetaData.value?.[0]?.converted_histogram_query && is_ui_histogram.value === true) {
+ dashboardPanelData.data.queries[0].query =
+ resultMetaData.value?.[0]?.converted_histogram_query;
+ }
+
const errors: any = [];
// will push errors in errors array
validatePanel(errors, true);
@@ -581,6 +595,7 @@ export default defineComponent({
);
return;
} else {
+
showAddToDashboardDialog.value = true;
}
};
@@ -767,15 +782,15 @@ export default defineComponent({
window.dispatchEvent(new Event("resize"));
};
- const onResultMetadataUpdate = (resultMetaData: any) => {
+ const onResultMetadataUpdate = (resultMetaDataParam: any) => {
+ // Store the result metadata
+ resultMetaData.value = resultMetaDataParam;
+
// only copy if is_ui_histogram is true
- if (
- resultMetaData?.[0]?.converted_histogram_query &&
- is_ui_histogram.value === true
- ) {
- dashboardPanelData.data.queries[0].query =
- resultMetaData?.[0]?.converted_histogram_query;
- }
+ // if (resultMetaDataParam?.[0]?.converted_histogram_query && is_ui_histogram.value === true) {
+ // dashboardPanelData.data.queries[0].query =
+ // resultMetaDataParam?.[0]?.converted_histogram_query;
+ // }
};
return {
diff --git a/web/src/plugins/metrics/Index.vue b/web/src/plugins/metrics/Index.vue
index 3fd2c874b2..b52083aa62 100644
--- a/web/src/plugins/metrics/Index.vue
+++ b/web/src/plugins/metrics/Index.vue
@@ -522,12 +522,13 @@ import DashboardErrorsComponent from "@/components/dashboards/addPanel/Dashboard
import VariablesValueSelector from "@/components/dashboards/VariablesValueSelector.vue";
import PanelSchemaRenderer from "@/components/dashboards/PanelSchemaRenderer.vue";
import RelativeTime from "@/components/common/RelativeTime.vue";
-import { isEqual } from "lodash-es";
+import { isEqual, debounce } from "lodash-es";
import { provide } from "vue";
import useNotifications from "@/composables/useNotifications";
import config from "@/aws-exports";
import useCancelQuery from "@/composables/dashboard/useCancelQuery";
import AutoRefreshInterval from "@/components/AutoRefreshInterval.vue";
+import { checkIfConfigChangeRequiredApiCallOrNot } from "@/utils/dashboard/checkConfigChangeApiCall";
import CustomChartEditor from "@/components/dashboards/addPanel/CustomChartEditor.vue";
const AddToDashboard = defineAsyncComponent(() => {
return import("./../metrics/AddToDashboard.vue");
@@ -779,6 +780,25 @@ export default defineComponent({
{ deep: true },
);
+ // Auto-apply config changes that don't require API calls (similar to dashboard)
+ const debouncedUpdateChartConfig = debounce((newVal, oldVal) => {
+ if (!isEqual(chartData.value, newVal)) {
+ const configNeedsApiCall = checkIfConfigChangeRequiredApiCallOrNot(
+ chartData.value,
+ newVal,
+ );
+
+ if (!configNeedsApiCall) {
+ chartData.value = JSON.parse(JSON.stringify(newVal));
+ window.dispatchEvent(new Event("resize"));
+ }
+ }
+ }, 1000);
+
+ watch(() => dashboardPanelData.data, debouncedUpdateChartConfig, {
+ deep: true,
+ });
+
//validate the data
const isValid = (onlyChart = false, isFieldsValidationRequired = true) => {
const errors = errorData.errors;
diff --git a/web/src/utils/dashboard/checkConfigChangeApiCall.ts b/web/src/utils/dashboard/checkConfigChangeApiCall.ts
new file mode 100644
index 0000000000..c36c18779a
--- /dev/null
+++ b/web/src/utils/dashboard/checkConfigChangeApiCall.ts
@@ -0,0 +1,65 @@
+// Whitelist of config keys that require API calls when changed
+const API_REQUIRING_CHANGES = new Set(["queries", "variables"]);
+
+/**
+ * Flattens an object's keys with dot notation
+ */
+const flattenObject = (obj: any, prefix = ""): Record => {
+ if (obj === null || obj === undefined) {
+ return {};
+ }
+ return Object.keys(obj).reduce((acc: Record, k: string) => {
+ const pre = prefix.length ? prefix + "." : "";
+ if (
+ typeof obj[k] === "object" &&
+ obj[k] !== null &&
+ !Array.isArray(obj[k])
+ ) {
+ Object.assign(acc, flattenObject(obj[k], pre + k));
+ } else {
+ acc[pre + k] = obj[k];
+ }
+ return acc;
+ }, {});
+};
+
+/**
+ * Gets changed keys between two objects
+ */
+const getChangedKeys = (oldObj: any, newObj: any): string[] => {
+ const flatOld = flattenObject(oldObj);
+ const flatNew = flattenObject(newObj);
+ const changedKeys: string[] = [];
+
+ // Check all keys in both objects
+ const allKeys = Array.from(
+ new Set([...Object.keys(flatOld), ...Object.keys(flatNew)]),
+ );
+
+ for (const key of allKeys) {
+ if (JSON.stringify(flatOld[key]) !== JSON.stringify(flatNew[key])) {
+ changedKeys.push(key);
+ }
+ }
+
+ return changedKeys;
+};
+
+/**
+ * Checks if the changes between old and new config require an API call
+ */
+export const checkIfConfigChangeRequiredApiCallOrNot = (
+ oldConfig: any,
+ newConfig: any,
+): boolean => {
+ const changedKeys = getChangedKeys(oldConfig, newConfig);
+ const configNeedsApiCall = changedKeys.some((key) =>
+ Array.from(API_REQUIRING_CHANGES).some((apiKey) => key.startsWith(apiKey)),
+ );
+
+ if (configNeedsApiCall) {
+ return true;
+ }
+
+ return false;
+};
diff --git a/web/src/views/Dashboards/addPanel/AddPanel.vue b/web/src/views/Dashboards/addPanel/AddPanel.vue
index ebc9d3c66d..ae77d224d7 100644
--- a/web/src/views/Dashboards/addPanel/AddPanel.vue
+++ b/web/src/views/Dashboards/addPanel/AddPanel.vue
@@ -510,13 +510,14 @@ import VariablesValueSelector from "../../../components/dashboards/VariablesValu
import PanelSchemaRenderer from "../../../components/dashboards/PanelSchemaRenderer.vue";
import RelativeTime from "@/components/common/RelativeTime.vue";
import { useLoading } from "@/composables/useLoading";
-import { isEqual } from "lodash-es";
+import { debounce, isEqual } from "lodash-es";
import { provide } from "vue";
import useNotifications from "@/composables/useNotifications";
import config from "@/aws-exports";
import useCancelQuery from "@/composables/dashboard/useCancelQuery";
import useAiChat from "@/composables/useAiChat";
import useStreams from "@/composables/useStreams";
+import { checkIfConfigChangeRequiredApiCallOrNot } from "@/utils/dashboard/checkConfigChangeApiCall";
const ConfigPanel = defineAsyncComponent(() => {
return import("../../../components/dashboards/addPanel/ConfigPanel.vue");
@@ -899,11 +900,19 @@ export default defineComponent({
const normalizedCurrent = normalizeVariables(variablesData);
const normalizedRefreshed = normalizeVariables(updatedVariablesData);
+ const variablesChanged = !isEqual(normalizedCurrent, normalizedRefreshed);
- return (
- !isEqual(chartData.value, dashboardPanelData.data) ||
- !isEqual(normalizedCurrent, normalizedRefreshed)
- );
+ const configChanged = !isEqual(chartData.value, dashboardPanelData.data);
+ let configNeedsApiCall = false;
+
+ if (configChanged) {
+ configNeedsApiCall = checkIfConfigChangeRequiredApiCallOrNot(
+ chartData.value,
+ dashboardPanelData.data,
+ );
+ }
+
+ return configNeedsApiCall || variablesChanged;
});
watch(isOutDated, () => {
@@ -1561,6 +1570,24 @@ export default defineComponent({
return { width: `${contentWidth}px` };
});
+ const debouncedUpdateChartConfig = debounce((newVal, oldVal) => {
+ if (!isEqual(chartData.value, newVal)) {
+ const configNeedsApiCall = checkIfConfigChangeRequiredApiCallOrNot(
+ chartData.value,
+ newVal,
+ );
+
+ if (!configNeedsApiCall) {
+ chartData.value = JSON.parse(JSON.stringify(newVal));
+
+ window.dispatchEvent(new Event("resize"));
+ }
+ }
+ }, 1000);
+
+ watch(() => dashboardPanelData.data, debouncedUpdateChartConfig, {
+ deep: true,
+ });
// [START] ai chat
// [START] O2 AI Context Handler