From 49e1a35cb17ddfb8d1c2bf1df9d9a92f36f4e11b Mon Sep 17 00:00:00 2001 From: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com> Date: Tue, 18 Mar 2025 12:25:02 +0530 Subject: [PATCH] feat: dashboard havingCondition for aggregation fields (#6252) --- src/config/src/meta/dashboards/v5/mod.rs | 8 + .../addPanel/DashboardGeoMapsQueryBuilder.vue | 98 ++++++++++- .../addPanel/DashboardMapsQueryBuilder.vue | 111 +++++++++++- .../addPanel/DashboardQueryBuilder.vue | 154 ++++++++++++++++- .../addPanel/DashboardSankeyChartBuilder.vue | 120 ++++++++++++- web/src/composables/useDashboardPanel.ts | 159 +++++++++++++++++- 6 files changed, 629 insertions(+), 21 deletions(-) diff --git a/src/config/src/meta/dashboards/v5/mod.rs b/src/config/src/meta/dashboards/v5/mod.rs index 567e391842..ae025903b7 100644 --- a/src/config/src/meta/dashboards/v5/mod.rs +++ b/src/config/src/meta/dashboards/v5/mod.rs @@ -163,6 +163,14 @@ pub struct AxisItem { pub args: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub is_derived: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub having_conditions: Option>, +} + +#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize, ToSchema)] +pub struct HavingConditions { + value: Option, + operator: Option, } #[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize, ToSchema)] diff --git a/web/src/components/dashboards/addPanel/DashboardGeoMapsQueryBuilder.vue b/web/src/components/dashboards/addPanel/DashboardGeoMapsQueryBuilder.vue index 9fdd9b3f1f..15da477482 100644 --- a/web/src/components/dashboards/addPanel/DashboardGeoMapsQueryBuilder.vue +++ b/web/src/components/dashboards/addPanel/DashboardGeoMapsQueryBuilder.vue @@ -413,6 +413,49 @@ along with this program. If not, see . " :rules="[(val: any) => val > 0 || 'Required']" /> +
+ Having + + + +
+ + + + + + +
+
-import { defineComponent, ref, reactive, watch, computed } from "vue"; +import { defineComponent, ref, reactive, watch, computed, nextTick } from "vue"; import { useI18n } from "vue-i18n"; import useDashboardPanelData from "../../../composables/useDashboardPanel"; import { getImageURL } from "../../../utils/zincutils"; @@ -500,6 +543,7 @@ export default defineComponent({ "dashboardPanelDataPageKey", "dashboard", ); + const { dashboardPanelData, addLatitude, @@ -709,6 +753,53 @@ export default defineComponent({ return commonBtnLabel(weightField); }); + const operators = ["=", "<>", ">=", "<=", ">", "<"]; + + const isHavingFilterVisible = () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields.weight; + + const isVisible = !!currentField?.havingConditions?.length; + return isVisible; + }; + + const toggleHavingFilter = async () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields.weight; + + if (!currentField.havingConditions) { + currentField.havingConditions = []; + } + + if (!currentField.havingConditions.length) { + currentField.havingConditions.push({ operator: null, value: null }); + } + + await nextTick(); + }; + + const cancelHavingFilter = async () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields.weight; + + currentField.havingConditions = []; + + await nextTick(); + }; + + const getHavingCondition = () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields.weight; + + return ( + currentField.havingConditions?.[0] || { operator: null, value: null } + ); + }; + return { t, dashboardPanelData, @@ -733,6 +824,11 @@ export default defineComponent({ promqlMode, weightLabel, onFieldDragStart, + operators, + isHavingFilterVisible, + toggleHavingFilter, + cancelHavingFilter, + getHavingCondition, options: [ "=", "<>", diff --git a/web/src/components/dashboards/addPanel/DashboardMapsQueryBuilder.vue b/web/src/components/dashboards/addPanel/DashboardMapsQueryBuilder.vue index 2495329603..2299f88221 100644 --- a/web/src/components/dashboards/addPanel/DashboardMapsQueryBuilder.vue +++ b/web/src/components/dashboards/addPanel/DashboardMapsQueryBuilder.vue @@ -278,6 +278,49 @@ along with this program. If not, see . " :rules="[(val: string) => !!val?.trim() || 'Required']" /> +
+ Having + + + +
+ + + + + + +
+
-import { defineComponent, ref, reactive, watch, computed, inject } from "vue"; +import { + defineComponent, + ref, + reactive, + watch, + computed, + inject, + nextTick, +} from "vue"; import { useI18n } from "vue-i18n"; import useDashboardPanelData from "../../../composables/useDashboardPanel"; import { getImageURL } from "../../../utils/zincutils"; @@ -364,7 +415,7 @@ export default defineComponent({ "dashboardPanelDataPageKey", "dashboard", ); - + const { dashboardPanelData, addMapName, @@ -559,6 +610,57 @@ export default defineComponent({ return commonBtnLabel(valueField); }); + const operators = ["=", "<>", ">=", "<=", ">", "<"]; + + const isHavingFilterVisible = () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields + .value_for_maps; + + const isVisible = !!currentField?.havingConditions?.length; + return isVisible; + }; + + const toggleHavingFilter = async () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields + .value_for_maps; + + if (!currentField.havingConditions) { + currentField.havingConditions = []; + } + + if (!currentField.havingConditions.length) { + currentField.havingConditions.push({ operator: null, value: null }); + } + + await nextTick(); + }; + + const cancelHavingFilter = async () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields + .value_for_maps; + + currentField.havingConditions = []; + + await nextTick(); + }; + + const getHavingCondition = () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields + .value_for_maps; + + return ( + currentField.havingConditions?.[0] || { operator: null, value: null } + ); + }; + return { t, dashboardPanelData, @@ -581,6 +683,11 @@ export default defineComponent({ Hint, promqlMode, onFieldDragStart, + operators, + isHavingFilterVisible, + toggleHavingFilter, + cancelHavingFilter, + getHavingCondition, options: [ "=", "<>", diff --git a/web/src/components/dashboards/addPanel/DashboardQueryBuilder.vue b/web/src/components/dashboards/addPanel/DashboardQueryBuilder.vue index 2d367036fc..17739f9171 100644 --- a/web/src/components/dashboards/addPanel/DashboardQueryBuilder.vue +++ b/web/src/components/dashboards/addPanel/DashboardQueryBuilder.vue @@ -715,6 +715,52 @@ along with this program. If not, see . " :rules="[(val: any) => val.length > 0 || 'Required']" /> +
+ Having + + + +
+ + + + + +
+
+
+ Having + + + +
+ + + + + +
+
-import { defineComponent, ref, reactive, watch, computed, inject } from "vue"; +import { + defineComponent, + ref, + reactive, + watch, + computed, + inject, + nextTick, +} from "vue"; import { useI18n } from "vue-i18n"; import useDashboardPanelData from "../../../composables/useDashboardPanel"; import { getImageURL } from "../../../utils/zincutils"; @@ -1452,6 +1552,53 @@ export default defineComponent({ return zFields.map(commonBtnLabel); }); + const operators = ["=", "<>", ">=", "<=", ">", "<"]; + + const isHavingFilterVisible = (index: any, axis: any) => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields[axis][index]; + + const isVisible = !!currentField?.havingConditions?.length; + return isVisible; + }; + + const toggleHavingFilter = async (index: any, axis: any) => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields[axis][index]; + + if (!currentField.havingConditions) { + currentField.havingConditions = []; + } + + if (!currentField.havingConditions.length) { + currentField.havingConditions.push({ operator: null, value: null }); + } + + await nextTick(); + }; + + const cancelHavingFilter = async (index: any, axis: any) => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields[axis][index]; + + currentField.havingConditions = []; + + await nextTick(); + }; + + const getHavingCondition = (index: any, axis: any) => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields[axis][index]; + + return ( + currentField.havingConditions?.[0] || { operator: null, value: null } + ); + }; + return { showXAxis, t, @@ -1500,6 +1647,11 @@ export default defineComponent({ onFieldDragStart, getHistoramIntervalField, onDragEnd, + operators, + isHavingFilterVisible, + toggleHavingFilter, + cancelHavingFilter, + getHavingCondition, }; }, }); diff --git a/web/src/components/dashboards/addPanel/DashboardSankeyChartBuilder.vue b/web/src/components/dashboards/addPanel/DashboardSankeyChartBuilder.vue index 24ca44eb87..9ab0caa950 100644 --- a/web/src/components/dashboards/addPanel/DashboardSankeyChartBuilder.vue +++ b/web/src/components/dashboards/addPanel/DashboardSankeyChartBuilder.vue @@ -55,7 +55,7 @@ along with this program. If not, see . dashboardPanelData.data.queries[ dashboardPanelData.layout.currentQueryIndex ].fields?.source.column, - 'source' + 'source', ) " > @@ -191,7 +191,7 @@ along with this program. If not, see . dashboardPanelData.data.queries[ dashboardPanelData.layout.currentQueryIndex ].fields?.target.column, - 'target' + 'target', ) " > @@ -327,7 +327,7 @@ along with this program. If not, see . dashboardPanelData.data.queries[ dashboardPanelData.layout.currentQueryIndex ].fields?.value.column, - 'value' + 'value', ) " > @@ -413,6 +413,49 @@ along with this program. If not, see . " :rules="[(val: any) => val > 0 || 'Required']" /> +
+ Having + + + +
+ + + + + + +
+
-import { defineComponent, ref, reactive, watch, computed, inject } from "vue"; +import { + defineComponent, + ref, + reactive, + watch, + computed, + inject, + nextTick, +} from "vue"; import { useI18n } from "vue-i18n"; import useDashboardPanelData from "../../../composables/useDashboardPanel"; import { getImageURL } from "../../../utils/zincutils"; @@ -498,8 +549,9 @@ export default defineComponent({ const dashboardPanelDataPageKey = inject( "dashboardPanelDataPageKey", - "dashboard" + "dashboard", ); + const { dashboardPanelData, addSource, @@ -547,7 +599,7 @@ export default defineComponent({ expansionItems.value = true; expansionItems.filter = true; } - } + }, ); const onDrop = (e: any, targetAxis: string) => { @@ -580,11 +632,11 @@ export default defineComponent({ const dragName = selectedStreamFieldsBasedOnUserDefinedSchema.value.find( - (item: any) => item?.name === dragElement + (item: any) => item?.name === dragElement, ); const customDragName = dashboardPanelData.meta.stream.customQueryFields.find( - (item: any) => item?.name === dragElement + (item: any) => item?.name === dragElement, ); if (dragName || customDragName) { @@ -700,6 +752,53 @@ export default defineComponent({ return commonBtnLabel(valueField); }); + const operators = ["=", "<>", ">=", "<=", ">", "<"]; + + const isHavingFilterVisible = () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields.value; + + const isVisible = !!currentField?.havingConditions?.length; + return isVisible; + }; + + const toggleHavingFilter = async () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields.value; + + if (!currentField.havingConditions) { + currentField.havingConditions = []; + } + + if (!currentField.havingConditions.length) { + currentField.havingConditions.push({ operator: null, value: null }); + } + + await nextTick(); + }; + + const cancelHavingFilter = async () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields.value; + + currentField.havingConditions = []; + + await nextTick(); + }; + + const getHavingCondition = () => { + const currentQueryIndex = dashboardPanelData.layout.currentQueryIndex; + const currentField = + dashboardPanelData.data.queries[currentQueryIndex].fields.value; + + return ( + currentField.havingConditions?.[0] || { operator: null, value: null } + ); + }; + return { t, dashboardPanelData, @@ -723,6 +822,11 @@ export default defineComponent({ promqlMode, valueLabel, onFieldDragStart, + operators, + isHavingFilterVisible, + toggleHavingFilter, + cancelHavingFilter, + getHavingCondition, options: [ "=", "<>", diff --git a/web/src/composables/useDashboardPanel.ts b/web/src/composables/useDashboardPanel.ts index a22a41cc23..779044f5d1 100644 --- a/web/src/composables/useDashboardPanel.ts +++ b/web/src/composables/useDashboardPanel.ts @@ -197,8 +197,8 @@ const dashboardPanelDataObj: any = { const getDefaultCustomChartText = () => { return `\ // To know more about ECharts , \n// visit: https://echarts.apache.org/examples/en/index.html \n// Example: https://echarts.apache.org/examples/en/editor.html?c=line-simple \n// Define your ECharts 'option' here. \n// 'data' variable is available for use and contains the response data from the search result and it is an array.\noption = { \n \n}; - ` -} + `; +}; const useDashboardPanelData = (pageKey: string = "dashboard") => { const store = useStore(); @@ -468,6 +468,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { : "ASC" : null, isDerived, + havingConditions: [], }); } @@ -528,6 +529,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { : "ASC" : null, isDerived, + havingConditions: [], }); } @@ -581,6 +583,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { ? null : "count", isDerived, + havingConditions: [], }); } updateArrayAlias(); @@ -630,6 +633,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { color: getNewColorValue(), aggregationFunction: isDerived ? null : "count", isDerived, + havingConditions: [], }); } updateArrayAlias(); @@ -651,6 +655,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { color: getNewColorValue(), aggregationFunction: null, // You can set the appropriate aggregation function here isDerived, + havingConditions: [], }; } }; @@ -671,6 +676,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { color: getNewColorValue(), aggregationFunction: null, // You can set the appropriate aggregation function here isDerived, + havingConditions: [], }; } }; @@ -691,6 +697,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { color: getNewColorValue(), aggregationFunction: isDerived ? null : "count", // You can set the appropriate aggregation function here isDerived, + havingConditions: [], }; } }; @@ -709,6 +716,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { column: row.name, color: getNewColorValue(), aggregationFunction: null, // You can set the appropriate aggregation function here + havingConditions: [], }; } }; @@ -727,6 +735,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { column: row.name, color: getNewColorValue(), aggregationFunction: "count", // You can set the appropriate aggregation function here + havingConditions: [], }; } }; @@ -747,6 +756,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { color: getNewColorValue(), aggregationFunction: null, // You can set the appropriate aggregation function here isDerived, + havingConditions: [], }; } }; @@ -767,6 +777,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { color: getNewColorValue(), aggregationFunction: null, // You can set the appropriate aggregation function here isDerived, + havingConditions: [], }; } }; @@ -787,6 +798,7 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { color: getNewColorValue(), aggregationFunction: isDerived ? null : "sum", // You can set the appropriate aggregation function here isDerived, + havingConditions: [], }; } }; @@ -831,7 +843,8 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { } dashboardPanelData.data.htmlContent = ""; dashboardPanelData.data.markdownContent = ""; - dashboardPanelData.data.customChartContent = getDefaultCustomChartText(); + dashboardPanelData.data.customChartContent = + getDefaultCustomChartText(); dashboardPanelData.data.queries[ dashboardPanelData.layout.currentQueryIndex ].config.time_shift = []; @@ -874,7 +887,8 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { } dashboardPanelData.data.htmlContent = ""; dashboardPanelData.data.markdownContent = ""; - dashboardPanelData.data.customChartContent = getDefaultCustomChartText(); + dashboardPanelData.data.customChartContent = + getDefaultCustomChartText(); break; case "table": case "pie": @@ -913,7 +927,8 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { } dashboardPanelData.data.htmlContent = ""; dashboardPanelData.data.markdownContent = ""; - dashboardPanelData.data.customChartContent = getDefaultCustomChartText(); + dashboardPanelData.data.customChartContent = + getDefaultCustomChartText(); dashboardPanelData.data.queries[ dashboardPanelData.layout.currentQueryIndex @@ -938,7 +953,8 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { ].fields.breakdown = []; dashboardPanelData.data.htmlContent = ""; dashboardPanelData.data.markdownContent = ""; - dashboardPanelData.data.customChartContent = getDefaultCustomChartText(); + dashboardPanelData.data.customChartContent = + getDefaultCustomChartText(); dashboardPanelData.data.queries?.forEach((query: any) => { query.fields.source = null; @@ -955,7 +971,8 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { case "html": dashboardPanelData.data.queries = getDefaultQueries(); dashboardPanelData.data.markdownContent = ""; - dashboardPanelData.data.customChartContent = getDefaultCustomChartText(); + dashboardPanelData.data.customChartContent = + getDefaultCustomChartText(); dashboardPanelData.data.queryType = ""; dashboardPanelData.data.queries[ dashboardPanelData.layout.currentQueryIndex @@ -964,7 +981,8 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { case "markdown": dashboardPanelData.data.queries = getDefaultQueries(); dashboardPanelData.data.htmlContent = ""; - dashboardPanelData.data.customChartContent = getDefaultCustomChartText(); + dashboardPanelData.data.customChartContent = + getDefaultCustomChartText(); dashboardPanelData.data.queryType = ""; dashboardPanelData.data.queries[ @@ -1024,7 +1042,8 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { }; dashboardPanelData.data.htmlContent = ""; dashboardPanelData.data.markdownContent = ""; - dashboardPanelData.data.customChartContent = getDefaultCustomChartText(); + dashboardPanelData.data.customChartContent = + getDefaultCustomChartText(); dashboardPanelData.data.queries?.forEach((query: any) => { query.fields.latitude = null; query.fields.longitude = null; @@ -2242,6 +2261,56 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { } } + // Add HAVING clause if y-axis or z-axis has operator and value + const yAxisFields = + dashboardPanelData.data.queries[ + dashboardPanelData.layout.currentQueryIndex + ].fields.y; + + const zAxisFields = + dashboardPanelData.data.queries[ + dashboardPanelData.layout.currentQueryIndex + ].fields?.z || []; + + const havingClauses: any = []; + + // Only add having clauses for non-heatmap charts from y-axis + if (dashboardPanelData.data.type !== "heatmap") { + // Process y-axis having conditions + yAxisFields.forEach((field: any) => { + if ( + field?.havingConditions?.[0]?.operator && + field?.havingConditions?.[0]?.value !== undefined && + field?.havingConditions?.[0]?.value !== null && + field?.havingConditions?.[0]?.value !== "" + ) { + const columnName = field.alias; + havingClauses.push( + `${columnName} ${field.havingConditions[0].operator} ${field.havingConditions[0].value}`, + ); + } + }); + } + + // Process z-axis having conditions + zAxisFields.forEach((field: any) => { + if ( + field?.havingConditions?.[0]?.operator && + field?.havingConditions?.[0]?.value !== undefined && + field?.havingConditions?.[0]?.value !== null && + field?.havingConditions?.[0]?.value !== "" + ) { + const columnName = field.alias; + havingClauses.push( + `${columnName} ${field.havingConditions[0].operator} ${field.havingConditions[0].value}`, + ); + } + }); + + if (havingClauses.length > 0) { + query += " HAVING " + havingClauses.join(" AND "); + } + // array of sorting fields with followed by asc or desc const orderByArr: string[] = []; @@ -2332,6 +2401,30 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { } } + // Add HAVING clause if y-axis has operator and value + const valueForMapsFields = + dashboardPanelData.data.queries[ + dashboardPanelData.layout.currentQueryIndex + ].fields.value_for_maps; + + const havingClauses: any = []; + + if ( + valueForMapsFields?.havingConditions?.[0]?.operator && + valueForMapsFields?.havingConditions?.[0]?.value !== undefined && + valueForMapsFields?.havingConditions?.[0]?.value !== null && + valueForMapsFields?.havingConditions?.[0]?.value !== "" + ) { + const columnName = valueForMapsFields.alias; + havingClauses.push( + `${columnName} ${valueForMapsFields.havingConditions[0].operator} ${valueForMapsFields.havingConditions[0].value}`, + ); + } + + if (havingClauses.length > 0) { + query += " HAVING " + havingClauses.join(" AND "); + } + // array of sorting fields with followed by asc or desc const orderByArr: string[] = []; @@ -2427,6 +2520,30 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { query += `GROUP BY ${aliases}`; } + // Add HAVING clause if y-axis has operator and value + const weightFields = + dashboardPanelData.data.queries[ + dashboardPanelData.layout.currentQueryIndex + ].fields.weight; + + const havingClauses: any = []; + + if ( + weightFields?.havingConditions?.[0]?.operator && + weightFields?.havingConditions?.[0]?.value !== undefined && + weightFields?.havingConditions?.[0]?.value !== null && + weightFields?.havingConditions?.[0]?.value !== "" + ) { + const columnName = weightFields.alias; + havingClauses.push( + `${columnName} ${weightFields.havingConditions[0].operator} ${weightFields.havingConditions[0].value}`, + ); + } + + if (havingClauses.length > 0) { + query += " HAVING " + havingClauses.join(" AND "); + } + // array of sorting fields with followed by asc or desc const orderByArr: string[] = []; @@ -2531,6 +2648,30 @@ const useDashboardPanelData = (pageKey: string = "dashboard") => { query += `GROUP BY ${aliases}`; } + // Add HAVING clause if y-axis has operator and value + const valueFields = + dashboardPanelData.data.queries[ + dashboardPanelData.layout.currentQueryIndex + ].fields.value; + + const havingClauses: any = []; + + if ( + valueFields?.havingConditions?.[0]?.operator && + valueFields?.havingConditions?.[0]?.value !== undefined && + valueFields?.havingConditions?.[0]?.value !== null && + valueFields?.havingConditions?.[0]?.value !== "" + ) { + const columnName = valueFields.alias; + havingClauses.push( + `${columnName} ${valueFields.havingConditions[0].operator} ${valueFields.havingConditions[0].value}`, + ); + } + + if (havingClauses.length > 0) { + query += " HAVING " + havingClauses.join(" AND "); + } + // Adding sorting const orderByArr: string[] = []; [source, target, value]